@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/provider-auth.cjs
CHANGED
|
@@ -31,6 +31,25 @@ const PROVIDER_AUTH_SCHEMES = {
|
|
|
31
31
|
elevenlabs: { kind: "xi-api-key" },
|
|
32
32
|
fal: { kind: "fal-key" }
|
|
33
33
|
};
|
|
34
|
+
/** Force a known provider credential onto an already-scrubbed outbound map. */
|
|
35
|
+
function applyConfiguredProviderAuth(forwardHeaders, target, providerKey, providerCredential) {
|
|
36
|
+
const scheme = PROVIDER_AUTH_SCHEMES[providerKey];
|
|
37
|
+
if (!scheme) return false;
|
|
38
|
+
switch (scheme.kind) {
|
|
39
|
+
case "bearer":
|
|
40
|
+
takeHeader(forwardHeaders, "authorization");
|
|
41
|
+
forwardHeaders.Authorization = `Bearer ${providerCredential}`;
|
|
42
|
+
break;
|
|
43
|
+
case "fal-key":
|
|
44
|
+
takeHeader(forwardHeaders, "authorization");
|
|
45
|
+
forwardHeaders.Authorization = `Key ${providerCredential}`;
|
|
46
|
+
break;
|
|
47
|
+
default:
|
|
48
|
+
takeHeader(forwardHeaders, authHeaderName(scheme));
|
|
49
|
+
forwardHeaders[authHeaderName(scheme)] = providerCredential;
|
|
50
|
+
}
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
34
53
|
/**
|
|
35
54
|
* Case-insensitively delete a header from a forward-header map, returning the
|
|
36
55
|
* value that was present (if any). Header maps preserve the original casing of
|
|
@@ -165,6 +184,7 @@ function readProviderKeysFromEnv(env = process.env) {
|
|
|
165
184
|
}
|
|
166
185
|
|
|
167
186
|
//#endregion
|
|
187
|
+
exports.applyConfiguredProviderAuth = applyConfiguredProviderAuth;
|
|
168
188
|
exports.applyProviderAuth = applyProviderAuth;
|
|
169
189
|
exports.readProviderKeysFromEnv = readProviderKeysFromEnv;
|
|
170
190
|
//# sourceMappingURL=provider-auth.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider-auth.cjs","names":[],"sources":["../src/provider-auth.ts"],"sourcesContent":["import type { RecordProviderKey } from \"./types.js\";\n\n/**\n * Default marker prefix identifying a \"dummy\" credential that a caller sends\n * only to satisfy an SDK's non-empty API-key requirement (e.g. `sk-aimock-...`).\n * A caller credential that is absent OR begins with this prefix is treated as a\n * placeholder that aimock is free to override with its own built-in upstream key.\n * A caller credential that does NOT begin with this prefix is treated as a real\n * key and forwarded verbatim (the caller overrides aimock).\n *\n * Overridable via the `AIMOCK_DUMMY_KEY_MARKER` env var for setups that mint\n * placeholder keys under a different prefix.\n */\nexport const DEFAULT_DUMMY_KEY_MARKER = \"sk-aimock-\";\n\n/** Resolve the active dummy-key marker, honoring the env override. */\nexport function getDummyKeyMarker(): string {\n const override = process.env.AIMOCK_DUMMY_KEY_MARKER;\n return override && override.length > 0 ? override : DEFAULT_DUMMY_KEY_MARKER;\n}\n\n/**\n * How a given provider carries its API credential on the wire. aimock injects\n * its built-in key using the provider's native scheme so the upstream accepts\n * it. Providers whose auth is a signed/exchanged credential (Bedrock SigV4,\n * Vertex AI OAuth) are deliberately absent — those are NOT simple static-key\n * schemes, so aimock never rewrites their auth and always forwards the caller's\n * credential unchanged.\n *\n * Scheme kinds (all static long-lived keys):\n * - bearer `Authorization: Bearer <key>` (OpenAI, OpenRouter, Cohere, Grok/xAI, Ollama)\n * - fal-key `Authorization: Key <key>` (fal.ai — note the `Key ` prefix, NOT `Bearer`)\n * - x-api-key `x-api-key: <key>` (Anthropic)\n * - x-goog-api-key `x-goog-api-key: <key>` (Gemini, Gemini Interactions, Veo)\n * - api-key `api-key: <key>` (Azure OpenAI static-key auth)\n * - xi-api-key `xi-api-key: <key>` (ElevenLabs)\n *\n * Note on Azure: Azure OpenAI also supports Microsoft Entra ID `Authorization:\n * Bearer <token>` OAuth. aimock only ever injects the static `api-key` header,\n * and a real Entra bearer token from the caller is never dummy-prefixed, so it\n * is always forwarded verbatim (the caller overrides aimock).\n */\ntype AuthScheme =\n | { kind: \"bearer\" } // Authorization: Bearer <key>\n | { kind: \"fal-key\" } // Authorization: Key <key>\n | { kind: \"x-api-key\" } // x-api-key: <key>\n | { kind: \"x-goog-api-key\" } // x-goog-api-key: <key>\n | { kind: \"api-key\" } // api-key: <key>\n | { kind: \"xi-api-key\" }; // xi-api-key: <key>\n\nconst PROVIDER_AUTH_SCHEMES: Partial<Record<RecordProviderKey, AuthScheme>> = {\n // Bearer-token providers.\n openai: { kind: \"bearer\" },\n openrouter: { kind: \"bearer\" },\n cohere: { kind: \"bearer\" },\n grok: { kind: \"bearer\" }, // xAI\n ollama: { kind: \"bearer\" }, // Ollama Cloud / bearer-gated Ollama servers\n // Anthropic.\n anthropic: { kind: \"x-api-key\" },\n // Google (Gemini + Veo) use the x-goog-api-key header scheme.\n gemini: { kind: \"x-goog-api-key\" },\n \"gemini-interactions\": { kind: \"x-goog-api-key\" },\n veo: { kind: \"x-goog-api-key\" },\n // Azure OpenAI static-key auth.\n azure: { kind: \"api-key\" },\n // ElevenLabs.\n elevenlabs: { kind: \"xi-api-key\" },\n // fal.ai — uses the `Key ` prefix on Authorization, NOT `Bearer `.\n fal: { kind: \"fal-key\" },\n};\n\n/**\n * Case-insensitively delete a header from a forward-header map, returning the\n * value that was present (if any). Header maps preserve the original casing of\n * incoming headers, so we can't assume a canonical key.\n */\nfunction takeHeader(headers: Record<string, string>, name: string): string | undefined {\n const lowerName = name.toLowerCase();\n let found: string | undefined;\n for (const key of Object.keys(headers)) {\n if (key.toLowerCase() === lowerName) {\n found = headers[key];\n delete headers[key];\n }\n }\n return found;\n}\n\n/**\n * Extract the caller's credential value for a given auth scheme from the\n * forwarded headers, if present. For bearer, strips the `Bearer ` prefix.\n */\nfunction readCallerCredential(\n headers: Record<string, string>,\n scheme: AuthScheme,\n): string | undefined {\n if (scheme.kind === \"bearer\" || scheme.kind === \"fal-key\") {\n const raw = scanHeader(headers, \"authorization\");\n if (raw === undefined) return undefined;\n // Strip the scheme prefix (`Bearer ` for bearer, `Key ` for fal) if present,\n // otherwise treat the whole value as the credential.\n const prefix = scheme.kind === \"fal-key\" ? \"Key\" : \"Bearer\";\n const match = new RegExp(`^\\\\s*${prefix}\\\\s+(.+)$`, \"i\").exec(raw);\n return match ? match[1].trim() : raw.trim();\n }\n return scanHeader(headers, authHeaderName(scheme));\n}\n\n/**\n * The wire header name a non-Authorization scheme carries its credential in.\n * (bearer/fal-key both use `Authorization` and are handled separately.)\n */\nfunction authHeaderName(scheme: AuthScheme): string {\n switch (scheme.kind) {\n case \"x-api-key\":\n return \"x-api-key\";\n case \"x-goog-api-key\":\n return \"x-goog-api-key\";\n case \"api-key\":\n return \"api-key\";\n case \"xi-api-key\":\n return \"xi-api-key\";\n case \"bearer\":\n case \"fal-key\":\n return \"authorization\";\n }\n}\n\n/** Case-insensitive header lookup that does not mutate the map. */\nfunction scanHeader(headers: Record<string, string>, name: string): string | undefined {\n const lowerName = name.toLowerCase();\n for (const key of Object.keys(headers)) {\n if (key.toLowerCase() === lowerName) return headers[key];\n }\n return undefined;\n}\n\n/**\n * Decide whether aimock should override the caller's credential with its own\n * built-in key. Override when the caller sent no credential OR sent a dummy\n * placeholder (prefixed with the active marker). A real caller key is left\n * untouched so the caller can always override aimock.\n */\nfunction shouldOverride(callerCredential: string | undefined, marker: string): boolean {\n if (callerCredential === undefined || callerCredential.length === 0) return true;\n return callerCredential.startsWith(marker);\n}\n\n/**\n * Inject aimock's built-in upstream credential into the forwarded headers when\n * appropriate (opt-in, backward-compatible). Mutates `forwardHeaders` in place\n * and may mutate `target` (query-param schemes; none currently).\n *\n * Precedence:\n * - No built-in key configured for this provider → no-op (forward verbatim).\n * - Provider not a static-key scheme → no-op (Bedrock SigV4 / Vertex AI OAuth).\n * - Caller credential absent or dummy-prefixed → inject built-in key.\n * - Caller credential is a real key → forward it unchanged.\n *\n * @param forwardHeaders headers already built by buildForwardHeaders(req)\n * @param target upstream URL (reserved for query-param auth schemes)\n * @param providerKey which provider this request is routed to\n * @param builtinKey aimock's configured key for this provider (if any)\n */\nexport function applyProviderAuth(\n forwardHeaders: Record<string, string>,\n target: URL,\n providerKey: RecordProviderKey,\n builtinKey: string | undefined,\n): void {\n void target; // reserved for future query-param schemes (e.g. Gemini ?key=)\n if (!builtinKey) return; // feature inert for this provider\n\n const scheme = PROVIDER_AUTH_SCHEMES[providerKey];\n if (!scheme) return; // signed/OAuth provider — never rewrite auth\n\n const marker = getDummyKeyMarker();\n const callerCredential = readCallerCredential(forwardHeaders, scheme);\n if (!shouldOverride(callerCredential, marker)) return; // caller sent a real key\n\n // Strip any existing auth headers for this scheme (across casings) before\n // setting aimock's key, so a dummy caller key can't linger alongside ours.\n switch (scheme.kind) {\n case \"bearer\":\n takeHeader(forwardHeaders, \"authorization\");\n forwardHeaders[\"Authorization\"] = `Bearer ${builtinKey}`;\n break;\n case \"fal-key\":\n takeHeader(forwardHeaders, \"authorization\");\n forwardHeaders[\"Authorization\"] = `Key ${builtinKey}`;\n break;\n case \"x-api-key\":\n takeHeader(forwardHeaders, \"x-api-key\");\n forwardHeaders[\"x-api-key\"] = builtinKey;\n break;\n case \"x-goog-api-key\":\n takeHeader(forwardHeaders, \"x-goog-api-key\");\n forwardHeaders[\"x-goog-api-key\"] = builtinKey;\n break;\n case \"api-key\":\n takeHeader(forwardHeaders, \"api-key\");\n forwardHeaders[\"api-key\"] = builtinKey;\n break;\n case \"xi-api-key\":\n takeHeader(forwardHeaders, \"xi-api-key\");\n forwardHeaders[\"xi-api-key\"] = builtinKey;\n break;\n }\n}\n\n/**\n * Read per-provider built-in keys from the environment. Returns undefined when\n * no provider key is configured so the feature stays fully inert by default.\n *\n * Each wired static-key provider reads `AIMOCK_PROVIDER_<PROVIDER>_KEY`. An\n * empty-string value is treated as unset (existing truthiness pattern), keeping\n * the feature inert. `gemini-interactions` is not read here: it reuses the\n * Gemini key via the lookup-key remap in `proxyAndRecord`. Signed/OAuth\n * providers (`bedrock`, `vertexai`) have no env var — aimock never rewrites\n * their auth.\n */\nexport function readProviderKeysFromEnv(\n env: NodeJS.ProcessEnv = process.env,\n): Partial<Record<RecordProviderKey, string>> | undefined {\n const keys: Partial<Record<RecordProviderKey, string>> = {};\n if (env.AIMOCK_PROVIDER_OPENAI_KEY) keys.openai = env.AIMOCK_PROVIDER_OPENAI_KEY;\n if (env.AIMOCK_PROVIDER_ANTHROPIC_KEY) keys.anthropic = env.AIMOCK_PROVIDER_ANTHROPIC_KEY;\n if (env.AIMOCK_PROVIDER_GEMINI_KEY) keys.gemini = env.AIMOCK_PROVIDER_GEMINI_KEY;\n if (env.AIMOCK_PROVIDER_OPENROUTER_KEY) keys.openrouter = env.AIMOCK_PROVIDER_OPENROUTER_KEY;\n if (env.AIMOCK_PROVIDER_COHERE_KEY) keys.cohere = env.AIMOCK_PROVIDER_COHERE_KEY;\n if (env.AIMOCK_PROVIDER_GROK_KEY) keys.grok = env.AIMOCK_PROVIDER_GROK_KEY;\n if (env.AIMOCK_PROVIDER_OLLAMA_KEY) keys.ollama = env.AIMOCK_PROVIDER_OLLAMA_KEY;\n if (env.AIMOCK_PROVIDER_VEO_KEY) keys.veo = env.AIMOCK_PROVIDER_VEO_KEY;\n if (env.AIMOCK_PROVIDER_AZURE_KEY) keys.azure = env.AIMOCK_PROVIDER_AZURE_KEY;\n if (env.AIMOCK_PROVIDER_ELEVENLABS_KEY) keys.elevenlabs = env.AIMOCK_PROVIDER_ELEVENLABS_KEY;\n if (env.AIMOCK_PROVIDER_FAL_KEY) keys.fal = env.AIMOCK_PROVIDER_FAL_KEY;\n return Object.keys(keys).length > 0 ? keys : undefined;\n}\n"],"mappings":";;;;;;;;;;;;;AAaA,MAAa,2BAA2B;;AAGxC,SAAgB,oBAA4B;CAC1C,MAAM,WAAW,QAAQ,IAAI;AAC7B,QAAO,YAAY,SAAS,SAAS,IAAI,WAAW;;AAgCtD,MAAM,wBAAwE;CAE5E,QAAQ,EAAE,MAAM,UAAU;CAC1B,YAAY,EAAE,MAAM,UAAU;CAC9B,QAAQ,EAAE,MAAM,UAAU;CAC1B,MAAM,EAAE,MAAM,UAAU;CACxB,QAAQ,EAAE,MAAM,UAAU;CAE1B,WAAW,EAAE,MAAM,aAAa;CAEhC,QAAQ,EAAE,MAAM,kBAAkB;CAClC,uBAAuB,EAAE,MAAM,kBAAkB;CACjD,KAAK,EAAE,MAAM,kBAAkB;CAE/B,OAAO,EAAE,MAAM,WAAW;CAE1B,YAAY,EAAE,MAAM,cAAc;CAElC,KAAK,EAAE,MAAM,WAAW;CACzB;;;;;;AAOD,SAAS,WAAW,SAAiC,MAAkC;CACrF,MAAM,YAAY,KAAK,aAAa;CACpC,IAAI;AACJ,MAAK,MAAM,OAAO,OAAO,KAAK,QAAQ,CACpC,KAAI,IAAI,aAAa,KAAK,WAAW;AACnC,UAAQ,QAAQ;AAChB,SAAO,QAAQ;;AAGnB,QAAO;;;;;;AAOT,SAAS,qBACP,SACA,QACoB;AACpB,KAAI,OAAO,SAAS,YAAY,OAAO,SAAS,WAAW;EACzD,MAAM,MAAM,WAAW,SAAS,gBAAgB;AAChD,MAAI,QAAQ,OAAW,QAAO;EAG9B,MAAM,SAAS,OAAO,SAAS,YAAY,QAAQ;EACnD,MAAM,QAAQ,IAAI,OAAO,QAAQ,OAAO,YAAY,IAAI,CAAC,KAAK,IAAI;AAClE,SAAO,QAAQ,MAAM,GAAG,MAAM,GAAG,IAAI,MAAM;;AAE7C,QAAO,WAAW,SAAS,eAAe,OAAO,CAAC;;;;;;AAOpD,SAAS,eAAe,QAA4B;AAClD,SAAQ,OAAO,MAAf;EACE,KAAK,YACH,QAAO;EACT,KAAK,iBACH,QAAO;EACT,KAAK,UACH,QAAO;EACT,KAAK,aACH,QAAO;EACT,KAAK;EACL,KAAK,UACH,QAAO;;;;AAKb,SAAS,WAAW,SAAiC,MAAkC;CACrF,MAAM,YAAY,KAAK,aAAa;AACpC,MAAK,MAAM,OAAO,OAAO,KAAK,QAAQ,CACpC,KAAI,IAAI,aAAa,KAAK,UAAW,QAAO,QAAQ;;;;;;;;AAWxD,SAAS,eAAe,kBAAsC,QAAyB;AACrF,KAAI,qBAAqB,UAAa,iBAAiB,WAAW,EAAG,QAAO;AAC5E,QAAO,iBAAiB,WAAW,OAAO;;;;;;;;;;;;;;;;;;AAmB5C,SAAgB,kBACd,gBACA,QACA,aACA,YACM;AAEN,KAAI,CAAC,WAAY;CAEjB,MAAM,SAAS,sBAAsB;AACrC,KAAI,CAAC,OAAQ;CAEb,MAAM,SAAS,mBAAmB;AAElC,KAAI,CAAC,eADoB,qBAAqB,gBAAgB,OAAO,EAC/B,OAAO,CAAE;AAI/C,SAAQ,OAAO,MAAf;EACE,KAAK;AACH,cAAW,gBAAgB,gBAAgB;AAC3C,kBAAe,mBAAmB,UAAU;AAC5C;EACF,KAAK;AACH,cAAW,gBAAgB,gBAAgB;AAC3C,kBAAe,mBAAmB,OAAO;AACzC;EACF,KAAK;AACH,cAAW,gBAAgB,YAAY;AACvC,kBAAe,eAAe;AAC9B;EACF,KAAK;AACH,cAAW,gBAAgB,iBAAiB;AAC5C,kBAAe,oBAAoB;AACnC;EACF,KAAK;AACH,cAAW,gBAAgB,UAAU;AACrC,kBAAe,aAAa;AAC5B;EACF,KAAK;AACH,cAAW,gBAAgB,aAAa;AACxC,kBAAe,gBAAgB;AAC/B;;;;;;;;;;;;;;AAeN,SAAgB,wBACd,MAAyB,QAAQ,KACuB;CACxD,MAAM,OAAmD,EAAE;AAC3D,KAAI,IAAI,2BAA4B,MAAK,SAAS,IAAI;AACtD,KAAI,IAAI,8BAA+B,MAAK,YAAY,IAAI;AAC5D,KAAI,IAAI,2BAA4B,MAAK,SAAS,IAAI;AACtD,KAAI,IAAI,+BAAgC,MAAK,aAAa,IAAI;AAC9D,KAAI,IAAI,2BAA4B,MAAK,SAAS,IAAI;AACtD,KAAI,IAAI,yBAA0B,MAAK,OAAO,IAAI;AAClD,KAAI,IAAI,2BAA4B,MAAK,SAAS,IAAI;AACtD,KAAI,IAAI,wBAAyB,MAAK,MAAM,IAAI;AAChD,KAAI,IAAI,0BAA2B,MAAK,QAAQ,IAAI;AACpD,KAAI,IAAI,+BAAgC,MAAK,aAAa,IAAI;AAC9D,KAAI,IAAI,wBAAyB,MAAK,MAAM,IAAI;AAChD,QAAO,OAAO,KAAK,KAAK,CAAC,SAAS,IAAI,OAAO"}
|
|
1
|
+
{"version":3,"file":"provider-auth.cjs","names":[],"sources":["../src/provider-auth.ts"],"sourcesContent":["import type { RecordProviderKey } from \"./types.js\";\n\n/**\n * Default marker prefix identifying a \"dummy\" credential that a caller sends\n * only to satisfy an SDK's non-empty API-key requirement (e.g. `sk-aimock-...`).\n * A caller credential that is absent OR begins with this prefix is treated as a\n * placeholder that aimock is free to override with its own built-in upstream key.\n * A caller credential that does NOT begin with this prefix is treated as a real\n * key and forwarded verbatim (the caller overrides aimock).\n *\n * Overridable via the `AIMOCK_DUMMY_KEY_MARKER` env var for setups that mint\n * placeholder keys under a different prefix.\n */\nexport const DEFAULT_DUMMY_KEY_MARKER = \"sk-aimock-\";\n\n/** Resolve the active dummy-key marker, honoring the env override. */\nexport function getDummyKeyMarker(): string {\n const override = process.env.AIMOCK_DUMMY_KEY_MARKER;\n return override && override.length > 0 ? override : DEFAULT_DUMMY_KEY_MARKER;\n}\n\n/**\n * How a given provider carries its API credential on the wire. aimock injects\n * its built-in key using the provider's native scheme so the upstream accepts\n * it. Providers whose auth is a signed/exchanged credential (Bedrock SigV4,\n * Vertex AI OAuth) are deliberately absent — those are NOT simple static-key\n * schemes, so aimock never rewrites their auth and always forwards the caller's\n * credential unchanged.\n *\n * Scheme kinds (all static long-lived keys):\n * - bearer `Authorization: Bearer <key>` (OpenAI, OpenRouter, Cohere, Grok/xAI, Ollama)\n * - fal-key `Authorization: Key <key>` (fal.ai — note the `Key ` prefix, NOT `Bearer`)\n * - x-api-key `x-api-key: <key>` (Anthropic)\n * - x-goog-api-key `x-goog-api-key: <key>` (Gemini, Gemini Interactions, Veo)\n * - api-key `api-key: <key>` (Azure OpenAI static-key auth)\n * - xi-api-key `xi-api-key: <key>` (ElevenLabs)\n *\n * Note on Azure: Azure OpenAI also supports Microsoft Entra ID `Authorization:\n * Bearer <token>` OAuth. aimock only ever injects the static `api-key` header,\n * and a real Entra bearer token from the caller is never dummy-prefixed, so it\n * is always forwarded verbatim (the caller overrides aimock).\n */\nexport type AuthScheme =\n | { kind: \"bearer\" } // Authorization: Bearer <key>\n | { kind: \"fal-key\" } // Authorization: Key <key>\n | { kind: \"x-api-key\" } // x-api-key: <key>\n | { kind: \"x-goog-api-key\" } // x-goog-api-key: <key>\n | { kind: \"api-key\" } // api-key: <key>\n | { kind: \"xi-api-key\" }; // xi-api-key: <key>\n\nconst PROVIDER_AUTH_SCHEMES: Partial<Record<RecordProviderKey, AuthScheme>> = {\n // Bearer-token providers.\n openai: { kind: \"bearer\" },\n openrouter: { kind: \"bearer\" },\n cohere: { kind: \"bearer\" },\n grok: { kind: \"bearer\" }, // xAI\n ollama: { kind: \"bearer\" }, // Ollama Cloud / bearer-gated Ollama servers\n // Anthropic.\n anthropic: { kind: \"x-api-key\" },\n // Google (Gemini + Veo) use the x-goog-api-key header scheme.\n gemini: { kind: \"x-goog-api-key\" },\n \"gemini-interactions\": { kind: \"x-goog-api-key\" },\n veo: { kind: \"x-goog-api-key\" },\n // Azure OpenAI static-key auth.\n azure: { kind: \"api-key\" },\n // ElevenLabs.\n elevenlabs: { kind: \"xi-api-key\" },\n // fal.ai — uses the `Key ` prefix on Authorization, NOT `Bearer `.\n fal: { kind: \"fal-key\" },\n};\n\nexport function hasStaticProviderAuthScheme(providerKey: RecordProviderKey): boolean {\n return PROVIDER_AUTH_SCHEMES[providerKey] !== undefined;\n}\n\n/** Force a known provider credential onto an already-scrubbed outbound map. */\nexport function applyConfiguredProviderAuth(\n forwardHeaders: Record<string, string>,\n target: URL,\n providerKey: RecordProviderKey,\n providerCredential: string,\n): boolean {\n void target;\n const scheme = PROVIDER_AUTH_SCHEMES[providerKey];\n if (!scheme) return false;\n switch (scheme.kind) {\n case \"bearer\":\n takeHeader(forwardHeaders, \"authorization\");\n forwardHeaders.Authorization = `Bearer ${providerCredential}`;\n break;\n case \"fal-key\":\n takeHeader(forwardHeaders, \"authorization\");\n forwardHeaders.Authorization = `Key ${providerCredential}`;\n break;\n default:\n takeHeader(forwardHeaders, authHeaderName(scheme));\n forwardHeaders[authHeaderName(scheme)] = providerCredential;\n }\n return true;\n}\n\n/**\n * Case-insensitively delete a header from a forward-header map, returning the\n * value that was present (if any). Header maps preserve the original casing of\n * incoming headers, so we can't assume a canonical key.\n */\nfunction takeHeader(headers: Record<string, string>, name: string): string | undefined {\n const lowerName = name.toLowerCase();\n let found: string | undefined;\n for (const key of Object.keys(headers)) {\n if (key.toLowerCase() === lowerName) {\n found = headers[key];\n delete headers[key];\n }\n }\n return found;\n}\n\n/**\n * Extract the caller's credential value for a given auth scheme from the\n * forwarded headers, if present. For bearer, strips the `Bearer ` prefix.\n */\nfunction readCallerCredential(\n headers: Record<string, string>,\n scheme: AuthScheme,\n): string | undefined {\n if (scheme.kind === \"bearer\" || scheme.kind === \"fal-key\") {\n const raw = scanHeader(headers, \"authorization\");\n if (raw === undefined) return undefined;\n // Strip the scheme prefix (`Bearer ` for bearer, `Key ` for fal) if present,\n // otherwise treat the whole value as the credential.\n const prefix = scheme.kind === \"fal-key\" ? \"Key\" : \"Bearer\";\n const match = new RegExp(`^\\\\s*${prefix}\\\\s+(.+)$`, \"i\").exec(raw);\n return match ? match[1].trim() : raw.trim();\n }\n return scanHeader(headers, authHeaderName(scheme));\n}\n\n/**\n * The wire header name a non-Authorization scheme carries its credential in.\n * (bearer/fal-key both use `Authorization` and are handled separately.)\n */\nfunction authHeaderName(scheme: AuthScheme): string {\n switch (scheme.kind) {\n case \"x-api-key\":\n return \"x-api-key\";\n case \"x-goog-api-key\":\n return \"x-goog-api-key\";\n case \"api-key\":\n return \"api-key\";\n case \"xi-api-key\":\n return \"xi-api-key\";\n case \"bearer\":\n case \"fal-key\":\n return \"authorization\";\n }\n}\n\n/** Case-insensitive header lookup that does not mutate the map. */\nfunction scanHeader(headers: Record<string, string>, name: string): string | undefined {\n const lowerName = name.toLowerCase();\n for (const key of Object.keys(headers)) {\n if (key.toLowerCase() === lowerName) return headers[key];\n }\n return undefined;\n}\n\n/**\n * Decide whether aimock should override the caller's credential with its own\n * built-in key. Override when the caller sent no credential OR sent a dummy\n * placeholder (prefixed with the active marker). A real caller key is left\n * untouched so the caller can always override aimock.\n */\nfunction shouldOverride(callerCredential: string | undefined, marker: string): boolean {\n if (callerCredential === undefined || callerCredential.length === 0) return true;\n return callerCredential.startsWith(marker);\n}\n\n/**\n * Inject aimock's built-in upstream credential into the forwarded headers when\n * appropriate (opt-in, backward-compatible). Mutates `forwardHeaders` in place\n * and may mutate `target` (query-param schemes; none currently).\n *\n * Precedence:\n * - No built-in key configured for this provider → no-op (forward verbatim).\n * - Provider not a static-key scheme → no-op (Bedrock SigV4 / Vertex AI OAuth).\n * - Caller credential absent or dummy-prefixed → inject built-in key.\n * - Caller credential is a real key → forward it unchanged.\n *\n * @param forwardHeaders headers already built by buildForwardHeaders(req)\n * @param target upstream URL (reserved for query-param auth schemes)\n * @param providerKey which provider this request is routed to\n * @param builtinKey aimock's configured key for this provider (if any)\n */\nexport function applyProviderAuth(\n forwardHeaders: Record<string, string>,\n target: URL,\n providerKey: RecordProviderKey,\n builtinKey: string | undefined,\n): void {\n void target; // reserved for future query-param schemes (e.g. Gemini ?key=)\n if (!builtinKey) return; // feature inert for this provider\n\n const scheme = PROVIDER_AUTH_SCHEMES[providerKey];\n if (!scheme) return; // signed/OAuth provider — never rewrite auth\n\n const marker = getDummyKeyMarker();\n const callerCredential = readCallerCredential(forwardHeaders, scheme);\n if (!shouldOverride(callerCredential, marker)) return; // caller sent a real key\n\n // Strip any existing auth headers for this scheme (across casings) before\n // setting aimock's key, so a dummy caller key can't linger alongside ours.\n switch (scheme.kind) {\n case \"bearer\":\n takeHeader(forwardHeaders, \"authorization\");\n forwardHeaders[\"Authorization\"] = `Bearer ${builtinKey}`;\n break;\n case \"fal-key\":\n takeHeader(forwardHeaders, \"authorization\");\n forwardHeaders[\"Authorization\"] = `Key ${builtinKey}`;\n break;\n case \"x-api-key\":\n takeHeader(forwardHeaders, \"x-api-key\");\n forwardHeaders[\"x-api-key\"] = builtinKey;\n break;\n case \"x-goog-api-key\":\n takeHeader(forwardHeaders, \"x-goog-api-key\");\n forwardHeaders[\"x-goog-api-key\"] = builtinKey;\n break;\n case \"api-key\":\n takeHeader(forwardHeaders, \"api-key\");\n forwardHeaders[\"api-key\"] = builtinKey;\n break;\n case \"xi-api-key\":\n takeHeader(forwardHeaders, \"xi-api-key\");\n forwardHeaders[\"xi-api-key\"] = builtinKey;\n break;\n }\n}\n\n/**\n * Read per-provider built-in keys from the environment. Returns undefined when\n * no provider key is configured so the feature stays fully inert by default.\n *\n * Each wired static-key provider reads `AIMOCK_PROVIDER_<PROVIDER>_KEY`. An\n * empty-string value is treated as unset (existing truthiness pattern), keeping\n * the feature inert. `gemini-interactions` is not read here: it reuses the\n * Gemini key via the lookup-key remap in `proxyAndRecord`. Signed/OAuth\n * providers (`bedrock`, `vertexai`) have no env var — aimock never rewrites\n * their auth.\n */\nexport function readProviderKeysFromEnv(\n env: NodeJS.ProcessEnv = process.env,\n): Partial<Record<RecordProviderKey, string>> | undefined {\n const keys: Partial<Record<RecordProviderKey, string>> = {};\n if (env.AIMOCK_PROVIDER_OPENAI_KEY) keys.openai = env.AIMOCK_PROVIDER_OPENAI_KEY;\n if (env.AIMOCK_PROVIDER_ANTHROPIC_KEY) keys.anthropic = env.AIMOCK_PROVIDER_ANTHROPIC_KEY;\n if (env.AIMOCK_PROVIDER_GEMINI_KEY) keys.gemini = env.AIMOCK_PROVIDER_GEMINI_KEY;\n if (env.AIMOCK_PROVIDER_OPENROUTER_KEY) keys.openrouter = env.AIMOCK_PROVIDER_OPENROUTER_KEY;\n if (env.AIMOCK_PROVIDER_COHERE_KEY) keys.cohere = env.AIMOCK_PROVIDER_COHERE_KEY;\n if (env.AIMOCK_PROVIDER_GROK_KEY) keys.grok = env.AIMOCK_PROVIDER_GROK_KEY;\n if (env.AIMOCK_PROVIDER_OLLAMA_KEY) keys.ollama = env.AIMOCK_PROVIDER_OLLAMA_KEY;\n if (env.AIMOCK_PROVIDER_VEO_KEY) keys.veo = env.AIMOCK_PROVIDER_VEO_KEY;\n if (env.AIMOCK_PROVIDER_AZURE_KEY) keys.azure = env.AIMOCK_PROVIDER_AZURE_KEY;\n if (env.AIMOCK_PROVIDER_ELEVENLABS_KEY) keys.elevenlabs = env.AIMOCK_PROVIDER_ELEVENLABS_KEY;\n if (env.AIMOCK_PROVIDER_FAL_KEY) keys.fal = env.AIMOCK_PROVIDER_FAL_KEY;\n return Object.keys(keys).length > 0 ? keys : undefined;\n}\n"],"mappings":"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"}
|
package/dist/provider-auth.js
CHANGED
|
@@ -30,6 +30,25 @@ const PROVIDER_AUTH_SCHEMES = {
|
|
|
30
30
|
elevenlabs: { kind: "xi-api-key" },
|
|
31
31
|
fal: { kind: "fal-key" }
|
|
32
32
|
};
|
|
33
|
+
/** Force a known provider credential onto an already-scrubbed outbound map. */
|
|
34
|
+
function applyConfiguredProviderAuth(forwardHeaders, target, providerKey, providerCredential) {
|
|
35
|
+
const scheme = PROVIDER_AUTH_SCHEMES[providerKey];
|
|
36
|
+
if (!scheme) return false;
|
|
37
|
+
switch (scheme.kind) {
|
|
38
|
+
case "bearer":
|
|
39
|
+
takeHeader(forwardHeaders, "authorization");
|
|
40
|
+
forwardHeaders.Authorization = `Bearer ${providerCredential}`;
|
|
41
|
+
break;
|
|
42
|
+
case "fal-key":
|
|
43
|
+
takeHeader(forwardHeaders, "authorization");
|
|
44
|
+
forwardHeaders.Authorization = `Key ${providerCredential}`;
|
|
45
|
+
break;
|
|
46
|
+
default:
|
|
47
|
+
takeHeader(forwardHeaders, authHeaderName(scheme));
|
|
48
|
+
forwardHeaders[authHeaderName(scheme)] = providerCredential;
|
|
49
|
+
}
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
33
52
|
/**
|
|
34
53
|
* Case-insensitively delete a header from a forward-header map, returning the
|
|
35
54
|
* value that was present (if any). Header maps preserve the original casing of
|
|
@@ -164,5 +183,5 @@ function readProviderKeysFromEnv(env = process.env) {
|
|
|
164
183
|
}
|
|
165
184
|
|
|
166
185
|
//#endregion
|
|
167
|
-
export { applyProviderAuth, readProviderKeysFromEnv };
|
|
186
|
+
export { applyConfiguredProviderAuth, applyProviderAuth, readProviderKeysFromEnv };
|
|
168
187
|
//# sourceMappingURL=provider-auth.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider-auth.js","names":[],"sources":["../src/provider-auth.ts"],"sourcesContent":["import type { RecordProviderKey } from \"./types.js\";\n\n/**\n * Default marker prefix identifying a \"dummy\" credential that a caller sends\n * only to satisfy an SDK's non-empty API-key requirement (e.g. `sk-aimock-...`).\n * A caller credential that is absent OR begins with this prefix is treated as a\n * placeholder that aimock is free to override with its own built-in upstream key.\n * A caller credential that does NOT begin with this prefix is treated as a real\n * key and forwarded verbatim (the caller overrides aimock).\n *\n * Overridable via the `AIMOCK_DUMMY_KEY_MARKER` env var for setups that mint\n * placeholder keys under a different prefix.\n */\nexport const DEFAULT_DUMMY_KEY_MARKER = \"sk-aimock-\";\n\n/** Resolve the active dummy-key marker, honoring the env override. */\nexport function getDummyKeyMarker(): string {\n const override = process.env.AIMOCK_DUMMY_KEY_MARKER;\n return override && override.length > 0 ? override : DEFAULT_DUMMY_KEY_MARKER;\n}\n\n/**\n * How a given provider carries its API credential on the wire. aimock injects\n * its built-in key using the provider's native scheme so the upstream accepts\n * it. Providers whose auth is a signed/exchanged credential (Bedrock SigV4,\n * Vertex AI OAuth) are deliberately absent — those are NOT simple static-key\n * schemes, so aimock never rewrites their auth and always forwards the caller's\n * credential unchanged.\n *\n * Scheme kinds (all static long-lived keys):\n * - bearer `Authorization: Bearer <key>` (OpenAI, OpenRouter, Cohere, Grok/xAI, Ollama)\n * - fal-key `Authorization: Key <key>` (fal.ai — note the `Key ` prefix, NOT `Bearer`)\n * - x-api-key `x-api-key: <key>` (Anthropic)\n * - x-goog-api-key `x-goog-api-key: <key>` (Gemini, Gemini Interactions, Veo)\n * - api-key `api-key: <key>` (Azure OpenAI static-key auth)\n * - xi-api-key `xi-api-key: <key>` (ElevenLabs)\n *\n * Note on Azure: Azure OpenAI also supports Microsoft Entra ID `Authorization:\n * Bearer <token>` OAuth. aimock only ever injects the static `api-key` header,\n * and a real Entra bearer token from the caller is never dummy-prefixed, so it\n * is always forwarded verbatim (the caller overrides aimock).\n */\ntype AuthScheme =\n | { kind: \"bearer\" } // Authorization: Bearer <key>\n | { kind: \"fal-key\" } // Authorization: Key <key>\n | { kind: \"x-api-key\" } // x-api-key: <key>\n | { kind: \"x-goog-api-key\" } // x-goog-api-key: <key>\n | { kind: \"api-key\" } // api-key: <key>\n | { kind: \"xi-api-key\" }; // xi-api-key: <key>\n\nconst PROVIDER_AUTH_SCHEMES: Partial<Record<RecordProviderKey, AuthScheme>> = {\n // Bearer-token providers.\n openai: { kind: \"bearer\" },\n openrouter: { kind: \"bearer\" },\n cohere: { kind: \"bearer\" },\n grok: { kind: \"bearer\" }, // xAI\n ollama: { kind: \"bearer\" }, // Ollama Cloud / bearer-gated Ollama servers\n // Anthropic.\n anthropic: { kind: \"x-api-key\" },\n // Google (Gemini + Veo) use the x-goog-api-key header scheme.\n gemini: { kind: \"x-goog-api-key\" },\n \"gemini-interactions\": { kind: \"x-goog-api-key\" },\n veo: { kind: \"x-goog-api-key\" },\n // Azure OpenAI static-key auth.\n azure: { kind: \"api-key\" },\n // ElevenLabs.\n elevenlabs: { kind: \"xi-api-key\" },\n // fal.ai — uses the `Key ` prefix on Authorization, NOT `Bearer `.\n fal: { kind: \"fal-key\" },\n};\n\n/**\n * Case-insensitively delete a header from a forward-header map, returning the\n * value that was present (if any). Header maps preserve the original casing of\n * incoming headers, so we can't assume a canonical key.\n */\nfunction takeHeader(headers: Record<string, string>, name: string): string | undefined {\n const lowerName = name.toLowerCase();\n let found: string | undefined;\n for (const key of Object.keys(headers)) {\n if (key.toLowerCase() === lowerName) {\n found = headers[key];\n delete headers[key];\n }\n }\n return found;\n}\n\n/**\n * Extract the caller's credential value for a given auth scheme from the\n * forwarded headers, if present. For bearer, strips the `Bearer ` prefix.\n */\nfunction readCallerCredential(\n headers: Record<string, string>,\n scheme: AuthScheme,\n): string | undefined {\n if (scheme.kind === \"bearer\" || scheme.kind === \"fal-key\") {\n const raw = scanHeader(headers, \"authorization\");\n if (raw === undefined) return undefined;\n // Strip the scheme prefix (`Bearer ` for bearer, `Key ` for fal) if present,\n // otherwise treat the whole value as the credential.\n const prefix = scheme.kind === \"fal-key\" ? \"Key\" : \"Bearer\";\n const match = new RegExp(`^\\\\s*${prefix}\\\\s+(.+)$`, \"i\").exec(raw);\n return match ? match[1].trim() : raw.trim();\n }\n return scanHeader(headers, authHeaderName(scheme));\n}\n\n/**\n * The wire header name a non-Authorization scheme carries its credential in.\n * (bearer/fal-key both use `Authorization` and are handled separately.)\n */\nfunction authHeaderName(scheme: AuthScheme): string {\n switch (scheme.kind) {\n case \"x-api-key\":\n return \"x-api-key\";\n case \"x-goog-api-key\":\n return \"x-goog-api-key\";\n case \"api-key\":\n return \"api-key\";\n case \"xi-api-key\":\n return \"xi-api-key\";\n case \"bearer\":\n case \"fal-key\":\n return \"authorization\";\n }\n}\n\n/** Case-insensitive header lookup that does not mutate the map. */\nfunction scanHeader(headers: Record<string, string>, name: string): string | undefined {\n const lowerName = name.toLowerCase();\n for (const key of Object.keys(headers)) {\n if (key.toLowerCase() === lowerName) return headers[key];\n }\n return undefined;\n}\n\n/**\n * Decide whether aimock should override the caller's credential with its own\n * built-in key. Override when the caller sent no credential OR sent a dummy\n * placeholder (prefixed with the active marker). A real caller key is left\n * untouched so the caller can always override aimock.\n */\nfunction shouldOverride(callerCredential: string | undefined, marker: string): boolean {\n if (callerCredential === undefined || callerCredential.length === 0) return true;\n return callerCredential.startsWith(marker);\n}\n\n/**\n * Inject aimock's built-in upstream credential into the forwarded headers when\n * appropriate (opt-in, backward-compatible). Mutates `forwardHeaders` in place\n * and may mutate `target` (query-param schemes; none currently).\n *\n * Precedence:\n * - No built-in key configured for this provider → no-op (forward verbatim).\n * - Provider not a static-key scheme → no-op (Bedrock SigV4 / Vertex AI OAuth).\n * - Caller credential absent or dummy-prefixed → inject built-in key.\n * - Caller credential is a real key → forward it unchanged.\n *\n * @param forwardHeaders headers already built by buildForwardHeaders(req)\n * @param target upstream URL (reserved for query-param auth schemes)\n * @param providerKey which provider this request is routed to\n * @param builtinKey aimock's configured key for this provider (if any)\n */\nexport function applyProviderAuth(\n forwardHeaders: Record<string, string>,\n target: URL,\n providerKey: RecordProviderKey,\n builtinKey: string | undefined,\n): void {\n void target; // reserved for future query-param schemes (e.g. Gemini ?key=)\n if (!builtinKey) return; // feature inert for this provider\n\n const scheme = PROVIDER_AUTH_SCHEMES[providerKey];\n if (!scheme) return; // signed/OAuth provider — never rewrite auth\n\n const marker = getDummyKeyMarker();\n const callerCredential = readCallerCredential(forwardHeaders, scheme);\n if (!shouldOverride(callerCredential, marker)) return; // caller sent a real key\n\n // Strip any existing auth headers for this scheme (across casings) before\n // setting aimock's key, so a dummy caller key can't linger alongside ours.\n switch (scheme.kind) {\n case \"bearer\":\n takeHeader(forwardHeaders, \"authorization\");\n forwardHeaders[\"Authorization\"] = `Bearer ${builtinKey}`;\n break;\n case \"fal-key\":\n takeHeader(forwardHeaders, \"authorization\");\n forwardHeaders[\"Authorization\"] = `Key ${builtinKey}`;\n break;\n case \"x-api-key\":\n takeHeader(forwardHeaders, \"x-api-key\");\n forwardHeaders[\"x-api-key\"] = builtinKey;\n break;\n case \"x-goog-api-key\":\n takeHeader(forwardHeaders, \"x-goog-api-key\");\n forwardHeaders[\"x-goog-api-key\"] = builtinKey;\n break;\n case \"api-key\":\n takeHeader(forwardHeaders, \"api-key\");\n forwardHeaders[\"api-key\"] = builtinKey;\n break;\n case \"xi-api-key\":\n takeHeader(forwardHeaders, \"xi-api-key\");\n forwardHeaders[\"xi-api-key\"] = builtinKey;\n break;\n }\n}\n\n/**\n * Read per-provider built-in keys from the environment. Returns undefined when\n * no provider key is configured so the feature stays fully inert by default.\n *\n * Each wired static-key provider reads `AIMOCK_PROVIDER_<PROVIDER>_KEY`. An\n * empty-string value is treated as unset (existing truthiness pattern), keeping\n * the feature inert. `gemini-interactions` is not read here: it reuses the\n * Gemini key via the lookup-key remap in `proxyAndRecord`. Signed/OAuth\n * providers (`bedrock`, `vertexai`) have no env var — aimock never rewrites\n * their auth.\n */\nexport function readProviderKeysFromEnv(\n env: NodeJS.ProcessEnv = process.env,\n): Partial<Record<RecordProviderKey, string>> | undefined {\n const keys: Partial<Record<RecordProviderKey, string>> = {};\n if (env.AIMOCK_PROVIDER_OPENAI_KEY) keys.openai = env.AIMOCK_PROVIDER_OPENAI_KEY;\n if (env.AIMOCK_PROVIDER_ANTHROPIC_KEY) keys.anthropic = env.AIMOCK_PROVIDER_ANTHROPIC_KEY;\n if (env.AIMOCK_PROVIDER_GEMINI_KEY) keys.gemini = env.AIMOCK_PROVIDER_GEMINI_KEY;\n if (env.AIMOCK_PROVIDER_OPENROUTER_KEY) keys.openrouter = env.AIMOCK_PROVIDER_OPENROUTER_KEY;\n if (env.AIMOCK_PROVIDER_COHERE_KEY) keys.cohere = env.AIMOCK_PROVIDER_COHERE_KEY;\n if (env.AIMOCK_PROVIDER_GROK_KEY) keys.grok = env.AIMOCK_PROVIDER_GROK_KEY;\n if (env.AIMOCK_PROVIDER_OLLAMA_KEY) keys.ollama = env.AIMOCK_PROVIDER_OLLAMA_KEY;\n if (env.AIMOCK_PROVIDER_VEO_KEY) keys.veo = env.AIMOCK_PROVIDER_VEO_KEY;\n if (env.AIMOCK_PROVIDER_AZURE_KEY) keys.azure = env.AIMOCK_PROVIDER_AZURE_KEY;\n if (env.AIMOCK_PROVIDER_ELEVENLABS_KEY) keys.elevenlabs = env.AIMOCK_PROVIDER_ELEVENLABS_KEY;\n if (env.AIMOCK_PROVIDER_FAL_KEY) keys.fal = env.AIMOCK_PROVIDER_FAL_KEY;\n return Object.keys(keys).length > 0 ? keys : undefined;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"provider-auth.js","names":[],"sources":["../src/provider-auth.ts"],"sourcesContent":["import type { RecordProviderKey } from \"./types.js\";\n\n/**\n * Default marker prefix identifying a \"dummy\" credential that a caller sends\n * only to satisfy an SDK's non-empty API-key requirement (e.g. `sk-aimock-...`).\n * A caller credential that is absent OR begins with this prefix is treated as a\n * placeholder that aimock is free to override with its own built-in upstream key.\n * A caller credential that does NOT begin with this prefix is treated as a real\n * key and forwarded verbatim (the caller overrides aimock).\n *\n * Overridable via the `AIMOCK_DUMMY_KEY_MARKER` env var for setups that mint\n * placeholder keys under a different prefix.\n */\nexport const DEFAULT_DUMMY_KEY_MARKER = \"sk-aimock-\";\n\n/** Resolve the active dummy-key marker, honoring the env override. */\nexport function getDummyKeyMarker(): string {\n const override = process.env.AIMOCK_DUMMY_KEY_MARKER;\n return override && override.length > 0 ? override : DEFAULT_DUMMY_KEY_MARKER;\n}\n\n/**\n * How a given provider carries its API credential on the wire. aimock injects\n * its built-in key using the provider's native scheme so the upstream accepts\n * it. Providers whose auth is a signed/exchanged credential (Bedrock SigV4,\n * Vertex AI OAuth) are deliberately absent — those are NOT simple static-key\n * schemes, so aimock never rewrites their auth and always forwards the caller's\n * credential unchanged.\n *\n * Scheme kinds (all static long-lived keys):\n * - bearer `Authorization: Bearer <key>` (OpenAI, OpenRouter, Cohere, Grok/xAI, Ollama)\n * - fal-key `Authorization: Key <key>` (fal.ai — note the `Key ` prefix, NOT `Bearer`)\n * - x-api-key `x-api-key: <key>` (Anthropic)\n * - x-goog-api-key `x-goog-api-key: <key>` (Gemini, Gemini Interactions, Veo)\n * - api-key `api-key: <key>` (Azure OpenAI static-key auth)\n * - xi-api-key `xi-api-key: <key>` (ElevenLabs)\n *\n * Note on Azure: Azure OpenAI also supports Microsoft Entra ID `Authorization:\n * Bearer <token>` OAuth. aimock only ever injects the static `api-key` header,\n * and a real Entra bearer token from the caller is never dummy-prefixed, so it\n * is always forwarded verbatim (the caller overrides aimock).\n */\nexport type AuthScheme =\n | { kind: \"bearer\" } // Authorization: Bearer <key>\n | { kind: \"fal-key\" } // Authorization: Key <key>\n | { kind: \"x-api-key\" } // x-api-key: <key>\n | { kind: \"x-goog-api-key\" } // x-goog-api-key: <key>\n | { kind: \"api-key\" } // api-key: <key>\n | { kind: \"xi-api-key\" }; // xi-api-key: <key>\n\nconst PROVIDER_AUTH_SCHEMES: Partial<Record<RecordProviderKey, AuthScheme>> = {\n // Bearer-token providers.\n openai: { kind: \"bearer\" },\n openrouter: { kind: \"bearer\" },\n cohere: { kind: \"bearer\" },\n grok: { kind: \"bearer\" }, // xAI\n ollama: { kind: \"bearer\" }, // Ollama Cloud / bearer-gated Ollama servers\n // Anthropic.\n anthropic: { kind: \"x-api-key\" },\n // Google (Gemini + Veo) use the x-goog-api-key header scheme.\n gemini: { kind: \"x-goog-api-key\" },\n \"gemini-interactions\": { kind: \"x-goog-api-key\" },\n veo: { kind: \"x-goog-api-key\" },\n // Azure OpenAI static-key auth.\n azure: { kind: \"api-key\" },\n // ElevenLabs.\n elevenlabs: { kind: \"xi-api-key\" },\n // fal.ai — uses the `Key ` prefix on Authorization, NOT `Bearer `.\n fal: { kind: \"fal-key\" },\n};\n\nexport function hasStaticProviderAuthScheme(providerKey: RecordProviderKey): boolean {\n return PROVIDER_AUTH_SCHEMES[providerKey] !== undefined;\n}\n\n/** Force a known provider credential onto an already-scrubbed outbound map. */\nexport function applyConfiguredProviderAuth(\n forwardHeaders: Record<string, string>,\n target: URL,\n providerKey: RecordProviderKey,\n providerCredential: string,\n): boolean {\n void target;\n const scheme = PROVIDER_AUTH_SCHEMES[providerKey];\n if (!scheme) return false;\n switch (scheme.kind) {\n case \"bearer\":\n takeHeader(forwardHeaders, \"authorization\");\n forwardHeaders.Authorization = `Bearer ${providerCredential}`;\n break;\n case \"fal-key\":\n takeHeader(forwardHeaders, \"authorization\");\n forwardHeaders.Authorization = `Key ${providerCredential}`;\n break;\n default:\n takeHeader(forwardHeaders, authHeaderName(scheme));\n forwardHeaders[authHeaderName(scheme)] = providerCredential;\n }\n return true;\n}\n\n/**\n * Case-insensitively delete a header from a forward-header map, returning the\n * value that was present (if any). Header maps preserve the original casing of\n * incoming headers, so we can't assume a canonical key.\n */\nfunction takeHeader(headers: Record<string, string>, name: string): string | undefined {\n const lowerName = name.toLowerCase();\n let found: string | undefined;\n for (const key of Object.keys(headers)) {\n if (key.toLowerCase() === lowerName) {\n found = headers[key];\n delete headers[key];\n }\n }\n return found;\n}\n\n/**\n * Extract the caller's credential value for a given auth scheme from the\n * forwarded headers, if present. For bearer, strips the `Bearer ` prefix.\n */\nfunction readCallerCredential(\n headers: Record<string, string>,\n scheme: AuthScheme,\n): string | undefined {\n if (scheme.kind === \"bearer\" || scheme.kind === \"fal-key\") {\n const raw = scanHeader(headers, \"authorization\");\n if (raw === undefined) return undefined;\n // Strip the scheme prefix (`Bearer ` for bearer, `Key ` for fal) if present,\n // otherwise treat the whole value as the credential.\n const prefix = scheme.kind === \"fal-key\" ? \"Key\" : \"Bearer\";\n const match = new RegExp(`^\\\\s*${prefix}\\\\s+(.+)$`, \"i\").exec(raw);\n return match ? match[1].trim() : raw.trim();\n }\n return scanHeader(headers, authHeaderName(scheme));\n}\n\n/**\n * The wire header name a non-Authorization scheme carries its credential in.\n * (bearer/fal-key both use `Authorization` and are handled separately.)\n */\nfunction authHeaderName(scheme: AuthScheme): string {\n switch (scheme.kind) {\n case \"x-api-key\":\n return \"x-api-key\";\n case \"x-goog-api-key\":\n return \"x-goog-api-key\";\n case \"api-key\":\n return \"api-key\";\n case \"xi-api-key\":\n return \"xi-api-key\";\n case \"bearer\":\n case \"fal-key\":\n return \"authorization\";\n }\n}\n\n/** Case-insensitive header lookup that does not mutate the map. */\nfunction scanHeader(headers: Record<string, string>, name: string): string | undefined {\n const lowerName = name.toLowerCase();\n for (const key of Object.keys(headers)) {\n if (key.toLowerCase() === lowerName) return headers[key];\n }\n return undefined;\n}\n\n/**\n * Decide whether aimock should override the caller's credential with its own\n * built-in key. Override when the caller sent no credential OR sent a dummy\n * placeholder (prefixed with the active marker). A real caller key is left\n * untouched so the caller can always override aimock.\n */\nfunction shouldOverride(callerCredential: string | undefined, marker: string): boolean {\n if (callerCredential === undefined || callerCredential.length === 0) return true;\n return callerCredential.startsWith(marker);\n}\n\n/**\n * Inject aimock's built-in upstream credential into the forwarded headers when\n * appropriate (opt-in, backward-compatible). Mutates `forwardHeaders` in place\n * and may mutate `target` (query-param schemes; none currently).\n *\n * Precedence:\n * - No built-in key configured for this provider → no-op (forward verbatim).\n * - Provider not a static-key scheme → no-op (Bedrock SigV4 / Vertex AI OAuth).\n * - Caller credential absent or dummy-prefixed → inject built-in key.\n * - Caller credential is a real key → forward it unchanged.\n *\n * @param forwardHeaders headers already built by buildForwardHeaders(req)\n * @param target upstream URL (reserved for query-param auth schemes)\n * @param providerKey which provider this request is routed to\n * @param builtinKey aimock's configured key for this provider (if any)\n */\nexport function applyProviderAuth(\n forwardHeaders: Record<string, string>,\n target: URL,\n providerKey: RecordProviderKey,\n builtinKey: string | undefined,\n): void {\n void target; // reserved for future query-param schemes (e.g. Gemini ?key=)\n if (!builtinKey) return; // feature inert for this provider\n\n const scheme = PROVIDER_AUTH_SCHEMES[providerKey];\n if (!scheme) return; // signed/OAuth provider — never rewrite auth\n\n const marker = getDummyKeyMarker();\n const callerCredential = readCallerCredential(forwardHeaders, scheme);\n if (!shouldOverride(callerCredential, marker)) return; // caller sent a real key\n\n // Strip any existing auth headers for this scheme (across casings) before\n // setting aimock's key, so a dummy caller key can't linger alongside ours.\n switch (scheme.kind) {\n case \"bearer\":\n takeHeader(forwardHeaders, \"authorization\");\n forwardHeaders[\"Authorization\"] = `Bearer ${builtinKey}`;\n break;\n case \"fal-key\":\n takeHeader(forwardHeaders, \"authorization\");\n forwardHeaders[\"Authorization\"] = `Key ${builtinKey}`;\n break;\n case \"x-api-key\":\n takeHeader(forwardHeaders, \"x-api-key\");\n forwardHeaders[\"x-api-key\"] = builtinKey;\n break;\n case \"x-goog-api-key\":\n takeHeader(forwardHeaders, \"x-goog-api-key\");\n forwardHeaders[\"x-goog-api-key\"] = builtinKey;\n break;\n case \"api-key\":\n takeHeader(forwardHeaders, \"api-key\");\n forwardHeaders[\"api-key\"] = builtinKey;\n break;\n case \"xi-api-key\":\n takeHeader(forwardHeaders, \"xi-api-key\");\n forwardHeaders[\"xi-api-key\"] = builtinKey;\n break;\n }\n}\n\n/**\n * Read per-provider built-in keys from the environment. Returns undefined when\n * no provider key is configured so the feature stays fully inert by default.\n *\n * Each wired static-key provider reads `AIMOCK_PROVIDER_<PROVIDER>_KEY`. An\n * empty-string value is treated as unset (existing truthiness pattern), keeping\n * the feature inert. `gemini-interactions` is not read here: it reuses the\n * Gemini key via the lookup-key remap in `proxyAndRecord`. Signed/OAuth\n * providers (`bedrock`, `vertexai`) have no env var — aimock never rewrites\n * their auth.\n */\nexport function readProviderKeysFromEnv(\n env: NodeJS.ProcessEnv = process.env,\n): Partial<Record<RecordProviderKey, string>> | undefined {\n const keys: Partial<Record<RecordProviderKey, string>> = {};\n if (env.AIMOCK_PROVIDER_OPENAI_KEY) keys.openai = env.AIMOCK_PROVIDER_OPENAI_KEY;\n if (env.AIMOCK_PROVIDER_ANTHROPIC_KEY) keys.anthropic = env.AIMOCK_PROVIDER_ANTHROPIC_KEY;\n if (env.AIMOCK_PROVIDER_GEMINI_KEY) keys.gemini = env.AIMOCK_PROVIDER_GEMINI_KEY;\n if (env.AIMOCK_PROVIDER_OPENROUTER_KEY) keys.openrouter = env.AIMOCK_PROVIDER_OPENROUTER_KEY;\n if (env.AIMOCK_PROVIDER_COHERE_KEY) keys.cohere = env.AIMOCK_PROVIDER_COHERE_KEY;\n if (env.AIMOCK_PROVIDER_GROK_KEY) keys.grok = env.AIMOCK_PROVIDER_GROK_KEY;\n if (env.AIMOCK_PROVIDER_OLLAMA_KEY) keys.ollama = env.AIMOCK_PROVIDER_OLLAMA_KEY;\n if (env.AIMOCK_PROVIDER_VEO_KEY) keys.veo = env.AIMOCK_PROVIDER_VEO_KEY;\n if (env.AIMOCK_PROVIDER_AZURE_KEY) keys.azure = env.AIMOCK_PROVIDER_AZURE_KEY;\n if (env.AIMOCK_PROVIDER_ELEVENLABS_KEY) keys.elevenlabs = env.AIMOCK_PROVIDER_ELEVENLABS_KEY;\n if (env.AIMOCK_PROVIDER_FAL_KEY) keys.fal = env.AIMOCK_PROVIDER_FAL_KEY;\n return Object.keys(keys).length > 0 ? keys : undefined;\n}\n"],"mappings":"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"}
|
package/dist/recorder.cjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const require_runtime = require('./_virtual/_rolldown/runtime.cjs');
|
|
2
2
|
const require_constants = require('./constants.cjs');
|
|
3
3
|
const require_model_utils = require('./model-utils.cjs');
|
|
4
|
+
const require_api_key_auth = require('./api-key-auth.cjs');
|
|
4
5
|
const require_helpers = require('./helpers.cjs');
|
|
5
6
|
const require_router = require('./router.cjs');
|
|
6
7
|
const require_sse_writer = require('./sse-writer.cjs');
|
|
@@ -20,6 +21,16 @@ node_path = require_runtime.__toESM(node_path);
|
|
|
20
21
|
let node_string_decoder = require("node:string_decoder");
|
|
21
22
|
|
|
22
23
|
//#region src/recorder.ts
|
|
24
|
+
/** True when an SSE frame completes an OpenAI stream. */
|
|
25
|
+
function isTerminalSSEFrame(frame) {
|
|
26
|
+
const data = frame.split(/\r?\n/).filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trimStart()).join("\n").trim();
|
|
27
|
+
if (data === "[DONE]") return true;
|
|
28
|
+
try {
|
|
29
|
+
return JSON.parse(data).type === "transcript.text.done";
|
|
30
|
+
} catch {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
23
34
|
/** Headers to strip when proxying — hop-by-hop (RFC 2616 §13.5.1) + client-set. */
|
|
24
35
|
/**
|
|
25
36
|
* Default ceiling (bytes) for the in-memory proxy-path buffer. Chosen well
|
|
@@ -103,11 +114,30 @@ function buildForwardHeaders(req) {
|
|
|
103
114
|
const out = {};
|
|
104
115
|
for (const [name, val] of Object.entries(req.headers)) {
|
|
105
116
|
const lower = name.toLowerCase();
|
|
106
|
-
if (val === void 0 || STRIP_HEADERS.has(lower) || lower.startsWith("x-aimock-chaos-")) continue;
|
|
117
|
+
if (val === void 0 || STRIP_HEADERS.has(lower) || lower.startsWith("x-aimock-chaos-") || require_api_key_auth.isAuthenticatedRequest(req) && require_api_key_auth.isRecognizedApiKeyHeader(lower)) continue;
|
|
107
118
|
out[name] = Array.isArray(val) ? val.join(", ") : val;
|
|
108
119
|
}
|
|
109
120
|
return out;
|
|
110
121
|
}
|
|
122
|
+
/** Remove every casing of a header from an egress map. */
|
|
123
|
+
function removeForwardHeader(headers, name) {
|
|
124
|
+
const lowerName = name.toLowerCase();
|
|
125
|
+
for (const key of Object.keys(headers)) if (key.toLowerCase() === lowerName) delete headers[key];
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Construct the only safe egress map when inbound access control is enabled.
|
|
129
|
+
* Test credentials are always removed; a static configured provider credential
|
|
130
|
+
* is mandatory so an accepted test credential can never become an upstream one.
|
|
131
|
+
*/
|
|
132
|
+
function prepareEgressHeaders(req, target, providerKey, configuredKey) {
|
|
133
|
+
const headers = buildForwardHeaders(req);
|
|
134
|
+
if (!require_api_key_auth.isAuthenticatedRequest(req)) {
|
|
135
|
+
require_provider_auth.applyProviderAuth(headers, target, providerKey, configuredKey);
|
|
136
|
+
return headers;
|
|
137
|
+
}
|
|
138
|
+
for (const name of Object.keys(headers)) if (require_api_key_auth.isRecognizedApiKeyHeader(name)) delete headers[name];
|
|
139
|
+
return configuredKey && require_provider_auth.applyConfiguredProviderAuth(headers, target, providerKey, configuredKey) ? headers : void 0;
|
|
140
|
+
}
|
|
111
141
|
/**
|
|
112
142
|
* Make an arbitrary string safe to set as an HTTP header value. Node's
|
|
113
143
|
* `res.setHeader` throws ERR_INVALID_CHAR on anything outside Latin-1 (plus
|
|
@@ -236,8 +266,14 @@ async function proxyAndRecord(req, res, request, providerKey, pathname, fixtures
|
|
|
236
266
|
return "relayed";
|
|
237
267
|
}
|
|
238
268
|
defaults.logger.warn(`NO FIXTURE MATCH — proxying to ${upstreamUrl}${pathname}`);
|
|
239
|
-
const forwardHeaders =
|
|
240
|
-
|
|
269
|
+
const forwardHeaders = prepareEgressHeaders(req, target, providerKey, record.providerKeys?.[lookupKey]);
|
|
270
|
+
if (!forwardHeaders) {
|
|
271
|
+
require_sse_writer.writeErrorResponse(res, 502, JSON.stringify({ error: {
|
|
272
|
+
message: "No configured provider credential",
|
|
273
|
+
type: "proxy_error"
|
|
274
|
+
} }));
|
|
275
|
+
return "relayed";
|
|
276
|
+
}
|
|
241
277
|
const requestBody = rawBody ?? JSON.stringify(request);
|
|
242
278
|
let upstreamStatus;
|
|
243
279
|
let upstreamHeaders;
|
|
@@ -324,7 +360,8 @@ async function proxyAndRecord(req, res, request, providerKey, pathname, fixtures
|
|
|
324
360
|
if (collapsed.truncated) defaults.logger.warn("Bedrock EventStream: CRC mismatch — response may be truncated");
|
|
325
361
|
if (collapsed.droppedChunks && collapsed.droppedChunks > 0) defaults.logger.warn(`${collapsed.droppedChunks} chunk(s) dropped during stream collapse${collapsed.firstDroppedSample ? ` — first: ${collapsed.firstDroppedSample}` : ""}`);
|
|
326
362
|
if (collapsed.harmonyUnparsed) defaults.logger.warn(`Harmony tokens present but unparseable — content preserved verbatim${collapsed.harmonyNote ? ` (${collapsed.harmonyNote})` : ""}`);
|
|
327
|
-
if (collapsed.
|
|
363
|
+
if (collapsed.transcription) fixtureResponse = { transcription: collapsed.transcription };
|
|
364
|
+
else if (collapsed.audioB64) {
|
|
328
365
|
const audioToolCallsSpread = collapsed.toolCalls && collapsed.toolCalls.length > 0 ? { toolCalls: collapsed.toolCalls.map((tc) => ({
|
|
329
366
|
...tc,
|
|
330
367
|
name: tc.name ?? "",
|
|
@@ -417,7 +454,7 @@ async function proxyAndRecord(req, res, request, providerKey, pathname, fixtures
|
|
|
417
454
|
json: parsedResponse,
|
|
418
455
|
status: upstreamStatus
|
|
419
456
|
};
|
|
420
|
-
} else fixtureResponse = buildFixtureResponse(parsedResponse, upstreamStatus, defaults.logger);
|
|
457
|
+
} else fixtureResponse = buildFixtureResponse(parsedResponse, upstreamStatus, defaults.logger, request);
|
|
421
458
|
}
|
|
422
459
|
if (clientDisconnected) {
|
|
423
460
|
if (!sawDone) {
|
|
@@ -650,7 +687,7 @@ function makeUpstreamRequest(target, headers, body, clientRes, method = "POST",
|
|
|
650
687
|
const frame = parts[fi].trim();
|
|
651
688
|
if (frame.length > 0) {
|
|
652
689
|
frameTimestamps.push(Date.now());
|
|
653
|
-
if (frame
|
|
690
|
+
if (isTerminalSSEFrame(frame)) sawDone = true;
|
|
654
691
|
}
|
|
655
692
|
}
|
|
656
693
|
if (!bufferTruncated) frameBuffer = parts[parts.length - 1];
|
|
@@ -743,7 +780,7 @@ function toToolCallArguments(raw) {
|
|
|
743
780
|
* Detect the response format from the parsed upstream JSON and convert
|
|
744
781
|
* it into an aimock FixtureResponse.
|
|
745
782
|
*/
|
|
746
|
-
function buildFixtureResponse(parsed, status, logger) {
|
|
783
|
+
function buildFixtureResponse(parsed, status, logger, request) {
|
|
747
784
|
if (parsed === null || parsed === void 0) return {
|
|
748
785
|
error: {
|
|
749
786
|
message: "Upstream returned non-JSON response",
|
|
@@ -796,10 +833,13 @@ function buildFixtureResponse(parsed, status, logger) {
|
|
|
796
833
|
if (images.length === 1) return { image: images[0] };
|
|
797
834
|
return { images };
|
|
798
835
|
}
|
|
799
|
-
|
|
836
|
+
const isTranscriptionRequest = request?._endpointType === "transcription" || request?._endpointType === "translation";
|
|
837
|
+
if (typeof obj.text === "string" && (isTranscriptionRequest || obj.task === "transcribe" || obj.language !== void 0 && obj.duration !== void 0) && !("choices" in obj) && !("candidates" in obj) && !("object" in obj) && !("message" in obj) && !("outputs" in obj)) return { transcription: {
|
|
800
838
|
text: obj.text,
|
|
801
839
|
...obj.language ? { language: String(obj.language) } : {},
|
|
840
|
+
...Array.isArray(obj.languages) ? { languages: obj.languages.filter((language) => typeof language === "object" && language !== null && typeof language.code === "string").map((language) => ({ code: language.code })) } : {},
|
|
802
841
|
...obj.duration !== void 0 ? { duration: Number(obj.duration) } : {},
|
|
842
|
+
...obj.usage && typeof obj.usage === "object" ? { usage: obj.usage } : {},
|
|
803
843
|
...Array.isArray(obj.words) ? { words: obj.words } : {},
|
|
804
844
|
...Array.isArray(obj.segments) ? { segments: obj.segments } : {}
|
|
805
845
|
} };
|
|
@@ -1130,6 +1170,8 @@ exports.buildFixtureMatch = buildFixtureMatch;
|
|
|
1130
1170
|
exports.buildForwardHeaders = buildForwardHeaders;
|
|
1131
1171
|
exports.clampTimeout = clampTimeout;
|
|
1132
1172
|
exports.persistFixture = persistFixture;
|
|
1173
|
+
exports.prepareEgressHeaders = prepareEgressHeaders;
|
|
1133
1174
|
exports.proxyAndRecord = proxyAndRecord;
|
|
1175
|
+
exports.removeForwardHeader = removeForwardHeader;
|
|
1134
1176
|
exports.sanitizeHeaderValue = sanitizeHeaderValue;
|
|
1135
1177
|
//# sourceMappingURL=recorder.cjs.map
|