@shpitdev/convex-cliproxy-observability 0.2.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/README.md +36 -1
- package/dist/client.d.ts +1 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +5 -1
- package/dist/client.js.map +1 -1
- package/dist/component/queries.d.ts +1 -1
- package/dist/component/queries.d.ts.map +1 -1
- package/dist/component/queries.js +13 -0
- package/dist/component/queries.js.map +1 -1
- package/dist/model-call/index.d.ts +42 -7
- package/dist/model-call/index.d.ts.map +1 -1
- package/dist/model-call/index.js +61 -17
- package/dist/model-call/index.js.map +1 -1
- package/dist/protocols/checkpoint.d.ts +1 -1
- package/dist/protocols/checkpoint.d.ts.map +1 -1
- package/dist/protocols/checkpoint.js +7 -7
- package/dist/protocols/checkpoint.js.map +1 -1
- package/dist/protocols/types.d.ts +1 -0
- package/dist/protocols/types.d.ts.map +1 -1
- package/dist/protocols/usage.d.ts +18 -0
- package/dist/protocols/usage.d.ts.map +1 -1
- package/dist/protocols/usage.js +87 -23
- package/dist/protocols/usage.js.map +1 -1
- package/package.json +1 -1
- package/src/client.ts +9 -2
- package/src/component/queries.ts +24 -1
- package/src/model-call/index.ts +92 -21
- package/src/protocols/checkpoint.ts +12 -12
- package/src/protocols/types.ts +1 -0
- package/src/protocols/usage.ts +91 -32
package/src/protocols/usage.ts
CHANGED
|
@@ -1,36 +1,95 @@
|
|
|
1
|
+
import { TOKEN_FIELDS, type TokenField } from "../model-call/index.js";
|
|
1
2
|
import type { CliproxyProjection, RecordValue } from "./types.js";
|
|
2
|
-
import { count
|
|
3
|
+
import { count } from "./values.js";
|
|
4
|
+
/** Names the rule below in each usage measurement. */
|
|
5
|
+
const USAGE_SEMANTICS = "client_protocol_usage_v2";
|
|
6
|
+
/**
|
|
7
|
+
* The one token normalization rule, read from terminal native usage (`details.field` for
|
|
8
|
+
* nested counts). `first` takes the first listed field reported. `base` needs its field and
|
|
9
|
+
* adds each `plus` field, counting an absent one as 0. Input includes cache reads and writes,
|
|
10
|
+
* as OpenAI-style usage reports it. Anthropic reports them apart from `input_tokens` and omits
|
|
11
|
+
* them when no cache is used, as do CLIProxy's translators, so its input is the sum.
|
|
12
|
+
* `totalTokens` is only ever the reported `total_tokens`; Anthropic reports none and it is
|
|
13
|
+
* never summed. Nothing is manufactured from byte or text lengths.
|
|
14
|
+
*/
|
|
15
|
+
const TOKEN_NORMALIZATION: Record<
|
|
16
|
+
Exclude<CliproxyProjection["protocol"], "unknown">,
|
|
17
|
+
Record<TokenField, { first: readonly string[] } | { base: string; plus: readonly string[] }>
|
|
18
|
+
> = {
|
|
19
|
+
anthropic_messages: {
|
|
20
|
+
inputTokens: {
|
|
21
|
+
base: "input_tokens",
|
|
22
|
+
plus: ["cache_read_input_tokens", "cache_creation_input_tokens"],
|
|
23
|
+
},
|
|
24
|
+
outputTokens: { first: ["output_tokens"] },
|
|
25
|
+
totalTokens: { first: ["total_tokens"] },
|
|
26
|
+
reasoningTokens: { first: ["output_tokens_details.thinking_tokens"] },
|
|
27
|
+
cachedInputTokens: { first: ["cache_read_input_tokens"] },
|
|
28
|
+
cacheCreationInputTokens: { first: ["cache_creation_input_tokens"] },
|
|
29
|
+
},
|
|
30
|
+
responses: {
|
|
31
|
+
inputTokens: { first: ["input_tokens"] },
|
|
32
|
+
outputTokens: { first: ["output_tokens"] },
|
|
33
|
+
totalTokens: { first: ["total_tokens"] },
|
|
34
|
+
reasoningTokens: { first: ["output_tokens_details.reasoning_tokens"] },
|
|
35
|
+
cachedInputTokens: { first: ["input_tokens_details.cached_tokens"] },
|
|
36
|
+
cacheCreationInputTokens: {
|
|
37
|
+
first: [
|
|
38
|
+
"input_tokens_details.cache_write_tokens",
|
|
39
|
+
"input_tokens_details.cache_creation_tokens",
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
chat_completions: {
|
|
44
|
+
inputTokens: { first: ["prompt_tokens"] },
|
|
45
|
+
outputTokens: { first: ["completion_tokens"] },
|
|
46
|
+
totalTokens: { first: ["total_tokens"] },
|
|
47
|
+
reasoningTokens: { first: ["completion_tokens_details.reasoning_tokens"] },
|
|
48
|
+
cachedInputTokens: { first: ["prompt_tokens_details.cached_tokens"] },
|
|
49
|
+
cacheCreationInputTokens: {
|
|
50
|
+
first: [
|
|
51
|
+
"prompt_tokens_details.cache_write_tokens",
|
|
52
|
+
"prompt_tokens_details.cached_creation_tokens",
|
|
53
|
+
"prompt_tokens_details.cache_creation_tokens",
|
|
54
|
+
],
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
export function normalizeUsage(
|
|
59
|
+
protocol: keyof typeof TOKEN_NORMALIZATION,
|
|
60
|
+
reported: ReadonlyMap<string, number>,
|
|
61
|
+
): Partial<Record<TokenField, number>> {
|
|
62
|
+
const out: Partial<Record<TokenField, number>> = {};
|
|
63
|
+
for (const field of TOKEN_FIELDS) {
|
|
64
|
+
const rule = TOKEN_NORMALIZATION[protocol][field];
|
|
65
|
+
if ("first" in rule) {
|
|
66
|
+
out[field] = rule.first
|
|
67
|
+
.map((name) => reported.get(name))
|
|
68
|
+
.find((value) => value !== undefined);
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
const base = reported.get(rule.base);
|
|
72
|
+
out[field] =
|
|
73
|
+
base === undefined
|
|
74
|
+
? undefined
|
|
75
|
+
: count(rule.plus.reduce((total, name) => total + (reported.get(name) ?? 0), base));
|
|
76
|
+
}
|
|
77
|
+
return out;
|
|
78
|
+
}
|
|
79
|
+
/** Native counts keyed as usage measurements name them. */
|
|
80
|
+
function reportedCounts(raw: RecordValue): Map<string, number> {
|
|
81
|
+
const reported = new Map<string, number>();
|
|
82
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
83
|
+
if (count(value) !== undefined) reported.set(key, value as number);
|
|
84
|
+
else if (value && typeof value === "object" && !Array.isArray(value))
|
|
85
|
+
for (const [field, nested] of Object.entries(value))
|
|
86
|
+
if (count(nested) !== undefined) reported.set(`${key}.${field}`, nested as number);
|
|
87
|
+
}
|
|
88
|
+
return reported;
|
|
89
|
+
}
|
|
3
90
|
export function extractUsage(projection: CliproxyProjection, raw: RecordValue) {
|
|
4
|
-
// Anthropic reports disjoint uncached/cache creation/cache read input counts.
|
|
5
|
-
// Never manufacture totals, costs, or token counts from byte/text lengths.
|
|
6
91
|
projection.metadata.usage = raw;
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const nativeInput = count(raw.input_tokens);
|
|
12
|
-
const cacheRead = count(raw.cache_read_input_tokens);
|
|
13
|
-
const cacheCreated = count(raw.cache_creation_input_tokens);
|
|
14
|
-
const anthropicInput =
|
|
15
|
-
nativeInput !== undefined && cacheRead !== undefined && cacheCreated !== undefined
|
|
16
|
-
? count(nativeInput + cacheRead + cacheCreated)
|
|
17
|
-
: undefined;
|
|
18
|
-
Object.assign(projection.scalars, {
|
|
19
|
-
inputTokens: anthropic ? anthropicInput : responses ? nativeInput : count(raw.prompt_tokens),
|
|
20
|
-
outputTokens: count(raw[anthropic || responses ? "output_tokens" : "completion_tokens"]),
|
|
21
|
-
totalTokens: count(raw.total_tokens),
|
|
22
|
-
cachedInputTokens: count(
|
|
23
|
-
anthropic
|
|
24
|
-
? raw.cache_read_input_tokens
|
|
25
|
-
: object(raw[responses ? "input_tokens_details" : "prompt_tokens_details"]).cached_tokens,
|
|
26
|
-
),
|
|
27
|
-
reasoningTokens: count(
|
|
28
|
-
object(raw[anthropic || responses ? "output_tokens_details" : "completion_tokens_details"])[
|
|
29
|
-
anthropic ? "thinking_tokens" : "reasoning_tokens"
|
|
30
|
-
],
|
|
31
|
-
),
|
|
32
|
-
});
|
|
33
|
-
projection.metadata.tokenSemantics = anthropic
|
|
34
|
-
? "input_is_sum_of_reported_input_cache_creation_cache_read; cached_input_is_cache_read; missing_components_mean_unknown; no_synthesized_total"
|
|
35
|
-
: "input_includes_reported_cached_input; terminal_usage_snapshot";
|
|
92
|
+
projection.metadata.tokenSemantics = USAGE_SEMANTICS;
|
|
93
|
+
if (projection.protocol !== "unknown")
|
|
94
|
+
Object.assign(projection.scalars, normalizeUsage(projection.protocol, reportedCounts(raw)));
|
|
36
95
|
}
|