@zhixuan92/multi-model-agent-core 3.12.2 → 3.12.4

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.
@@ -0,0 +1,121 @@
1
+ // HTTP-level usage capture for openai-compatible providers, independent of
2
+ // @openai/agents' internal aggregation.
3
+ //
4
+ // Why this exists: the @openai/agents SDK's stream consumer
5
+ // (node_modules/@openai/agents-openai/dist/openaiChatCompletionsStreaming.js
6
+ // line ~38) overwrites its local `usage` variable on every SSE chunk:
7
+ //
8
+ // usage = chunk.usage || undefined;
9
+ //
10
+ // For OpenAI proper this is benign — only the final `[DONE]`-adjacent chunk
11
+ // carries usage, so the last write is the right one. For DeepSeek (and
12
+ // likely other openai-compatible providers) with multi-turn tool-use, later
13
+ // chunks can have `usage:undefined` AFTER an earlier chunk reported real
14
+ // numbers, wiping the captured usage. The SDK then ends with
15
+ // `state.usage.inputTokens=0`, costUSD=0, despite real tokens having
16
+ // flowed. mma 3.12.2 telemetry showed every DeepSeek-as-reviewer call
17
+ // logging 21+ turns and zero tokens, untraceable until the SDK source was
18
+ // inspected.
19
+ //
20
+ // Fix: wrap the OpenAI client's `chat.completions.create` so we see the
21
+ // raw HTTP response (or the raw SSE stream) BEFORE the SDK consumes it, and
22
+ // accumulate usage into an out-of-band counter the runner can read at
23
+ // result-time as a source-of-truth fallback. The wrapper does not modify
24
+ // SDK-visible behavior — it observes only.
25
+ function addUsage(acc, u) {
26
+ return {
27
+ promptTokens: acc.promptTokens + (u.prompt_tokens ?? 0),
28
+ completionTokens: acc.completionTokens + (u.completion_tokens ?? 0),
29
+ cachedReadTokens: acc.cachedReadTokens + (u.prompt_tokens_details?.cached_tokens ?? 0),
30
+ reasoningTokens: acc.reasoningTokens + (u.completion_tokens_details?.reasoning_tokens ?? 0),
31
+ responses: acc.responses + 1,
32
+ };
33
+ }
34
+ /** Wrap an OpenAI client so usage from every chat.completions.create response
35
+ * is captured into the returned UsageAccumulator.
36
+ *
37
+ * Mutates `client.chat.completions.create` in place. The wrapped client is
38
+ * otherwise unchanged — same return shapes, same error behavior. The SDK
39
+ * sees the same response stream byte-for-byte (we tee, we don't transform).
40
+ */
41
+ export function wrapClientForUsageCapture(client) {
42
+ let acc = {
43
+ promptTokens: 0,
44
+ completionTokens: 0,
45
+ cachedReadTokens: 0,
46
+ reasoningTokens: 0,
47
+ responses: 0,
48
+ };
49
+ let observedNonZero = false;
50
+ const observe = (u) => {
51
+ if (!u)
52
+ return;
53
+ const before = acc;
54
+ acc = addUsage(acc, u);
55
+ // hasObservedUsage flips true the first time a response carries real
56
+ // input or output tokens. Counters can stay at 0 for empty responses
57
+ // (auth failures, immediate refusals) — those don't count as evidence
58
+ // that the provider IS reporting usage.
59
+ if ((u.prompt_tokens ?? 0) > 0 || (u.completion_tokens ?? 0) > 0) {
60
+ observedNonZero = true;
61
+ }
62
+ void before;
63
+ };
64
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
65
+ const completions = client.chat.completions;
66
+ const originalCreate = completions.create.bind(completions);
67
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
68
+ completions.create = async function wrappedCreate(...args) {
69
+ const result = await originalCreate(...args);
70
+ // Non-streaming branch: result is a ChatCompletion object with usage.
71
+ // Detect via the presence of `choices` (eagerly resolved) and `usage`.
72
+ if (result && typeof result === 'object' && Array.isArray(result.choices)) {
73
+ observe(result.usage);
74
+ return result;
75
+ }
76
+ // Streaming branch: result is an AsyncIterable of chunks. We wrap the
77
+ // iterator so each yielded chunk's usage feeds the accumulator without
78
+ // changing what the SDK sees.
79
+ if (result && typeof result[Symbol.asyncIterator] === 'function') {
80
+ const inner = result;
81
+ // Build a thin wrapper that preserves the original async-iterable
82
+ // identity (so the SDK can still .controller-abort if it does), but
83
+ // observes usage on every chunk that has it. Note: we accumulate the
84
+ // FIRST non-null usage we see from each request (chunks-with-usage are
85
+ // typically only 1 per stream, sent on the [DONE] frame). If a
86
+ // provider sends multiple usage frames, we add each one — that matches
87
+ // the cumulative semantics.
88
+ const wrapped = {
89
+ async *[Symbol.asyncIterator]() {
90
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
91
+ let lastUsage;
92
+ for await (const chunk of inner) {
93
+ if (chunk && chunk.usage) {
94
+ lastUsage = chunk.usage;
95
+ }
96
+ yield chunk;
97
+ }
98
+ // Accumulate at end-of-stream so multiple intra-stream `usage`
99
+ // chunks (DeepSeek behavior) don't double-count. The last seen
100
+ // usage is the cumulative-for-this-request value the provider
101
+ // wanted us to see.
102
+ observe(lastUsage);
103
+ },
104
+ };
105
+ // Some SDK call sites poke at `.controller` on the stream object for
106
+ // abort handling. Forward it if present.
107
+ const ctrl = result.controller;
108
+ if (ctrl !== undefined)
109
+ wrapped.controller = ctrl;
110
+ return wrapped;
111
+ }
112
+ // Unknown shape (shouldn't happen for chat.completions.create) — pass
113
+ // through unmodified.
114
+ return result;
115
+ };
116
+ return {
117
+ snapshot: () => ({ ...acc }),
118
+ hasObservedUsage: () => observedNonZero,
119
+ };
120
+ }
121
+ //# sourceMappingURL=openai-usage-interceptor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openai-usage-interceptor.js","sourceRoot":"","sources":["../../src/runners/openai-usage-interceptor.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhixuan92/multi-model-agent-core",
3
- "version": "3.12.2",
3
+ "version": "3.12.4",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "Core library for multi-model-agent: provider runners (Claude, Codex, OpenAI-compatible), routing logic, config schema, and tool/sandbox primitives.",