Files
OmniRoute/open-sse/utils/generationThroughput.ts
Ravi Tharuma 3d2bcc9f12 feat(api): emit gateway-measured tokens-per-second excluding TTFT (#12631)
Validado em lote numa worktree combinada com os 10 PRs desta leva sobre o tip de `release/v3.8.51`: `typecheck:core` limpo, `check-file-size` OK e **241/242** nos 29 arquivos de teste que os PRs tocam.

A única "falha" não é falha: `tests/unit/autoCombo/strict-zero-cost-filter.test.ts` é um teste em estilo Vitest que eu incluí por engano na invocação do runner nativo do Node — ele quebra no import (`@vitest/runner`), não numa asserção. Ao investigar, descobri que esse arquivo não roda em nenhum dos dois runners hoje (o glob do `test:unit` não lista `autoCombo` e o `include` do Vitest só pega `.tsx` nessa pasta); é um problema pré-existente do repositório, sem relação com esta leva, e vou registrá-lo separadamente.

O #12636 conflitava apenas na lista de testes do `@omniroute/opencode-plugin/package.json`, de forma aditiva: o tip já tinha `models-fetcher.test.ts` (do #12607, irmão desta mesma leva) e o #12636 acrescenta `telemetry.test.ts`. Fiz a união dos dois lados (25 arquivos contra 24 de cada) em vez de escolher um, o que teria removido um arquivo da suíte do plugin em silêncio.

Obrigado, @RaviTharuma.
2026-09-03 23:38:37 -03:00

45 lines
1.8 KiB
TypeScript

/**
* Gateway-measured generation throughput (#12616).
*
* tok/s MUST exclude TTFT. `output_tokens / total_latency` includes queueing and
* first-token wait and is not generation speed. When TTFT is unknown (typical
* non-streaming JSON), omit the field rather than guessing.
*/
export function generationDurationMs(
totalMs: number,
ttftMs: number | null | undefined
): number | null {
if (!Number.isFinite(totalMs) || totalMs <= 0) return null;
if (ttftMs == null || !Number.isFinite(ttftMs) || ttftMs < 0) return null;
const generationMs = totalMs - ttftMs;
return generationMs > 0 ? generationMs : null;
}
export function tokensPerSecond(
outputTokens: number,
generationMs: number | null | undefined
): number | null {
if (generationMs == null || !Number.isFinite(generationMs) || generationMs <= 0) return null;
if (!Number.isFinite(outputTokens) || outputTokens <= 0) return null;
return outputTokens / (generationMs / 1000);
}
function outputTokenCount(usage: Record<string, unknown>): number {
const raw =
usage.completion_tokens ??
usage.output_tokens ??
usage.candidatesTokenCount ??
usage.outputTokens ??
usage.completionTokens;
const n = typeof raw === "number" ? raw : typeof raw === "string" ? Number(raw) : NaN;
return Number.isFinite(n) ? n : 0;
}
/** Attach `tokens_per_second` when generation duration (excluding TTFT) is known. */
export function attachTokensPerSecond<T>(usage: T, generationMs: number | null | undefined): T {
if (!usage || typeof usage !== "object" || Array.isArray(usage)) return usage;
const tps = tokensPerSecond(outputTokenCount(usage as Record<string, unknown>), generationMs);
if (tps == null) return usage;
return { ...(usage as Record<string, unknown>), tokens_per_second: Number(tps.toFixed(3)) } as T;
}