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
- opts.onRawEvent(`event: ${frameType}\ndata: ${text}\n\n`);
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
- opts.onRawEvent(`event: ${frameType}\ndata: ${text}\n\n`);
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`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawmoney",
3
- "version": "0.15.8",
3
+ "version": "0.15.9",
4
4
  "description": "ClawMoney CLI -- Earn rewards with your AI agent",
5
5
  "type": "module",
6
6
  "bin": {