@copilotkit/aimock 1.38.0 → 1.40.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.
Files changed (81) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +56 -0
  4. package/README.md +24 -2
  5. package/dist/agui-handler.cjs +20 -18
  6. package/dist/agui-handler.cjs.map +1 -1
  7. package/dist/agui-handler.d.cts +8 -1
  8. package/dist/agui-handler.d.cts.map +1 -1
  9. package/dist/agui-handler.d.ts +8 -1
  10. package/dist/agui-handler.d.ts.map +1 -1
  11. package/dist/agui-handler.js +20 -18
  12. package/dist/agui-handler.js.map +1 -1
  13. package/dist/agui-stub.d.cts +2 -2
  14. package/dist/agui-stub.d.ts +2 -2
  15. package/dist/agui-types.d.cts +84 -3
  16. package/dist/agui-types.d.cts.map +1 -1
  17. package/dist/agui-types.d.ts +84 -3
  18. package/dist/agui-types.d.ts.map +1 -1
  19. package/dist/aimock-cli.cjs +1 -1
  20. package/dist/aimock-cli.cjs.map +1 -1
  21. package/dist/aimock-cli.js +1 -1
  22. package/dist/aimock-cli.js.map +1 -1
  23. package/dist/cli.cjs +10 -10
  24. package/dist/cli.cjs.map +1 -1
  25. package/dist/cli.js +10 -10
  26. package/dist/cli.js.map +1 -1
  27. package/dist/config-loader.d.cts.map +1 -1
  28. package/dist/helpers.cjs +8 -4
  29. package/dist/helpers.cjs.map +1 -1
  30. package/dist/helpers.d.cts.map +1 -1
  31. package/dist/helpers.d.ts.map +1 -1
  32. package/dist/helpers.js +8 -4
  33. package/dist/helpers.js.map +1 -1
  34. package/dist/index.d.cts +2 -2
  35. package/dist/index.d.ts +2 -2
  36. package/dist/llmock.cjs +23 -9
  37. package/dist/llmock.cjs.map +1 -1
  38. package/dist/llmock.d.cts +22 -0
  39. package/dist/llmock.d.cts.map +1 -1
  40. package/dist/llmock.d.ts +22 -0
  41. package/dist/llmock.d.ts.map +1 -1
  42. package/dist/llmock.js +25 -11
  43. package/dist/llmock.js.map +1 -1
  44. package/dist/openrouter-chat.cjs +41 -0
  45. package/dist/openrouter-chat.cjs.map +1 -1
  46. package/dist/openrouter-chat.js +41 -0
  47. package/dist/openrouter-chat.js.map +1 -1
  48. package/dist/openrouter-video.cjs.map +1 -1
  49. package/dist/openrouter-video.js.map +1 -1
  50. package/dist/recorder.cjs +77 -8
  51. package/dist/recorder.cjs.map +1 -1
  52. package/dist/recorder.d.cts.map +1 -1
  53. package/dist/recorder.d.ts.map +1 -1
  54. package/dist/recorder.js +77 -8
  55. package/dist/recorder.js.map +1 -1
  56. package/dist/server.cjs +80 -17
  57. package/dist/server.cjs.map +1 -1
  58. package/dist/server.d.cts +6 -1
  59. package/dist/server.d.cts.map +1 -1
  60. package/dist/server.d.ts +6 -1
  61. package/dist/server.d.ts.map +1 -1
  62. package/dist/server.js +80 -18
  63. package/dist/server.js.map +1 -1
  64. package/dist/stream-collapse.cjs +97 -6
  65. package/dist/stream-collapse.cjs.map +1 -1
  66. package/dist/stream-collapse.d.cts +16 -0
  67. package/dist/stream-collapse.d.cts.map +1 -1
  68. package/dist/stream-collapse.d.ts +16 -0
  69. package/dist/stream-collapse.d.ts.map +1 -1
  70. package/dist/stream-collapse.js +97 -6
  71. package/dist/stream-collapse.js.map +1 -1
  72. package/dist/types.d.cts +22 -3
  73. package/dist/types.d.cts.map +1 -1
  74. package/dist/types.d.ts +22 -3
  75. package/dist/types.d.ts.map +1 -1
  76. package/dist/vector-types.d.ts.map +1 -1
  77. package/dist/ws-gemini-live.cjs +41 -6
  78. package/dist/ws-gemini-live.cjs.map +1 -1
  79. package/dist/ws-gemini-live.js +41 -6
  80. package/dist/ws-gemini-live.js.map +1 -1
  81. package/package.json +5 -3
