426e9eeabd
Voice Workbench / headless workbench (mocked backends) (push) Has been cancelled
Voice Workbench / real acoustic lane (nightly, provisioned only) (push) Has been cancelled
ci / test (push) Has been cancelled
ci / lint-and-format (push) Has been cancelled
ci / build (push) Has been cancelled
ci / dev-startup (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
Markdown Links / Relative Markdown Links (push) Has been cancelled
Quality (Extended) / Homepage Build (PR smoke) (push) Has been cancelled
Quality (Extended) / Comment-only diff guard (push) Has been cancelled
Quality (Extended) / Format + Type Safety Ratchet (push) Has been cancelled
Quality (Extended) / Develop Gate (secret scan + UI determinism) (push) Has been cancelled
Quality (Extended) / Develop Gate (lint) (push) Has been cancelled
Chat shell gestures / Chat shell gesture + parity e2e (push) Has been cancelled
Cloud Gateway Discord / Test (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx @biomejs/biome check packages/lifeops-bench/src, benchmark-lint) (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx vitest run --config packages/lifeops-bench/vitest.config.ts --root packages/lifeops-bench --passWithNoTests, benchmark-tests) (push) Has been cancelled
Build Agent Image / build-and-push (push) Has been cancelled
Dev Smoke / bun run dev onboarding chat (push) Has been cancelled
Dev Smoke / Vite HMR dependency-level smoke (push) Has been cancelled
Electrobun Submodule Guard / electrobun gitlink is fetchable (push) Has been cancelled
Publish @elizaos/example-code / check_npm (push) Has been cancelled
Publish @elizaos/example-code / publish_npm (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / verify_version (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / publish_npm (push) Has been cancelled
Sandbox Live Smoke / Sandbox live smoke (push) Has been cancelled
Snap Build & Test / Build Snap (amd64) (push) Has been cancelled
Snap Build & Test / Build Snap (arm64) (push) Has been cancelled
Test Packaging / elizaos CLI global-install smoke (node + bun) (push) Has been cancelled
Cloud Gateway Webhook / Test (push) Has been cancelled
Cloud Tests / lint-and-types (push) Has been cancelled
Cloud Tests / unit-tests (push) Has been cancelled
Cloud Tests / integration-tests (push) Has been cancelled
Cloud Tests / e2e-tests (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Apps Worker (Product 2) / Determine environment (push) Has been cancelled
Deploy Apps Worker (Product 2) / Deploy apps worker to apps-control host (${{ needs.determine-env.outputs.environment }}) (push) Has been cancelled
Deploy Eliza Provisioning Worker / Determine environment (push) Has been cancelled
Deploy Eliza Provisioning Worker / Deploy worker to Hetzner host (${{ needs.determine-env.outputs.environment }} @ ${{ needs.determine-env.outputs.deployment_sha }}) (push) Has been cancelled
Dev Smoke / Classify changed paths (push) Has been cancelled
supply-chain / sbom (push) Has been cancelled
supply-chain / vulnerability-scan (push) Has been cancelled
Build, Push & Deploy to Phala Cloud / build-and-push (push) Has been cancelled
Test Packaging / Validate Packaging Configs (push) Has been cancelled
Test Packaging / Build & Test PyPI Package (push) Has been cancelled
Test Packaging / PyPI on Python ${{ matrix.python }} (push) Has been cancelled
Test Packaging / Pack & Test JS Tarballs (push) Has been cancelled
UI Fixture E2E / ui-fixture-e2e (push) Has been cancelled
UI Fixture E2E / fixture-e2e (push) Has been cancelled
UI Story Gate / story-gate (push) Has been cancelled
vault-ci / test (macos-latest) (push) Has been cancelled
vault-ci / test (ubuntu-latest) (push) Has been cancelled
vault-ci / test (windows-latest) (push) Has been cancelled
vault-ci / app-core wiring tests (push) Has been cancelled
verify-patches / verify patches/CHECKSUMS.sha256 (push) Has been cancelled
Voice Benchmark Smoke / voice-emotion fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voiceagentbench fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench-quality unit smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench TypeScript unit (no audio) (push) Has been cancelled
Voice Benchmark Smoke / voice bench smoke summary (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/app-core test bun run --cwd packages/elizaos test bun run --cwd packages/cloud/shared test], app-and-cli) (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/scenario-runner test bun run --cwd packages/vault test bun run --cwd packages/security test bun run --cwd plugins/plugin-coding-tools test], framework-packages) (push) Has been cancelled
Windows CI / windows ([bun run --cwd plugins/plugin-elizacloud test bun run --cwd plugins/plugin-discord test bun run --cwd plugins/plugin-anthropic test bun run --cwd plugins/plugin-openai test bun run --cwd plugins/plugin-app-control test bun run --cwd plugins/pl… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run build --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/agent --concurrency=4 node packages/scripts/run-bash-linux-only.mjs scripts/verify-riscv64-buildpaths.sh node packages/scripts/run… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run typecheck --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/cloud-shared --concurrency=4 bun run --cwd packages/core test bun run --cwd packages/shared test], core-runtime, 75) (push) Has been cancelled
130 lines
3.8 KiB
TypeScript
130 lines
3.8 KiB
TypeScript
/**
|
|
* Token usage normalization and `MODEL_USED` event emission for LM Studio calls.
|
|
*
|
|
* LM Studio's OpenAI-compatible API returns usage with the same field names as
|
|
* OpenAI (`prompt_tokens`, `completion_tokens`, `total_tokens`), while the AI SDK
|
|
* normalizes them to `inputTokens` / `outputTokens` / `totalTokens` before handing
|
|
* them to us. We accept either shape and fall back to a length-based estimate when
|
|
* a model omits usage (some local builds do).
|
|
*/
|
|
|
|
import type { EventPayload, IAgentRuntime, ModelTypeName } from "@elizaos/core";
|
|
import { EventType } from "@elizaos/core";
|
|
|
|
type ProviderUsage = {
|
|
inputTokens?: number;
|
|
outputTokens?: number;
|
|
promptTokens?: number;
|
|
completionTokens?: number;
|
|
totalTokens?: number;
|
|
};
|
|
|
|
export type NormalizedUsage = {
|
|
promptTokens: number;
|
|
completionTokens: number;
|
|
totalTokens: number;
|
|
estimated?: boolean;
|
|
};
|
|
|
|
function toFiniteNumber(value: unknown): number | undefined {
|
|
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
return undefined;
|
|
}
|
|
return Math.max(0, Math.round(value));
|
|
}
|
|
|
|
export function normalizeTokenUsage(usage: unknown): NormalizedUsage | null {
|
|
if (!usage || typeof usage !== "object") {
|
|
return null;
|
|
}
|
|
|
|
const record = usage as ProviderUsage;
|
|
const promptTokens = toFiniteNumber(record.inputTokens ?? record.promptTokens);
|
|
const completionTokens = toFiniteNumber(record.outputTokens ?? record.completionTokens);
|
|
const totalTokens = toFiniteNumber(record.totalTokens);
|
|
|
|
if (promptTokens === undefined && completionTokens === undefined && totalTokens === undefined) {
|
|
return null;
|
|
}
|
|
|
|
const normalizedPromptTokens =
|
|
promptTokens ??
|
|
(completionTokens === undefined && totalTokens !== undefined
|
|
? totalTokens
|
|
: Math.max(0, (totalTokens ?? 0) - (completionTokens ?? 0)));
|
|
const normalizedCompletionTokens =
|
|
completionTokens ??
|
|
Math.max(0, (totalTokens ?? normalizedPromptTokens) - normalizedPromptTokens);
|
|
|
|
return {
|
|
promptTokens: normalizedPromptTokens,
|
|
completionTokens: normalizedCompletionTokens,
|
|
totalTokens: totalTokens ?? normalizedPromptTokens + normalizedCompletionTokens,
|
|
};
|
|
}
|
|
|
|
export function estimateTokenCount(text: string): number {
|
|
return text.length === 0 ? 0 : Math.ceil(text.length / 4);
|
|
}
|
|
|
|
function stringifyForUsage(value: unknown): string {
|
|
if (typeof value === "string") {
|
|
return value;
|
|
}
|
|
try {
|
|
return JSON.stringify(value);
|
|
} catch {
|
|
// error-policy:J7 token-count estimation only — a non-serializable value
|
|
// degrades to String() so usage telemetry never throws out of a successful
|
|
// generation. Not a data path.
|
|
return String(value);
|
|
}
|
|
}
|
|
|
|
export function estimateUsage(prompt: string, response: unknown): NormalizedUsage {
|
|
const promptTokens = estimateTokenCount(prompt);
|
|
const completionTokens = estimateTokenCount(stringifyForUsage(response));
|
|
return {
|
|
promptTokens,
|
|
completionTokens,
|
|
totalTokens: promptTokens + completionTokens,
|
|
estimated: true,
|
|
};
|
|
}
|
|
|
|
export function estimateEmbeddingUsage(text: string): NormalizedUsage {
|
|
const promptTokens = estimateTokenCount(text);
|
|
return {
|
|
promptTokens,
|
|
completionTokens: 0,
|
|
totalTokens: promptTokens,
|
|
estimated: true,
|
|
};
|
|
}
|
|
|
|
export function emitModelUsed(
|
|
runtime: IAgentRuntime,
|
|
type: ModelTypeName,
|
|
model: string,
|
|
usage: NormalizedUsage
|
|
): void {
|
|
void runtime.emitEvent(
|
|
EventType.MODEL_USED as string,
|
|
{
|
|
runtime,
|
|
source: "lmstudio",
|
|
provider: "lmstudio",
|
|
type,
|
|
model,
|
|
modelName: model,
|
|
tokens: {
|
|
prompt: usage.promptTokens,
|
|
completion: usage.completionTokens,
|
|
total: usage.totalTokens,
|
|
...(usage.estimated ? { estimated: true } : {}),
|
|
},
|
|
...(usage.estimated ? { usageEstimated: true } : {}),
|
|
} as EventPayload
|
|
);
|
|
}
|