@copilotkit/aimock 1.40.0 → 1.41.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +16 -0
  4. package/README.md +3 -2
  5. package/dist/agui-handler.d.cts.map +1 -1
  6. package/dist/byteplus-video.cjs +954 -0
  7. package/dist/byteplus-video.cjs.map +1 -0
  8. package/dist/byteplus-video.d.cts +176 -0
  9. package/dist/byteplus-video.d.cts.map +1 -0
  10. package/dist/byteplus-video.d.ts +176 -0
  11. package/dist/byteplus-video.d.ts.map +1 -0
  12. package/dist/byteplus-video.js +947 -0
  13. package/dist/byteplus-video.js.map +1 -0
  14. package/dist/cli.cjs +5 -1
  15. package/dist/cli.cjs.map +1 -1
  16. package/dist/cli.js +5 -1
  17. package/dist/cli.js.map +1 -1
  18. package/dist/config-loader.d.cts.map +1 -1
  19. package/dist/config-loader.d.ts.map +1 -1
  20. package/dist/images.cjs +2 -2
  21. package/dist/images.cjs.map +1 -1
  22. package/dist/images.d.cts +11 -3
  23. package/dist/images.d.cts.map +1 -1
  24. package/dist/images.d.ts +11 -3
  25. package/dist/images.d.ts.map +1 -1
  26. package/dist/images.js +2 -2
  27. package/dist/images.js.map +1 -1
  28. package/dist/index.cjs +7 -0
  29. package/dist/index.d.cts +2 -1
  30. package/dist/index.d.ts +2 -1
  31. package/dist/index.js +2 -1
  32. package/dist/metrics.cjs +20 -0
  33. package/dist/metrics.cjs.map +1 -1
  34. package/dist/metrics.d.cts.map +1 -1
  35. package/dist/metrics.d.ts.map +1 -1
  36. package/dist/metrics.js +19 -1
  37. package/dist/metrics.js.map +1 -1
  38. package/dist/provider-auth.cjs +2 -0
  39. package/dist/provider-auth.cjs.map +1 -1
  40. package/dist/provider-auth.js +2 -0
  41. package/dist/provider-auth.js.map +1 -1
  42. package/dist/router.cjs +1 -0
  43. package/dist/router.cjs.map +1 -1
  44. package/dist/router.d.cts.map +1 -1
  45. package/dist/router.d.ts.map +1 -1
  46. package/dist/router.js +1 -0
  47. package/dist/router.js.map +1 -1
  48. package/dist/server.cjs +91 -6
  49. package/dist/server.cjs.map +1 -1
  50. package/dist/server.d.cts +2 -0
  51. package/dist/server.d.cts.map +1 -1
  52. package/dist/server.d.ts +2 -0
  53. package/dist/server.d.ts.map +1 -1
  54. package/dist/server.js +92 -7
  55. package/dist/server.js.map +1 -1
  56. package/dist/stream-collapse.cjs +2 -1
  57. package/dist/stream-collapse.cjs.map +1 -1
  58. package/dist/stream-collapse.js +2 -1
  59. package/dist/stream-collapse.js.map +1 -1
  60. package/dist/types.d.cts +19 -3
  61. package/dist/types.d.cts.map +1 -1
  62. package/dist/types.d.ts +19 -3
  63. package/dist/types.d.ts.map +1 -1
  64. package/dist/vector-types.d.cts.map +1 -1
  65. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"metrics.cjs","names":[],"sources":["../src/metrics.ts"],"sourcesContent":["/**\n * Lightweight Prometheus metrics registry for LLMock.\n *\n * Zero external dependencies — implements counters, histograms, and gauges\n * with Prometheus text exposition format serialization.\n */\n\n// ---------------------------------------------------------------------------\n// Public interface\n// ---------------------------------------------------------------------------\n\nexport interface MetricsRegistry {\n incrementCounter(name: string, labels: Record<string, string>): void;\n observeHistogram(name: string, labels: Record<string, string>, value: number): void;\n setGauge(name: string, labels: Record<string, string>, value: number): void;\n serialize(): string;\n reset(): void;\n}\n\n// ---------------------------------------------------------------------------\n// Histogram bucket boundaries (Prometheus default-ish)\n// ---------------------------------------------------------------------------\n\nconst HISTOGRAM_BUCKETS = [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10];\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n/** Build a stable label key string for map lookups: `label1=\"v1\",label2=\"v2\"` */\nfunction labelKey(labels: Record<string, string>): string {\n const entries = Object.entries(labels).sort(([a], [b]) => a.localeCompare(b));\n if (entries.length === 0) return \"\";\n return entries.map(([k, v]) => `${k}=\"${escapeLabelValue(v)}\"`).join(\",\");\n}\n\n/** Escape a label value per Prometheus text exposition format. */\nfunction escapeLabelValue(v: string): string {\n return v.replace(/\\\\/g, \"\\\\\\\\\").replace(/\"/g, '\\\\\"').replace(/\\n/g, \"\\\\n\");\n}\n\n/** Format labels for Prometheus output: `{label1=\"v1\",label2=\"v2\"}` */\nfunction formatLabels(labels: Record<string, string>): string {\n return `{${labelKey(labels)}}`;\n}\n\n// ---------------------------------------------------------------------------\n// Internal metric storage types\n// ---------------------------------------------------------------------------\n\ninterface CounterData {\n type: \"counter\";\n /** Map from labelKey → value */\n series: Map<string, { labels: Record<string, string>; value: number }>;\n}\n\ninterface HistogramData {\n type: \"histogram\";\n /** Map from labelKey → bucket counts, sum, count */\n series: Map<\n string,\n {\n labels: Record<string, string>;\n bucketCounts: number[]; // one per HISTOGRAM_BUCKETS entry\n sum: number;\n count: number;\n }\n >;\n}\n\ninterface GaugeData {\n type: \"gauge\";\n /** Map from labelKey → value */\n series: Map<string, { labels: Record<string, string>; value: number }>;\n}\n\ntype MetricData = CounterData | HistogramData | GaugeData;\n\n// ---------------------------------------------------------------------------\n// Registry implementation\n// ---------------------------------------------------------------------------\n\nexport function createMetricsRegistry(): MetricsRegistry {\n /** Ordered map: metric name → data. Insertion order preserved for stable output. */\n const metrics = new Map<string, MetricData>();\n\n function getOrCreateCounter(name: string): CounterData {\n let data = metrics.get(name);\n if (!data) {\n data = { type: \"counter\", series: new Map() };\n metrics.set(name, data);\n }\n if (data.type !== \"counter\") throw new Error(`Metric ${name} is not a counter`);\n return data as CounterData;\n }\n\n function getOrCreateHistogram(name: string): HistogramData {\n let data = metrics.get(name);\n if (!data) {\n data = { type: \"histogram\", series: new Map() };\n metrics.set(name, data);\n }\n if (data.type !== \"histogram\") throw new Error(`Metric ${name} is not a histogram`);\n return data as HistogramData;\n }\n\n function getOrCreateGauge(name: string): GaugeData {\n let data = metrics.get(name);\n if (!data) {\n data = { type: \"gauge\", series: new Map() };\n metrics.set(name, data);\n }\n if (data.type !== \"gauge\") throw new Error(`Metric ${name} is not a gauge`);\n return data as GaugeData;\n }\n\n return {\n incrementCounter(name: string, labels: Record<string, string>): void {\n const counter = getOrCreateCounter(name);\n const key = labelKey(labels);\n const existing = counter.series.get(key);\n if (existing) {\n existing.value += 1;\n } else {\n counter.series.set(key, { labels, value: 1 });\n }\n },\n\n observeHistogram(name: string, labels: Record<string, string>, value: number): void {\n const histogram = getOrCreateHistogram(name);\n const key = labelKey(labels);\n let existing = histogram.series.get(key);\n if (!existing) {\n existing = {\n labels,\n bucketCounts: new Array(HISTOGRAM_BUCKETS.length).fill(0) as number[],\n sum: 0,\n count: 0,\n };\n histogram.series.set(key, existing);\n }\n // Update cumulative bucket counts\n for (let i = 0; i < HISTOGRAM_BUCKETS.length; i++) {\n if (value <= HISTOGRAM_BUCKETS[i]) {\n existing.bucketCounts[i] += 1;\n }\n }\n existing.sum += value;\n existing.count += 1;\n },\n\n setGauge(name: string, labels: Record<string, string>, value: number): void {\n const gauge = getOrCreateGauge(name);\n const key = labelKey(labels);\n const existing = gauge.series.get(key);\n if (existing) {\n existing.value = value;\n } else {\n gauge.series.set(key, { labels, value });\n }\n },\n\n serialize(): string {\n const lines: string[] = [];\n\n for (const [name, data] of metrics) {\n switch (data.type) {\n case \"counter\": {\n lines.push(`# TYPE ${name} counter`);\n for (const series of data.series.values()) {\n lines.push(`${name}${formatLabels(series.labels)} ${series.value}`);\n }\n break;\n }\n case \"histogram\": {\n lines.push(`# TYPE ${name} histogram`);\n for (const series of data.series.values()) {\n const lblStr = labelKey(series.labels);\n const lblPrefix = lblStr ? `${lblStr},` : \"\";\n // Bucket lines\n for (let i = 0; i < HISTOGRAM_BUCKETS.length; i++) {\n lines.push(\n `${name}_bucket{${lblPrefix}le=\"${HISTOGRAM_BUCKETS[i]}\"} ${series.bucketCounts[i]}`,\n );\n }\n // +Inf bucket\n lines.push(`${name}_bucket{${lblPrefix}le=\"+Inf\"} ${series.count}`);\n // Sum and count\n lines.push(`${name}_sum${formatLabels(series.labels)} ${series.sum}`);\n lines.push(`${name}_count${formatLabels(series.labels)} ${series.count}`);\n }\n break;\n }\n case \"gauge\": {\n lines.push(`# TYPE ${name} gauge`);\n for (const series of data.series.values()) {\n lines.push(`${name}${formatLabels(series.labels)} ${series.value}`);\n }\n break;\n }\n }\n }\n\n return lines.length > 0 ? lines.join(\"\\n\") + \"\\n\" : \"\";\n },\n\n reset(): void {\n metrics.clear();\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// Path normalization for metric labels\n// ---------------------------------------------------------------------------\n\n// Regex patterns for parametric API routes\nconst BEDROCK_RE =\n /^\\/model\\/([^/]+)\\/(invoke|invoke-with-response-stream|converse|converse-stream)$/;\nconst GEMINI_RE = /^\\/v1beta\\/models\\/([^:]+):(generateContent|streamGenerateContent)$/;\nconst AZURE_RE = /^\\/openai\\/deployments\\/([^/]+)\\/(chat\\/completions|embeddings)$/;\nconst ELEVENLABS_TTS_RE = /^\\/v1\\/text-to-speech\\/([^/]+)$/;\nconst VERTEX_RE =\n /^\\/v1\\/projects\\/([^/]+)\\/locations\\/([^/]+)\\/publishers\\/google\\/models\\/([^:]+):(.+)$/;\n// Exported: server.ts route dispatch matches the same OpenRouter and OpenAI\n// video paths.\nexport const OPENROUTER_VIDEO_CONTENT_RE = /^\\/api\\/v1\\/videos\\/([^/]+)\\/content$/;\nexport const OPENROUTER_VIDEO_STATUS_RE = /^\\/api\\/v1\\/videos\\/([^/]+)$/;\nexport const OPENAI_VIDEO_STATUS_RE = /^\\/v1\\/videos\\/([^/]+)$/;\n// Exported: server.ts route dispatch matches the same Google Veo and xAI Grok\n// video paths. Veo submit (`:predictLongRunning`) is anchored so it never\n// collides with the bare Gemini `:predict` route; Veo operations live in a\n// fresh `/v1beta/operations/...` namespace. Grok submit is an exact literal\n// path; Grok status reuses the OpenAI `/v1/videos/{id}` shape (the dispatch\n// guards `id !== \"generations\"` and the job-map lookup disambiguates Sora).\nexport const VEO_PREDICT_LRO_RE = /^\\/v1beta\\/models\\/([^:]+):predictLongRunning$/;\nexport const VEO_OPERATION_RE = /^\\/v1beta\\/(operations\\/.+)$/;\nexport const GROK_VIDEO_SUBMIT_PATH = \"/v1/videos/generations\";\nexport const GROK_VIDEO_STATUS_RE = /^\\/v1\\/videos\\/([^/]+)$/;\n\n/**\n * Normalize parametric API paths to route patterns for use as metric labels.\n * Replaces dynamic segments (model IDs, deployment names, etc.) with placeholders.\n */\nexport function normalizePathLabel(pathname: string): string {\n // Bedrock: /model/{modelId}/{operation}\n const bedrockMatch = pathname.match(BEDROCK_RE);\n if (bedrockMatch) {\n return `/model/{modelId}/${bedrockMatch[2]}`;\n }\n\n // Gemini: /v1beta/models/{model}:{action}\n const geminiMatch = pathname.match(GEMINI_RE);\n if (geminiMatch) {\n return `/v1beta/models/{model}:${geminiMatch[2]}`;\n }\n\n // Azure: /openai/deployments/{id}/{operation}\n const azureMatch = pathname.match(AZURE_RE);\n if (azureMatch) {\n return `/openai/deployments/{id}/${azureMatch[2]}`;\n }\n\n // Vertex AI: /v1/projects/{p}/locations/{l}/publishers/google/models/{m}:{action}\n const vertexMatch = pathname.match(VERTEX_RE);\n if (vertexMatch) {\n return `/v1/projects/{p}/locations/{l}/publishers/google/models/{m}:${vertexMatch[4]}`;\n }\n\n // ElevenLabs TTS: /v1/text-to-speech/{voice_id}\n if (ELEVENLABS_TTS_RE.test(pathname)) {\n return \"/v1/text-to-speech/{voice_id}\";\n }\n\n // OpenRouter video: /api/v1/videos/{jobId}[/content] — jobIds are random\n // UUIDs, so raw paths would mint unbounded label cardinality. The static\n // /api/v1/videos/models listing route must not collapse into {jobId}.\n if (OPENROUTER_VIDEO_CONTENT_RE.test(pathname)) {\n return \"/api/v1/videos/{jobId}/content\";\n }\n if (pathname !== \"/api/v1/videos/models\" && OPENROUTER_VIDEO_STATUS_RE.test(pathname)) {\n return \"/api/v1/videos/{jobId}\";\n }\n\n // Google Veo video: submit `:predictLongRunning` + poll `/v1beta/operations/{name}`.\n // Operation names are random UUIDs, so the raw path would mint unbounded\n // label cardinality.\n if (VEO_PREDICT_LRO_RE.test(pathname)) {\n return \"/v1beta/models/{model}:predictLongRunning\";\n }\n if (VEO_OPERATION_RE.test(pathname)) {\n return \"/v1beta/operations/{name}\";\n }\n\n // xAI Grok Imagine submit is a static literal path — keep it distinct from\n // the `/v1/videos/{id}` status label below (which it would otherwise collapse\n // into), exactly as `/api/v1/videos/models` is kept out of the jobId bucket.\n if (pathname === GROK_VIDEO_SUBMIT_PATH) {\n return GROK_VIDEO_SUBMIT_PATH;\n }\n\n // OpenAI/Grok video status: /v1/videos/{id}\n if (OPENAI_VIDEO_STATUS_RE.test(pathname)) {\n return \"/v1/videos/{id}\";\n }\n\n // Static path — return as-is\n return pathname;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"metrics.cjs","names":[],"sources":["../src/metrics.ts"],"sourcesContent":["/**\n * Lightweight Prometheus metrics registry for LLMock.\n *\n * Zero external dependencies — implements counters, histograms, and gauges\n * with Prometheus text exposition format serialization.\n */\n\n// ---------------------------------------------------------------------------\n// Public interface\n// ---------------------------------------------------------------------------\n\nexport interface MetricsRegistry {\n incrementCounter(name: string, labels: Record<string, string>): void;\n observeHistogram(name: string, labels: Record<string, string>, value: number): void;\n setGauge(name: string, labels: Record<string, string>, value: number): void;\n serialize(): string;\n reset(): void;\n}\n\n// ---------------------------------------------------------------------------\n// Histogram bucket boundaries (Prometheus default-ish)\n// ---------------------------------------------------------------------------\n\nconst HISTOGRAM_BUCKETS = [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10];\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n/** Build a stable label key string for map lookups: `label1=\"v1\",label2=\"v2\"` */\nfunction labelKey(labels: Record<string, string>): string {\n const entries = Object.entries(labels).sort(([a], [b]) => a.localeCompare(b));\n if (entries.length === 0) return \"\";\n return entries.map(([k, v]) => `${k}=\"${escapeLabelValue(v)}\"`).join(\",\");\n}\n\n/** Escape a label value per Prometheus text exposition format. */\nfunction escapeLabelValue(v: string): string {\n return v.replace(/\\\\/g, \"\\\\\\\\\").replace(/\"/g, '\\\\\"').replace(/\\n/g, \"\\\\n\");\n}\n\n/** Format labels for Prometheus output: `{label1=\"v1\",label2=\"v2\"}` */\nfunction formatLabels(labels: Record<string, string>): string {\n return `{${labelKey(labels)}}`;\n}\n\n// ---------------------------------------------------------------------------\n// Internal metric storage types\n// ---------------------------------------------------------------------------\n\ninterface CounterData {\n type: \"counter\";\n /** Map from labelKey → value */\n series: Map<string, { labels: Record<string, string>; value: number }>;\n}\n\ninterface HistogramData {\n type: \"histogram\";\n /** Map from labelKey → bucket counts, sum, count */\n series: Map<\n string,\n {\n labels: Record<string, string>;\n bucketCounts: number[]; // one per HISTOGRAM_BUCKETS entry\n sum: number;\n count: number;\n }\n >;\n}\n\ninterface GaugeData {\n type: \"gauge\";\n /** Map from labelKey → value */\n series: Map<string, { labels: Record<string, string>; value: number }>;\n}\n\ntype MetricData = CounterData | HistogramData | GaugeData;\n\n// ---------------------------------------------------------------------------\n// Registry implementation\n// ---------------------------------------------------------------------------\n\nexport function createMetricsRegistry(): MetricsRegistry {\n /** Ordered map: metric name → data. Insertion order preserved for stable output. */\n const metrics = new Map<string, MetricData>();\n\n function getOrCreateCounter(name: string): CounterData {\n let data = metrics.get(name);\n if (!data) {\n data = { type: \"counter\", series: new Map() };\n metrics.set(name, data);\n }\n if (data.type !== \"counter\") throw new Error(`Metric ${name} is not a counter`);\n return data as CounterData;\n }\n\n function getOrCreateHistogram(name: string): HistogramData {\n let data = metrics.get(name);\n if (!data) {\n data = { type: \"histogram\", series: new Map() };\n metrics.set(name, data);\n }\n if (data.type !== \"histogram\") throw new Error(`Metric ${name} is not a histogram`);\n return data as HistogramData;\n }\n\n function getOrCreateGauge(name: string): GaugeData {\n let data = metrics.get(name);\n if (!data) {\n data = { type: \"gauge\", series: new Map() };\n metrics.set(name, data);\n }\n if (data.type !== \"gauge\") throw new Error(`Metric ${name} is not a gauge`);\n return data as GaugeData;\n }\n\n return {\n incrementCounter(name: string, labels: Record<string, string>): void {\n const counter = getOrCreateCounter(name);\n const key = labelKey(labels);\n const existing = counter.series.get(key);\n if (existing) {\n existing.value += 1;\n } else {\n counter.series.set(key, { labels, value: 1 });\n }\n },\n\n observeHistogram(name: string, labels: Record<string, string>, value: number): void {\n const histogram = getOrCreateHistogram(name);\n const key = labelKey(labels);\n let existing = histogram.series.get(key);\n if (!existing) {\n existing = {\n labels,\n bucketCounts: new Array(HISTOGRAM_BUCKETS.length).fill(0) as number[],\n sum: 0,\n count: 0,\n };\n histogram.series.set(key, existing);\n }\n // Update cumulative bucket counts\n for (let i = 0; i < HISTOGRAM_BUCKETS.length; i++) {\n if (value <= HISTOGRAM_BUCKETS[i]) {\n existing.bucketCounts[i] += 1;\n }\n }\n existing.sum += value;\n existing.count += 1;\n },\n\n setGauge(name: string, labels: Record<string, string>, value: number): void {\n const gauge = getOrCreateGauge(name);\n const key = labelKey(labels);\n const existing = gauge.series.get(key);\n if (existing) {\n existing.value = value;\n } else {\n gauge.series.set(key, { labels, value });\n }\n },\n\n serialize(): string {\n const lines: string[] = [];\n\n for (const [name, data] of metrics) {\n switch (data.type) {\n case \"counter\": {\n lines.push(`# TYPE ${name} counter`);\n for (const series of data.series.values()) {\n lines.push(`${name}${formatLabels(series.labels)} ${series.value}`);\n }\n break;\n }\n case \"histogram\": {\n lines.push(`# TYPE ${name} histogram`);\n for (const series of data.series.values()) {\n const lblStr = labelKey(series.labels);\n const lblPrefix = lblStr ? `${lblStr},` : \"\";\n // Bucket lines\n for (let i = 0; i < HISTOGRAM_BUCKETS.length; i++) {\n lines.push(\n `${name}_bucket{${lblPrefix}le=\"${HISTOGRAM_BUCKETS[i]}\"} ${series.bucketCounts[i]}`,\n );\n }\n // +Inf bucket\n lines.push(`${name}_bucket{${lblPrefix}le=\"+Inf\"} ${series.count}`);\n // Sum and count\n lines.push(`${name}_sum${formatLabels(series.labels)} ${series.sum}`);\n lines.push(`${name}_count${formatLabels(series.labels)} ${series.count}`);\n }\n break;\n }\n case \"gauge\": {\n lines.push(`# TYPE ${name} gauge`);\n for (const series of data.series.values()) {\n lines.push(`${name}${formatLabels(series.labels)} ${series.value}`);\n }\n break;\n }\n }\n }\n\n return lines.length > 0 ? lines.join(\"\\n\") + \"\\n\" : \"\";\n },\n\n reset(): void {\n metrics.clear();\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// Path normalization for metric labels\n// ---------------------------------------------------------------------------\n\n// Regex patterns for parametric API routes\nconst BEDROCK_RE =\n /^\\/model\\/([^/]+)\\/(invoke|invoke-with-response-stream|converse|converse-stream)$/;\nconst GEMINI_RE = /^\\/v1beta\\/models\\/([^:]+):(generateContent|streamGenerateContent)$/;\nconst AZURE_RE = /^\\/openai\\/deployments\\/([^/]+)\\/(chat\\/completions|embeddings)$/;\nconst ELEVENLABS_TTS_RE = /^\\/v1\\/text-to-speech\\/([^/]+)$/;\nconst VERTEX_RE =\n /^\\/v1\\/projects\\/([^/]+)\\/locations\\/([^/]+)\\/publishers\\/google\\/models\\/([^:]+):(.+)$/;\n// Exported: server.ts route dispatch matches the same OpenRouter and OpenAI\n// video paths.\nexport const OPENROUTER_VIDEO_CONTENT_RE = /^\\/api\\/v1\\/videos\\/([^/]+)\\/content$/;\nexport const OPENROUTER_VIDEO_STATUS_RE = /^\\/api\\/v1\\/videos\\/([^/]+)$/;\nexport const OPENAI_VIDEO_STATUS_RE = /^\\/v1\\/videos\\/([^/]+)$/;\n// Exported: server.ts route dispatch matches the same Google Veo and xAI Grok\n// video paths. Veo submit (`:predictLongRunning`) is anchored so it never\n// collides with the bare Gemini `:predict` route; Veo operations live in a\n// fresh `/v1beta/operations/...` namespace. Grok submit is an exact literal\n// path; Grok status reuses the OpenAI `/v1/videos/{id}` shape (the dispatch\n// guards `id !== \"generations\"` and the job-map lookup disambiguates Sora).\nexport const VEO_PREDICT_LRO_RE = /^\\/v1beta\\/models\\/([^:]+):predictLongRunning$/;\nexport const VEO_OPERATION_RE = /^\\/v1beta\\/(operations\\/.+)$/;\nexport const GROK_VIDEO_SUBMIT_PATH = \"/v1/videos/generations\";\nexport const GROK_VIDEO_STATUS_RE = /^\\/v1\\/videos\\/([^/]+)$/;\n\n/**\n * BytePlus Ark (Seedance) async video task routes. The `/api/v3` prefix is\n * ENUMERATED rather than wildcarded: Ark's data-plane base carries it, but a\n * client may point `baseURL` at a bare aimock root instead, so both forms must\n * route. A tolerant `(?:\\/[^?]*)?` prefix would additionally claim\n * `/fal/contents/generations/tasks` — and these routes dispatch in the\n * pre-rewrite band, ~1,100 lines ahead of every fal branch, so it would take\n * that path away from the fal proxy. Enumerating the one real prefix also\n * blocks a doubled suffix from matching the status RE.\n *\n * Declared here (not in server.ts) because normalizePathLabel below consumes\n * them, matching the existing OpenRouter/Veo/Grok route-regex edge where\n * server.ts imports its route regexes from this module.\n */\nexport const BYTEPLUS_VIDEO_SUBMIT_RE = /^(?:\\/api\\/v3)?\\/contents\\/generations\\/tasks$/;\nexport const BYTEPLUS_VIDEO_STATUS_RE = /^(?:\\/api\\/v3)?\\/contents\\/generations\\/tasks\\/([^/]+)$/;\n\n/**\n * Normalize parametric API paths to route patterns for use as metric labels.\n * Replaces dynamic segments (model IDs, deployment names, etc.) with placeholders.\n */\nexport function normalizePathLabel(pathname: string): string {\n // Bedrock: /model/{modelId}/{operation}\n const bedrockMatch = pathname.match(BEDROCK_RE);\n if (bedrockMatch) {\n return `/model/{modelId}/${bedrockMatch[2]}`;\n }\n\n // Gemini: /v1beta/models/{model}:{action}\n const geminiMatch = pathname.match(GEMINI_RE);\n if (geminiMatch) {\n return `/v1beta/models/{model}:${geminiMatch[2]}`;\n }\n\n // Azure: /openai/deployments/{id}/{operation}\n const azureMatch = pathname.match(AZURE_RE);\n if (azureMatch) {\n return `/openai/deployments/{id}/${azureMatch[2]}`;\n }\n\n // Vertex AI: /v1/projects/{p}/locations/{l}/publishers/google/models/{m}:{action}\n const vertexMatch = pathname.match(VERTEX_RE);\n if (vertexMatch) {\n return `/v1/projects/{p}/locations/{l}/publishers/google/models/{m}:${vertexMatch[4]}`;\n }\n\n // ElevenLabs TTS: /v1/text-to-speech/{voice_id}\n if (ELEVENLABS_TTS_RE.test(pathname)) {\n return \"/v1/text-to-speech/{voice_id}\";\n }\n\n // OpenRouter video: /api/v1/videos/{jobId}[/content] — jobIds are random\n // UUIDs, so raw paths would mint unbounded label cardinality. The static\n // /api/v1/videos/models listing route must not collapse into {jobId}.\n if (OPENROUTER_VIDEO_CONTENT_RE.test(pathname)) {\n return \"/api/v1/videos/{jobId}/content\";\n }\n if (pathname !== \"/api/v1/videos/models\" && OPENROUTER_VIDEO_STATUS_RE.test(pathname)) {\n return \"/api/v1/videos/{jobId}\";\n }\n\n // Google Veo video: submit `:predictLongRunning` + poll `/v1beta/operations/{name}`.\n // Operation names are random UUIDs, so the raw path would mint unbounded\n // label cardinality.\n if (VEO_PREDICT_LRO_RE.test(pathname)) {\n return \"/v1beta/models/{model}:predictLongRunning\";\n }\n if (VEO_OPERATION_RE.test(pathname)) {\n return \"/v1beta/operations/{name}\";\n }\n\n // xAI Grok Imagine submit is a static literal path — keep it distinct from\n // the `/v1/videos/{id}` status label below (which it would otherwise collapse\n // into), exactly as `/api/v1/videos/models` is kept out of the jobId bucket.\n if (pathname === GROK_VIDEO_SUBMIT_PATH) {\n return GROK_VIDEO_SUBMIT_PATH;\n }\n\n // OpenAI/Grok video status: /v1/videos/{id}\n if (OPENAI_VIDEO_STATUS_RE.test(pathname)) {\n return \"/v1/videos/{id}\";\n }\n\n // BytePlus Ark video: /[api/v3/]contents/generations/tasks[/{id}] — task ids\n // (`cgt-…`) are unbounded cardinality. Status before submit: the status RE is\n // the more specific of the two and the submit RE cannot match a trailing id\n // segment, so the order is belt-and-braces. Appended at the end of the\n // cascade because the anchored REs above cannot match any path an earlier\n // entry claims.\n if (BYTEPLUS_VIDEO_STATUS_RE.test(pathname)) {\n return \"/contents/generations/tasks/{id}\";\n }\n if (BYTEPLUS_VIDEO_SUBMIT_RE.test(pathname)) {\n return \"/contents/generations/tasks\";\n }\n\n // Static path — return as-is\n return pathname;\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"metrics.d.cts","names":[],"sources":["../src/metrics.ts"],"sourcesContent":[],"mappings":";;AAWA;;;;;AAGuC,UAHtB,eAAA,CAGsB;EAoEvB,gBAAA,CAAA,IAAA,EAAA,MAAqB,EAAA,MAAI,EAtEA,MAsEA,CAAA,MAAe,EAAA,MAAA,CAAA,CAAA,EAAA,IAAA;EAkKxC,gBAAA,CAAA,IAAkB,EAAA,MAAA,EAAA,MAAA,EAvOO,MAuOP,CAAA,MAAA,EAAA,MAAA,CAAA,EAAA,KAAA,EAAA,MAAA,CAAA,EAAA,IAAA;iCAtOD;;;;iBAoEjB,qBAAA,CAAA,GAAyB;;;;;iBAkKzB,kBAAA"}
1
+ {"version":3,"file":"metrics.d.cts","names":[],"sources":["../src/metrics.ts"],"sourcesContent":[],"mappings":";;AAWA;;;;;AAGuC,UAHtB,eAAA,CAGsB;EAoEvB,gBAAA,CAAA,IAAA,EAAA,MAAqB,EAAA,MAAI,EAtEA,MAsEA,CAAA,MAAe,EAAA,MAAA,CAAA,CAAA,EAAA,IAAA;EAmLxC,gBAAA,CAAA,IAAkB,EAAA,MAAA,EAAA,MAAA,EAxPO,MAwPP,CAAA,MAAA,EAAA,MAAA,CAAA,EAAA,KAAA,EAAA,MAAA,CAAA,EAAA,IAAA;iCAvPD;;;;iBAoEjB,qBAAA,CAAA,GAAyB;;;;;iBAmLzB,kBAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"metrics.d.ts","names":[],"sources":["../src/metrics.ts"],"sourcesContent":[],"mappings":";;AAWA;;;;;AAGuC,UAHtB,eAAA,CAGsB;EAoEvB,gBAAA,CAAA,IAAA,EAAA,MAAqB,EAAA,MAAI,EAtEA,MAsEA,CAAA,MAAe,EAAA,MAAA,CAAA,CAAA,EAAA,IAAA;EAkKxC,gBAAA,CAAA,IAAkB,EAAA,MAAA,EAAA,MAAA,EAvOO,MAuOP,CAAA,MAAA,EAAA,MAAA,CAAA,EAAA,KAAA,EAAA,MAAA,CAAA,EAAA,IAAA;iCAtOD;;;;iBAoEjB,qBAAA,CAAA,GAAyB;;;;;iBAkKzB,kBAAA"}
1
+ {"version":3,"file":"metrics.d.ts","names":[],"sources":["../src/metrics.ts"],"sourcesContent":[],"mappings":";;AAWA;;;;;AAGuC,UAHtB,eAAA,CAGsB;EAoEvB,gBAAA,CAAA,IAAA,EAAA,MAAqB,EAAA,MAAI,EAtEA,MAsEA,CAAA,MAAe,EAAA,MAAA,CAAA,CAAA,EAAA,IAAA;EAmLxC,gBAAA,CAAA,IAAkB,EAAA,MAAA,EAAA,MAAA,EAxPO,MAwPP,CAAA,MAAA,EAAA,MAAA,CAAA,EAAA,KAAA,EAAA,MAAA,CAAA,EAAA,IAAA;iCAvPD;;;;iBAoEjB,qBAAA,CAAA,GAAyB;;;;;iBAmLzB,kBAAA"}
package/dist/metrics.js CHANGED
@@ -146,6 +146,22 @@ const VEO_OPERATION_RE = /^\/v1beta\/(operations\/.+)$/;
146
146
  const GROK_VIDEO_SUBMIT_PATH = "/v1/videos/generations";
147
147
  const GROK_VIDEO_STATUS_RE = /^\/v1\/videos\/([^/]+)$/;
148
148
  /**
149
+ * BytePlus Ark (Seedance) async video task routes. The `/api/v3` prefix is
150
+ * ENUMERATED rather than wildcarded: Ark's data-plane base carries it, but a
151
+ * client may point `baseURL` at a bare aimock root instead, so both forms must
152
+ * route. A tolerant `(?:\/[^?]*)?` prefix would additionally claim
153
+ * `/fal/contents/generations/tasks` — and these routes dispatch in the
154
+ * pre-rewrite band, ~1,100 lines ahead of every fal branch, so it would take
155
+ * that path away from the fal proxy. Enumerating the one real prefix also
156
+ * blocks a doubled suffix from matching the status RE.
157
+ *
158
+ * Declared here (not in server.ts) because normalizePathLabel below consumes
159
+ * them, matching the existing OpenRouter/Veo/Grok route-regex edge where
160
+ * server.ts imports its route regexes from this module.
161
+ */
162
+ const BYTEPLUS_VIDEO_SUBMIT_RE = /^(?:\/api\/v3)?\/contents\/generations\/tasks$/;
163
+ const BYTEPLUS_VIDEO_STATUS_RE = /^(?:\/api\/v3)?\/contents\/generations\/tasks\/([^/]+)$/;
164
+ /**
149
165
  * Normalize parametric API paths to route patterns for use as metric labels.
150
166
  * Replaces dynamic segments (model IDs, deployment names, etc.) with placeholders.
151
167
  */
@@ -165,9 +181,11 @@ function normalizePathLabel(pathname) {
165
181
  if (VEO_OPERATION_RE.test(pathname)) return "/v1beta/operations/{name}";
166
182
  if (pathname === GROK_VIDEO_SUBMIT_PATH) return GROK_VIDEO_SUBMIT_PATH;
167
183
  if (OPENAI_VIDEO_STATUS_RE.test(pathname)) return "/v1/videos/{id}";
184
+ if (BYTEPLUS_VIDEO_STATUS_RE.test(pathname)) return "/contents/generations/tasks/{id}";
185
+ if (BYTEPLUS_VIDEO_SUBMIT_RE.test(pathname)) return "/contents/generations/tasks";
168
186
  return pathname;
169
187
  }
170
188
 
171
189
  //#endregion
172
- export { GROK_VIDEO_STATUS_RE, GROK_VIDEO_SUBMIT_PATH, OPENROUTER_VIDEO_CONTENT_RE, OPENROUTER_VIDEO_STATUS_RE, VEO_OPERATION_RE, VEO_PREDICT_LRO_RE, createMetricsRegistry, normalizePathLabel };
190
+ export { BYTEPLUS_VIDEO_STATUS_RE, BYTEPLUS_VIDEO_SUBMIT_RE, GROK_VIDEO_STATUS_RE, GROK_VIDEO_SUBMIT_PATH, OPENROUTER_VIDEO_CONTENT_RE, OPENROUTER_VIDEO_STATUS_RE, VEO_OPERATION_RE, VEO_PREDICT_LRO_RE, createMetricsRegistry, normalizePathLabel };
173
191
  //# sourceMappingURL=metrics.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"metrics.js","names":[],"sources":["../src/metrics.ts"],"sourcesContent":["/**\n * Lightweight Prometheus metrics registry for LLMock.\n *\n * Zero external dependencies — implements counters, histograms, and gauges\n * with Prometheus text exposition format serialization.\n */\n\n// ---------------------------------------------------------------------------\n// Public interface\n// ---------------------------------------------------------------------------\n\nexport interface MetricsRegistry {\n incrementCounter(name: string, labels: Record<string, string>): void;\n observeHistogram(name: string, labels: Record<string, string>, value: number): void;\n setGauge(name: string, labels: Record<string, string>, value: number): void;\n serialize(): string;\n reset(): void;\n}\n\n// ---------------------------------------------------------------------------\n// Histogram bucket boundaries (Prometheus default-ish)\n// ---------------------------------------------------------------------------\n\nconst HISTOGRAM_BUCKETS = [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10];\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n/** Build a stable label key string for map lookups: `label1=\"v1\",label2=\"v2\"` */\nfunction labelKey(labels: Record<string, string>): string {\n const entries = Object.entries(labels).sort(([a], [b]) => a.localeCompare(b));\n if (entries.length === 0) return \"\";\n return entries.map(([k, v]) => `${k}=\"${escapeLabelValue(v)}\"`).join(\",\");\n}\n\n/** Escape a label value per Prometheus text exposition format. */\nfunction escapeLabelValue(v: string): string {\n return v.replace(/\\\\/g, \"\\\\\\\\\").replace(/\"/g, '\\\\\"').replace(/\\n/g, \"\\\\n\");\n}\n\n/** Format labels for Prometheus output: `{label1=\"v1\",label2=\"v2\"}` */\nfunction formatLabels(labels: Record<string, string>): string {\n return `{${labelKey(labels)}}`;\n}\n\n// ---------------------------------------------------------------------------\n// Internal metric storage types\n// ---------------------------------------------------------------------------\n\ninterface CounterData {\n type: \"counter\";\n /** Map from labelKey → value */\n series: Map<string, { labels: Record<string, string>; value: number }>;\n}\n\ninterface HistogramData {\n type: \"histogram\";\n /** Map from labelKey → bucket counts, sum, count */\n series: Map<\n string,\n {\n labels: Record<string, string>;\n bucketCounts: number[]; // one per HISTOGRAM_BUCKETS entry\n sum: number;\n count: number;\n }\n >;\n}\n\ninterface GaugeData {\n type: \"gauge\";\n /** Map from labelKey → value */\n series: Map<string, { labels: Record<string, string>; value: number }>;\n}\n\ntype MetricData = CounterData | HistogramData | GaugeData;\n\n// ---------------------------------------------------------------------------\n// Registry implementation\n// ---------------------------------------------------------------------------\n\nexport function createMetricsRegistry(): MetricsRegistry {\n /** Ordered map: metric name → data. Insertion order preserved for stable output. */\n const metrics = new Map<string, MetricData>();\n\n function getOrCreateCounter(name: string): CounterData {\n let data = metrics.get(name);\n if (!data) {\n data = { type: \"counter\", series: new Map() };\n metrics.set(name, data);\n }\n if (data.type !== \"counter\") throw new Error(`Metric ${name} is not a counter`);\n return data as CounterData;\n }\n\n function getOrCreateHistogram(name: string): HistogramData {\n let data = metrics.get(name);\n if (!data) {\n data = { type: \"histogram\", series: new Map() };\n metrics.set(name, data);\n }\n if (data.type !== \"histogram\") throw new Error(`Metric ${name} is not a histogram`);\n return data as HistogramData;\n }\n\n function getOrCreateGauge(name: string): GaugeData {\n let data = metrics.get(name);\n if (!data) {\n data = { type: \"gauge\", series: new Map() };\n metrics.set(name, data);\n }\n if (data.type !== \"gauge\") throw new Error(`Metric ${name} is not a gauge`);\n return data as GaugeData;\n }\n\n return {\n incrementCounter(name: string, labels: Record<string, string>): void {\n const counter = getOrCreateCounter(name);\n const key = labelKey(labels);\n const existing = counter.series.get(key);\n if (existing) {\n existing.value += 1;\n } else {\n counter.series.set(key, { labels, value: 1 });\n }\n },\n\n observeHistogram(name: string, labels: Record<string, string>, value: number): void {\n const histogram = getOrCreateHistogram(name);\n const key = labelKey(labels);\n let existing = histogram.series.get(key);\n if (!existing) {\n existing = {\n labels,\n bucketCounts: new Array(HISTOGRAM_BUCKETS.length).fill(0) as number[],\n sum: 0,\n count: 0,\n };\n histogram.series.set(key, existing);\n }\n // Update cumulative bucket counts\n for (let i = 0; i < HISTOGRAM_BUCKETS.length; i++) {\n if (value <= HISTOGRAM_BUCKETS[i]) {\n existing.bucketCounts[i] += 1;\n }\n }\n existing.sum += value;\n existing.count += 1;\n },\n\n setGauge(name: string, labels: Record<string, string>, value: number): void {\n const gauge = getOrCreateGauge(name);\n const key = labelKey(labels);\n const existing = gauge.series.get(key);\n if (existing) {\n existing.value = value;\n } else {\n gauge.series.set(key, { labels, value });\n }\n },\n\n serialize(): string {\n const lines: string[] = [];\n\n for (const [name, data] of metrics) {\n switch (data.type) {\n case \"counter\": {\n lines.push(`# TYPE ${name} counter`);\n for (const series of data.series.values()) {\n lines.push(`${name}${formatLabels(series.labels)} ${series.value}`);\n }\n break;\n }\n case \"histogram\": {\n lines.push(`# TYPE ${name} histogram`);\n for (const series of data.series.values()) {\n const lblStr = labelKey(series.labels);\n const lblPrefix = lblStr ? `${lblStr},` : \"\";\n // Bucket lines\n for (let i = 0; i < HISTOGRAM_BUCKETS.length; i++) {\n lines.push(\n `${name}_bucket{${lblPrefix}le=\"${HISTOGRAM_BUCKETS[i]}\"} ${series.bucketCounts[i]}`,\n );\n }\n // +Inf bucket\n lines.push(`${name}_bucket{${lblPrefix}le=\"+Inf\"} ${series.count}`);\n // Sum and count\n lines.push(`${name}_sum${formatLabels(series.labels)} ${series.sum}`);\n lines.push(`${name}_count${formatLabels(series.labels)} ${series.count}`);\n }\n break;\n }\n case \"gauge\": {\n lines.push(`# TYPE ${name} gauge`);\n for (const series of data.series.values()) {\n lines.push(`${name}${formatLabels(series.labels)} ${series.value}`);\n }\n break;\n }\n }\n }\n\n return lines.length > 0 ? lines.join(\"\\n\") + \"\\n\" : \"\";\n },\n\n reset(): void {\n metrics.clear();\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// Path normalization for metric labels\n// ---------------------------------------------------------------------------\n\n// Regex patterns for parametric API routes\nconst BEDROCK_RE =\n /^\\/model\\/([^/]+)\\/(invoke|invoke-with-response-stream|converse|converse-stream)$/;\nconst GEMINI_RE = /^\\/v1beta\\/models\\/([^:]+):(generateContent|streamGenerateContent)$/;\nconst AZURE_RE = /^\\/openai\\/deployments\\/([^/]+)\\/(chat\\/completions|embeddings)$/;\nconst ELEVENLABS_TTS_RE = /^\\/v1\\/text-to-speech\\/([^/]+)$/;\nconst VERTEX_RE =\n /^\\/v1\\/projects\\/([^/]+)\\/locations\\/([^/]+)\\/publishers\\/google\\/models\\/([^:]+):(.+)$/;\n// Exported: server.ts route dispatch matches the same OpenRouter and OpenAI\n// video paths.\nexport const OPENROUTER_VIDEO_CONTENT_RE = /^\\/api\\/v1\\/videos\\/([^/]+)\\/content$/;\nexport const OPENROUTER_VIDEO_STATUS_RE = /^\\/api\\/v1\\/videos\\/([^/]+)$/;\nexport const OPENAI_VIDEO_STATUS_RE = /^\\/v1\\/videos\\/([^/]+)$/;\n// Exported: server.ts route dispatch matches the same Google Veo and xAI Grok\n// video paths. Veo submit (`:predictLongRunning`) is anchored so it never\n// collides with the bare Gemini `:predict` route; Veo operations live in a\n// fresh `/v1beta/operations/...` namespace. Grok submit is an exact literal\n// path; Grok status reuses the OpenAI `/v1/videos/{id}` shape (the dispatch\n// guards `id !== \"generations\"` and the job-map lookup disambiguates Sora).\nexport const VEO_PREDICT_LRO_RE = /^\\/v1beta\\/models\\/([^:]+):predictLongRunning$/;\nexport const VEO_OPERATION_RE = /^\\/v1beta\\/(operations\\/.+)$/;\nexport const GROK_VIDEO_SUBMIT_PATH = \"/v1/videos/generations\";\nexport const GROK_VIDEO_STATUS_RE = /^\\/v1\\/videos\\/([^/]+)$/;\n\n/**\n * Normalize parametric API paths to route patterns for use as metric labels.\n * Replaces dynamic segments (model IDs, deployment names, etc.) with placeholders.\n */\nexport function normalizePathLabel(pathname: string): string {\n // Bedrock: /model/{modelId}/{operation}\n const bedrockMatch = pathname.match(BEDROCK_RE);\n if (bedrockMatch) {\n return `/model/{modelId}/${bedrockMatch[2]}`;\n }\n\n // Gemini: /v1beta/models/{model}:{action}\n const geminiMatch = pathname.match(GEMINI_RE);\n if (geminiMatch) {\n return `/v1beta/models/{model}:${geminiMatch[2]}`;\n }\n\n // Azure: /openai/deployments/{id}/{operation}\n const azureMatch = pathname.match(AZURE_RE);\n if (azureMatch) {\n return `/openai/deployments/{id}/${azureMatch[2]}`;\n }\n\n // Vertex AI: /v1/projects/{p}/locations/{l}/publishers/google/models/{m}:{action}\n const vertexMatch = pathname.match(VERTEX_RE);\n if (vertexMatch) {\n return `/v1/projects/{p}/locations/{l}/publishers/google/models/{m}:${vertexMatch[4]}`;\n }\n\n // ElevenLabs TTS: /v1/text-to-speech/{voice_id}\n if (ELEVENLABS_TTS_RE.test(pathname)) {\n return \"/v1/text-to-speech/{voice_id}\";\n }\n\n // OpenRouter video: /api/v1/videos/{jobId}[/content] — jobIds are random\n // UUIDs, so raw paths would mint unbounded label cardinality. The static\n // /api/v1/videos/models listing route must not collapse into {jobId}.\n if (OPENROUTER_VIDEO_CONTENT_RE.test(pathname)) {\n return \"/api/v1/videos/{jobId}/content\";\n }\n if (pathname !== \"/api/v1/videos/models\" && OPENROUTER_VIDEO_STATUS_RE.test(pathname)) {\n return \"/api/v1/videos/{jobId}\";\n }\n\n // Google Veo video: submit `:predictLongRunning` + poll `/v1beta/operations/{name}`.\n // Operation names are random UUIDs, so the raw path would mint unbounded\n // label cardinality.\n if (VEO_PREDICT_LRO_RE.test(pathname)) {\n return \"/v1beta/models/{model}:predictLongRunning\";\n }\n if (VEO_OPERATION_RE.test(pathname)) {\n return \"/v1beta/operations/{name}\";\n }\n\n // xAI Grok Imagine submit is a static literal path — keep it distinct from\n // the `/v1/videos/{id}` status label below (which it would otherwise collapse\n // into), exactly as `/api/v1/videos/models` is kept out of the jobId bucket.\n if (pathname === GROK_VIDEO_SUBMIT_PATH) {\n return GROK_VIDEO_SUBMIT_PATH;\n }\n\n // OpenAI/Grok video status: /v1/videos/{id}\n if (OPENAI_VIDEO_STATUS_RE.test(pathname)) {\n return \"/v1/videos/{id}\";\n }\n\n // Static path — return as-is\n return pathname;\n}\n"],"mappings":";AAuBA,MAAM,oBAAoB;CAAC;CAAO;CAAM;CAAO;CAAM;CAAK;CAAM;CAAK;CAAG;CAAK;CAAG;CAAG;;AAOnF,SAAS,SAAS,QAAwC;CACxD,MAAM,UAAU,OAAO,QAAQ,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,cAAc,EAAE,CAAC;AAC7E,KAAI,QAAQ,WAAW,EAAG,QAAO;AACjC,QAAO,QAAQ,KAAK,CAAC,GAAG,OAAO,GAAG,EAAE,IAAI,iBAAiB,EAAE,CAAC,GAAG,CAAC,KAAK,IAAI;;;AAI3E,SAAS,iBAAiB,GAAmB;AAC3C,QAAO,EAAE,QAAQ,OAAO,OAAO,CAAC,QAAQ,MAAM,OAAM,CAAC,QAAQ,OAAO,MAAM;;;AAI5E,SAAS,aAAa,QAAwC;AAC5D,QAAO,IAAI,SAAS,OAAO,CAAC;;AAuC9B,SAAgB,wBAAyC;;CAEvD,MAAM,0BAAU,IAAI,KAAyB;CAE7C,SAAS,mBAAmB,MAA2B;EACrD,IAAI,OAAO,QAAQ,IAAI,KAAK;AAC5B,MAAI,CAAC,MAAM;AACT,UAAO;IAAE,MAAM;IAAW,wBAAQ,IAAI,KAAK;IAAE;AAC7C,WAAQ,IAAI,MAAM,KAAK;;AAEzB,MAAI,KAAK,SAAS,UAAW,OAAM,IAAI,MAAM,UAAU,KAAK,mBAAmB;AAC/E,SAAO;;CAGT,SAAS,qBAAqB,MAA6B;EACzD,IAAI,OAAO,QAAQ,IAAI,KAAK;AAC5B,MAAI,CAAC,MAAM;AACT,UAAO;IAAE,MAAM;IAAa,wBAAQ,IAAI,KAAK;IAAE;AAC/C,WAAQ,IAAI,MAAM,KAAK;;AAEzB,MAAI,KAAK,SAAS,YAAa,OAAM,IAAI,MAAM,UAAU,KAAK,qBAAqB;AACnF,SAAO;;CAGT,SAAS,iBAAiB,MAAyB;EACjD,IAAI,OAAO,QAAQ,IAAI,KAAK;AAC5B,MAAI,CAAC,MAAM;AACT,UAAO;IAAE,MAAM;IAAS,wBAAQ,IAAI,KAAK;IAAE;AAC3C,WAAQ,IAAI,MAAM,KAAK;;AAEzB,MAAI,KAAK,SAAS,QAAS,OAAM,IAAI,MAAM,UAAU,KAAK,iBAAiB;AAC3E,SAAO;;AAGT,QAAO;EACL,iBAAiB,MAAc,QAAsC;GACnE,MAAM,UAAU,mBAAmB,KAAK;GACxC,MAAM,MAAM,SAAS,OAAO;GAC5B,MAAM,WAAW,QAAQ,OAAO,IAAI,IAAI;AACxC,OAAI,SACF,UAAS,SAAS;OAElB,SAAQ,OAAO,IAAI,KAAK;IAAE;IAAQ,OAAO;IAAG,CAAC;;EAIjD,iBAAiB,MAAc,QAAgC,OAAqB;GAClF,MAAM,YAAY,qBAAqB,KAAK;GAC5C,MAAM,MAAM,SAAS,OAAO;GAC5B,IAAI,WAAW,UAAU,OAAO,IAAI,IAAI;AACxC,OAAI,CAAC,UAAU;AACb,eAAW;KACT;KACA,cAAc,IAAI,MAAM,kBAAkB,OAAO,CAAC,KAAK,EAAE;KACzD,KAAK;KACL,OAAO;KACR;AACD,cAAU,OAAO,IAAI,KAAK,SAAS;;AAGrC,QAAK,IAAI,IAAI,GAAG,IAAI,kBAAkB,QAAQ,IAC5C,KAAI,SAAS,kBAAkB,GAC7B,UAAS,aAAa,MAAM;AAGhC,YAAS,OAAO;AAChB,YAAS,SAAS;;EAGpB,SAAS,MAAc,QAAgC,OAAqB;GAC1E,MAAM,QAAQ,iBAAiB,KAAK;GACpC,MAAM,MAAM,SAAS,OAAO;GAC5B,MAAM,WAAW,MAAM,OAAO,IAAI,IAAI;AACtC,OAAI,SACF,UAAS,QAAQ;OAEjB,OAAM,OAAO,IAAI,KAAK;IAAE;IAAQ;IAAO,CAAC;;EAI5C,YAAoB;GAClB,MAAM,QAAkB,EAAE;AAE1B,QAAK,MAAM,CAAC,MAAM,SAAS,QACzB,SAAQ,KAAK,MAAb;IACE,KAAK;AACH,WAAM,KAAK,UAAU,KAAK,UAAU;AACpC,UAAK,MAAM,UAAU,KAAK,OAAO,QAAQ,CACvC,OAAM,KAAK,GAAG,OAAO,aAAa,OAAO,OAAO,CAAC,GAAG,OAAO,QAAQ;AAErE;IAEF,KAAK;AACH,WAAM,KAAK,UAAU,KAAK,YAAY;AACtC,UAAK,MAAM,UAAU,KAAK,OAAO,QAAQ,EAAE;MACzC,MAAM,SAAS,SAAS,OAAO,OAAO;MACtC,MAAM,YAAY,SAAS,GAAG,OAAO,KAAK;AAE1C,WAAK,IAAI,IAAI,GAAG,IAAI,kBAAkB,QAAQ,IAC5C,OAAM,KACJ,GAAG,KAAK,UAAU,UAAU,MAAM,kBAAkB,GAAG,KAAK,OAAO,aAAa,KACjF;AAGH,YAAM,KAAK,GAAG,KAAK,UAAU,UAAU,aAAa,OAAO,QAAQ;AAEnE,YAAM,KAAK,GAAG,KAAK,MAAM,aAAa,OAAO,OAAO,CAAC,GAAG,OAAO,MAAM;AACrE,YAAM,KAAK,GAAG,KAAK,QAAQ,aAAa,OAAO,OAAO,CAAC,GAAG,OAAO,QAAQ;;AAE3E;IAEF,KAAK;AACH,WAAM,KAAK,UAAU,KAAK,QAAQ;AAClC,UAAK,MAAM,UAAU,KAAK,OAAO,QAAQ,CACvC,OAAM,KAAK,GAAG,OAAO,aAAa,OAAO,OAAO,CAAC,GAAG,OAAO,QAAQ;AAErE;;AAKN,UAAO,MAAM,SAAS,IAAI,MAAM,KAAK,KAAK,GAAG,OAAO;;EAGtD,QAAc;AACZ,WAAQ,OAAO;;EAElB;;AAQH,MAAM,aACJ;AACF,MAAM,YAAY;AAClB,MAAM,WAAW;AACjB,MAAM,oBAAoB;AAC1B,MAAM,YACJ;AAGF,MAAa,8BAA8B;AAC3C,MAAa,6BAA6B;AAC1C,MAAa,yBAAyB;AAOtC,MAAa,qBAAqB;AAClC,MAAa,mBAAmB;AAChC,MAAa,yBAAyB;AACtC,MAAa,uBAAuB;;;;;AAMpC,SAAgB,mBAAmB,UAA0B;CAE3D,MAAM,eAAe,SAAS,MAAM,WAAW;AAC/C,KAAI,aACF,QAAO,oBAAoB,aAAa;CAI1C,MAAM,cAAc,SAAS,MAAM,UAAU;AAC7C,KAAI,YACF,QAAO,0BAA0B,YAAY;CAI/C,MAAM,aAAa,SAAS,MAAM,SAAS;AAC3C,KAAI,WACF,QAAO,4BAA4B,WAAW;CAIhD,MAAM,cAAc,SAAS,MAAM,UAAU;AAC7C,KAAI,YACF,QAAO,+DAA+D,YAAY;AAIpF,KAAI,kBAAkB,KAAK,SAAS,CAClC,QAAO;AAMT,KAAI,4BAA4B,KAAK,SAAS,CAC5C,QAAO;AAET,KAAI,aAAa,2BAA2B,2BAA2B,KAAK,SAAS,CACnF,QAAO;AAMT,KAAI,mBAAmB,KAAK,SAAS,CACnC,QAAO;AAET,KAAI,iBAAiB,KAAK,SAAS,CACjC,QAAO;AAMT,KAAI,aAAa,uBACf,QAAO;AAIT,KAAI,uBAAuB,KAAK,SAAS,CACvC,QAAO;AAIT,QAAO"}
1
+ {"version":3,"file":"metrics.js","names":[],"sources":["../src/metrics.ts"],"sourcesContent":["/**\n * Lightweight Prometheus metrics registry for LLMock.\n *\n * Zero external dependencies — implements counters, histograms, and gauges\n * with Prometheus text exposition format serialization.\n */\n\n// ---------------------------------------------------------------------------\n// Public interface\n// ---------------------------------------------------------------------------\n\nexport interface MetricsRegistry {\n incrementCounter(name: string, labels: Record<string, string>): void;\n observeHistogram(name: string, labels: Record<string, string>, value: number): void;\n setGauge(name: string, labels: Record<string, string>, value: number): void;\n serialize(): string;\n reset(): void;\n}\n\n// ---------------------------------------------------------------------------\n// Histogram bucket boundaries (Prometheus default-ish)\n// ---------------------------------------------------------------------------\n\nconst HISTOGRAM_BUCKETS = [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10];\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n/** Build a stable label key string for map lookups: `label1=\"v1\",label2=\"v2\"` */\nfunction labelKey(labels: Record<string, string>): string {\n const entries = Object.entries(labels).sort(([a], [b]) => a.localeCompare(b));\n if (entries.length === 0) return \"\";\n return entries.map(([k, v]) => `${k}=\"${escapeLabelValue(v)}\"`).join(\",\");\n}\n\n/** Escape a label value per Prometheus text exposition format. */\nfunction escapeLabelValue(v: string): string {\n return v.replace(/\\\\/g, \"\\\\\\\\\").replace(/\"/g, '\\\\\"').replace(/\\n/g, \"\\\\n\");\n}\n\n/** Format labels for Prometheus output: `{label1=\"v1\",label2=\"v2\"}` */\nfunction formatLabels(labels: Record<string, string>): string {\n return `{${labelKey(labels)}}`;\n}\n\n// ---------------------------------------------------------------------------\n// Internal metric storage types\n// ---------------------------------------------------------------------------\n\ninterface CounterData {\n type: \"counter\";\n /** Map from labelKey → value */\n series: Map<string, { labels: Record<string, string>; value: number }>;\n}\n\ninterface HistogramData {\n type: \"histogram\";\n /** Map from labelKey → bucket counts, sum, count */\n series: Map<\n string,\n {\n labels: Record<string, string>;\n bucketCounts: number[]; // one per HISTOGRAM_BUCKETS entry\n sum: number;\n count: number;\n }\n >;\n}\n\ninterface GaugeData {\n type: \"gauge\";\n /** Map from labelKey → value */\n series: Map<string, { labels: Record<string, string>; value: number }>;\n}\n\ntype MetricData = CounterData | HistogramData | GaugeData;\n\n// ---------------------------------------------------------------------------\n// Registry implementation\n// ---------------------------------------------------------------------------\n\nexport function createMetricsRegistry(): MetricsRegistry {\n /** Ordered map: metric name → data. Insertion order preserved for stable output. */\n const metrics = new Map<string, MetricData>();\n\n function getOrCreateCounter(name: string): CounterData {\n let data = metrics.get(name);\n if (!data) {\n data = { type: \"counter\", series: new Map() };\n metrics.set(name, data);\n }\n if (data.type !== \"counter\") throw new Error(`Metric ${name} is not a counter`);\n return data as CounterData;\n }\n\n function getOrCreateHistogram(name: string): HistogramData {\n let data = metrics.get(name);\n if (!data) {\n data = { type: \"histogram\", series: new Map() };\n metrics.set(name, data);\n }\n if (data.type !== \"histogram\") throw new Error(`Metric ${name} is not a histogram`);\n return data as HistogramData;\n }\n\n function getOrCreateGauge(name: string): GaugeData {\n let data = metrics.get(name);\n if (!data) {\n data = { type: \"gauge\", series: new Map() };\n metrics.set(name, data);\n }\n if (data.type !== \"gauge\") throw new Error(`Metric ${name} is not a gauge`);\n return data as GaugeData;\n }\n\n return {\n incrementCounter(name: string, labels: Record<string, string>): void {\n const counter = getOrCreateCounter(name);\n const key = labelKey(labels);\n const existing = counter.series.get(key);\n if (existing) {\n existing.value += 1;\n } else {\n counter.series.set(key, { labels, value: 1 });\n }\n },\n\n observeHistogram(name: string, labels: Record<string, string>, value: number): void {\n const histogram = getOrCreateHistogram(name);\n const key = labelKey(labels);\n let existing = histogram.series.get(key);\n if (!existing) {\n existing = {\n labels,\n bucketCounts: new Array(HISTOGRAM_BUCKETS.length).fill(0) as number[],\n sum: 0,\n count: 0,\n };\n histogram.series.set(key, existing);\n }\n // Update cumulative bucket counts\n for (let i = 0; i < HISTOGRAM_BUCKETS.length; i++) {\n if (value <= HISTOGRAM_BUCKETS[i]) {\n existing.bucketCounts[i] += 1;\n }\n }\n existing.sum += value;\n existing.count += 1;\n },\n\n setGauge(name: string, labels: Record<string, string>, value: number): void {\n const gauge = getOrCreateGauge(name);\n const key = labelKey(labels);\n const existing = gauge.series.get(key);\n if (existing) {\n existing.value = value;\n } else {\n gauge.series.set(key, { labels, value });\n }\n },\n\n serialize(): string {\n const lines: string[] = [];\n\n for (const [name, data] of metrics) {\n switch (data.type) {\n case \"counter\": {\n lines.push(`# TYPE ${name} counter`);\n for (const series of data.series.values()) {\n lines.push(`${name}${formatLabels(series.labels)} ${series.value}`);\n }\n break;\n }\n case \"histogram\": {\n lines.push(`# TYPE ${name} histogram`);\n for (const series of data.series.values()) {\n const lblStr = labelKey(series.labels);\n const lblPrefix = lblStr ? `${lblStr},` : \"\";\n // Bucket lines\n for (let i = 0; i < HISTOGRAM_BUCKETS.length; i++) {\n lines.push(\n `${name}_bucket{${lblPrefix}le=\"${HISTOGRAM_BUCKETS[i]}\"} ${series.bucketCounts[i]}`,\n );\n }\n // +Inf bucket\n lines.push(`${name}_bucket{${lblPrefix}le=\"+Inf\"} ${series.count}`);\n // Sum and count\n lines.push(`${name}_sum${formatLabels(series.labels)} ${series.sum}`);\n lines.push(`${name}_count${formatLabels(series.labels)} ${series.count}`);\n }\n break;\n }\n case \"gauge\": {\n lines.push(`# TYPE ${name} gauge`);\n for (const series of data.series.values()) {\n lines.push(`${name}${formatLabels(series.labels)} ${series.value}`);\n }\n break;\n }\n }\n }\n\n return lines.length > 0 ? lines.join(\"\\n\") + \"\\n\" : \"\";\n },\n\n reset(): void {\n metrics.clear();\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// Path normalization for metric labels\n// ---------------------------------------------------------------------------\n\n// Regex patterns for parametric API routes\nconst BEDROCK_RE =\n /^\\/model\\/([^/]+)\\/(invoke|invoke-with-response-stream|converse|converse-stream)$/;\nconst GEMINI_RE = /^\\/v1beta\\/models\\/([^:]+):(generateContent|streamGenerateContent)$/;\nconst AZURE_RE = /^\\/openai\\/deployments\\/([^/]+)\\/(chat\\/completions|embeddings)$/;\nconst ELEVENLABS_TTS_RE = /^\\/v1\\/text-to-speech\\/([^/]+)$/;\nconst VERTEX_RE =\n /^\\/v1\\/projects\\/([^/]+)\\/locations\\/([^/]+)\\/publishers\\/google\\/models\\/([^:]+):(.+)$/;\n// Exported: server.ts route dispatch matches the same OpenRouter and OpenAI\n// video paths.\nexport const OPENROUTER_VIDEO_CONTENT_RE = /^\\/api\\/v1\\/videos\\/([^/]+)\\/content$/;\nexport const OPENROUTER_VIDEO_STATUS_RE = /^\\/api\\/v1\\/videos\\/([^/]+)$/;\nexport const OPENAI_VIDEO_STATUS_RE = /^\\/v1\\/videos\\/([^/]+)$/;\n// Exported: server.ts route dispatch matches the same Google Veo and xAI Grok\n// video paths. Veo submit (`:predictLongRunning`) is anchored so it never\n// collides with the bare Gemini `:predict` route; Veo operations live in a\n// fresh `/v1beta/operations/...` namespace. Grok submit is an exact literal\n// path; Grok status reuses the OpenAI `/v1/videos/{id}` shape (the dispatch\n// guards `id !== \"generations\"` and the job-map lookup disambiguates Sora).\nexport const VEO_PREDICT_LRO_RE = /^\\/v1beta\\/models\\/([^:]+):predictLongRunning$/;\nexport const VEO_OPERATION_RE = /^\\/v1beta\\/(operations\\/.+)$/;\nexport const GROK_VIDEO_SUBMIT_PATH = \"/v1/videos/generations\";\nexport const GROK_VIDEO_STATUS_RE = /^\\/v1\\/videos\\/([^/]+)$/;\n\n/**\n * BytePlus Ark (Seedance) async video task routes. The `/api/v3` prefix is\n * ENUMERATED rather than wildcarded: Ark's data-plane base carries it, but a\n * client may point `baseURL` at a bare aimock root instead, so both forms must\n * route. A tolerant `(?:\\/[^?]*)?` prefix would additionally claim\n * `/fal/contents/generations/tasks` — and these routes dispatch in the\n * pre-rewrite band, ~1,100 lines ahead of every fal branch, so it would take\n * that path away from the fal proxy. Enumerating the one real prefix also\n * blocks a doubled suffix from matching the status RE.\n *\n * Declared here (not in server.ts) because normalizePathLabel below consumes\n * them, matching the existing OpenRouter/Veo/Grok route-regex edge where\n * server.ts imports its route regexes from this module.\n */\nexport const BYTEPLUS_VIDEO_SUBMIT_RE = /^(?:\\/api\\/v3)?\\/contents\\/generations\\/tasks$/;\nexport const BYTEPLUS_VIDEO_STATUS_RE = /^(?:\\/api\\/v3)?\\/contents\\/generations\\/tasks\\/([^/]+)$/;\n\n/**\n * Normalize parametric API paths to route patterns for use as metric labels.\n * Replaces dynamic segments (model IDs, deployment names, etc.) with placeholders.\n */\nexport function normalizePathLabel(pathname: string): string {\n // Bedrock: /model/{modelId}/{operation}\n const bedrockMatch = pathname.match(BEDROCK_RE);\n if (bedrockMatch) {\n return `/model/{modelId}/${bedrockMatch[2]}`;\n }\n\n // Gemini: /v1beta/models/{model}:{action}\n const geminiMatch = pathname.match(GEMINI_RE);\n if (geminiMatch) {\n return `/v1beta/models/{model}:${geminiMatch[2]}`;\n }\n\n // Azure: /openai/deployments/{id}/{operation}\n const azureMatch = pathname.match(AZURE_RE);\n if (azureMatch) {\n return `/openai/deployments/{id}/${azureMatch[2]}`;\n }\n\n // Vertex AI: /v1/projects/{p}/locations/{l}/publishers/google/models/{m}:{action}\n const vertexMatch = pathname.match(VERTEX_RE);\n if (vertexMatch) {\n return `/v1/projects/{p}/locations/{l}/publishers/google/models/{m}:${vertexMatch[4]}`;\n }\n\n // ElevenLabs TTS: /v1/text-to-speech/{voice_id}\n if (ELEVENLABS_TTS_RE.test(pathname)) {\n return \"/v1/text-to-speech/{voice_id}\";\n }\n\n // OpenRouter video: /api/v1/videos/{jobId}[/content] — jobIds are random\n // UUIDs, so raw paths would mint unbounded label cardinality. The static\n // /api/v1/videos/models listing route must not collapse into {jobId}.\n if (OPENROUTER_VIDEO_CONTENT_RE.test(pathname)) {\n return \"/api/v1/videos/{jobId}/content\";\n }\n if (pathname !== \"/api/v1/videos/models\" && OPENROUTER_VIDEO_STATUS_RE.test(pathname)) {\n return \"/api/v1/videos/{jobId}\";\n }\n\n // Google Veo video: submit `:predictLongRunning` + poll `/v1beta/operations/{name}`.\n // Operation names are random UUIDs, so the raw path would mint unbounded\n // label cardinality.\n if (VEO_PREDICT_LRO_RE.test(pathname)) {\n return \"/v1beta/models/{model}:predictLongRunning\";\n }\n if (VEO_OPERATION_RE.test(pathname)) {\n return \"/v1beta/operations/{name}\";\n }\n\n // xAI Grok Imagine submit is a static literal path — keep it distinct from\n // the `/v1/videos/{id}` status label below (which it would otherwise collapse\n // into), exactly as `/api/v1/videos/models` is kept out of the jobId bucket.\n if (pathname === GROK_VIDEO_SUBMIT_PATH) {\n return GROK_VIDEO_SUBMIT_PATH;\n }\n\n // OpenAI/Grok video status: /v1/videos/{id}\n if (OPENAI_VIDEO_STATUS_RE.test(pathname)) {\n return \"/v1/videos/{id}\";\n }\n\n // BytePlus Ark video: /[api/v3/]contents/generations/tasks[/{id}] — task ids\n // (`cgt-…`) are unbounded cardinality. Status before submit: the status RE is\n // the more specific of the two and the submit RE cannot match a trailing id\n // segment, so the order is belt-and-braces. Appended at the end of the\n // cascade because the anchored REs above cannot match any path an earlier\n // entry claims.\n if (BYTEPLUS_VIDEO_STATUS_RE.test(pathname)) {\n return \"/contents/generations/tasks/{id}\";\n }\n if (BYTEPLUS_VIDEO_SUBMIT_RE.test(pathname)) {\n return \"/contents/generations/tasks\";\n }\n\n // Static path — return as-is\n return pathname;\n}\n"],"mappings":"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"}
@@ -22,6 +22,7 @@ const PROVIDER_AUTH_SCHEMES = {
22
22
  openrouter: { kind: "bearer" },
23
23
  cohere: { kind: "bearer" },
24
24
  grok: { kind: "bearer" },
25
+ byteplus: { kind: "bearer" },
25
26
  ollama: { kind: "bearer" },
26
27
  anthropic: { kind: "x-api-key" },
27
28
  gemini: { kind: "x-goog-api-key" },
@@ -175,6 +176,7 @@ function readProviderKeysFromEnv(env = process.env) {
175
176
  if (env.AIMOCK_PROVIDER_OPENROUTER_KEY) keys.openrouter = env.AIMOCK_PROVIDER_OPENROUTER_KEY;
176
177
  if (env.AIMOCK_PROVIDER_COHERE_KEY) keys.cohere = env.AIMOCK_PROVIDER_COHERE_KEY;
177
178
  if (env.AIMOCK_PROVIDER_GROK_KEY) keys.grok = env.AIMOCK_PROVIDER_GROK_KEY;
179
+ if (env.AIMOCK_PROVIDER_BYTEPLUS_KEY) keys.byteplus = env.AIMOCK_PROVIDER_BYTEPLUS_KEY;
178
180
  if (env.AIMOCK_PROVIDER_OLLAMA_KEY) keys.ollama = env.AIMOCK_PROVIDER_OLLAMA_KEY;
179
181
  if (env.AIMOCK_PROVIDER_VEO_KEY) keys.veo = env.AIMOCK_PROVIDER_VEO_KEY;
180
182
  if (env.AIMOCK_PROVIDER_AZURE_KEY) keys.azure = env.AIMOCK_PROVIDER_AZURE_KEY;
@@ -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 */\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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"}
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, BytePlus Ark, 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 byteplus: { kind: \"bearer\" }, // BytePlus Ark (ModelArk) — Authorization: Bearer $ARK_API_KEY\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_BYTEPLUS_KEY) keys.byteplus = env.AIMOCK_PROVIDER_BYTEPLUS_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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"}
@@ -21,6 +21,7 @@ const PROVIDER_AUTH_SCHEMES = {
21
21
  openrouter: { kind: "bearer" },
22
22
  cohere: { kind: "bearer" },
23
23
  grok: { kind: "bearer" },
24
+ byteplus: { kind: "bearer" },
24
25
  ollama: { kind: "bearer" },
25
26
  anthropic: { kind: "x-api-key" },
26
27
  gemini: { kind: "x-goog-api-key" },
@@ -174,6 +175,7 @@ function readProviderKeysFromEnv(env = process.env) {
174
175
  if (env.AIMOCK_PROVIDER_OPENROUTER_KEY) keys.openrouter = env.AIMOCK_PROVIDER_OPENROUTER_KEY;
175
176
  if (env.AIMOCK_PROVIDER_COHERE_KEY) keys.cohere = env.AIMOCK_PROVIDER_COHERE_KEY;
176
177
  if (env.AIMOCK_PROVIDER_GROK_KEY) keys.grok = env.AIMOCK_PROVIDER_GROK_KEY;
178
+ if (env.AIMOCK_PROVIDER_BYTEPLUS_KEY) keys.byteplus = env.AIMOCK_PROVIDER_BYTEPLUS_KEY;
177
179
  if (env.AIMOCK_PROVIDER_OLLAMA_KEY) keys.ollama = env.AIMOCK_PROVIDER_OLLAMA_KEY;
178
180
  if (env.AIMOCK_PROVIDER_VEO_KEY) keys.veo = env.AIMOCK_PROVIDER_VEO_KEY;
179
181
  if (env.AIMOCK_PROVIDER_AZURE_KEY) keys.azure = env.AIMOCK_PROVIDER_AZURE_KEY;
@@ -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 */\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":";;;;;;;;;;;;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,SAAgB,4BACd,gBACA,QACA,aACA,oBACS;CAET,MAAM,SAAS,sBAAsB;AACrC,KAAI,CAAC,OAAQ,QAAO;AACpB,SAAQ,OAAO,MAAf;EACE,KAAK;AACH,cAAW,gBAAgB,gBAAgB;AAC3C,kBAAe,gBAAgB,UAAU;AACzC;EACF,KAAK;AACH,cAAW,gBAAgB,gBAAgB;AAC3C,kBAAe,gBAAgB,OAAO;AACtC;EACF;AACE,cAAW,gBAAgB,eAAe,OAAO,CAAC;AAClD,kBAAe,eAAe,OAAO,IAAI;;AAE7C,QAAO;;;;;;;AAQT,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.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, BytePlus Ark, 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 byteplus: { kind: \"bearer\" }, // BytePlus Ark (ModelArk) — Authorization: Bearer $ARK_API_KEY\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_BYTEPLUS_KEY) keys.byteplus = env.AIMOCK_PROVIDER_BYTEPLUS_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/router.cjs CHANGED
@@ -202,6 +202,7 @@ function matchFixtureDiagnostic(fixtures, req, matchCounts, requestTransform, op
202
202
  const reqEndpoint = effective._endpointType;
203
203
  if (match.endpoint !== void 0) {
204
204
  if (match.endpoint !== reqEndpoint) continue;
205
+ if (reqEndpoint === "video" && req._videoProvider !== "byteplus" && typeof fixture.response !== "function" && require_helpers.isJSONResponse(fixture.response)) continue;
205
206
  } else if (reqEndpoint && reqEndpoint !== "chat" && reqEndpoint !== "embedding" && !reqEndpoint.startsWith("realtime")) {
206
207
  const r = fixture.response;
207
208
  if (typeof r !== "function") {