@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
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
const require_helpers = require('./helpers.cjs');
|
|
2
|
+
|
|
3
|
+
//#region src/openrouter-chat.ts
|
|
4
|
+
/**
|
|
5
|
+
* OpenRouter chat/LLM router shaping. aimock detects an OpenRouter request by
|
|
6
|
+
* the ORIGINAL request path (before `normalizeCompatPath` erases it) starting
|
|
7
|
+
* with the OpenRouter canonical base `/api/v1/` — mirroring a real client that
|
|
8
|
+
* points an OpenAI SDK at `baseURL: https://openrouter.ai/api/v1`. When a
|
|
9
|
+
* request is OpenRouter, the same OpenAI-compatible chat response is shaped
|
|
10
|
+
* with OpenRouter's distinguishing fields (a `gen-` id prefix, a top-level
|
|
11
|
+
* `provider`, `native_finish_reason`, and a rich cost-bearing `usage`) and the
|
|
12
|
+
* `models[]` fallback array is simulated deterministically against fixtures.
|
|
13
|
+
* OpenAI (`/v1/...`) callers are left byte-for-byte unchanged.
|
|
14
|
+
*
|
|
15
|
+
* The shaping is applied as a post-pass over the objects the existing OpenAI
|
|
16
|
+
* builders produce (never a fork of those builders), so every OpenAI code path
|
|
17
|
+
* is untouched.
|
|
18
|
+
*/
|
|
19
|
+
/** OpenRouter's canonical API base — the detection prefix. */
|
|
20
|
+
const OPENROUTER_BASE_PREFIX = "/api/v1/";
|
|
21
|
+
const OPENAI_ID_PREFIX = "chatcmpl-";
|
|
22
|
+
const OPENROUTER_ID_PREFIX = "gen-";
|
|
23
|
+
/**
|
|
24
|
+
* Return true when the ORIGINAL request path marks an OpenRouter request. Must
|
|
25
|
+
* be evaluated on the pre-normalization pathname: `normalizeCompatPath`
|
|
26
|
+
* rewrites `/api/v1/chat/completions` → `/v1/chat/completions`, erasing the
|
|
27
|
+
* `/api/v1/` signal.
|
|
28
|
+
*/
|
|
29
|
+
function isOpenRouterPath(originalPathname) {
|
|
30
|
+
return originalPathname.startsWith(OPENROUTER_BASE_PREFIX);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Derive the default OpenRouter `provider` from a model slug: the author
|
|
34
|
+
* segment before the first `/` (`openai/gpt-4o` → `"openai"`). A slug without
|
|
35
|
+
* a `/` yields the whole slug. An absent/empty slug — a request that never set
|
|
36
|
+
* `model` yet matched a model-less fixture — yields `""` rather than throwing
|
|
37
|
+
* (guards against a `TypeError` becoming a generic 500).
|
|
38
|
+
*/
|
|
39
|
+
function deriveOpenRouterProvider(model) {
|
|
40
|
+
if (!model) return "";
|
|
41
|
+
const slash = model.indexOf("/");
|
|
42
|
+
return slash === -1 ? model : model.slice(0, slash);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Build the OpenRouter fallback candidate list `[body.model, ...body.models]`,
|
|
46
|
+
* de-duplicated with insertion order preserved. A request without `models`
|
|
47
|
+
* yields just `[body.model]` — identical to today's single-model behavior.
|
|
48
|
+
*/
|
|
49
|
+
function buildOpenRouterCandidates(body) {
|
|
50
|
+
const raw = [body.model, ...Array.isArray(body.models) ? body.models : []];
|
|
51
|
+
const seen = /* @__PURE__ */ new Set();
|
|
52
|
+
const out = [];
|
|
53
|
+
for (const m of raw) {
|
|
54
|
+
if (typeof m !== "string" || m.length === 0) continue;
|
|
55
|
+
if (seen.has(m)) continue;
|
|
56
|
+
seen.add(m);
|
|
57
|
+
out.push(m);
|
|
58
|
+
}
|
|
59
|
+
return out;
|
|
60
|
+
}
|
|
61
|
+
/** Rewrite an auto-generated `chatcmpl-` id to OpenRouter's `gen-` prefix. */
|
|
62
|
+
function toGenId(id) {
|
|
63
|
+
return id.startsWith(OPENAI_ID_PREFIX) ? OPENROUTER_ID_PREFIX + id.slice(9) : id;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Resolve the shaping context from a fixture's response overrides and the
|
|
67
|
+
* winning model slug. `provider` defaults to the winning slug's author;
|
|
68
|
+
* `native_finish_reason` mirrors `finish_reason` unless overridden. `cost` and
|
|
69
|
+
* `cost_details` are NULLABLE — emitted only when the fixture supplies a `cost`
|
|
70
|
+
* (never defaulted to 0); when emitted, `cost_details` carries all three
|
|
71
|
+
* upstream fields (a partial object breaks the canonical SDK). The token-detail
|
|
72
|
+
* breakdowns and `is_byok` are included only when the fixture set them, and an
|
|
73
|
+
* emitted breakdown always carries its required member (`cached_tokens` /
|
|
74
|
+
* `reasoning_tokens`).
|
|
75
|
+
*/
|
|
76
|
+
function resolveOpenRouterShaping(overrides, winningModel) {
|
|
77
|
+
const u = overrides?.usage;
|
|
78
|
+
const usageExtras = {};
|
|
79
|
+
if (u?.cost !== void 0) {
|
|
80
|
+
usageExtras.cost = u.cost;
|
|
81
|
+
const cd = u.cost_details;
|
|
82
|
+
usageExtras.cost_details = {
|
|
83
|
+
...cd ?? {},
|
|
84
|
+
upstream_inference_cost: cd?.upstream_inference_cost ?? 0,
|
|
85
|
+
upstream_inference_prompt_cost: cd?.upstream_inference_prompt_cost ?? 0,
|
|
86
|
+
upstream_inference_completions_cost: cd?.upstream_inference_completions_cost ?? 0
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
if (u?.prompt_tokens_details !== void 0) usageExtras.prompt_tokens_details = {
|
|
90
|
+
...u.prompt_tokens_details,
|
|
91
|
+
cached_tokens: u.prompt_tokens_details.cached_tokens ?? 0
|
|
92
|
+
};
|
|
93
|
+
if (u?.completion_tokens_details !== void 0) usageExtras.completion_tokens_details = {
|
|
94
|
+
...u.completion_tokens_details,
|
|
95
|
+
reasoning_tokens: u.completion_tokens_details.reasoning_tokens ?? 0
|
|
96
|
+
};
|
|
97
|
+
if (u?.is_byok !== void 0) usageExtras.is_byok = u.is_byok;
|
|
98
|
+
return {
|
|
99
|
+
provider: overrides?.provider ?? deriveOpenRouterProvider(winningModel),
|
|
100
|
+
...overrides?.nativeFinishReason !== void 0 && { nativeFinishReason: overrides.nativeFinishReason },
|
|
101
|
+
usageExtras
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
/** Merge the OpenRouter usage extras onto an existing (OpenAI-shape) usage object. */
|
|
105
|
+
function augmentUsage(usage, shaping) {
|
|
106
|
+
return {
|
|
107
|
+
...usage,
|
|
108
|
+
...shaping.usageExtras
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
/** Resolve a choice's native_finish_reason (mirrors finish_reason unless overridden). */
|
|
112
|
+
function nativeFinish(finishReason, shaping) {
|
|
113
|
+
if (finishReason == null) return null;
|
|
114
|
+
return shaping.nativeFinishReason ?? finishReason;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Shape a non-streaming OpenAI completion into OpenRouter form, in place, to
|
|
118
|
+
* match real OpenRouter bytes (or-capture/chat-nonstream.json): `gen-` id
|
|
119
|
+
* (unless the fixture pinned `id`), top-level `provider`, `service_tier: null`,
|
|
120
|
+
* an always-present `system_fingerprint` (null when unset — the canonical
|
|
121
|
+
* `@openrouter/sdk` requires it), per-choice `native_finish_reason`, a
|
|
122
|
+
* `message.reasoning` field (mirrors `reasoning_content`, else null), and
|
|
123
|
+
* cost-bearing `usage`.
|
|
124
|
+
*/
|
|
125
|
+
function shapeOpenRouterCompletion(completion, shaping, idOverridden) {
|
|
126
|
+
if (!idOverridden) completion.id = toGenId(completion.id);
|
|
127
|
+
completion.provider = shaping.provider;
|
|
128
|
+
if (completion.system_fingerprint === void 0) completion.system_fingerprint = null;
|
|
129
|
+
if (completion.service_tier === void 0) completion.service_tier = null;
|
|
130
|
+
for (const choice of completion.choices) {
|
|
131
|
+
choice.native_finish_reason = shaping.nativeFinishReason ?? choice.finish_reason;
|
|
132
|
+
if (choice.message.reasoning === void 0) choice.message.reasoning = choice.message.reasoning_content ?? null;
|
|
133
|
+
}
|
|
134
|
+
completion.usage = augmentUsage(completion.usage, shaping);
|
|
135
|
+
return completion;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Shape streaming OpenAI chunks into OpenRouter form, in place, to match real
|
|
139
|
+
* OpenRouter bytes (or-capture/chat-stream.sse): EVERY chunk carries the
|
|
140
|
+
* `gen-` id (unless pinned), top-level `provider`, and `system_fingerprint`
|
|
141
|
+
* (null when unset); EVERY delta choice carries `native_finish_reason` (null
|
|
142
|
+
* until the finish chunk, where it mirrors finish_reason); real OR repeats
|
|
143
|
+
* `role: "assistant"` on every content delta (mirrored here); and the final
|
|
144
|
+
* usage-bearing chunk gets cost-bearing usage plus `service_tier: null`.
|
|
145
|
+
*/
|
|
146
|
+
function shapeOpenRouterChunks(chunks, shaping, idOverridden) {
|
|
147
|
+
for (const chunk of chunks) {
|
|
148
|
+
if (!idOverridden) chunk.id = toGenId(chunk.id);
|
|
149
|
+
chunk.provider = shaping.provider;
|
|
150
|
+
if (chunk.system_fingerprint === void 0) chunk.system_fingerprint = null;
|
|
151
|
+
for (const choice of chunk.choices) {
|
|
152
|
+
choice.native_finish_reason = nativeFinish(choice.finish_reason, shaping);
|
|
153
|
+
if (typeof choice.delta.content === "string" && choice.delta.role === void 0) choice.delta.role = "assistant";
|
|
154
|
+
}
|
|
155
|
+
if (chunk.usage) {
|
|
156
|
+
chunk.usage = augmentUsage(chunk.usage, shaping);
|
|
157
|
+
chunk.service_tier = null;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return chunks;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Serialize an OpenRouter error envelope: `{ error: { code, message,
|
|
164
|
+
* metadata? } }` where `code` equals the HTTP status (contrast the OpenAI
|
|
165
|
+
* shape `{ error: { message, type, param, code } }`). Used for every error
|
|
166
|
+
* emitted on an OpenRouter request — fixture errors, no-match, strict, and
|
|
167
|
+
* routing failures.
|
|
168
|
+
*/
|
|
169
|
+
function serializeOpenRouterError(status, message, metadata) {
|
|
170
|
+
return JSON.stringify({ error: {
|
|
171
|
+
code: status,
|
|
172
|
+
message,
|
|
173
|
+
...metadata !== void 0 && { metadata }
|
|
174
|
+
} });
|
|
175
|
+
}
|
|
176
|
+
/** Default model slug set for `GET /api/v1/models` when no chat fixtures name one. */
|
|
177
|
+
const DEFAULT_OPENROUTER_MODELS = [
|
|
178
|
+
"openai/gpt-4o",
|
|
179
|
+
"anthropic/claude-3.5-sonnet",
|
|
180
|
+
"google/gemini-2.0-flash-001"
|
|
181
|
+
];
|
|
182
|
+
/**
|
|
183
|
+
* Build one OpenRouter model-catalog object (deterministic static filler).
|
|
184
|
+
* Field set mirrors real OpenRouter bytes (or-capture/models-first.json):
|
|
185
|
+
* pricing values are STRINGS; the object also carries hugging_face_id,
|
|
186
|
+
* knowledge_cutoff, supported_voices, links, default_parameters,
|
|
187
|
+
* expiration_date, and a `reasoning` descriptor (SDKs tolerate extras).
|
|
188
|
+
*/
|
|
189
|
+
function openRouterModelObject(id) {
|
|
190
|
+
return {
|
|
191
|
+
id,
|
|
192
|
+
canonical_slug: id,
|
|
193
|
+
hugging_face_id: null,
|
|
194
|
+
name: id,
|
|
195
|
+
created: 0,
|
|
196
|
+
description: `Mock OpenRouter model ${id} served by aimock.`,
|
|
197
|
+
context_length: 128e3,
|
|
198
|
+
architecture: {
|
|
199
|
+
modality: "text->text",
|
|
200
|
+
input_modalities: ["text"],
|
|
201
|
+
output_modalities: ["text"],
|
|
202
|
+
tokenizer: "Other",
|
|
203
|
+
instruct_type: null
|
|
204
|
+
},
|
|
205
|
+
pricing: {
|
|
206
|
+
prompt: "0",
|
|
207
|
+
completion: "0",
|
|
208
|
+
request: "0",
|
|
209
|
+
image: "0",
|
|
210
|
+
web_search: "0",
|
|
211
|
+
internal_reasoning: "0",
|
|
212
|
+
input_cache_read: "0",
|
|
213
|
+
input_cache_write: "0"
|
|
214
|
+
},
|
|
215
|
+
top_provider: {
|
|
216
|
+
context_length: 128e3,
|
|
217
|
+
max_completion_tokens: 16384,
|
|
218
|
+
is_moderated: false
|
|
219
|
+
},
|
|
220
|
+
per_request_limits: null,
|
|
221
|
+
supported_parameters: [
|
|
222
|
+
"tools",
|
|
223
|
+
"tool_choice",
|
|
224
|
+
"max_tokens",
|
|
225
|
+
"temperature",
|
|
226
|
+
"top_p",
|
|
227
|
+
"stop",
|
|
228
|
+
"reasoning"
|
|
229
|
+
],
|
|
230
|
+
default_parameters: null,
|
|
231
|
+
supported_voices: null,
|
|
232
|
+
knowledge_cutoff: null,
|
|
233
|
+
expiration_date: null,
|
|
234
|
+
links: { details: `/api/v1/models/${id}/endpoints` },
|
|
235
|
+
reasoning: {
|
|
236
|
+
mandatory: false,
|
|
237
|
+
default_enabled: false
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Collect the model ids advertised by `GET /api/v1/models`: every loaded chat
|
|
243
|
+
* (or endpoint-less) fixture with a string `match.model`, in first-seen order;
|
|
244
|
+
* the default set when none contribute one. Mirrors the Ollama `/api/tags` and
|
|
245
|
+
* OpenRouter video `/models` synthesis idioms.
|
|
246
|
+
*/
|
|
247
|
+
function collectModelIds(fixtures) {
|
|
248
|
+
const ids = [];
|
|
249
|
+
const seen = /* @__PURE__ */ new Set();
|
|
250
|
+
for (const f of fixtures) {
|
|
251
|
+
if (f.match.endpoint !== void 0 && f.match.endpoint !== "chat") continue;
|
|
252
|
+
const m = f.match.model;
|
|
253
|
+
if (typeof m === "string" && m.length > 0 && !seen.has(m)) {
|
|
254
|
+
seen.add(m);
|
|
255
|
+
ids.push(m);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return ids.length > 0 ? ids : DEFAULT_OPENROUTER_MODELS;
|
|
259
|
+
}
|
|
260
|
+
function journalGet(req, journal, path, status) {
|
|
261
|
+
journal.add({
|
|
262
|
+
method: req.method ?? "GET",
|
|
263
|
+
path,
|
|
264
|
+
headers: require_helpers.flattenHeaders(req.headers),
|
|
265
|
+
body: null,
|
|
266
|
+
response: {
|
|
267
|
+
status,
|
|
268
|
+
fixture: null
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
/** GET /api/v1/models — the OpenRouter model catalog synthesized from fixtures. */
|
|
273
|
+
function handleOpenRouterModels(req, res, fixtures, journal, _defaults, setCorsHeaders) {
|
|
274
|
+
setCorsHeaders(res);
|
|
275
|
+
const path = req.url ?? "/api/v1/models";
|
|
276
|
+
const data = collectModelIds(fixtures).map(openRouterModelObject);
|
|
277
|
+
journalGet(req, journal, path, 200);
|
|
278
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
279
|
+
res.end(JSON.stringify({ data }));
|
|
280
|
+
}
|
|
281
|
+
/** GET /api/v1/key — the caller's key metadata/limits/usage (static defaults). */
|
|
282
|
+
function handleOpenRouterKey(req, res, journal, setCorsHeaders) {
|
|
283
|
+
setCorsHeaders(res);
|
|
284
|
+
journalGet(req, journal, req.url ?? "/api/v1/key", 200);
|
|
285
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
286
|
+
res.end(JSON.stringify({ data: {
|
|
287
|
+
label: "sk-or-v1-aimock",
|
|
288
|
+
is_management_key: false,
|
|
289
|
+
is_provisioning_key: false,
|
|
290
|
+
limit: null,
|
|
291
|
+
limit_reset: null,
|
|
292
|
+
limit_remaining: null,
|
|
293
|
+
include_byok_in_limit: false,
|
|
294
|
+
usage: 0,
|
|
295
|
+
usage_daily: 0,
|
|
296
|
+
usage_weekly: 0,
|
|
297
|
+
usage_monthly: 0,
|
|
298
|
+
byok_usage: 0,
|
|
299
|
+
byok_usage_daily: 0,
|
|
300
|
+
byok_usage_weekly: 0,
|
|
301
|
+
byok_usage_monthly: 0,
|
|
302
|
+
is_free_tier: false,
|
|
303
|
+
expires_at: null,
|
|
304
|
+
creator_user_id: null,
|
|
305
|
+
rate_limit: {
|
|
306
|
+
requests: -1,
|
|
307
|
+
interval: "10s",
|
|
308
|
+
note: "This field is deprecated and safe to ignore."
|
|
309
|
+
}
|
|
310
|
+
} }));
|
|
311
|
+
}
|
|
312
|
+
/** GET /api/v1/credits — account credit totals (static defaults). */
|
|
313
|
+
function handleOpenRouterCredits(req, res, journal, setCorsHeaders) {
|
|
314
|
+
setCorsHeaders(res);
|
|
315
|
+
journalGet(req, journal, req.url ?? "/api/v1/credits", 200);
|
|
316
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
317
|
+
res.end(JSON.stringify({ data: {
|
|
318
|
+
total_credits: 0,
|
|
319
|
+
total_usage: 0
|
|
320
|
+
} }));
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
//#endregion
|
|
324
|
+
exports.buildOpenRouterCandidates = buildOpenRouterCandidates;
|
|
325
|
+
exports.handleOpenRouterCredits = handleOpenRouterCredits;
|
|
326
|
+
exports.handleOpenRouterKey = handleOpenRouterKey;
|
|
327
|
+
exports.handleOpenRouterModels = handleOpenRouterModels;
|
|
328
|
+
exports.isOpenRouterPath = isOpenRouterPath;
|
|
329
|
+
exports.resolveOpenRouterShaping = resolveOpenRouterShaping;
|
|
330
|
+
exports.serializeOpenRouterError = serializeOpenRouterError;
|
|
331
|
+
exports.shapeOpenRouterChunks = shapeOpenRouterChunks;
|
|
332
|
+
exports.shapeOpenRouterCompletion = shapeOpenRouterCompletion;
|
|
333
|
+
//# sourceMappingURL=openrouter-chat.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openrouter-chat.cjs","names":["flattenHeaders"],"sources":["../src/openrouter-chat.ts"],"sourcesContent":["import type * as http from \"node:http\";\nimport type {\n ChatCompletion,\n ChatCompletionRequest,\n Fixture,\n HandlerDefaults,\n OpenRouterUsageExtras,\n ResponseOverrides,\n SSEChunk,\n} from \"./types.js\";\nimport type { Journal } from \"./journal.js\";\nimport { flattenHeaders } from \"./helpers.js\";\n\n/**\n * OpenRouter chat/LLM router shaping. aimock detects an OpenRouter request by\n * the ORIGINAL request path (before `normalizeCompatPath` erases it) starting\n * with the OpenRouter canonical base `/api/v1/` — mirroring a real client that\n * points an OpenAI SDK at `baseURL: https://openrouter.ai/api/v1`. When a\n * request is OpenRouter, the same OpenAI-compatible chat response is shaped\n * with OpenRouter's distinguishing fields (a `gen-` id prefix, a top-level\n * `provider`, `native_finish_reason`, and a rich cost-bearing `usage`) and the\n * `models[]` fallback array is simulated deterministically against fixtures.\n * OpenAI (`/v1/...`) callers are left byte-for-byte unchanged.\n *\n * The shaping is applied as a post-pass over the objects the existing OpenAI\n * builders produce (never a fork of those builders), so every OpenAI code path\n * is untouched.\n */\n\n/** OpenRouter's canonical API base — the detection prefix. */\nexport const OPENROUTER_BASE_PREFIX = \"/api/v1/\";\n\nconst OPENAI_ID_PREFIX = \"chatcmpl-\";\nconst OPENROUTER_ID_PREFIX = \"gen-\";\n\n/**\n * Return true when the ORIGINAL request path marks an OpenRouter request. Must\n * be evaluated on the pre-normalization pathname: `normalizeCompatPath`\n * rewrites `/api/v1/chat/completions` → `/v1/chat/completions`, erasing the\n * `/api/v1/` signal.\n */\nexport function isOpenRouterPath(originalPathname: string): boolean {\n return originalPathname.startsWith(OPENROUTER_BASE_PREFIX);\n}\n\n/**\n * Derive the default OpenRouter `provider` from a model slug: the author\n * segment before the first `/` (`openai/gpt-4o` → `\"openai\"`). A slug without\n * a `/` yields the whole slug. An absent/empty slug — a request that never set\n * `model` yet matched a model-less fixture — yields `\"\"` rather than throwing\n * (guards against a `TypeError` becoming a generic 500).\n */\nexport function deriveOpenRouterProvider(model: string | undefined): string {\n if (!model) return \"\";\n const slash = model.indexOf(\"/\");\n return slash === -1 ? model : model.slice(0, slash);\n}\n\n/**\n * Build the OpenRouter fallback candidate list `[body.model, ...body.models]`,\n * de-duplicated with insertion order preserved. A request without `models`\n * yields just `[body.model]` — identical to today's single-model behavior.\n */\nexport function buildOpenRouterCandidates(body: ChatCompletionRequest): string[] {\n const raw = [body.model, ...(Array.isArray(body.models) ? body.models : [])];\n const seen = new Set<string>();\n const out: string[] = [];\n for (const m of raw) {\n if (typeof m !== \"string\" || m.length === 0) continue;\n if (seen.has(m)) continue;\n seen.add(m);\n out.push(m);\n }\n return out;\n}\n\n/** Rewrite an auto-generated `chatcmpl-` id to OpenRouter's `gen-` prefix. */\nfunction toGenId(id: string): string {\n return id.startsWith(OPENAI_ID_PREFIX)\n ? OPENROUTER_ID_PREFIX + id.slice(OPENAI_ID_PREFIX.length)\n : id;\n}\n\n/**\n * The resolved OpenRouter shaping context for one response. `provider` and the\n * always-present `cost`/`cost_details` are computed here; the detail\n * breakdowns and `is_byok` are carried only when a fixture overrode them.\n */\nexport interface OpenRouterShaping {\n provider: string;\n nativeFinishReason?: string;\n usageExtras: OpenRouterUsageExtras;\n}\n\n/**\n * Resolve the shaping context from a fixture's response overrides and the\n * winning model slug. `provider` defaults to the winning slug's author;\n * `native_finish_reason` mirrors `finish_reason` unless overridden. `cost` and\n * `cost_details` are NULLABLE — emitted only when the fixture supplies a `cost`\n * (never defaulted to 0); when emitted, `cost_details` carries all three\n * upstream fields (a partial object breaks the canonical SDK). The token-detail\n * breakdowns and `is_byok` are included only when the fixture set them, and an\n * emitted breakdown always carries its required member (`cached_tokens` /\n * `reasoning_tokens`).\n */\nexport function resolveOpenRouterShaping(\n overrides: ResponseOverrides | undefined,\n winningModel: string | undefined,\n): OpenRouterShaping {\n const u = overrides?.usage;\n const usageExtras: OpenRouterUsageExtras = {};\n if (u?.cost !== undefined) {\n usageExtras.cost = u.cost;\n const cd = u.cost_details;\n usageExtras.cost_details = {\n ...(cd ?? {}),\n upstream_inference_cost: cd?.upstream_inference_cost ?? 0,\n upstream_inference_prompt_cost: cd?.upstream_inference_prompt_cost ?? 0,\n upstream_inference_completions_cost: cd?.upstream_inference_completions_cost ?? 0,\n };\n }\n if (u?.prompt_tokens_details !== undefined) {\n usageExtras.prompt_tokens_details = {\n ...u.prompt_tokens_details,\n cached_tokens: u.prompt_tokens_details.cached_tokens ?? 0,\n };\n }\n if (u?.completion_tokens_details !== undefined) {\n usageExtras.completion_tokens_details = {\n ...u.completion_tokens_details,\n reasoning_tokens: u.completion_tokens_details.reasoning_tokens ?? 0,\n };\n }\n if (u?.is_byok !== undefined) {\n usageExtras.is_byok = u.is_byok;\n }\n return {\n provider: overrides?.provider ?? deriveOpenRouterProvider(winningModel),\n ...(overrides?.nativeFinishReason !== undefined && {\n nativeFinishReason: overrides.nativeFinishReason,\n }),\n usageExtras,\n };\n}\n\n/** Merge the OpenRouter usage extras onto an existing (OpenAI-shape) usage object. */\nfunction augmentUsage<T extends object>(\n usage: T,\n shaping: OpenRouterShaping,\n): T & OpenRouterUsageExtras {\n return { ...usage, ...shaping.usageExtras };\n}\n\n/** Resolve a choice's native_finish_reason (mirrors finish_reason unless overridden). */\nfunction nativeFinish(finishReason: string | null, shaping: OpenRouterShaping): string | null {\n if (finishReason == null) return null;\n return shaping.nativeFinishReason ?? finishReason;\n}\n\n/**\n * Shape a non-streaming OpenAI completion into OpenRouter form, in place, to\n * match real OpenRouter bytes (or-capture/chat-nonstream.json): `gen-` id\n * (unless the fixture pinned `id`), top-level `provider`, `service_tier: null`,\n * an always-present `system_fingerprint` (null when unset — the canonical\n * `@openrouter/sdk` requires it), per-choice `native_finish_reason`, a\n * `message.reasoning` field (mirrors `reasoning_content`, else null), and\n * cost-bearing `usage`.\n */\nexport function shapeOpenRouterCompletion(\n completion: ChatCompletion,\n shaping: OpenRouterShaping,\n idOverridden: boolean,\n): ChatCompletion {\n if (!idOverridden) completion.id = toGenId(completion.id);\n completion.provider = shaping.provider;\n if (completion.system_fingerprint === undefined) completion.system_fingerprint = null;\n if (completion.service_tier === undefined) completion.service_tier = null;\n for (const choice of completion.choices) {\n // Non-streaming finish_reason is always a string, so native mirrors it.\n choice.native_finish_reason = shaping.nativeFinishReason ?? choice.finish_reason;\n if (choice.message.reasoning === undefined) {\n choice.message.reasoning = choice.message.reasoning_content ?? null;\n }\n }\n completion.usage = augmentUsage(completion.usage, shaping);\n return completion;\n}\n\n/**\n * Shape streaming OpenAI chunks into OpenRouter form, in place, to match real\n * OpenRouter bytes (or-capture/chat-stream.sse): EVERY chunk carries the\n * `gen-` id (unless pinned), top-level `provider`, and `system_fingerprint`\n * (null when unset); EVERY delta choice carries `native_finish_reason` (null\n * until the finish chunk, where it mirrors finish_reason); real OR repeats\n * `role: \"assistant\"` on every content delta (mirrored here); and the final\n * usage-bearing chunk gets cost-bearing usage plus `service_tier: null`.\n */\nexport function shapeOpenRouterChunks(\n chunks: SSEChunk[],\n shaping: OpenRouterShaping,\n idOverridden: boolean,\n): SSEChunk[] {\n for (const chunk of chunks) {\n if (!idOverridden) chunk.id = toGenId(chunk.id);\n chunk.provider = shaping.provider;\n if (chunk.system_fingerprint === undefined) chunk.system_fingerprint = null;\n for (const choice of chunk.choices) {\n choice.native_finish_reason = nativeFinish(choice.finish_reason, shaping);\n // Real OR stamps role:\"assistant\" on every content-bearing delta. A\n // `content: null` delta is NOT content-bearing, so require string content.\n if (typeof choice.delta.content === \"string\" && choice.delta.role === undefined) {\n choice.delta.role = \"assistant\";\n }\n }\n if (chunk.usage) {\n chunk.usage = augmentUsage(chunk.usage, shaping);\n chunk.service_tier = null;\n }\n }\n return chunks;\n}\n\n/**\n * Serialize an OpenRouter error envelope: `{ error: { code, message,\n * metadata? } }` where `code` equals the HTTP status (contrast the OpenAI\n * shape `{ error: { message, type, param, code } }`). Used for every error\n * emitted on an OpenRouter request — fixture errors, no-match, strict, and\n * routing failures.\n */\nexport function serializeOpenRouterError(\n status: number,\n message: string,\n metadata?: Record<string, unknown>,\n): string {\n return JSON.stringify({\n error: {\n code: status,\n message,\n ...(metadata !== undefined && { metadata }),\n },\n });\n}\n\n// ─── Discovery endpoints ─────────────────────────────────────────────────────\n\n/** Default model slug set for `GET /api/v1/models` when no chat fixtures name one. */\nconst DEFAULT_OPENROUTER_MODELS = [\n \"openai/gpt-4o\",\n \"anthropic/claude-3.5-sonnet\",\n \"google/gemini-2.0-flash-001\",\n];\n\n/**\n * Build one OpenRouter model-catalog object (deterministic static filler).\n * Field set mirrors real OpenRouter bytes (or-capture/models-first.json):\n * pricing values are STRINGS; the object also carries hugging_face_id,\n * knowledge_cutoff, supported_voices, links, default_parameters,\n * expiration_date, and a `reasoning` descriptor (SDKs tolerate extras).\n */\nfunction openRouterModelObject(id: string): Record<string, unknown> {\n return {\n id,\n canonical_slug: id,\n hugging_face_id: null,\n name: id,\n created: 0,\n description: `Mock OpenRouter model ${id} served by aimock.`,\n context_length: 128000,\n architecture: {\n modality: \"text->text\",\n input_modalities: [\"text\"],\n output_modalities: [\"text\"],\n tokenizer: \"Other\",\n instruct_type: null,\n },\n pricing: {\n prompt: \"0\",\n completion: \"0\",\n request: \"0\",\n image: \"0\",\n web_search: \"0\",\n internal_reasoning: \"0\",\n input_cache_read: \"0\",\n input_cache_write: \"0\",\n },\n top_provider: {\n context_length: 128000,\n max_completion_tokens: 16384,\n is_moderated: false,\n },\n per_request_limits: null,\n supported_parameters: [\n \"tools\",\n \"tool_choice\",\n \"max_tokens\",\n \"temperature\",\n \"top_p\",\n \"stop\",\n \"reasoning\",\n ],\n default_parameters: null,\n supported_voices: null,\n knowledge_cutoff: null,\n expiration_date: null,\n links: { details: `/api/v1/models/${id}/endpoints` },\n reasoning: { mandatory: false, default_enabled: false },\n };\n}\n\n/**\n * Collect the model ids advertised by `GET /api/v1/models`: every loaded chat\n * (or endpoint-less) fixture with a string `match.model`, in first-seen order;\n * the default set when none contribute one. Mirrors the Ollama `/api/tags` and\n * OpenRouter video `/models` synthesis idioms.\n */\nfunction collectModelIds(fixtures: Fixture[]): string[] {\n const ids: string[] = [];\n const seen = new Set<string>();\n for (const f of fixtures) {\n if (f.match.endpoint !== undefined && f.match.endpoint !== \"chat\") continue;\n const m = f.match.model;\n if (typeof m === \"string\" && m.length > 0 && !seen.has(m)) {\n seen.add(m);\n ids.push(m);\n }\n }\n return ids.length > 0 ? ids : DEFAULT_OPENROUTER_MODELS;\n}\n\nfunction journalGet(\n req: http.IncomingMessage,\n journal: Journal,\n path: string,\n status: number,\n): void {\n journal.add({\n method: req.method ?? \"GET\",\n path,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status, fixture: null },\n });\n}\n\n/** GET /api/v1/models — the OpenRouter model catalog synthesized from fixtures. */\nexport function handleOpenRouterModels(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n fixtures: Fixture[],\n journal: Journal,\n _defaults: HandlerDefaults,\n setCorsHeaders: (res: http.ServerResponse) => void,\n): void {\n setCorsHeaders(res);\n const path = req.url ?? \"/api/v1/models\";\n const data = collectModelIds(fixtures).map(openRouterModelObject);\n journalGet(req, journal, path, 200);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ data }));\n}\n\n/** GET /api/v1/key — the caller's key metadata/limits/usage (static defaults). */\nexport function handleOpenRouterKey(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n journal: Journal,\n setCorsHeaders: (res: http.ServerResponse) => void,\n): void {\n setCorsHeaders(res);\n const path = req.url ?? \"/api/v1/key\";\n journalGet(req, journal, path, 200);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n // Field set + order mirror real OpenRouter bytes (or-capture/key.json).\n // null limit / limit_remaining = unlimited. `creator_user_id` is an account\n // echo — null for the mock (no account). rate_limit is deprecated upstream.\n res.end(\n JSON.stringify({\n data: {\n label: \"sk-or-v1-aimock\",\n is_management_key: false,\n is_provisioning_key: false,\n limit: null,\n limit_reset: null,\n limit_remaining: null,\n include_byok_in_limit: false,\n usage: 0,\n usage_daily: 0,\n usage_weekly: 0,\n usage_monthly: 0,\n byok_usage: 0,\n byok_usage_daily: 0,\n byok_usage_weekly: 0,\n byok_usage_monthly: 0,\n is_free_tier: false,\n expires_at: null,\n creator_user_id: null,\n rate_limit: {\n requests: -1,\n interval: \"10s\",\n note: \"This field is deprecated and safe to ignore.\",\n },\n },\n }),\n );\n}\n\n/** GET /api/v1/credits — account credit totals (static defaults). */\nexport function handleOpenRouterCredits(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n journal: Journal,\n setCorsHeaders: (res: http.ServerResponse) => void,\n): void {\n setCorsHeaders(res);\n const path = req.url ?? \"/api/v1/credits\";\n journalGet(req, journal, path, 200);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ data: { total_credits: 0, total_usage: 0 } }));\n}\n"],"mappings":"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"}
|