@copilotkit/aimock 1.37.4 → 1.38.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 (153) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +22 -0
  4. package/README.md +27 -9
  5. package/dist/agui-recorder.cjs +6 -0
  6. package/dist/agui-recorder.cjs.map +1 -1
  7. package/dist/agui-recorder.d.cts.map +1 -1
  8. package/dist/agui-recorder.d.ts.map +1 -1
  9. package/dist/agui-recorder.js +6 -0
  10. package/dist/agui-recorder.js.map +1 -1
  11. package/dist/agui-types.d.ts.map +1 -1
  12. package/dist/api-key-auth.cjs +166 -0
  13. package/dist/api-key-auth.cjs.map +1 -0
  14. package/dist/api-key-auth.d.cts +17 -0
  15. package/dist/api-key-auth.d.cts.map +1 -0
  16. package/dist/api-key-auth.d.ts +17 -0
  17. package/dist/api-key-auth.d.ts.map +1 -0
  18. package/dist/api-key-auth.js +158 -0
  19. package/dist/api-key-auth.js.map +1 -0
  20. package/dist/cli.cjs +4 -1
  21. package/dist/cli.cjs.map +1 -1
  22. package/dist/cli.js +4 -1
  23. package/dist/cli.js.map +1 -1
  24. package/dist/config-loader.cjs +4 -2
  25. package/dist/config-loader.cjs.map +1 -1
  26. package/dist/config-loader.d.cts +2 -1
  27. package/dist/config-loader.d.cts.map +1 -1
  28. package/dist/config-loader.d.ts +2 -1
  29. package/dist/config-loader.d.ts.map +1 -1
  30. package/dist/config-loader.js +5 -3
  31. package/dist/config-loader.js.map +1 -1
  32. package/dist/fal-audio.cjs +2 -0
  33. package/dist/fal-audio.cjs.map +1 -1
  34. package/dist/fal-audio.d.cts.map +1 -1
  35. package/dist/fal-audio.d.ts.map +1 -1
  36. package/dist/fal-audio.js +2 -0
  37. package/dist/fal-audio.js.map +1 -1
  38. package/dist/fal.cjs +4 -1
  39. package/dist/fal.cjs.map +1 -1
  40. package/dist/fal.d.cts.map +1 -1
  41. package/dist/fal.d.ts.map +1 -1
  42. package/dist/fal.js +4 -1
  43. package/dist/fal.js.map +1 -1
  44. package/dist/fixture-loader.cjs +0 -0
  45. package/dist/fixture-loader.cjs.map +1 -1
  46. package/dist/fixture-loader.d.cts +3 -1
  47. package/dist/fixture-loader.d.cts.map +1 -1
  48. package/dist/fixture-loader.d.ts +3 -1
  49. package/dist/fixture-loader.d.ts.map +1 -1
  50. package/dist/fixture-loader.js +0 -0
  51. package/dist/fixture-loader.js.map +1 -1
  52. package/dist/grok-video.cjs +9 -2
  53. package/dist/grok-video.cjs.map +1 -1
  54. package/dist/grok-video.js +10 -3
  55. package/dist/grok-video.js.map +1 -1
  56. package/dist/helpers.cjs +16 -22
  57. package/dist/helpers.cjs.map +1 -1
  58. package/dist/helpers.d.cts.map +1 -1
  59. package/dist/helpers.d.ts.map +1 -1
  60. package/dist/helpers.js +16 -21
  61. package/dist/helpers.js.map +1 -1
  62. package/dist/index.d.cts +2 -2
  63. package/dist/index.d.ts +2 -2
  64. package/dist/llmock.cjs +14 -3
  65. package/dist/llmock.cjs.map +1 -1
  66. package/dist/llmock.d.cts +4 -2
  67. package/dist/llmock.d.cts.map +1 -1
  68. package/dist/llmock.d.ts +4 -2
  69. package/dist/llmock.d.ts.map +1 -1
  70. package/dist/llmock.js +15 -5
  71. package/dist/llmock.js.map +1 -1
  72. package/dist/openrouter-chat.cjs +333 -0
  73. package/dist/openrouter-chat.cjs.map +1 -0
  74. package/dist/openrouter-chat.js +325 -0
  75. package/dist/openrouter-chat.js.map +1 -0
  76. package/dist/openrouter-video.cjs +24 -7
  77. package/dist/openrouter-video.cjs.map +1 -1
  78. package/dist/openrouter-video.d.cts.map +1 -1
  79. package/dist/openrouter-video.d.ts.map +1 -1
  80. package/dist/openrouter-video.js +25 -8
  81. package/dist/openrouter-video.js.map +1 -1
  82. package/dist/provider-auth.cjs +20 -0
  83. package/dist/provider-auth.cjs.map +1 -1
  84. package/dist/provider-auth.js +20 -1
  85. package/dist/provider-auth.js.map +1 -1
  86. package/dist/recorder.cjs +50 -8
  87. package/dist/recorder.cjs.map +1 -1
  88. package/dist/recorder.d.cts.map +1 -1
  89. package/dist/recorder.d.ts.map +1 -1
  90. package/dist/recorder.js +50 -10
  91. package/dist/recorder.js.map +1 -1
  92. package/dist/responses.cjs +200 -89
  93. package/dist/responses.cjs.map +1 -1
  94. package/dist/responses.d.cts +18 -3
  95. package/dist/responses.d.cts.map +1 -1
  96. package/dist/responses.d.ts +18 -3
  97. package/dist/responses.d.ts.map +1 -1
  98. package/dist/responses.js +199 -90
  99. package/dist/responses.js.map +1 -1
  100. package/dist/server.cjs +156 -66
  101. package/dist/server.cjs.map +1 -1
  102. package/dist/server.d.cts +1 -1
  103. package/dist/server.d.cts.map +1 -1
  104. package/dist/server.d.ts +1 -1
  105. package/dist/server.d.ts.map +1 -1
  106. package/dist/server.js +157 -68
  107. package/dist/server.js.map +1 -1
  108. package/dist/sse-writer.cjs +3 -0
  109. package/dist/sse-writer.cjs.map +1 -1
  110. package/dist/sse-writer.d.cts +8 -0
  111. package/dist/sse-writer.d.cts.map +1 -1
  112. package/dist/sse-writer.d.ts +8 -0
  113. package/dist/sse-writer.d.ts.map +1 -1
  114. package/dist/sse-writer.js +3 -0
  115. package/dist/sse-writer.js.map +1 -1
  116. package/dist/stream-collapse.cjs +26 -0
  117. package/dist/stream-collapse.cjs.map +1 -1
  118. package/dist/stream-collapse.d.cts +7 -0
  119. package/dist/stream-collapse.d.cts.map +1 -1
  120. package/dist/stream-collapse.d.ts +7 -0
  121. package/dist/stream-collapse.d.ts.map +1 -1
  122. package/dist/stream-collapse.js +26 -0
  123. package/dist/stream-collapse.js.map +1 -1
  124. package/dist/transcription.cjs +49 -2
  125. package/dist/transcription.cjs.map +1 -1
  126. package/dist/transcription.d.cts.map +1 -1
  127. package/dist/transcription.d.ts.map +1 -1
  128. package/dist/transcription.js +50 -3
  129. package/dist/transcription.js.map +1 -1
  130. package/dist/types.d.cts +187 -5
  131. package/dist/types.d.cts.map +1 -1
  132. package/dist/types.d.ts +187 -5
  133. package/dist/types.d.ts.map +1 -1
  134. package/dist/veo-video.cjs +9 -2
  135. package/dist/veo-video.cjs.map +1 -1
  136. package/dist/veo-video.js +10 -3
  137. package/dist/veo-video.js.map +1 -1
  138. package/dist/ws-realtime.cjs +306 -57
  139. package/dist/ws-realtime.cjs.map +1 -1
  140. package/dist/ws-realtime.d.cts +1 -0
  141. package/dist/ws-realtime.d.cts.map +1 -1
  142. package/dist/ws-realtime.d.ts +1 -0
  143. package/dist/ws-realtime.d.ts.map +1 -1
  144. package/dist/ws-realtime.js +307 -58
  145. package/dist/ws-realtime.js.map +1 -1
  146. package/dist/ws-responses.cjs +11 -6
  147. package/dist/ws-responses.cjs.map +1 -1
  148. package/dist/ws-responses.d.cts.map +1 -1
  149. package/dist/ws-responses.d.ts.map +1 -1
  150. package/dist/ws-responses.js +12 -7
  151. package/dist/ws-responses.js.map +1 -1
  152. package/package.json +1 -1
  153. package/skills/write-fixtures/SKILL.md +53 -19
