@copilotkit/aimock 1.37.4 → 1.39.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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +48 -0
- package/README.md +50 -11
- package/dist/agui-handler.cjs +20 -18
- package/dist/agui-handler.cjs.map +1 -1
- package/dist/agui-handler.d.cts +8 -1
- package/dist/agui-handler.d.cts.map +1 -1
- package/dist/agui-handler.d.ts +8 -1
- package/dist/agui-handler.d.ts.map +1 -1
- package/dist/agui-handler.js +20 -18
- package/dist/agui-handler.js.map +1 -1
- package/dist/agui-recorder.cjs +6 -0
- package/dist/agui-recorder.cjs.map +1 -1
- package/dist/agui-recorder.d.cts.map +1 -1
- package/dist/agui-recorder.d.ts.map +1 -1
- package/dist/agui-recorder.js +6 -0
- package/dist/agui-recorder.js.map +1 -1
- package/dist/agui-stub.d.cts +2 -2
- package/dist/agui-stub.d.ts +2 -2
- package/dist/agui-types.d.cts +19 -1
- package/dist/agui-types.d.cts.map +1 -1
- package/dist/agui-types.d.ts +19 -1
- package/dist/agui-types.d.ts.map +1 -1
- package/dist/aimock-cli.cjs +1 -1
- package/dist/aimock-cli.cjs.map +1 -1
- package/dist/aimock-cli.js +1 -1
- package/dist/aimock-cli.js.map +1 -1
- package/dist/api-key-auth.cjs +166 -0
- package/dist/api-key-auth.cjs.map +1 -0
- package/dist/api-key-auth.d.cts +17 -0
- package/dist/api-key-auth.d.cts.map +1 -0
- package/dist/api-key-auth.d.ts +17 -0
- package/dist/api-key-auth.d.ts.map +1 -0
- package/dist/api-key-auth.js +158 -0
- package/dist/api-key-auth.js.map +1 -0
- package/dist/cli.cjs +14 -11
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +14 -11
- package/dist/cli.js.map +1 -1
- package/dist/config-loader.cjs +4 -2
- package/dist/config-loader.cjs.map +1 -1
- package/dist/config-loader.d.cts +2 -1
- package/dist/config-loader.d.cts.map +1 -1
- package/dist/config-loader.d.ts +2 -1
- package/dist/config-loader.d.ts.map +1 -1
- package/dist/config-loader.js +5 -3
- package/dist/config-loader.js.map +1 -1
- package/dist/fal-audio.cjs +2 -0
- package/dist/fal-audio.cjs.map +1 -1
- package/dist/fal-audio.d.cts.map +1 -1
- package/dist/fal-audio.d.ts.map +1 -1
- package/dist/fal-audio.js +2 -0
- package/dist/fal-audio.js.map +1 -1
- package/dist/fal.cjs +4 -1
- package/dist/fal.cjs.map +1 -1
- package/dist/fal.d.cts.map +1 -1
- package/dist/fal.d.ts.map +1 -1
- package/dist/fal.js +4 -1
- package/dist/fal.js.map +1 -1
- package/dist/fixture-loader.cjs +0 -0
- package/dist/fixture-loader.cjs.map +1 -1
- package/dist/fixture-loader.d.cts +3 -1
- package/dist/fixture-loader.d.cts.map +1 -1
- package/dist/fixture-loader.d.ts +3 -1
- package/dist/fixture-loader.d.ts.map +1 -1
- package/dist/fixture-loader.js +0 -0
- package/dist/fixture-loader.js.map +1 -1
- package/dist/grok-video.cjs +9 -2
- package/dist/grok-video.cjs.map +1 -1
- package/dist/grok-video.js +10 -3
- package/dist/grok-video.js.map +1 -1
- package/dist/helpers.cjs +16 -22
- package/dist/helpers.cjs.map +1 -1
- package/dist/helpers.d.cts.map +1 -1
- package/dist/helpers.d.ts.map +1 -1
- package/dist/helpers.js +16 -21
- package/dist/helpers.js.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/llmock.cjs +37 -12
- package/dist/llmock.cjs.map +1 -1
- package/dist/llmock.d.cts +26 -2
- package/dist/llmock.d.cts.map +1 -1
- package/dist/llmock.d.ts +26 -2
- package/dist/llmock.d.ts.map +1 -1
- package/dist/llmock.js +39 -15
- package/dist/llmock.js.map +1 -1
- package/dist/openrouter-chat.cjs +333 -0
- package/dist/openrouter-chat.cjs.map +1 -0
- package/dist/openrouter-chat.js +325 -0
- package/dist/openrouter-chat.js.map +1 -0
- package/dist/openrouter-video.cjs +24 -7
- package/dist/openrouter-video.cjs.map +1 -1
- package/dist/openrouter-video.d.cts.map +1 -1
- package/dist/openrouter-video.d.ts.map +1 -1
- package/dist/openrouter-video.js +25 -8
- package/dist/openrouter-video.js.map +1 -1
- package/dist/provider-auth.cjs +20 -0
- package/dist/provider-auth.cjs.map +1 -1
- package/dist/provider-auth.js +20 -1
- package/dist/provider-auth.js.map +1 -1
- package/dist/recorder.cjs +50 -8
- package/dist/recorder.cjs.map +1 -1
- package/dist/recorder.d.cts.map +1 -1
- package/dist/recorder.d.ts.map +1 -1
- package/dist/recorder.js +50 -10
- package/dist/recorder.js.map +1 -1
- package/dist/responses.cjs +200 -89
- package/dist/responses.cjs.map +1 -1
- package/dist/responses.d.cts +18 -3
- package/dist/responses.d.cts.map +1 -1
- package/dist/responses.d.ts +18 -3
- package/dist/responses.d.ts.map +1 -1
- package/dist/responses.js +199 -90
- package/dist/responses.js.map +1 -1
- package/dist/server.cjs +190 -83
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +7 -2
- package/dist/server.d.cts.map +1 -1
- package/dist/server.d.ts +7 -2
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +190 -85
- package/dist/server.js.map +1 -1
- package/dist/sse-writer.cjs +3 -0
- package/dist/sse-writer.cjs.map +1 -1
- package/dist/sse-writer.d.cts +8 -0
- package/dist/sse-writer.d.cts.map +1 -1
- package/dist/sse-writer.d.ts +8 -0
- package/dist/sse-writer.d.ts.map +1 -1
- package/dist/sse-writer.js +3 -0
- package/dist/sse-writer.js.map +1 -1
- package/dist/stream-collapse.cjs +103 -0
- package/dist/stream-collapse.cjs.map +1 -1
- package/dist/stream-collapse.d.cts +7 -0
- package/dist/stream-collapse.d.cts.map +1 -1
- package/dist/stream-collapse.d.ts +7 -0
- package/dist/stream-collapse.d.ts.map +1 -1
- package/dist/stream-collapse.js +103 -0
- package/dist/stream-collapse.js.map +1 -1
- package/dist/transcription.cjs +49 -2
- package/dist/transcription.cjs.map +1 -1
- package/dist/transcription.d.cts.map +1 -1
- package/dist/transcription.d.ts.map +1 -1
- package/dist/transcription.js +50 -3
- package/dist/transcription.js.map +1 -1
- package/dist/types.d.cts +187 -5
- package/dist/types.d.cts.map +1 -1
- package/dist/types.d.ts +187 -5
- package/dist/types.d.ts.map +1 -1
- package/dist/vector-types.d.ts.map +1 -1
- package/dist/veo-video.cjs +9 -2
- package/dist/veo-video.cjs.map +1 -1
- package/dist/veo-video.js +10 -3
- package/dist/veo-video.js.map +1 -1
- package/dist/ws-gemini-live.cjs +41 -6
- package/dist/ws-gemini-live.cjs.map +1 -1
- package/dist/ws-gemini-live.js +41 -6
- package/dist/ws-gemini-live.js.map +1 -1
- package/dist/ws-realtime.cjs +306 -57
- package/dist/ws-realtime.cjs.map +1 -1
- package/dist/ws-realtime.d.cts +1 -0
- package/dist/ws-realtime.d.cts.map +1 -1
- package/dist/ws-realtime.d.ts +1 -0
- package/dist/ws-realtime.d.ts.map +1 -1
- package/dist/ws-realtime.js +307 -58
- package/dist/ws-realtime.js.map +1 -1
- package/dist/ws-responses.cjs +11 -6
- package/dist/ws-responses.cjs.map +1 -1
- package/dist/ws-responses.d.cts.map +1 -1
- package/dist/ws-responses.d.ts.map +1 -1
- package/dist/ws-responses.js +12 -7
- package/dist/ws-responses.js.map +1 -1
- package/package.json +3 -2
- package/skills/write-fixtures/SKILL.md +53 -19
package/dist/server.d.cts
CHANGED
|
@@ -20,6 +20,12 @@ interface ServerInstance {
|
|
|
20
20
|
veoVideoJobs: VeoVideoJobMap;
|
|
21
21
|
grokVideoJobs: GrokVideoJobMap;
|
|
22
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* The per-server state a full reset clears. `ServerInstance` structurally
|
|
25
|
+
* satisfies this, so `LLMock.reset()` and the control-API full-reset route
|
|
26
|
+
* share a single definition of "everything" instead of two lists that drift.
|
|
27
|
+
*/
|
|
28
|
+
|
|
23
29
|
interface ServiceFixtures {
|
|
24
30
|
search: SearchFixture[];
|
|
25
31
|
rerank: RerankFixture[];
|
|
@@ -29,8 +35,7 @@ declare function createServer(fixtures: Fixture[], options?: MockServerOptions,
|
|
|
29
35
|
path: string;
|
|
30
36
|
handler: Mountable;
|
|
31
37
|
}>, serviceFixtures?: ServiceFixtures): Promise<ServerInstance>;
|
|
32
|
-
|
|
33
|
-
|
|
38
|
+
/** @internal Config startup uses this to avoid parsing a selected auth source twice. */
|
|
34
39
|
//#endregion
|
|
35
40
|
export { ServerInstance, ServiceFixtures, createServer };
|
|
36
41
|
//# sourceMappingURL=server.d.cts.map
|
package/dist/server.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.cts","names":[],"sources":["../src/server.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;
|
|
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;;;AAorChB;;;;;UAAiB,eAAA;UACP;UACA;cACI;;iBAMQ,YAAA,WACV,qBACA,4BACD;;WAA+B;sBACtB,kBACjB,QAAQ"}
|
package/dist/server.d.ts
CHANGED
|
@@ -20,6 +20,12 @@ interface ServerInstance {
|
|
|
20
20
|
veoVideoJobs: VeoVideoJobMap;
|
|
21
21
|
grokVideoJobs: GrokVideoJobMap;
|
|
22
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* The per-server state a full reset clears. `ServerInstance` structurally
|
|
25
|
+
* satisfies this, so `LLMock.reset()` and the control-API full-reset route
|
|
26
|
+
* share a single definition of "everything" instead of two lists that drift.
|
|
27
|
+
*/
|
|
28
|
+
|
|
23
29
|
interface ServiceFixtures {
|
|
24
30
|
search: SearchFixture[];
|
|
25
31
|
rerank: RerankFixture[];
|
|
@@ -29,8 +35,7 @@ declare function createServer(fixtures: Fixture[], options?: MockServerOptions,
|
|
|
29
35
|
path: string;
|
|
30
36
|
handler: Mountable;
|
|
31
37
|
}>, serviceFixtures?: ServiceFixtures): Promise<ServerInstance>;
|
|
32
|
-
|
|
33
|
-
|
|
38
|
+
/** @internal Config startup uses this to avoid parsing a selected auth source twice. */
|
|
34
39
|
//#endregion
|
|
35
40
|
export { ServerInstance, ServiceFixtures, createServer };
|
|
36
41
|
//# sourceMappingURL=server.d.ts.map
|
package/dist/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","names":[],"sources":["../src/server.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;
|
|
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;;;AAorChB;;;;;UAAiB,eAAA;UACP;UACA;cACI;;iBAMQ,YAAA,WACV,qBACA,4BACD;;WAA+B;sBACtB,kBACjB,QAAQ"}
|
package/dist/server.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import {
|
|
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";
|
|
@@ -141,23 +143,31 @@ function handleNotFound(res, message) {
|
|
|
141
143
|
}
|
|
142
144
|
const CONTROL_PREFIX = "/__aimock";
|
|
143
145
|
/**
|
|
144
|
-
* Perform a full
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
*
|
|
146
|
+
* Perform a full reset: clear the fixtures array, the journal (entries *and*
|
|
147
|
+
* per-test fixture match-counts, i.e. sequence position), the video and fal.ai
|
|
148
|
+
* job/queue state, and the Gemini interaction/event-id counters, then re-zero
|
|
149
|
+
* the `aimock_fixtures_loaded` gauge.
|
|
150
|
+
*
|
|
151
|
+
* `targets` is `null` when no server is running (an in-process `reset()` before
|
|
152
|
+
* `start()`). The process-global generation state is reset either way, since it
|
|
153
|
+
* is not owned by any one server instance.
|
|
154
|
+
*
|
|
155
|
+
* Shared by `POST /__aimock/reset` (canonical), its deprecated
|
|
156
|
+
* `POST /__aimock/reset/fixtures` alias, and `LLMock.reset()`.
|
|
148
157
|
*/
|
|
149
|
-
function
|
|
158
|
+
function performFullReset(fixtures, targets) {
|
|
150
159
|
fixtures.length = 0;
|
|
151
|
-
journal.clear();
|
|
152
|
-
videoStates.clear();
|
|
153
|
-
openRouterVideoJobs.clear();
|
|
154
|
-
veoVideoJobs.clear();
|
|
155
|
-
grokVideoJobs.clear();
|
|
156
160
|
falJobs.clear();
|
|
157
161
|
falQueueStates.clear();
|
|
158
162
|
resetInteractionCounter();
|
|
159
163
|
resetEventIdCounter();
|
|
160
|
-
if (
|
|
164
|
+
if (!targets) return;
|
|
165
|
+
targets.journal.clear();
|
|
166
|
+
targets.videoStates.clear();
|
|
167
|
+
targets.openRouterVideoJobs.clear();
|
|
168
|
+
targets.veoVideoJobs.clear();
|
|
169
|
+
targets.grokVideoJobs.clear();
|
|
170
|
+
if (targets.defaults.registry) targets.defaults.registry.setGauge("aimock_fixtures_loaded", {}, fixtures.length);
|
|
161
171
|
}
|
|
162
172
|
/**
|
|
163
173
|
* Handle requests under `/__aimock/`. Returns `true` if the request was
|
|
@@ -226,8 +236,16 @@ async function handleControlAPI(req, res, pathname, fixtures, journal, videoStat
|
|
|
226
236
|
res.end(JSON.stringify({ cleared: true }));
|
|
227
237
|
return true;
|
|
228
238
|
}
|
|
229
|
-
|
|
230
|
-
|
|
239
|
+
const resetTargets = () => ({
|
|
240
|
+
journal,
|
|
241
|
+
videoStates,
|
|
242
|
+
openRouterVideoJobs,
|
|
243
|
+
veoVideoJobs,
|
|
244
|
+
grokVideoJobs,
|
|
245
|
+
defaults
|
|
246
|
+
});
|
|
247
|
+
if (subPath === "/reset" && req.method === "POST") {
|
|
248
|
+
performFullReset(fixtures, resetTargets());
|
|
231
249
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
232
250
|
res.end(JSON.stringify({ reset: true }));
|
|
233
251
|
return true;
|
|
@@ -238,10 +256,10 @@ async function handleControlAPI(req, res, pathname, fixtures, journal, videoStat
|
|
|
238
256
|
res.end(JSON.stringify({ reset: true }));
|
|
239
257
|
return true;
|
|
240
258
|
}
|
|
241
|
-
if (subPath === "/reset" && req.method === "POST") {
|
|
242
|
-
|
|
243
|
-
const deprecation = "POST /__aimock/reset is deprecated; use POST /__aimock/reset
|
|
244
|
-
defaults.logger.warn("POST /__aimock/reset is deprecated; use /__aimock/reset
|
|
259
|
+
if (subPath === "/reset/fixtures" && req.method === "POST") {
|
|
260
|
+
performFullReset(fixtures, resetTargets());
|
|
261
|
+
const deprecation = "POST /__aimock/reset/fixtures is deprecated; use POST /__aimock/reset (full reset) or POST /__aimock/reset/journal (journal only)";
|
|
262
|
+
defaults.logger.warn("POST /__aimock/reset/fixtures is deprecated; use /__aimock/reset or /__aimock/reset/journal");
|
|
245
263
|
res.writeHead(200, {
|
|
246
264
|
"Content-Type": "application/json",
|
|
247
265
|
Deprecation: "true"
|
|
@@ -309,7 +327,7 @@ async function handleControlAPI(req, res, pathname, fixtures, journal, videoStat
|
|
|
309
327
|
handleNotFound(res, `Unknown control endpoint: ${pathname}`);
|
|
310
328
|
return true;
|
|
311
329
|
}
|
|
312
|
-
async function handleCompletions(req, res, fixtures, journal, defaults, modelFallback, providerKey) {
|
|
330
|
+
async function handleCompletions(req, res, fixtures, journal, defaults, modelFallback, providerKey, openRouter = false) {
|
|
313
331
|
setCorsHeaders(res);
|
|
314
332
|
let raw;
|
|
315
333
|
try {
|
|
@@ -326,7 +344,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
326
344
|
fixture: null
|
|
327
345
|
}
|
|
328
346
|
});
|
|
329
|
-
writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
347
|
+
writeErrorResponse(res, 500, openRouter ? serializeOpenRouterError(500, `Request body read failed: ${msg}`) : JSON.stringify({ error: {
|
|
330
348
|
message: `Request body read failed: ${msg}`,
|
|
331
349
|
type: "server_error"
|
|
332
350
|
} }));
|
|
@@ -348,7 +366,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
348
366
|
fixture: null
|
|
349
367
|
}
|
|
350
368
|
});
|
|
351
|
-
writeErrorResponse(res, 400, JSON.stringify({ error: {
|
|
369
|
+
writeErrorResponse(res, 400, openRouter ? serializeOpenRouterError(400, `Malformed JSON: ${detail}`) : JSON.stringify({ error: {
|
|
352
370
|
message: `Malformed JSON: ${detail}`,
|
|
353
371
|
type: "invalid_request_error",
|
|
354
372
|
param: null,
|
|
@@ -367,7 +385,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
367
385
|
fixture: null
|
|
368
386
|
}
|
|
369
387
|
});
|
|
370
|
-
writeErrorResponse(res, 400, JSON.stringify({ error: {
|
|
388
|
+
writeErrorResponse(res, 400, openRouter ? serializeOpenRouterError(400, "Missing required parameter: 'messages'") : JSON.stringify({ error: {
|
|
371
389
|
message: "Missing required parameter: 'messages'",
|
|
372
390
|
type: "invalid_request_error",
|
|
373
391
|
param: null,
|
|
@@ -381,9 +399,66 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
381
399
|
body._endpointType = "chat";
|
|
382
400
|
body._context = getContext(req);
|
|
383
401
|
const testId = getTestId(req);
|
|
384
|
-
const
|
|
402
|
+
const matchOptions = recordMatchOptions(!!(defaults.record && providerKey), defaults.logger);
|
|
403
|
+
let fixture = null;
|
|
404
|
+
let skippedBySequenceOrTurn = 0;
|
|
405
|
+
let preResolvedResponse = null;
|
|
406
|
+
let responseModel = body.model;
|
|
407
|
+
let fixtureCountIncremented = false;
|
|
408
|
+
if (openRouter && Array.isArray(body.models) && body.models.length > 0) {
|
|
409
|
+
const candidates = buildOpenRouterCandidates(body);
|
|
410
|
+
let matchCounts = journal.getFixtureMatchCountsForTest(testId);
|
|
411
|
+
const allowFallbacks = body.provider?.allow_fallbacks !== false;
|
|
412
|
+
let lastErrorFixture = null;
|
|
413
|
+
let lastErrorResponse = null;
|
|
414
|
+
const resolveCandidate = async (candidate) => {
|
|
415
|
+
const probe = {
|
|
416
|
+
...body,
|
|
417
|
+
model: candidate
|
|
418
|
+
};
|
|
419
|
+
const attempt = matchFixtureDiagnostic(fixtures, probe, matchCounts, defaults.requestTransform, matchOptions);
|
|
420
|
+
skippedBySequenceOrTurn = Math.max(skippedBySequenceOrTurn, attempt.skippedBySequenceOrTurn);
|
|
421
|
+
if (!attempt.fixture) return null;
|
|
422
|
+
const response = await resolveResponse(attempt.fixture, probe);
|
|
423
|
+
return {
|
|
424
|
+
fixture: attempt.fixture,
|
|
425
|
+
response,
|
|
426
|
+
isError: isErrorResponse(response)
|
|
427
|
+
};
|
|
428
|
+
};
|
|
429
|
+
for (const candidate of candidates) {
|
|
430
|
+
const outcome = await resolveCandidate(candidate);
|
|
431
|
+
if (!outcome) {
|
|
432
|
+
if (!allowFallbacks) break;
|
|
433
|
+
continue;
|
|
434
|
+
}
|
|
435
|
+
journal.incrementFixtureMatchCount(outcome.fixture, fixtures, testId);
|
|
436
|
+
fixtureCountIncremented = true;
|
|
437
|
+
matchCounts = journal.getFixtureMatchCountsForTest(testId);
|
|
438
|
+
responseModel = candidate;
|
|
439
|
+
if (outcome.isError) {
|
|
440
|
+
const errorFallsThrough = outcome.response.fallthrough !== false;
|
|
441
|
+
if (allowFallbacks && errorFallsThrough) {
|
|
442
|
+
lastErrorFixture = outcome.fixture;
|
|
443
|
+
lastErrorResponse = outcome.response;
|
|
444
|
+
continue;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
fixture = outcome.fixture;
|
|
448
|
+
preResolvedResponse = outcome.response;
|
|
449
|
+
break;
|
|
450
|
+
}
|
|
451
|
+
if (!fixture && lastErrorFixture) {
|
|
452
|
+
fixture = lastErrorFixture;
|
|
453
|
+
preResolvedResponse = lastErrorResponse;
|
|
454
|
+
}
|
|
455
|
+
} else {
|
|
456
|
+
const single = matchFixtureDiagnostic(fixtures, body, journal.getFixtureMatchCountsForTest(testId), defaults.requestTransform, matchOptions);
|
|
457
|
+
fixture = single.fixture;
|
|
458
|
+
skippedBySequenceOrTurn = single.skippedBySequenceOrTurn;
|
|
459
|
+
}
|
|
385
460
|
if (fixture) {
|
|
386
|
-
journal.incrementFixtureMatchCount(fixture, fixtures, testId);
|
|
461
|
+
if (!fixtureCountIncremented) journal.incrementFixtureMatchCount(fixture, fixtures, testId);
|
|
387
462
|
defaults.logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);
|
|
388
463
|
} else {
|
|
389
464
|
const lastUserMsg = body.messages.filter((m) => m.role === "user").pop();
|
|
@@ -421,7 +496,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
421
496
|
...strictOverrideField(defaults.strict, req.headers)
|
|
422
497
|
}
|
|
423
498
|
});
|
|
424
|
-
writeErrorResponse(res, strictStatus, JSON.stringify({ error: {
|
|
499
|
+
writeErrorResponse(res, strictStatus, openRouter ? serializeOpenRouterError(strictStatus, strictMessage) : JSON.stringify({ error: {
|
|
425
500
|
message: strictMessage,
|
|
426
501
|
type: "invalid_request_error",
|
|
427
502
|
param: null,
|
|
@@ -472,7 +547,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
472
547
|
...strictOverrideField(defaults.strict, req.headers)
|
|
473
548
|
}
|
|
474
549
|
});
|
|
475
|
-
writeErrorResponse(res, 404, JSON.stringify({ error: {
|
|
550
|
+
writeErrorResponse(res, 404, openRouter ? serializeOpenRouterError(404, "No fixture matched") : JSON.stringify({ error: {
|
|
476
551
|
message: "No fixture matched",
|
|
477
552
|
type: "invalid_request_error",
|
|
478
553
|
param: null,
|
|
@@ -480,10 +555,23 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
480
555
|
} }));
|
|
481
556
|
return;
|
|
482
557
|
}
|
|
483
|
-
const response = await resolveResponse(fixture, body);
|
|
558
|
+
const response = preResolvedResponse ?? await resolveResponse(fixture, body);
|
|
484
559
|
const latency = fixture.latency ?? defaults.latency;
|
|
485
560
|
const chunkSize = Math.max(1, fixture.chunkSize ?? defaults.chunkSize);
|
|
486
|
-
const
|
|
561
|
+
const emitStreamingUsage = body.stream === true && body.stream_options?.include_usage === true || openRouter;
|
|
562
|
+
const streamingPromptText = body.messages.map((m) => typeof m.content === "string" ? m.content : Array.isArray(m.content) ? m.content.map((p) => p.text ?? "").join("") : "").join("");
|
|
563
|
+
const resolveStreamingUsageTokens = (overrides, completionText) => resolveUsage(overrides, streamingPromptText, completionText);
|
|
564
|
+
const shapeORCompletion = (completion, overrides) => {
|
|
565
|
+
if (!openRouter) return completion;
|
|
566
|
+
return shapeOpenRouterCompletion(completion, resolveOpenRouterShaping(overrides, overrides?.model ?? responseModel), overrides?.id !== void 0);
|
|
567
|
+
};
|
|
568
|
+
const shapeORChunks = (chunks, usageChunk, overrides) => {
|
|
569
|
+
if (!openRouter) return;
|
|
570
|
+
const shaping = resolveOpenRouterShaping(overrides, overrides?.model ?? responseModel);
|
|
571
|
+
shapeOpenRouterChunks(chunks, shaping, overrides?.id !== void 0);
|
|
572
|
+
if (usageChunk) shapeOpenRouterChunks([usageChunk], shaping, overrides?.id !== void 0);
|
|
573
|
+
};
|
|
574
|
+
const openRouterProcessing = !!(openRouter && fixture.openRouterProcessing);
|
|
487
575
|
if (isErrorResponse(response)) {
|
|
488
576
|
const status = response.status ?? 500;
|
|
489
577
|
journal.add({
|
|
@@ -496,7 +584,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
496
584
|
fixture
|
|
497
585
|
}
|
|
498
586
|
});
|
|
499
|
-
writeErrorResponse(res, status, serializeErrorResponse(response), { retryAfter: response.retryAfter });
|
|
587
|
+
writeErrorResponse(res, status, openRouter ? serializeOpenRouterError(status, response.error.message, response.error.metadata) : serializeErrorResponse(response), { retryAfter: response.retryAfter });
|
|
500
588
|
return;
|
|
501
589
|
}
|
|
502
590
|
if (isAudioResponse(response)) {
|
|
@@ -510,7 +598,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
510
598
|
fixture
|
|
511
599
|
}
|
|
512
600
|
});
|
|
513
|
-
writeErrorResponse(res, 422, JSON.stringify({ error: {
|
|
601
|
+
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
602
|
message: "Audio responses are not supported on the chat completions endpoint. Use Gemini generateContent or a dedicated audio endpoint.",
|
|
515
603
|
type: "invalid_request_error"
|
|
516
604
|
} }));
|
|
@@ -520,7 +608,9 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
520
608
|
if (response.webSearches?.length) defaults.logger.warn("webSearches in fixture response are not supported for Chat Completions API — ignoring");
|
|
521
609
|
const overrides = extractOverrides(response);
|
|
522
610
|
const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);
|
|
523
|
-
const effReasoning = resolveReasoningForModel(response.reasoning,
|
|
611
|
+
const effReasoning = resolveReasoningForModel(response.reasoning, responseModel, effectiveStrict, defaults.logger);
|
|
612
|
+
const streaming = body.stream === true;
|
|
613
|
+
const streamingBlocks = streaming && response.blocks && response.blocks.length > 0 ? resolveFixtureBlocks(response.blocks) : void 0;
|
|
524
614
|
const journalEntry = journal.add({
|
|
525
615
|
method: req.method ?? "POST",
|
|
526
616
|
path: req.url ?? COMPLETIONS_PATH,
|
|
@@ -531,22 +621,15 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
531
621
|
fixture
|
|
532
622
|
}
|
|
533
623
|
});
|
|
534
|
-
if (
|
|
535
|
-
const completion = buildContentWithToolCallsCompletion(response.content ?? "", response.toolCalls ?? [],
|
|
624
|
+
if (!streaming) {
|
|
625
|
+
const completion = buildContentWithToolCallsCompletion(response.content ?? "", response.toolCalls ?? [], responseModel, effReasoning, overrides, body.messages);
|
|
536
626
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
537
|
-
res.end(JSON.stringify(completion));
|
|
627
|
+
res.end(JSON.stringify(shapeORCompletion(completion, overrides)));
|
|
538
628
|
} else {
|
|
539
|
-
const chunks = buildContentWithToolCallsChunks(response.content ?? "", response.toolCalls ?? [],
|
|
540
|
-
const completionText = (response.content ?? "") + (response.toolCalls ?? []).map((tc) => tc.name + tc.arguments).join("");
|
|
541
|
-
const usageChunk =
|
|
542
|
-
|
|
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;
|
|
629
|
+
const chunks = buildContentWithToolCallsChunks(response.content ?? "", response.toolCalls ?? [], responseModel, chunkSize, effReasoning, overrides, streamingBlocks);
|
|
630
|
+
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("");
|
|
631
|
+
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;
|
|
632
|
+
shapeORChunks(chunks, usageChunk, overrides);
|
|
550
633
|
const interruption = createInterruptionSignal(fixture);
|
|
551
634
|
if (!await writeSSEStream(res, chunks, {
|
|
552
635
|
latency,
|
|
@@ -555,7 +638,8 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
555
638
|
onChunkSent: interruption?.tick,
|
|
556
639
|
usageChunk,
|
|
557
640
|
recordedTimings: fixture.recordedTimings,
|
|
558
|
-
replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed
|
|
641
|
+
replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,
|
|
642
|
+
openRouterProcessing
|
|
559
643
|
})) {
|
|
560
644
|
if (!res.writableEnded) res.destroy();
|
|
561
645
|
journalEntry.response.interrupted = true;
|
|
@@ -569,7 +653,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
569
653
|
if (response.webSearches?.length) defaults.logger.warn("webSearches in fixture response are not supported for Chat Completions API — ignoring");
|
|
570
654
|
const overrides = extractOverrides(response);
|
|
571
655
|
const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);
|
|
572
|
-
const effReasoning = resolveReasoningForModel(response.reasoning,
|
|
656
|
+
const effReasoning = resolveReasoningForModel(response.reasoning, responseModel, effectiveStrict, defaults.logger);
|
|
573
657
|
const journalEntry = journal.add({
|
|
574
658
|
method: req.method ?? "POST",
|
|
575
659
|
path: req.url ?? COMPLETIONS_PATH,
|
|
@@ -581,20 +665,13 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
581
665
|
}
|
|
582
666
|
});
|
|
583
667
|
if (body.stream !== true) {
|
|
584
|
-
const completion = buildTextCompletion(response.content,
|
|
668
|
+
const completion = buildTextCompletion(response.content, responseModel, effReasoning, overrides, body.messages);
|
|
585
669
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
586
|
-
res.end(JSON.stringify(completion));
|
|
670
|
+
res.end(JSON.stringify(shapeORCompletion(completion, overrides)));
|
|
587
671
|
} else {
|
|
588
|
-
const chunks = buildTextChunks(response.content,
|
|
589
|
-
const usageChunk =
|
|
590
|
-
|
|
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;
|
|
672
|
+
const chunks = buildTextChunks(response.content, responseModel, chunkSize, effReasoning, overrides);
|
|
673
|
+
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;
|
|
674
|
+
shapeORChunks(chunks, usageChunk, overrides);
|
|
598
675
|
const interruption = createInterruptionSignal(fixture);
|
|
599
676
|
if (!await writeSSEStream(res, chunks, {
|
|
600
677
|
latency,
|
|
@@ -603,7 +680,8 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
603
680
|
onChunkSent: interruption?.tick,
|
|
604
681
|
usageChunk,
|
|
605
682
|
recordedTimings: fixture.recordedTimings,
|
|
606
|
-
replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed
|
|
683
|
+
replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,
|
|
684
|
+
openRouterProcessing
|
|
607
685
|
})) {
|
|
608
686
|
if (!res.writableEnded) res.destroy();
|
|
609
687
|
journalEntry.response.interrupted = true;
|
|
@@ -617,7 +695,7 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
617
695
|
if (response.webSearches?.length) defaults.logger.warn("webSearches in fixture response are not supported for Chat Completions API — ignoring");
|
|
618
696
|
const overrides = extractOverrides(response);
|
|
619
697
|
const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);
|
|
620
|
-
const effReasoning = resolveReasoningForModel(response.reasoning,
|
|
698
|
+
const effReasoning = resolveReasoningForModel(response.reasoning, responseModel, effectiveStrict, defaults.logger);
|
|
621
699
|
const journalEntry = journal.add({
|
|
622
700
|
method: req.method ?? "POST",
|
|
623
701
|
path: req.url ?? COMPLETIONS_PATH,
|
|
@@ -629,21 +707,14 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
629
707
|
}
|
|
630
708
|
});
|
|
631
709
|
if (body.stream !== true) {
|
|
632
|
-
const completion = buildToolCallCompletion(response.toolCalls,
|
|
710
|
+
const completion = buildToolCallCompletion(response.toolCalls, responseModel, effReasoning, overrides, body.messages);
|
|
633
711
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
634
|
-
res.end(JSON.stringify(completion));
|
|
712
|
+
res.end(JSON.stringify(shapeORCompletion(completion, overrides)));
|
|
635
713
|
} else {
|
|
636
|
-
const chunks = buildToolCallChunks(response.toolCalls,
|
|
714
|
+
const chunks = buildToolCallChunks(response.toolCalls, responseModel, chunkSize, effReasoning, overrides);
|
|
637
715
|
const completionText = response.toolCalls.map((tc) => tc.name + tc.arguments).join("");
|
|
638
|
-
const usageChunk =
|
|
639
|
-
|
|
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;
|
|
716
|
+
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;
|
|
717
|
+
shapeORChunks(chunks, usageChunk, overrides);
|
|
647
718
|
const interruption = createInterruptionSignal(fixture);
|
|
648
719
|
if (!await writeSSEStream(res, chunks, {
|
|
649
720
|
latency,
|
|
@@ -652,7 +723,8 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
652
723
|
onChunkSent: interruption?.tick,
|
|
653
724
|
usageChunk,
|
|
654
725
|
recordedTimings: fixture.recordedTimings,
|
|
655
|
-
replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed
|
|
726
|
+
replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,
|
|
727
|
+
openRouterProcessing
|
|
656
728
|
})) {
|
|
657
729
|
if (!res.writableEnded) res.destroy();
|
|
658
730
|
journalEntry.response.interrupted = true;
|
|
@@ -672,12 +744,19 @@ async function handleCompletions(req, res, fixtures, journal, defaults, modelFal
|
|
|
672
744
|
fixture
|
|
673
745
|
}
|
|
674
746
|
});
|
|
675
|
-
writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
747
|
+
writeErrorResponse(res, 500, openRouter ? serializeOpenRouterError(500, "Fixture response did not match any known type") : JSON.stringify({ error: {
|
|
676
748
|
message: "Fixture response did not match any known type",
|
|
677
749
|
type: "server_error"
|
|
678
750
|
} }));
|
|
679
751
|
}
|
|
680
752
|
async function createServer(fixtures, options, mounts, serviceFixtures) {
|
|
753
|
+
return createServerWithResolvedAuth(fixtures, options, resolveInboundAuth({
|
|
754
|
+
value: options?.auth,
|
|
755
|
+
label: "options.auth"
|
|
756
|
+
}), mounts, serviceFixtures);
|
|
757
|
+
}
|
|
758
|
+
/** @internal Config startup uses this to avoid parsing a selected auth source twice. */
|
|
759
|
+
async function createServerWithResolvedAuth(fixtures, options, resolvedAuth, mounts, serviceFixtures) {
|
|
681
760
|
const host = options?.host ?? "127.0.0.1";
|
|
682
761
|
const port = options?.port ?? 0;
|
|
683
762
|
const logger = new Logger(options?.logLevel ?? "silent");
|
|
@@ -791,13 +870,10 @@ async function createServer(fixtures, options, mounts, serviceFixtures) {
|
|
|
791
870
|
});
|
|
792
871
|
});
|
|
793
872
|
async function handleHttpRequest(req, res) {
|
|
794
|
-
if (req.method === "OPTIONS") {
|
|
795
|
-
handleOptions(res);
|
|
796
|
-
return;
|
|
797
|
-
}
|
|
798
873
|
const startTime = registry ? process.hrtime.bigint() : 0n;
|
|
799
874
|
const parsedUrl = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
|
|
800
875
|
let pathname = parsedUrl.pathname;
|
|
876
|
+
const isOpenRouter = isOpenRouterPath(pathname);
|
|
801
877
|
if (registry) res.on("finish", () => {
|
|
802
878
|
try {
|
|
803
879
|
const normalizedPath = normalizePathLabel(pathname);
|
|
@@ -817,6 +893,17 @@ async function createServer(fixtures, options, mounts, serviceFixtures) {
|
|
|
817
893
|
defaults.logger.warn("metrics instrumentation error", err);
|
|
818
894
|
}
|
|
819
895
|
});
|
|
896
|
+
if (req.method === "OPTIONS" && (!resolvedAuth.policy.enabled || req.headers.origin !== void 0 && req.headers["access-control-request-method"] !== void 0)) {
|
|
897
|
+
handleOptions(res);
|
|
898
|
+
return;
|
|
899
|
+
}
|
|
900
|
+
if (!(req.method === "GET" && (pathname === HEALTH_PATH || pathname === READY_PATH || pathname === "/metrics"))) {
|
|
901
|
+
if (!validateRequestApiKey(req, resolvedAuth.policy).ok) {
|
|
902
|
+
writeApiKeyHttpRejection(res, setCorsHeaders);
|
|
903
|
+
return;
|
|
904
|
+
}
|
|
905
|
+
markAuthenticatedRequest(req, resolvedAuth.policy);
|
|
906
|
+
}
|
|
820
907
|
if (pathname.startsWith(CONTROL_PREFIX)) {
|
|
821
908
|
await handleControlAPI(req, res, pathname, fixtures, journal, videoStates, openRouterVideoJobs, veoVideoJobs, grokVideoJobs, defaults);
|
|
822
909
|
return;
|
|
@@ -1001,6 +1088,18 @@ async function createServer(fixtures, options, mounts, serviceFixtures) {
|
|
|
1001
1088
|
}
|
|
1002
1089
|
return;
|
|
1003
1090
|
}
|
|
1091
|
+
if (pathname === "/api/v1/models" && req.method === "GET") {
|
|
1092
|
+
handleOpenRouterModels(req, res, fixtures, journal, defaults, setCorsHeaders);
|
|
1093
|
+
return;
|
|
1094
|
+
}
|
|
1095
|
+
if (pathname === "/api/v1/key" && req.method === "GET") {
|
|
1096
|
+
handleOpenRouterKey(req, res, journal, setCorsHeaders);
|
|
1097
|
+
return;
|
|
1098
|
+
}
|
|
1099
|
+
if (pathname === "/api/v1/credits" && req.method === "GET") {
|
|
1100
|
+
handleOpenRouterCredits(req, res, journal, setCorsHeaders);
|
|
1101
|
+
return;
|
|
1102
|
+
}
|
|
1004
1103
|
let azureDeploymentId;
|
|
1005
1104
|
const azureMatch = pathname.match(AZURE_DEPLOYMENT_RE);
|
|
1006
1105
|
if (azureMatch && req.method === "POST") {
|
|
@@ -1726,17 +1825,17 @@ async function createServer(fixtures, options, mounts, serviceFixtures) {
|
|
|
1726
1825
|
handleNotFound(res, "Not found");
|
|
1727
1826
|
return;
|
|
1728
1827
|
}
|
|
1729
|
-
const completionsProvider = azureDeploymentId ? "azure" : "openai";
|
|
1828
|
+
const completionsProvider = azureDeploymentId ? "azure" : isOpenRouter ? "openrouter" : "openai";
|
|
1730
1829
|
try {
|
|
1731
|
-
await handleCompletions(req, res, fixtures, journal, defaults, azureDeploymentId, completionsProvider);
|
|
1830
|
+
await handleCompletions(req, res, fixtures, journal, defaults, azureDeploymentId, completionsProvider, isOpenRouter);
|
|
1732
1831
|
} catch (err) {
|
|
1733
1832
|
const msg = err instanceof Error ? err.message : "Internal error";
|
|
1734
|
-
if (!res.headersSent) writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
1833
|
+
if (!res.headersSent) writeErrorResponse(res, 500, isOpenRouter ? serializeOpenRouterError(500, msg) : JSON.stringify({ error: {
|
|
1735
1834
|
message: msg,
|
|
1736
1835
|
type: "server_error"
|
|
1737
1836
|
} }));
|
|
1738
1837
|
else if (!res.writableEnded) try {
|
|
1739
|
-
res.write(`data: ${JSON.stringify({ error: {
|
|
1838
|
+
res.write(`data: ${isOpenRouter ? serializeOpenRouterError(500, msg) : JSON.stringify({ error: {
|
|
1740
1839
|
message: msg,
|
|
1741
1840
|
type: "server_error"
|
|
1742
1841
|
} })}\n\n`);
|
|
@@ -1757,6 +1856,10 @@ async function createServer(fixtures, options, mounts, serviceFixtures) {
|
|
|
1757
1856
|
async function handleUpgradeRequest(req, socket, head) {
|
|
1758
1857
|
const parsedUrl = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
|
|
1759
1858
|
let pathname = parsedUrl.pathname;
|
|
1859
|
+
if (!validateRequestApiKey(req, resolvedAuth.policy).ok) {
|
|
1860
|
+
writeApiKeyUpgradeRejection(socket);
|
|
1861
|
+
return;
|
|
1862
|
+
}
|
|
1760
1863
|
if (mounts) {
|
|
1761
1864
|
for (const { path: mountPath, handler } of mounts) if ((pathname === mountPath || pathname.startsWith(mountPath + "/")) && handler.handleUpgrade) {
|
|
1762
1865
|
const subPath = pathname.slice(mountPath.length) || "/";
|
|
@@ -1795,10 +1898,12 @@ async function createServer(fixtures, options, mounts, serviceFixtures) {
|
|
|
1795
1898
|
upgradeHeaders: req.headers
|
|
1796
1899
|
});
|
|
1797
1900
|
else if (pathname === REALTIME_PATH) {
|
|
1798
|
-
const
|
|
1901
|
+
const transcriptionIntent = parsedUrl.searchParams.get("intent") === "transcription";
|
|
1902
|
+
const model = transcriptionIntent ? "gpt-transcribe" : parsedUrl.searchParams.get("model") ?? "gpt-realtime-2";
|
|
1799
1903
|
handleWebSocketRealtime(ws, fixtures, journal, {
|
|
1800
1904
|
...defaults,
|
|
1801
1905
|
model,
|
|
1906
|
+
transcriptionIntent,
|
|
1802
1907
|
testId: wsTestId,
|
|
1803
1908
|
upgradeHeaders: req.headers
|
|
1804
1909
|
});
|
|
@@ -1847,5 +1952,5 @@ async function createServer(fixtures, options, mounts, serviceFixtures) {
|
|
|
1847
1952
|
}
|
|
1848
1953
|
|
|
1849
1954
|
//#endregion
|
|
1850
|
-
export { createServer };
|
|
1955
|
+
export { createServer, createServerWithResolvedAuth, performFullReset };
|
|
1851
1956
|
//# sourceMappingURL=server.js.map
|