package/dist/recorder.cjs CHANGED
@@ -360,6 +360,8 @@ async function proxyAndRecord(req, res, request, providerKey, pathname, fixtures
360
360
  if (collapsed.truncated) defaults.logger.warn("Bedrock EventStream: CRC mismatch — response may be truncated");
361
361
  if (collapsed.droppedChunks && collapsed.droppedChunks > 0) defaults.logger.warn(`${collapsed.droppedChunks} chunk(s) dropped during stream collapse${collapsed.firstDroppedSample ? ` — first: ${collapsed.firstDroppedSample}` : ""}`);
362
362
  if (collapsed.harmonyUnparsed) defaults.logger.warn(`Harmony tokens present but unparseable — content preserved verbatim${collapsed.harmonyNote ? ` (${collapsed.harmonyNote})` : ""}`);
363
+ const collapsedUsage = sanitizeRecordedUsage(collapsed.usage);
364
+ const usageSpread = collapsedUsage ? { usage: collapsedUsage } : {};
363
365
  if (collapsed.transcription) fixtureResponse = { transcription: collapsed.transcription };
364
366
  else if (collapsed.audioB64) {
365
367
  const audioToolCallsSpread = collapsed.toolCalls && collapsed.toolCalls.length > 0 ? { toolCalls: collapsed.toolCalls.map((tc) => ({
@@ -390,7 +392,8 @@ async function proxyAndRecord(req, res, request, providerKey, pathname, fixtures
390
392
  ...reasoningSpread,
391
393
  ...reasoningSignatureSpread,
392
394
  ...redactedThinkingSpread,
393
- ...webSearchesSpread
395
+ ...webSearchesSpread,
396
+ ...usageSpread
394
397
  };
395
398
  } else {
396
399
  const reasoningSpread = collapsed.reasoning ? { reasoning: collapsed.reasoning } : {};
@@ -413,21 +416,24 @@ async function proxyAndRecord(req, res, request, providerKey, pathname, fixtures
413
416
  ...reasoningSpread,
414
417
  ...reasoningSignatureSpread,
415
418
  ...redactedThinkingSpread,
416
- ...webSearchesSpread
419
+ ...webSearchesSpread,
420
+ ...usageSpread
417
421
  };
418
422
  } else fixtureResponse = {
419
423
  toolCalls: sanitizedToolCalls,
420
424
  ...reasoningSpread,
421
425
  ...reasoningSignatureSpread,
422
426
  ...redactedThinkingSpread,
423
- ...webSearchesSpread
427
+ ...webSearchesSpread,
428
+ ...usageSpread
424
429
  };
425
430
  } else fixtureResponse = {
426
431
  content: collapsed.content ?? "",
427
432
  ...reasoningSpread,
428
433
  ...reasoningSignatureSpread,
429
434
  ...redactedThinkingSpread,
430
- ...webSearchesSpread
435
+ ...webSearchesSpread,
436
+ ...usageSpread
431
437
  };
432
438
  }
433
439
  } else {
@@ -777,6 +783,63 @@ function toToolCallArguments(raw) {
777
783
  return JSON.stringify(raw);
778
784
  }
779
785
  /**
786
+ * `usage` sub-objects that carry numeric breakdowns rather than a scalar. The
787
+ * inner fields are NOT allowlisted here on purpose: the recorder keeps every
788
+ * finite-numeric inner field (documented or forward-compat), matching the
789
+ * load-time validator in fixture-loader.ts, whose index-signature escape hatch
790
+ * accepts undocumented numeric inner keys too. A `Set` (own-key membership via
791
+ * `.has()`, no prototype-chain walk) mirrors that validator's
792
+ * `new Set(Object.keys(...))` classification so anything this recorder writes
793
+ * loads cleanly.
794
+ */
795
+ const USAGE_OBJECT_FIELDS = new Set([
796
+ "cost_details",
797
+ "prompt_tokens_details",
798
+ "completion_tokens_details"
799
+ ]);
800
+ /**
801
+ * Sanitize a provider-reported `usage` object into the fixture
802
+ * `response.usage` override shape (#368).
803
+ *
804
+ * The recorder persists real upstream usage so replay can serve the provider's
805
+ * ACTUAL token counts (instead of the `ceil(len/4)` estimate) and OpenRouter's
806
+ * `cost` — which previously never survived recording at all. Upstream usage is
807
+ * provider-specific and forward-extending, so this passes fields through rather
808
+ * than whitelisting them, but only in the shapes the fixture contract accepts:
809
+ *
810
+ * - numeric scalars (`prompt_tokens`, `cost`, `native_tokens_prompt`, …) — kept
811
+ * - `is_byok` boolean — kept
812
+ * - the documented breakdown objects — kept, with non-numeric inner fields
813
+ * dropped (the load-time validator rejects those, and a fixture the recorder
814
+ * writes must always load)
815
+ * - anything else (strings, arrays, nulls, unknown objects) — DROPPED, because
816
+ * `fixture-loader.ts` errors on a non-numeric extra `usage` key and a recorded
817
+ * fixture that fails validation is worse than one missing an exotic field
818
+ *
819
+ * Returns `undefined` when nothing survives, so the caller omits `usage`
820
+ * entirely and keeps pre-#368 fixtures byte-identical.
821
+ */
822
+ function sanitizeRecordedUsage(raw) {
823
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return void 0;
824
+ const out = {};
825
+ for (const [key, val] of Object.entries(raw)) {
826
+ if (val === void 0 || val === null) continue;
827
+ if (USAGE_OBJECT_FIELDS.has(key)) {
828
+ if (typeof val !== "object" || val === null || Array.isArray(val)) continue;
829
+ const inner = {};
830
+ for (const [innerKey, innerVal] of Object.entries(val)) if (typeof innerVal === "number" && Number.isFinite(innerVal)) inner[innerKey] = innerVal;
831
+ if (Object.keys(inner).length > 0) out[key] = inner;
832
+ continue;
833
+ }
834
+ if (key === "is_byok") {
835
+ if (typeof val === "boolean") out[key] = val;
836
+ continue;
837
+ }
838
+ if (typeof val === "number" && Number.isFinite(val)) out[key] = val;
839
+ }
840
+ return Object.keys(out).length > 0 ? out : void 0;
841
+ }
842
+ /**
780
843
  * Detect the response format from the parsed upstream JSON and convert
781
844
  * it into an aimock FixtureResponse.
782
845
  */
@@ -883,6 +946,8 @@ function buildFixtureResponse(parsed, status, logger, request) {
883
946
  const hasToolCalls = Array.isArray(message.tool_calls) && message.tool_calls.length > 0;
884
947
  const hasContent = typeof message.content === "string" && message.content.length > 0;
885
948
  const openaiReasoning = typeof message.reasoning_content === "string" && message.reasoning_content.length > 0 ? message.reasoning_content : typeof message.reasoning === "string" && message.reasoning.length > 0 ? message.reasoning : void 0;
949
+ const openaiUsage = sanitizeRecordedUsage(obj.usage);
950
+ const usageSpread = openaiUsage ? { usage: openaiUsage } : {};
886
951
  if (hasToolCalls) {
887
952
  const toolCalls = message.tool_calls.map((tc) => {
888
953
  const fn = tc.function;
@@ -895,20 +960,24 @@ function buildFixtureResponse(parsed, status, logger, request) {
895
960
  if (hasContent) return {
896
961
  content: message.content,
897
962
  toolCalls,
898
- ...openaiReasoning ? { reasoning: openaiReasoning } : {}
963
+ ...openaiReasoning ? { reasoning: openaiReasoning } : {},
964
+ ...usageSpread
899
965
  };
900
966
  return {
901
967
  toolCalls,
902
- ...openaiReasoning ? { reasoning: openaiReasoning } : {}
968
+ ...openaiReasoning ? { reasoning: openaiReasoning } : {},
969
+ ...usageSpread
903
970
  };
904
971
  }
905
972
  if (hasContent) return {
906
973
  content: message.content,
907
- ...openaiReasoning ? { reasoning: openaiReasoning } : {}
974
+ ...openaiReasoning ? { reasoning: openaiReasoning } : {},
975
+ ...usageSpread
908
976
  };
909
977
  return {
910
978
  content: "",
911
- ...openaiReasoning ? { reasoning: openaiReasoning } : {}
979
+ ...openaiReasoning ? { reasoning: openaiReasoning } : {},
980
+ ...usageSpread
912
981
  };
913
982
  }
914
983
  }