mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-13 18:32:12 +03:00
Mantém tokens de cache-write no formato de usage do OpenAI, com teste próprio (`cache-write-openai-shape.test.ts`) e atualização do teste existente de tokens detalhados. Validado no worktree combinado (typecheck limpo, 351/351 testes focados). Obrigado!
199 lines
7.0 KiB
TypeScript
199 lines
7.0 KiB
TypeScript
/**
|
|
* Regression: cache-write (cache creation) tokens were dropped whenever the usage
|
|
* payload arrived in an OpenAI-shaped container.
|
|
*
|
|
* Symptom: the same Claude model shows "Cache Write: 1,911" through an
|
|
* anthropic-compatible provider but "Cache Write: N/A" through an
|
|
* openai-compatible `/v1/chat/completions` provider, because every consumer only
|
|
* recognised the top-level Claude key `cache_creation_input_tokens`.
|
|
*
|
|
* Three shapes are produced inside this repo and none of them were read back:
|
|
* - `prompt_tokens_details.cache_creation_tokens`
|
|
* (open-sse/translator/response/claude-to-openai.ts, #2215)
|
|
* - `input_tokens_details.cache_write_tokens`
|
|
* (open-sse/vendor/codex-chatgpt-web/bridge.ts)
|
|
* - top-level `cache_write_tokens`
|
|
* (open-sse/executors/devin-desktop.ts, OpenRouter)
|
|
*
|
|
* A provider that genuinely has no cache-write concept (plain gpt/codex) must
|
|
* still report `null`, NOT `0` — `null` means "not reported", `0` means
|
|
* "reported as zero". That distinction is load-bearing for cache debugging.
|
|
*/
|
|
|
|
import { describe, it } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
|
|
import {
|
|
getPromptCacheCreationTokens,
|
|
getPromptCacheCreationTokensOrNull,
|
|
} from "../../src/lib/usage/tokenAccounting.ts";
|
|
import { buildCacheUsageLogMeta } from "../../open-sse/handlers/chatCore/cacheUsageMeta.ts";
|
|
import { extractUsage, normalizeUsage } from "../../open-sse/utils/usageTracking.ts";
|
|
import { extractUsageFromResponse } from "../../open-sse/handlers/usageExtractor.ts";
|
|
|
|
describe("cache-write tokens survive OpenAI-shaped usage", () => {
|
|
describe("tokenAccounting alias coverage", () => {
|
|
it("reads prompt_tokens_details.cache_creation_tokens (claude-to-openai #2215 shape)", () => {
|
|
const tokens = {
|
|
prompt_tokens: 5000,
|
|
completion_tokens: 100,
|
|
prompt_tokens_details: { cached_tokens: 0, cache_creation_tokens: 1911 },
|
|
};
|
|
assert.equal(getPromptCacheCreationTokens(tokens), 1911);
|
|
assert.equal(getPromptCacheCreationTokensOrNull(tokens), 1911);
|
|
});
|
|
|
|
it("reads input_tokens_details.cache_write_tokens (codex-chatgpt-web bridge shape)", () => {
|
|
const tokens = {
|
|
prompt_tokens: 5000,
|
|
completion_tokens: 100,
|
|
input_tokens_details: { cached_tokens: 0, cache_write_tokens: 1911 },
|
|
};
|
|
assert.equal(getPromptCacheCreationTokens(tokens), 1911);
|
|
assert.equal(getPromptCacheCreationTokensOrNull(tokens), 1911);
|
|
});
|
|
|
|
it("reads top-level cache_write_tokens (devin-desktop / OpenRouter shape)", () => {
|
|
const tokens = {
|
|
prompt_tokens: 5,
|
|
completion_tokens: 100,
|
|
prompt_tokens_details: { cached_tokens: 0 },
|
|
cache_write_tokens: 1911,
|
|
};
|
|
assert.equal(getPromptCacheCreationTokens(tokens), 1911);
|
|
assert.equal(getPromptCacheCreationTokensOrNull(tokens), 1911);
|
|
});
|
|
|
|
it("reported-zero cache_write_tokens stays 0, never null", () => {
|
|
const tokens = {
|
|
prompt_tokens: 5,
|
|
completion_tokens: 100,
|
|
prompt_tokens_details: { cached_tokens: 0, cache_write_tokens: 0 },
|
|
};
|
|
assert.equal(getPromptCacheCreationTokensOrNull(tokens), 0);
|
|
});
|
|
|
|
it("plain gpt/codex usage (no cache-write concept) still returns null", () => {
|
|
const tokens = {
|
|
prompt_tokens: 54042,
|
|
completion_tokens: 8000,
|
|
prompt_tokens_details: { cached_tokens: 53221 },
|
|
completion_tokens_details: { reasoning_tokens: 6433 },
|
|
};
|
|
assert.equal(
|
|
getPromptCacheCreationTokensOrNull(tokens),
|
|
null,
|
|
"not reported must stay null, not 0"
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("extractUsageFromResponse (non-streaming)", () => {
|
|
it("keeps cache creation from an OpenAI-shaped body", () => {
|
|
const usage = extractUsageFromResponse(
|
|
{
|
|
usage: {
|
|
prompt_tokens: 5000,
|
|
completion_tokens: 100,
|
|
prompt_tokens_details: { cached_tokens: 0, cache_creation_tokens: 1911 },
|
|
},
|
|
},
|
|
"openai-compatible"
|
|
);
|
|
assert.equal(getPromptCacheCreationTokensOrNull(usage), 1911);
|
|
});
|
|
|
|
it("keeps a top-level cache_write_tokens alias", () => {
|
|
const usage = extractUsageFromResponse(
|
|
{
|
|
usage: {
|
|
prompt_tokens: 5000,
|
|
completion_tokens: 100,
|
|
cache_write_tokens: 1911,
|
|
},
|
|
},
|
|
"openai-compatible"
|
|
);
|
|
assert.equal(getPromptCacheCreationTokensOrNull(usage), 1911);
|
|
});
|
|
|
|
it("does not invent a cache-creation field when the provider omits it", () => {
|
|
const usage = extractUsageFromResponse(
|
|
{ usage: { prompt_tokens: 10, completion_tokens: 2 } },
|
|
"openai-compatible"
|
|
);
|
|
assert.equal(getPromptCacheCreationTokensOrNull(usage), null);
|
|
});
|
|
});
|
|
|
|
describe("extractUsage (streaming chunk)", () => {
|
|
it("keeps cache creation nested in prompt_tokens_details", () => {
|
|
const usage = extractUsage({
|
|
usage: {
|
|
prompt_tokens: 5000,
|
|
completion_tokens: 100,
|
|
prompt_tokens_details: { cached_tokens: 0, cache_creation_tokens: 1911 },
|
|
},
|
|
});
|
|
assert.equal(getPromptCacheCreationTokensOrNull(usage), 1911);
|
|
});
|
|
|
|
it("keeps cache creation from a Responses-API completed event", () => {
|
|
const usage = extractUsage({
|
|
type: "response.completed",
|
|
response: {
|
|
usage: {
|
|
input_tokens: 5000,
|
|
output_tokens: 100,
|
|
input_tokens_details: { cached_tokens: 0, cache_write_tokens: 1911 },
|
|
},
|
|
},
|
|
});
|
|
assert.equal(getPromptCacheCreationTokensOrNull(usage), 1911);
|
|
});
|
|
});
|
|
|
|
describe("normalizeUsage", () => {
|
|
it("maps the cache_write_tokens alias onto the canonical key", () => {
|
|
const normalized = normalizeUsage({
|
|
prompt_tokens: 5000,
|
|
completion_tokens: 100,
|
|
cache_write_tokens: 1911,
|
|
});
|
|
assert.equal(normalized?.cache_creation_input_tokens, 1911);
|
|
});
|
|
|
|
it("does not overwrite an explicit canonical value", () => {
|
|
const normalized = normalizeUsage({
|
|
prompt_tokens: 5000,
|
|
completion_tokens: 100,
|
|
cache_creation_input_tokens: 1911,
|
|
cache_write_tokens: 7,
|
|
});
|
|
assert.equal(normalized?.cache_creation_input_tokens, 1911);
|
|
});
|
|
});
|
|
|
|
describe("buildCacheUsageLogMeta", () => {
|
|
it("reports cache creation from the OpenAI-shaped nested key", () => {
|
|
const meta = buildCacheUsageLogMeta({
|
|
prompt_tokens: 5000,
|
|
prompt_tokens_details: { cached_tokens: 0, cache_creation_tokens: 1911 },
|
|
});
|
|
assert.equal(meta?.cacheCreationTokens, 1911);
|
|
});
|
|
|
|
it("reports cache creation from the cache_write_tokens alias", () => {
|
|
const meta = buildCacheUsageLogMeta({
|
|
prompt_tokens: 5000,
|
|
cache_write_tokens: 1911,
|
|
});
|
|
assert.equal(meta?.cacheCreationTokens, 1911);
|
|
});
|
|
|
|
it("still returns null when no cache field is present at all", () => {
|
|
assert.equal(buildCacheUsageLogMeta({ prompt_tokens: 10, completion_tokens: 2 }), null);
|
|
});
|
|
});
|
|
});
|