package/dist/server.d.cts CHANGED
@@ -29,7 +29,7 @@ declare function createServer(fixtures: Fixture[], options?: MockServerOptions,
29
29
  path: string;
30
30
  handler: Mountable;
31
31
  }>, serviceFixtures?: ServiceFixtures): Promise<ServerInstance>;
32
- //# sourceMappingURL=server.d.ts.map
32
+ /** @internal Config startup uses this to avoid parsing a selected auth source twice. */
33
33
 
34
34
  //#endregion
35
35
  export { ServerInstance, ServiceFixtures, createServer };
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.cts","names":[],"sources":["../src/server.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;UA+FiB,cAAA;UACP,MAAA,CAAK;EADE,OAAA,EAEN,OAFM;EAAc,GAAA,EAAA,MAAA;UACrB,EAGE,eAHG;aACJ,EAGI,aAHJ;qBAEC,EAEW,qBAFX;cACG,EAEC,cAFD;eACQ,EAEN,eAFM;;AAEN,UAq+BA,eAAA,CAr+BA;EAAe,MAAA,EAs+BtB,aAt+BsB,EAAA;EAq+Bf,MAAA,EAEP,aAFsB,EAAA;EAAA,UAAA,EAGlB,iBAHkB,EAAA;;AAEtB,iBAOY,YAAA,CAPZ,QAAA,EAQE,OARF,EAAA,EAAA,OAAA,CAAA,EASE,iBATF,EAAA,MAOV,CAPU,EAUC,KAVD,CAAA;MACI,EAAA,MAAA;EAAiB,OAAA,EASW,SATX;AAM/B,CAAA,CAAA,EAAsB,eAAY,CAAA,EAId,eAJc,CAAA,EAK/B,OAL+B,CAKvB,cALuB,CAAA"}
1
+ {"version":3,"file":"server.d.cts","names":[],"sources":["../src/server.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;UAyHiB,cAAA;EAAA,MAAA,EACP,MAAA,CAAK,MADgB;EAAA,OAAA,EAEpB,OAFoB;KACrB,EAAA,MAAK;UACJ,EAEC,eAFD;aAEC,EACG,aADH;qBACG,EACQ,qBADR;cACQ,EACP,cADO;eACP,EACC,eADD;;AACgB,UAyqCf,eAAA,CAzqCe;EAyqCf,MAAA,EACP,aADsB,EAAA;EAAA,MAAA,EAEtB,aAFsB,EAAA;YACtB,EAEI,iBAFJ,EAAA;;AAEI,iBAMQ,YAAA,CANR,QAAA,EAOF,OAPE,EAAA,EAAA,OAAA,CAAA,EAQF,iBARE,EAAA,MAMoB,CANpB,EASH,KATG,CAAA;EAAiB,IAAA,EAAA,MAAA;EAMT,OAAA,EAGoB,SAHR;CAAA,CAAA,EAAA,eAAA,CAAA,EAId,eAJc,CAAA,EAK/B,OAL+B,CAKvB,cALuB,CAAA"}
package/dist/server.d.ts CHANGED
@@ -29,7 +29,7 @@ declare function createServer(fixtures: Fixture[], options?: MockServerOptions,
29
29
  path: string;
30
30
  handler: Mountable;
31
31
  }>, serviceFixtures?: ServiceFixtures): Promise<ServerInstance>;
