@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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +22 -0
- package/README.md +27 -9
- 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-types.d.ts.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 +4 -1
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +4 -1
- 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 +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/llmock.cjs +14 -3
- package/dist/llmock.cjs.map +1 -1
- package/dist/llmock.d.cts +4 -2
- package/dist/llmock.d.cts.map +1 -1
- package/dist/llmock.d.ts +4 -2
- package/dist/llmock.d.ts.map +1 -1
- package/dist/llmock.js +15 -5
- 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 +156 -66
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +1 -1
- package/dist/server.d.cts.map +1 -1
- package/dist/server.d.ts +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +157 -68
- 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 +26 -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 +26 -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/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-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 +1 -1
- 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
|
-
|
|
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 };
|
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;;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
|
-
|
|
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 };
|
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;;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 {
|
|
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
|
|
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
|
|
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,
|
|
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 (
|
|
535
|
-
const completion = buildContentWithToolCallsCompletion(response.content ?? "", response.toolCalls ?? [],
|
|
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 ?? [],
|
|
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;
|
|
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,
|
|
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,
|
|
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,
|
|
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;
|
|
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,
|
|
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,
|
|
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,
|
|
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 =
|
|
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;
|
|
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
|
|
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
|