clawmoney 0.15.8 → 0.15.9
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.
|
@@ -449,6 +449,47 @@ function buildCodexRequestFrame(prompt, model, fingerprint, sessionId, turnMetad
|
|
|
449
449
|
}
|
|
450
450
|
return frame;
|
|
451
451
|
}
|
|
452
|
+
// Patch a raw ChatGPT WS frame before we forward it to the Hub as SSE.
|
|
453
|
+
// ChatGPT's internal response.completed frames come from a proprietary
|
|
454
|
+
// backend that does NOT populate usage.total_tokens — the Codex CLI Rust
|
|
455
|
+
// parser is strict about this field (stream disconnected before completion:
|
|
456
|
+
// failed to parse ResponseCompleted: missing field `total_tokens`), so we
|
|
457
|
+
// inject it here when we can compute it from input_tokens + output_tokens.
|
|
458
|
+
// Returns the possibly-rewritten frame JSON; on parse/shape error returns
|
|
459
|
+
// the original untouched so a malformed input never turns into a crash.
|
|
460
|
+
function patchCodexFrameForForwarding(raw) {
|
|
461
|
+
try {
|
|
462
|
+
const evt = JSON.parse(raw);
|
|
463
|
+
const type = evt["type"];
|
|
464
|
+
if (type !== "response.completed" && type !== "response.done") {
|
|
465
|
+
return raw;
|
|
466
|
+
}
|
|
467
|
+
const resp = evt["response"];
|
|
468
|
+
if (!resp || typeof resp !== "object")
|
|
469
|
+
return raw;
|
|
470
|
+
const usage = resp["usage"];
|
|
471
|
+
if (!usage || typeof usage !== "object")
|
|
472
|
+
return raw;
|
|
473
|
+
if (typeof usage["total_tokens"] === "number")
|
|
474
|
+
return raw;
|
|
475
|
+
const input = Number(usage["input_tokens"] ?? 0);
|
|
476
|
+
const output = Number(usage["output_tokens"] ?? 0);
|
|
477
|
+
usage["total_tokens"] = input + output;
|
|
478
|
+
// Also ensure the nested *_details objects exist — Codex CLI's
|
|
479
|
+
// schema checks for them on the response.completed frame.
|
|
480
|
+
if (!usage["input_tokens_details"] || typeof usage["input_tokens_details"] !== "object") {
|
|
481
|
+
const cached = Number(usage.cache_read_input_tokens ?? 0);
|
|
482
|
+
usage["input_tokens_details"] = { cached_tokens: cached };
|
|
483
|
+
}
|
|
484
|
+
if (!usage["output_tokens_details"] || typeof usage["output_tokens_details"] !== "object") {
|
|
485
|
+
usage["output_tokens_details"] = { reasoning_tokens: 0 };
|
|
486
|
+
}
|
|
487
|
+
return JSON.stringify(evt);
|
|
488
|
+
}
|
|
489
|
+
catch {
|
|
490
|
+
return raw;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
452
493
|
function handleFrame(raw, acc) {
|
|
453
494
|
let evt;
|
|
454
495
|
try {
|
|
@@ -877,7 +918,10 @@ async function doCallCodexApi(opts) {
|
|
|
877
918
|
try {
|
|
878
919
|
const parsedFrame = JSON.parse(text);
|
|
879
920
|
const frameType = typeof parsedFrame.type === "string" ? parsedFrame.type : "message";
|
|
880
|
-
|
|
921
|
+
// Inject usage.total_tokens on response.completed frames so
|
|
922
|
+
// the end client's strict parser doesn't abort the stream.
|
|
923
|
+
const patched = patchCodexFrameForForwarding(text);
|
|
924
|
+
opts.onRawEvent(`event: ${frameType}\ndata: ${patched}\n\n`);
|
|
881
925
|
}
|
|
882
926
|
catch {
|
|
883
927
|
// Non-JSON frame — forward as a plain data event.
|
|
@@ -1183,7 +1227,8 @@ async function doCallCodexApiPassthrough(opts) {
|
|
|
1183
1227
|
try {
|
|
1184
1228
|
const parsedFrame = JSON.parse(text);
|
|
1185
1229
|
const frameType = typeof parsedFrame.type === "string" ? parsedFrame.type : "message";
|
|
1186
|
-
|
|
1230
|
+
const patched = patchCodexFrameForForwarding(text);
|
|
1231
|
+
opts.onRawEvent(`event: ${frameType}\ndata: ${patched}\n\n`);
|
|
1187
1232
|
}
|
|
1188
1233
|
catch {
|
|
1189
1234
|
opts.onRawEvent(`event: message\ndata: ${text}\n\n`);
|