32
- //# sourceMappingURL=server.d.ts.map
32
+ /** @internal Config startup uses this to avoid parsing a selected auth source twice. */
33
33
 
34
34
  //#endregion
35
35
  export { ServerInstance, ServiceFixtures, createServer };
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","names":[],"sources":["../src/server.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;UA+FiB,cAAA;UACP,MAAA,CAAK;EADE,OAAA,EAEN,OAFM;EAAc,GAAA,EAAA,MAAA;UACrB,EAGE,eAHG;aACJ,EAGI,aAHJ;qBAEC,EAEW,qBAFX;cACG,EAEC,cAFD;eACQ,EAEN,eAFM;;AAEN,UAq+BA,eAAA,CAr+BA;EAAe,MAAA,EAs+BtB,aAt+BsB,EAAA;EAq+Bf,MAAA,EAEP,aAFsB,EAAA;EAAA,UAAA,EAGlB,iBAHkB,EAAA;;AAEtB,iBAOY,YAAA,CAPZ,QAAA,EAQE,OARF,EAAA,EAAA,OAAA,CAAA,EASE,iBATF,EAAA,MAOV,CAPU,EAUC,KAVD,CAAA;MACI,EAAA,MAAA;EAAiB,OAAA,EASW,SATX;AAM/B,CAAA,CAAA,EAAsB,eAAY,CAAA,EAId,eAJc,CAAA,EAK/B,OAL+B,CAKvB,cALuB,CAAA"}
1
+ {"version":3,"file":"server.d.ts","names":[],"sources":["../src/server.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;UAyHiB,cAAA;EAAA,MAAA,EACP,MAAA,CAAK,MADgB;EAAA,OAAA,EAEpB,OAFoB;KACrB,EAAA,MAAK;UACJ,EAEC,eAFD;aAEC,EACG,aADH;qBACG,EACQ,qBADR;cACQ,EACP,cADO;eACP,EACC,eADD;;AACgB,UAyqCf,eAAA,CAzqCe;EAyqCf,MAAA,EACP,aADsB,EAAA;EAAA,MAAA,EAEtB,aAFsB,EAAA;YACtB,EAEI,iBAFJ,EAAA;;AAEI,iBAMQ,YAAA,CANR,QAAA,EAOF,OAPE,EAAA,EAAA,OAAA,CAAA,EAQF,iBARE,EAAA,MAMoB,CANpB,EASH,KATG,CAAA;EAAiB,IAAA,EAAA,MAAA;EAMT,OAAA,EAGoB,SAHR;CAAA,CAAA,EAAA,eAAA,CAAA,EAId,eAJc,CAAA,EAK/B,OAL+B,CAKvB,cALuB,CAAA"}
package/dist/server.js CHANGED
@@ -1,10 +1,12 @@
1
- import { buildContentWithToolCallsChunks, buildContentWithToolCallsCompletion, buildTextChunks, buildTextCompletion, buildToolCallChunks, buildToolCallCompletion, buildUsageChunk, estimatePromptTokens, estimateTokens, extractOverrides, flattenHeaders, getContext, getTestId, isAudioResponse, isContentWithToolCallsResponse, isErrorResponse, isTextResponse, isToolCallResponse, readBody, resolveReasoningForModel, resolveResponse, resolveStrictMode, serializeErrorResponse, strictNoMatchLogLine, strictNoMatchMessage, strictOverrideField } from "./helpers.js";
1
+ import { markAuthenticatedRequest, resolveInboundAuth, validateRequestApiKey, writeApiKeyHttpRejection, writeApiKeyUpgradeRejection } from "./api-key-auth.js";
2
+ import { buildContentWithToolCallsChunks, buildContentWithToolCallsCompletion, buildTextChunks, buildTextCompletion, buildToolCallChunks, buildToolCallCompletion, buildUsageChunk, extractOverrides, flattenHeaders, getContext, getTestId, isAudioResponse, isContentWithToolCallsResponse, isErrorResponse, isTextResponse, isToolCallResponse, readBody, resolveFixtureBlocks, resolveReasoningForModel, resolveResponse, resolveStrictMode, resolveUsage, serializeErrorResponse, strictNoMatchLogLine, strictNoMatchMessage, strictOverrideField } from "./helpers.js";
2
3
  import { Logger } from "./logger.js";
3
4
  import { Journal } from "./journal.js";
4
5
  import { matchFixtureDiagnostic, recordMatchOptions } from "./router.js";
5
6
  import { entryToFixture, validateFixtures } from "./fixture-loader.js";
6
7
  import { writeErrorResponse, writeSSEStream } from "./sse-writer.js";
7
8
  import { createInterruptionSignal } from "./interruption.js";
9
+ import { buildOpenRouterCandidates, handleOpenRouterCredits, handleOpenRouterKey, handleOpenRouterModels, isOpenRouterPath, resolveOpenRouterShaping, serializeOpenRouterError, shapeOpenRouterChunks, shapeOpenRouterCompletion } from "./openrouter-chat.js";
8
10
  import { applyChaosAction, evaluateChaos } from "./chaos.js";
9
11
  import { proxyAndRecord } from "./recorder.js";
10
12
  import { handleResponses } from "./responses.js";
@@ -309,7 +311,7 @@ async function handleControlAPI(req, res, pathname, fixtures, journal, videoStat
309
311
  handleNotFound(res, `Unknown control endpoint: ${pathname}`);
310
312
  return true;
311
313
  }
312
- async function handleCompletions(req, res, fixtures, journal, defaults, modelFallback, providerKey) {
314
+ async function handleCompletions(req, res, fixtures, journal, defaults, modelFallback, providerKey, openRouter = false) {
313
315
  setCorsHeaders(res);
314
316
  let raw;
315
317
  try {
@@ -326,7 +328,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
326
328
  fixture: null
327
329
  }
328
330
  });
329
- writeErrorResponse(res, 500, JSON.stringify({ error: {
331
+ writeErrorResponse(res, 500, openRouter ? serializeOpenRouterError(500, `Request body read failed: ${msg}`) : JSON.stringify({ error: {
330
332
  message: `Request body read failed: ${msg}`,
331
333
  type: "server_error"
332
334
  } }));
@@ -348,7 +350,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
348
350
  fixture: null
349
351
  }
350
352
  });
351
- writeErrorResponse(res, 400, JSON.stringify({ error: {
353
+ writeErrorResponse(res, 400, openRouter ? serializeOpenRouterError(400, `Malformed JSON: ${detail}`) : JSON.stringify({ error: {
352
354
  message: `Malformed JSON: ${detail}`,
353
355
  type: "invalid_request_error",
354
356
  param: null,
@@ -367,7 +369,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
367
369
  fixture: null
368
370
  }
369
371
  });
370
- writeErrorResponse(res, 400, JSON.stringify({ error: {
372
+ writeErrorResponse(res, 400, openRouter ? serializeOpenRouterError(400, "Missing required parameter: 'messages'") : JSON.stringify({ error: {
371
373
  message: "Missing required parameter: 'messages'",
372
374
  type: "invalid_request_error",
373
375
  param: null,
@@ -381,9 +383,66 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
381
383
  body._endpointType = "chat";
382
384
  body._context = getContext(req);
383
385
  const testId = getTestId(req);
384
- const { fixture, skippedBySequenceOrTurn } = matchFixtureDiagnostic(fixtures, body, journal.getFixtureMatchCountsForTest(testId), defaults.requestTransform, recordMatchOptions(!!(defaults.record && providerKey), defaults.logger));
386
+ const matchOptions = recordMatchOptions(!!(defaults.record && providerKey), defaults.logger);
387
+ let fixture = null;
388
+ let skippedBySequenceOrTurn = 0;
389
+ let preResolvedResponse = null;
390
+ let responseModel = body.model;
391
+ let fixtureCountIncremented = false;
392
+ if (openRouter && Array.isArray(body.models) && body.models.length > 0) {
393
+ const candidates = buildOpenRouterCandidates(body);
394
+ let matchCounts = journal.getFixtureMatchCountsForTest(testId);
395
+ const allowFallbacks = body.provider?.allow_fallbacks !== false;
396
+ let lastErrorFixture = null;
397
+ let lastErrorResponse = null;
398
+ const resolveCandidate = async (candidate) => {
399
+ const probe = {
400
+ ...body,
401
+ model: candidate
402
+ };
403
+ const attempt = matchFixtureDiagnostic(fixtures, probe, matchCounts, defaults.requestTransform, matchOptions);
404
+ skippedBySequenceOrTurn = Math.max(skippedBySequenceOrTurn, attempt.skippedBySequenceOrTurn);
405
+ if (!attempt.fixture) return null;
406
+ const response = await resolveResponse(attempt.fixture, probe);
407
+ return {
408
+ fixture: attempt.fixture,
409
+ response,
410
+ isError: isErrorResponse(response)
411
+ };
412
+ };
413
+ for (const candidate of candidates) {
414
+ const outcome = await resolveCandidate(candidate);
415
+ if (!outcome) {
416
+ if (!allowFallbacks) break;
417
+ continue;
418
+ }
419
+ journal.incrementFixtureMatchCount(outcome.fixture, fixtures, testId);
420
+ fixtureCountIncremented = true;
421
+ matchCounts = journal.getFixtureMatchCountsForTest(testId);
422
+ responseModel = candidate;
423
+ if (outcome.isError) {
424
+ const errorFallsThrough = outcome.response.fallthrough !== false;
425
+ if (allowFallbacks && errorFallsThrough) {
426
+ lastErrorFixture = outcome.fixture;
427
+ lastErrorResponse = outcome.response;
428
+ continue;
429
+ }
430
+ }
431
+ fixture = outcome.fixture;
432
+ preResolvedResponse = outcome.response;
433
+ break;
434
+ }
435
+ if (!fixture && lastErrorFixture) {
436
+ fixture = lastErrorFixture;
437
+ preResolvedResponse = lastErrorResponse;
438
+ }
439
+ } else {
440
+ const single = matchFixtureDiagnostic(fixtures, body, journal.getFixtureMatchCountsForTest(testId), defaults.requestTransform, matchOptions);
441
+ fixture = single.fixture;
442
+ skippedBySequenceOrTurn = single.skippedBySequenceOrTurn;
443
+ }
385
444
  if (fixture) {
386
- journal.incrementFixtureMatchCount(fixture, fixtures, testId);
445
+ if (!fixtureCountIncremented) journal.incrementFixtureMatchCount(fixture, fixtures, testId);
387
446
  defaults.logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);
388
447
  } else {
389
448
  const lastUserMsg = body.messages.filter((m) => m.role === "user").pop();
@@ -421,7 +480,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
421
480
  ...strictOverrideField(defaults.strict, req.headers)
422
481
  }
423
482
  });
424
- writeErrorResponse(res, strictStatus, JSON.stringify({ error: {
483
+ writeErrorResponse(res, strictStatus, openRouter ? serializeOpenRouterError(strictStatus, strictMessage) : JSON.stringify({ error: {
425
484
  message: strictMessage,
426
485
  type: "invalid_request_error",
427
486
  param: null,
@@ -472,7 +531,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
472
531
  ...strictOverrideField(defaults.strict, req.headers)
473
532
  }
474
533
  });
475
- writeErrorResponse(res, 404, JSON.stringify({ error: {
534
+ writeErrorResponse(res, 404, openRouter ? serializeOpenRouterError(404, "No fixture matched") : JSON.stringify({ error: {
476
535
  message: "No fixture matched",
477
536
  type: "invalid_request_error",
478
537
  param: null,
@@ -480,10 +539,23 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
480
539
  } }));
481
540
  return;
482
541
  }
483
- const response = await resolveResponse(fixture, body);
542
+ const response = preResolvedResponse ?? await resolveResponse(fixture, body);
484
543
  const latency = fixture.latency ?? defaults.latency;
485
544
  const chunkSize = Math.max(1, fixture.chunkSize ?? defaults.chunkSize);
486
- const includeUsage = body.stream === true && body.stream_options?.include_usage === true;
545
+ const emitStreamingUsage = body.stream === true && body.stream_options?.include_usage === true || openRouter;
546
+ const streamingPromptText = body.messages.map((m) => typeof m.content === "string" ? m.content : Array.isArray(m.content) ? m.content.map((p) => p.text ?? "").join("") : "").join("");
547
+ const resolveStreamingUsageTokens = (overrides, completionText) => resolveUsage(overrides, streamingPromptText, completionText);
548
+ const shapeORCompletion = (completion, overrides) => {
549
+ if (!openRouter) return completion;
550
+ return shapeOpenRouterCompletion(completion, resolveOpenRouterShaping(overrides, overrides?.model ?? responseModel), overrides?.id !== void 0);
551
+ };
552
+ const shapeORChunks = (chunks, usageChunk, overrides) => {
553
+ if (!openRouter) return;
554
+ const shaping = resolveOpenRouterShaping(overrides, overrides?.model ?? responseModel);
555
+ shapeOpenRouterChunks(chunks, shaping, overrides?.id !== void 0);
556
+ if (usageChunk) shapeOpenRouterChunks([usageChunk], shaping, overrides?.id !== void 0);
557
+ };
558
+ const openRouterProcessing = !!(openRouter && fixture.openRouterProcessing);
487
559
  if (isErrorResponse(response)) {
488
560
  const status = response.status ?? 500;
489
561
  journal.add({
@@ -496,7 +568,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
496
568
  fixture
497
569
  }
498
570
  });
499
- writeErrorResponse(res, status, serializeErrorResponse(response), { retryAfter: response.retryAfter });
571
+ writeErrorResponse(res, status, openRouter ? serializeOpenRouterError(status, response.error.message, response.error.metadata) : serializeErrorResponse(response), { retryAfter: response.retryAfter });
500
572
  return;
501
573
  }
502
574
  if (isAudioResponse(response)) {
@@ -510,7 +582,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
510
582
  fixture
511
583
  }
512
584
  });
513
- writeErrorResponse(res, 422, JSON.stringify({ error: {
585
+ writeErrorResponse(res, 422, openRouter ? serializeOpenRouterError(422, "Audio responses are not supported on the chat completions endpoint. Use Gemini generateContent or a dedicated audio endpoint.") : JSON.stringify({ error: {
514
586
  message: "Audio responses are not supported on the chat completions endpoint. Use Gemini generateContent or a dedicated audio endpoint.",
515
587
  type: "invalid_request_error"
516
588
  } }));
@@ -520,7 +592,9 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
520
592
  if (response.webSearches?.length) defaults.logger.warn("webSearches in fixture response are not supported for Chat Completions API — ignoring");
521
593
  const overrides = extractOverrides(response);
522
594
  const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);
523
- const effReasoning = resolveReasoningForModel(response.reasoning, body.model, effectiveStrict, defaults.logger);
595
+ const effReasoning = resolveReasoningForModel(response.reasoning, responseModel, effectiveStrict, defaults.logger);
596
+ const streaming = body.stream === true;
597
+ const streamingBlocks = streaming && response.blocks && response.blocks.length > 0 ? resolveFixtureBlocks(response.blocks) : void 0;
524
598
  const journalEntry = journal.add({
525
599
  method: req.method ?? "POST",
526
600
  path: req.url ?? COMPLETIONS_PATH,
@@ -531,22 +605,15 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
531
605
  fixture
532
606
  }
533
607
  });
534
- if (body.stream !== true) {
535
- const completion = buildContentWithToolCallsCompletion(response.content ?? "", response.toolCalls ?? [], body.model, effReasoning, overrides, body.messages);
608
+ if (!streaming) {
609
+ const completion = buildContentWithToolCallsCompletion(response.content ?? "", response.toolCalls ?? [], responseModel, effReasoning, overrides, body.messages);
536
610
  res.writeHead(200, { "Content-Type": "application/json" });
537
- res.end(JSON.stringify(completion));
611
+ res.end(JSON.stringify(shapeORCompletion(completion, overrides)));
538
612
  } else {
539
- const chunks = buildContentWithToolCallsChunks(response.content ?? "", response.toolCalls ?? [], body.model, chunkSize, effReasoning, overrides, response.blocks);
540
- const completionText = (response.content ?? "") + (response.toolCalls ?? []).map((tc) => tc.name + tc.arguments).join("");
541
- const usageChunk = includeUsage ? buildUsageChunk(chunks[0]?.id ?? "chatcmpl-unknown", overrides?.model ?? body.model, chunks[0]?.created ?? Math.floor(Date.now() / 1e3), overrides?.usage ? {
542
- prompt_tokens: overrides.usage.prompt_tokens ?? 0,
543
- completion_tokens: overrides.usage.completion_tokens ?? 0,
544
- total_tokens: overrides.usage.total_tokens ?? (overrides.usage.prompt_tokens ?? 0) + (overrides.usage.completion_tokens ?? 0)
545
- } : {
546
- prompt_tokens: estimatePromptTokens(body.messages),
547
- completion_tokens: estimateTokens(completionText),
548
- total_tokens: estimatePromptTokens(body.messages) + estimateTokens(completionText)
549
- }, overrides?.systemFingerprint) : void 0;
613
+ const chunks = buildContentWithToolCallsChunks(response.content ?? "", response.toolCalls ?? [], responseModel, chunkSize, effReasoning, overrides, streamingBlocks);
614
+ const completionText = streamingBlocks ? streamingBlocks.map((b) => b.type === "text" ? b.text : b.name + b.arguments).join("") : (response.content ?? "") + (response.toolCalls ?? []).map((tc) => tc.name + tc.arguments).join("");
615
+ const usageChunk = emitStreamingUsage ? buildUsageChunk(chunks[0]?.id ?? "chatcmpl-unknown", overrides?.model ?? responseModel, chunks[0]?.created ?? Math.floor(Date.now() / 1e3), resolveStreamingUsageTokens(overrides, completionText), overrides?.systemFingerprint) : void 0;
616
+ shapeORChunks(chunks, usageChunk, overrides);
550
617
  const interruption = createInterruptionSignal(fixture);
551
618
  if (!await writeSSEStream(res, chunks, {
552
619
  latency,
@@ -555,7 +622,8 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
555
622
  onChunkSent: interruption?.tick,
556
623
  usageChunk,
557
624
  recordedTimings: fixture.recordedTimings,
558
- replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed
625
+ replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,
626
+ openRouterProcessing
559
627
  })) {
560
628
  if (!res.writableEnded) res.destroy();
561
629
  journalEntry.response.interrupted = true;
@@ -569,7 +637,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
569
637
  if (response.webSearches?.length) defaults.logger.warn("webSearches in fixture response are not supported for Chat Completions API — ignoring");
570
638
  const overrides = extractOverrides(response);
571
639
  const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);
572
- const effReasoning = resolveReasoningForModel(response.reasoning, body.model, effectiveStrict, defaults.logger);
640
+ const effReasoning = resolveReasoningForModel(response.reasoning, responseModel, effectiveStrict, defaults.logger);
573
641
  const journalEntry = journal.add({
574
642
  method: req.method ?? "POST",
575
643
  path: req.url ?? COMPLETIONS_PATH,
@@ -581,20 +649,13 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
581
649
  }
582
650
  });
583
651
  if (body.stream !== true) {
584
- const completion = buildTextCompletion(response.content, body.model, effReasoning, overrides, body.messages);
652
+ const completion = buildTextCompletion(response.content, responseModel, effReasoning, overrides, body.messages);
585
653
  res.writeHead(200, { "Content-Type": "application/json" });
586
- res.end(JSON.stringify(completion));
654
+ res.end(JSON.stringify(shapeORCompletion(completion, overrides)));
587
655
  } else {
588
- const chunks = buildTextChunks(response.content, body.model, chunkSize, effReasoning, overrides);
589
- const usageChunk = includeUsage ? buildUsageChunk(chunks[0]?.id ?? "chatcmpl-unknown", overrides?.model ?? body.model, chunks[0]?.created ?? Math.floor(Date.now() / 1e3), overrides?.usage ? {
590
- prompt_tokens: overrides.usage.prompt_tokens ?? 0,
591
- completion_tokens: overrides.usage.completion_tokens ?? 0,
592
- total_tokens: overrides.usage.total_tokens ?? (overrides.usage.prompt_tokens ?? 0) + (overrides.usage.completion_tokens ?? 0)
593
- } : {
594
- prompt_tokens: estimatePromptTokens(body.messages),
595
- completion_tokens: estimateTokens(response.content),
596
- total_tokens: estimatePromptTokens(body.messages) + estimateTokens(response.content)
597
- }, overrides?.systemFingerprint) : void 0;
656
+ const chunks = buildTextChunks(response.content, responseModel, chunkSize, effReasoning, overrides);
657
+ const usageChunk = emitStreamingUsage ? buildUsageChunk(chunks[0]?.id ?? "chatcmpl-unknown", overrides?.model ?? responseModel, chunks[0]?.created ?? Math.floor(Date.now() / 1e3), resolveStreamingUsageTokens(overrides, response.content), overrides?.systemFingerprint) : void 0;
658
+ shapeORChunks(chunks, usageChunk, overrides);
598
659
  const interruption = createInterruptionSignal(fixture);
599
660
  if (!await writeSSEStream(res, chunks, {
600
661
  latency,
@@ -603,7 +664,8 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
603
664
  onChunkSent: interruption?.tick,
604
665
  usageChunk,
605
666
  recordedTimings: fixture.recordedTimings,
606
- replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed
667
+ replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,
668
+ openRouterProcessing
607
669
  })) {
608
670
  if (!res.writableEnded) res.destroy();
609
671
  journalEntry.response.interrupted = true;
@@ -617,7 +679,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
617
679
  if (response.webSearches?.length) defaults.logger.warn("webSearches in fixture response are not supported for Chat Completions API — ignoring");
618
680
  const overrides = extractOverrides(response);
619
681
  const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);
620
- const effReasoning = resolveReasoningForModel(response.reasoning, body.model, effectiveStrict, defaults.logger);
682
+ const effReasoning = resolveReasoningForModel(response.reasoning, responseModel, effectiveStrict, defaults.logger);
621
683
  const journalEntry = journal.add({
622
684
  method: req.method ?? "POST",
623
685
  path: req.url ?? COMPLETIONS_PATH,
@@ -629,21 +691,14 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
629
691
  }
630
692
  });
631
693
  if (body.stream !== true) {
632
- const completion = buildToolCallCompletion(response.toolCalls, body.model, effReasoning, overrides, body.messages);
694
+ const completion = buildToolCallCompletion(response.toolCalls, responseModel, effReasoning, overrides, body.messages);
633
695
  res.writeHead(200, { "Content-Type": "application/json" });
634
- res.end(JSON.stringify(completion));
696
+ res.end(JSON.stringify(shapeORCompletion(completion, overrides)));
635
697
  } else {
636
- const chunks = buildToolCallChunks(response.toolCalls, body.model, chunkSize, effReasoning, overrides);
698
+ const chunks = buildToolCallChunks(response.toolCalls, responseModel, chunkSize, effReasoning, overrides);
637
699
  const completionText = response.toolCalls.map((tc) => tc.name + tc.arguments).join("");
638
- const usageChunk = includeUsage ? buildUsageChunk(chunks[0]?.id ?? "chatcmpl-unknown", overrides?.model ?? body.model, chunks[0]?.created ?? Math.floor(Date.now() / 1e3), overrides?.usage ? {
639
- prompt_tokens: overrides.usage.prompt_tokens ?? 0,
640
- completion_tokens: overrides.usage.completion_tokens ?? 0,
641
- total_tokens: overrides.usage.total_tokens ?? (overrides.usage.prompt_tokens ?? 0) + (overrides.usage.completion_tokens ?? 0)
642
- } : {
643
- prompt_tokens: estimatePromptTokens(body.messages),
644
- completion_tokens: estimateTokens(completionText),
645
- total_tokens: estimatePromptTokens(body.messages) + estimateTokens(completionText)
646
- }, overrides?.systemFingerprint) : void 0;
700
+ const usageChunk = emitStreamingUsage ? buildUsageChunk(chunks[0]?.id ?? "chatcmpl-unknown", overrides?.model ?? responseModel, chunks[0]?.created ?? Math.floor(Date.now() / 1e3), resolveStreamingUsageTokens(overrides, completionText), overrides?.systemFingerprint) : void 0;
701
+ shapeORChunks(chunks, usageChunk, overrides);
647
702
  const interruption = createInterruptionSignal(fixture);
648
703
  if (!await writeSSEStream(res, chunks, {
649
704
  latency,
@@ -652,7 +707,8 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
652
707
  onChunkSent: interruption?.tick,
653
708
  usageChunk,
654
709
  recordedTimings: fixture.recordedTimings,
655
- replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed
710
+ replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,
711
+ openRouterProcessing
656
712
  })) {
657
713
  if (!res.writableEnded) res.destroy();
658
714
  journalEntry.response.interrupted = true;
@@ -672,12 +728,19 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
672
728
  fixture
673
729
  }
674
730
  });
675
- writeErrorResponse(res, 500, JSON.stringify({ error: {
731
+ writeErrorResponse(res, 500, openRouter ? serializeOpenRouterError(500, "Fixture response did not match any known type") : JSON.stringify({ error: {
676
732
  message: "Fixture response did not match any known type",
677
733
  type: "server_error"
678
734
  } }));
679
735
  }
680
736
  async function createServer(fixtures, options, mounts, serviceFixtures) {
737
+ return createServerWithResolvedAuth(fixtures, options, resolveInboundAuth({
738
+ value: options?.auth,
739
+ label: "options.auth"
740
+ }), mounts, serviceFixtures);
741
+ }
742
+ /** @internal Config startup uses this to avoid parsing a selected auth source twice. */
743
+ async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mounts, serviceFixtures) {
681
744
  const host = options?.host ?? "127.0.0.1";
682
745
  const port = options?.port ?? 0;
683
746
  const logger = new Logger(options?.logLevel ?? "silent");
@@ -791,13 +854,10 @@ async function createServer(fixtures, options, mounts, serviceFixtures) {
791
854
  });
792
855
  });
793
856
  async function handleHttpRequest(req, res) {
794
- if (req.method === "OPTIONS") {
795
- handleOptions(res);
796
- return;
797
- }
798
857
  const startTime = registry ? process.hrtime.bigint() : 0n;
799
858
  const parsedUrl = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
800
859
  let pathname = parsedUrl.pathname;
860
+ const isOpenRouter = isOpenRouterPath(pathname);
801
861
  if (registry) res.on("finish", () => {
802
862
  try {
803
863
  const normalizedPath = normalizePathLabel(pathname);
@@ -817,6 +877,17 @@ async function createServer(fixtures, options, mounts, serviceFixtures) {
817
877
  defaults.logger.warn("metrics instrumentation error", err);
818
878
  }
819
879
  });
880
+ if (req.method === "OPTIONS" && (!resolvedAuth.policy.enabled || req.headers.origin !== void 0 && req.headers["access-control-request-method"] !== void 0)) {
881
+ handleOptions(res);
882
+ return;
883
+ }
884
+ if (!(req.method === "GET" && (pathname === HEALTH_PATH || pathname === READY_PATH || pathname === "/metrics"))) {
885
+ if (!validateRequestApiKey(req, resolvedAuth.policy).ok) {
886
+ writeApiKeyHttpRejection(res, setCorsHeaders);
887
+ return;
888
+ }
889
+ markAuthenticatedRequest(req, resolvedAuth.policy);
890
+ }
820
891
  if (pathname.startsWith(CONTROL_PREFIX)) {
821
892
  await handleControlAPI(req, res, pathname, fixtures, journal, videoStates, openRouterVideoJobs, veoVideoJobs, grokVideoJobs, defaults);
822
893
  return;
@@ -1001,6 +1072,18 @@ async function createServer(fixtures, options, mounts, serviceFixtures) {
1001
1072
  }
1002
1073
  return;
1003
1074
  }
1075
+ if (pathname === "/api/v1/models" && req.method === "GET") {
1076
+ handleOpenRouterModels(req, res, fixtures, journal, defaults, setCorsHeaders);
1077
+ return;
1078
+ }
1079
+ if (pathname === "/api/v1/key" && req.method === "GET") {
1080
+ handleOpenRouterKey(req, res, journal, setCorsHeaders);
1081
+ return;
1082
+ }
1083
+ if (pathname === "/api/v1/credits" && req.method === "GET") {
1084
+ handleOpenRouterCredits(req, res, journal, setCorsHeaders);
1085
+ return;
1086
+ }
1004
1087
  let azureDeploymentId;
1005
1088
  const azureMatch = pathname.match(AZURE_DEPLOYMENT_RE);
1006
1089
  if (azureMatch && req.method === "POST") {
@@ -1726,17 +1809,17 @@ async function createServer(fixtures, options, mounts, serviceFixtures) {
1726
1809
  handleNotFound(res, "Not found");
1727
1810
  return;
1728
1811
  }
1729
- const completionsProvider = azureDeploymentId ? "azure" : "openai";
1812
+ const completionsProvider = azureDeploymentId ? "azure" : isOpenRouter ? "openrouter" : "openai";
1730
1813
  try {
1731
- await handleCompletions(req, res, fixtures, journal, defaults, azureDeploymentId, completionsProvider);
1814
+ await handleCompletions(req, res, fixtures, journal, defaults, azureDeploymentId, completionsProvider, isOpenRouter);
1732
1815
  } catch (err) {
1733
1816
  const msg = err instanceof Error ? err.message : "Internal error";
1734
- if (!res.headersSent) writeErrorResponse(res, 500, JSON.stringify({ error: {
1817
+ if (!res.headersSent) writeErrorResponse(res, 500, isOpenRouter ? serializeOpenRouterError(500, msg) : JSON.stringify({ error: {
1735
1818
  message: msg,
1736
1819
  type: "server_error"
1737
1820
  } }));
1738
1821
  else if (!res.writableEnded) try {
1739
- res.write(`data: ${JSON.stringify({ error: {
1822
+ res.write(`data: ${isOpenRouter ? serializeOpenRouterError(500, msg) : JSON.stringify({ error: {
1740
1823
  message: msg,
1741
1824
  type: "server_error"
1742
1825
  } })}\n\n`);
@@ -1757,6 +1840,10 @@ async function createServer(fixtures, options, mounts, serviceFixtures) {
1757
1840
  async function handleUpgradeRequest(req, socket, head) {
1758
1841
  const parsedUrl = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
1759
1842
  let pathname = parsedUrl.pathname;
1843
+ if (!validateRequestApiKey(req, resolvedAuth.policy).ok) {
1844
+ writeApiKeyUpgradeRejection(socket);
1845
+ return;
1846
+ }
1760
1847
  if (mounts) {
1761
1848
  for (const { path: mountPath, handler } of mounts) if ((pathname === mountPath || pathname.startsWith(mountPath + "/")) && handler.handleUpgrade) {
1762
1849
  const subPath = pathname.slice(mountPath.length) || "/";
@@ -1795,10 +1882,12 @@ async function createServer(fixtures, options, mounts, serviceFixtures) {
1795
1882
  upgradeHeaders: req.headers
1796
1883
  });
1797
1884
  else if (pathname === REALTIME_PATH) {
1798
- const model = parsedUrl.searchParams.get("model") ?? "gpt-realtime-2";
1885
+ const transcriptionIntent = parsedUrl.searchParams.get("intent") === "transcription";
1886
+ const model = transcriptionIntent ? "gpt-transcribe" : parsedUrl.searchParams.get("model") ?? "gpt-realtime-2";
1799
1887
  handleWebSocketRealtime(ws, fixtures, journal, {
1800
1888
  ...defaults,
1801
1889
  model,
1890
+ transcriptionIntent,
1802
1891
  testId: wsTestId,
1803
1892
  upgradeHeaders: req.headers
1804
1893
  });
@@ -1847,5 +1936,5 @@ async function createServer(fixtures, options, mounts, serviceFixtures) {
1847
1936
  }
1848
1937
 
1849
1938
  //#endregion
1850
- export { createServer };
1939
+ export { createServer, createServerWithResolvedAuth };
1851
1940
  //# sourceMappingURL=server.js.map