@copilotkit/aimock 1.14.8 → 1.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -0
- package/dist/bedrock-converse.cjs +4 -4
- package/dist/bedrock-converse.cjs.map +1 -1
- package/dist/bedrock-converse.d.cts.map +1 -1
- package/dist/bedrock-converse.d.ts.map +1 -1
- package/dist/bedrock-converse.js +4 -4
- package/dist/bedrock-converse.js.map +1 -1
- package/dist/bedrock.cjs +4 -4
- package/dist/bedrock.cjs.map +1 -1
- package/dist/bedrock.d.cts.map +1 -1
- package/dist/bedrock.d.ts.map +1 -1
- package/dist/bedrock.js +4 -4
- package/dist/bedrock.js.map +1 -1
- package/dist/chaos.cjs +35 -9
- package/dist/chaos.cjs.map +1 -1
- package/dist/chaos.d.cts +17 -2
- package/dist/chaos.d.cts.map +1 -1
- package/dist/chaos.d.ts +17 -2
- package/dist/chaos.d.ts.map +1 -1
- package/dist/chaos.js +35 -10
- package/dist/chaos.js.map +1 -1
- package/dist/cohere.cjs +2 -2
- package/dist/cohere.cjs.map +1 -1
- package/dist/cohere.js +2 -2
- package/dist/cohere.js.map +1 -1
- package/dist/config-loader.d.ts.map +1 -1
- package/dist/embeddings.cjs +2 -2
- package/dist/embeddings.cjs.map +1 -1
- package/dist/embeddings.js +2 -2
- package/dist/embeddings.js.map +1 -1
- package/dist/gemini.cjs +2 -2
- package/dist/gemini.cjs.map +1 -1
- package/dist/gemini.js +2 -2
- package/dist/gemini.js.map +1 -1
- package/dist/images.cjs +2 -2
- package/dist/images.cjs.map +1 -1
- package/dist/images.js +2 -2
- package/dist/images.js.map +1 -1
- package/dist/messages.cjs +2 -2
- package/dist/messages.cjs.map +1 -1
- package/dist/messages.js +2 -2
- package/dist/messages.js.map +1 -1
- package/dist/ollama.cjs +4 -4
- package/dist/ollama.cjs.map +1 -1
- package/dist/ollama.d.cts.map +1 -1
- package/dist/ollama.d.ts.map +1 -1
- package/dist/ollama.js +4 -4
- package/dist/ollama.js.map +1 -1
- package/dist/recorder.cjs +16 -10
- package/dist/recorder.cjs.map +1 -1
- package/dist/recorder.d.cts +50 -5
- package/dist/recorder.d.cts.map +1 -1
- package/dist/recorder.d.ts +50 -5
- package/dist/recorder.d.ts.map +1 -1
- package/dist/recorder.js +16 -10
- package/dist/recorder.js.map +1 -1
- package/dist/responses.cjs +2 -2
- package/dist/responses.cjs.map +1 -1
- package/dist/responses.js +2 -2
- package/dist/responses.js.map +1 -1
- package/dist/server.cjs +35 -9
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +36 -10
- package/dist/server.js.map +1 -1
- package/dist/speech.cjs +2 -2
- package/dist/speech.cjs.map +1 -1
- package/dist/speech.js +2 -2
- package/dist/speech.js.map +1 -1
- package/dist/transcription.cjs +2 -2
- package/dist/transcription.cjs.map +1 -1
- package/dist/transcription.js +2 -2
- package/dist/transcription.js.map +1 -1
- package/dist/types.d.cts +16 -0
- package/dist/types.d.cts.map +1 -1
- package/dist/types.d.ts +16 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/vector-types.d.ts.map +1 -1
- package/dist/video.cjs +9 -3
- package/dist/video.cjs.map +1 -1
- package/dist/video.d.cts +1 -1
- package/dist/video.d.cts.map +1 -1
- package/dist/video.d.ts +1 -1
- package/dist/video.d.ts.map +1 -1
- package/dist/video.js +9 -3
- package/dist/video.js.map +1 -1
- package/package.json +1 -2
package/dist/bedrock.js
CHANGED
|
@@ -192,10 +192,10 @@ async function handleBedrock(req, res, raw, modelId, fixtures, journal, defaults
|
|
|
192
192
|
path: urlPath,
|
|
193
193
|
headers: flattenHeaders(req.headers),
|
|
194
194
|
body: completionReq
|
|
195
|
-
}, defaults.registry, defaults.logger)) return;
|
|
195
|
+
}, fixture ? "fixture" : "proxy", defaults.registry, defaults.logger)) return;
|
|
196
196
|
if (!fixture) {
|
|
197
197
|
if (defaults.record) {
|
|
198
|
-
if (await proxyAndRecord(req, res, completionReq, "bedrock", urlPath, fixtures, defaults, raw)) {
|
|
198
|
+
if (await proxyAndRecord(req, res, completionReq, "bedrock", urlPath, fixtures, defaults, raw) !== "not_configured") {
|
|
199
199
|
journal.add({
|
|
200
200
|
method: req.method ?? "POST",
|
|
201
201
|
path: urlPath,
|
|
@@ -464,10 +464,10 @@ async function handleBedrockStream(req, res, raw, modelId, fixtures, journal, de
|
|
|
464
464
|
path: urlPath,
|
|
465
465
|
headers: flattenHeaders(req.headers),
|
|
466
466
|
body: completionReq
|
|
467
|
-
}, defaults.registry, defaults.logger)) return;
|
|
467
|
+
}, fixture ? "fixture" : "proxy", defaults.registry, defaults.logger)) return;
|
|
468
468
|
if (!fixture) {
|
|
469
469
|
if (defaults.record) {
|
|
470
|
-
if (await proxyAndRecord(req, res, completionReq, "bedrock", urlPath, fixtures, defaults, raw)) {
|
|
470
|
+
if (await proxyAndRecord(req, res, completionReq, "bedrock", urlPath, fixtures, defaults, raw) !== "not_configured") {
|
|
471
471
|
journal.add({
|
|
472
472
|
method: req.method ?? "POST",
|
|
473
473
|
path: urlPath,
|
package/dist/bedrock.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bedrock.js","names":[],"sources":["../src/bedrock.ts"],"sourcesContent":["/**\n * AWS Bedrock Claude endpoint support — invoke and invoke-with-response-stream.\n *\n * Handles four Bedrock endpoint families (split across two modules):\n *\n * This file (bedrock.ts):\n * - POST /model/{modelId}/invoke — non-streaming invoke\n * - POST /model/{modelId}/invoke-with-response-stream — binary EventStream streaming\n *\n * bedrock-converse.ts:\n * - POST /model/{modelId}/converse — Converse API (non-streaming)\n * - POST /model/{modelId}/converse-stream — Converse API (EventStream streaming)\n *\n * Translates incoming Bedrock Claude format into the ChatCompletionRequest\n * format used by the fixture router, and converts fixture responses back into\n * the appropriate Bedrock response format (JSON for invoke, AWS Event Stream\n * binary encoding for streaming).\n */\n\nimport type * as http from \"node:http\";\nimport type {\n ChatCompletionRequest,\n ChatMessage,\n Fixture,\n HandlerDefaults,\n ToolCall,\n ToolDefinition,\n} from \"./types.js\";\nimport {\n generateMessageId,\n generateToolUseId,\n isTextResponse,\n isToolCallResponse,\n isErrorResponse,\n flattenHeaders,\n getTestId,\n} from \"./helpers.js\";\nimport { matchFixture } from \"./router.js\";\nimport { writeErrorResponse } from \"./sse-writer.js\";\nimport { writeEventStream } from \"./aws-event-stream.js\";\nimport { createInterruptionSignal } from \"./interruption.js\";\nimport type { Journal } from \"./journal.js\";\nimport type { Logger } from \"./logger.js\";\nimport { applyChaos } from \"./chaos.js\";\nimport { proxyAndRecord } from \"./recorder.js\";\n\n// ─── Bedrock Claude request types ────────────────────────────────────────────\n\ninterface BedrockContentBlock {\n type: \"text\" | \"tool_use\" | \"tool_result\" | \"image\" | \"document\";\n text?: string;\n id?: string;\n name?: string;\n input?: unknown;\n tool_use_id?: string;\n content?: string | BedrockContentBlock[];\n is_error?: boolean;\n}\n\ninterface BedrockMessage {\n role: \"user\" | \"assistant\";\n content: string | BedrockContentBlock[];\n}\n\ninterface BedrockToolDef {\n name: string;\n description?: string;\n input_schema?: object;\n}\n\ninterface BedrockRequest {\n anthropic_version?: string;\n messages: BedrockMessage[];\n system?: string | BedrockContentBlock[];\n tools?: BedrockToolDef[];\n tool_choice?: unknown;\n max_tokens: number;\n temperature?: number;\n [key: string]: unknown;\n}\n\n// ─── Input conversion: Bedrock → ChatCompletionRequest ──────────────────────\n\nfunction extractTextContent(content: string | BedrockContentBlock[]): string {\n if (typeof content === \"string\") return content;\n return content\n .filter((b) => b.type === \"text\")\n .map((b) => b.text ?? \"\")\n .join(\"\");\n}\n\nexport function bedrockToCompletionRequest(\n req: BedrockRequest,\n modelId: string,\n): ChatCompletionRequest {\n const messages: ChatMessage[] = [];\n\n // system field → system message\n if (req.system) {\n const systemText =\n typeof req.system === \"string\"\n ? req.system\n : req.system\n .filter((b) => b.type === \"text\")\n .map((b) => b.text ?? \"\")\n .join(\"\");\n if (systemText) {\n messages.push({ role: \"system\", content: systemText });\n }\n }\n\n for (const msg of req.messages) {\n if (msg.role === \"user\") {\n // Check for tool_result blocks\n if (typeof msg.content !== \"string\" && Array.isArray(msg.content)) {\n const toolResults = msg.content.filter((b) => b.type === \"tool_result\");\n const textBlocks = msg.content.filter((b) => b.type === \"text\");\n\n if (toolResults.length > 0) {\n for (const tr of toolResults) {\n const resultContent =\n typeof tr.content === \"string\"\n ? tr.content\n : Array.isArray(tr.content)\n ? tr.content\n .filter((b) => b.type === \"text\")\n .map((b) => b.text ?? \"\")\n .join(\"\")\n : \"\";\n messages.push({\n role: \"tool\",\n content: resultContent,\n tool_call_id: tr.tool_use_id,\n });\n }\n if (textBlocks.length > 0) {\n messages.push({\n role: \"user\",\n content: textBlocks.map((b) => b.text ?? \"\").join(\"\"),\n });\n }\n continue;\n }\n }\n messages.push({\n role: \"user\",\n content: extractTextContent(msg.content),\n });\n } else if (msg.role === \"assistant\") {\n if (typeof msg.content === \"string\") {\n messages.push({ role: \"assistant\", content: msg.content });\n } else if (Array.isArray(msg.content)) {\n const toolUseBlocks = msg.content.filter((b) => b.type === \"tool_use\");\n const textContent = extractTextContent(msg.content);\n\n if (toolUseBlocks.length > 0) {\n messages.push({\n role: \"assistant\",\n content: textContent || null,\n tool_calls: toolUseBlocks.map((b) => ({\n id: b.id ?? generateToolUseId(),\n type: \"function\" as const,\n function: {\n name: b.name ?? \"\",\n arguments: typeof b.input === \"string\" ? b.input : JSON.stringify(b.input ?? {}),\n },\n })),\n });\n } else {\n messages.push({ role: \"assistant\", content: textContent || null });\n }\n } else {\n messages.push({ role: \"assistant\", content: null });\n }\n }\n }\n\n // Convert tools\n let tools: ToolDefinition[] | undefined;\n if (req.tools && req.tools.length > 0) {\n tools = req.tools.map((t) => ({\n type: \"function\" as const,\n function: {\n name: t.name,\n description: t.description,\n parameters: t.input_schema,\n },\n }));\n }\n\n return {\n model: modelId,\n messages,\n stream: false,\n temperature: req.temperature,\n tools,\n };\n}\n\n// ─── Response builders ──────────────────────────────────────────────────────\n\nfunction buildBedrockTextResponse(content: string, model: string, reasoning?: string): object {\n const contentBlocks: object[] = [];\n if (reasoning) {\n contentBlocks.push({ type: \"thinking\", thinking: reasoning });\n }\n contentBlocks.push({ type: \"text\", text: content });\n\n return {\n id: generateMessageId(),\n type: \"message\",\n role: \"assistant\",\n content: contentBlocks,\n model,\n stop_reason: \"end_turn\",\n stop_sequence: null,\n usage: { input_tokens: 0, output_tokens: 0 },\n };\n}\n\nfunction buildBedrockToolCallResponse(\n toolCalls: ToolCall[],\n model: string,\n logger: Logger,\n): object {\n return {\n id: generateMessageId(),\n type: \"message\",\n role: \"assistant\",\n content: toolCalls.map((tc) => {\n let argsObj: unknown;\n try {\n argsObj = JSON.parse(tc.arguments || \"{}\");\n } catch {\n logger.warn(\n `Malformed JSON in fixture tool call arguments for \"${tc.name}\": ${tc.arguments}`,\n );\n argsObj = {};\n }\n return {\n type: \"tool_use\",\n id: tc.id || generateToolUseId(),\n name: tc.name,\n input: argsObj,\n };\n }),\n model,\n stop_reason: \"tool_use\",\n stop_sequence: null,\n usage: { input_tokens: 0, output_tokens: 0 },\n };\n}\n\n// ─── Request handler ────────────────────────────────────────────────────────\n\nexport async function handleBedrock(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n raw: string,\n modelId: string,\n fixtures: Fixture[],\n journal: Journal,\n defaults: HandlerDefaults,\n setCorsHeaders: (res: http.ServerResponse) => void,\n): Promise<void> {\n const { logger } = defaults;\n setCorsHeaders(res);\n\n const urlPath = req.url ?? `/model/${modelId}/invoke`;\n\n let bedrockReq: BedrockRequest;\n try {\n bedrockReq = JSON.parse(raw) as BedrockRequest;\n } catch {\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({\n error: {\n message: \"Malformed JSON\",\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n if (!bedrockReq.messages || !Array.isArray(bedrockReq.messages)) {\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({\n error: {\n message: \"Invalid request: messages array is required\",\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n // Convert to ChatCompletionRequest for fixture matching\n const completionReq = bedrockToCompletionRequest(bedrockReq, modelId);\n completionReq._endpointType = \"chat\";\n\n const testId = getTestId(req);\n const fixture = matchFixture(\n fixtures,\n completionReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n );\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n }\n\n if (\n applyChaos(\n res,\n fixture,\n defaults.chaos,\n req.headers,\n journal,\n {\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n },\n defaults.registry,\n defaults.logger,\n )\n )\n return;\n\n if (!fixture) {\n if (defaults.record) {\n const proxied = await proxyAndRecord(\n req,\n res,\n completionReq,\n \"bedrock\",\n urlPath,\n fixtures,\n defaults,\n raw,\n );\n if (proxied) {\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: res.statusCode ?? 200, fixture: null },\n });\n return;\n }\n }\n const strictStatus = defaults.strict ? 503 : 404;\n const strictMessage = defaults.strict\n ? \"Strict mode: no fixture matched\"\n : \"No fixture matched\";\n if (defaults.strict) {\n logger.error(`STRICT: No fixture matched for ${req.method ?? \"POST\"} ${urlPath}`);\n }\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: strictStatus, fixture: null },\n });\n writeErrorResponse(\n res,\n strictStatus,\n JSON.stringify({\n error: {\n message: strictMessage,\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n const response = fixture.response;\n\n // Error response\n if (isErrorResponse(response)) {\n const status = response.status ?? 500;\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status, fixture },\n });\n // Anthropic-style error format (Bedrock uses Claude): { type: \"error\", error: { type, message } }\n const anthropicError = {\n type: \"error\",\n error: {\n type: response.error.type ?? \"api_error\",\n message: response.error.message,\n },\n };\n writeErrorResponse(res, status, JSON.stringify(anthropicError));\n return;\n }\n\n // Text response\n if (isTextResponse(response)) {\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 200, fixture },\n });\n const body = buildBedrockTextResponse(\n response.content,\n completionReq.model,\n response.reasoning,\n );\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n return;\n }\n\n // Tool call response\n if (isToolCallResponse(response)) {\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 200, fixture },\n });\n const body = buildBedrockToolCallResponse(response.toolCalls, completionReq.model, logger);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n return;\n }\n\n // Unknown response type\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 500, fixture },\n });\n writeErrorResponse(\n res,\n 500,\n JSON.stringify({\n error: {\n message: \"Fixture response did not match any known type\",\n type: \"server_error\",\n },\n }),\n );\n}\n\n// ─── Streaming event builders ───────────────────────────────────────────────\n\nexport function buildBedrockStreamTextEvents(\n content: string,\n chunkSize: number,\n reasoning?: string,\n): Array<{ eventType: string; payload: object }> {\n const events: Array<{ eventType: string; payload: object }> = [];\n\n events.push({\n eventType: \"messageStart\",\n payload: { role: \"assistant\" },\n });\n\n // Thinking block (emitted before text when reasoning is present)\n if (reasoning) {\n const blockIndex = 0;\n events.push({\n eventType: \"contentBlockStart\",\n payload: { contentBlockIndex: blockIndex, start: { type: \"thinking\" } },\n });\n\n for (let i = 0; i < reasoning.length; i += chunkSize) {\n const slice = reasoning.slice(i, i + chunkSize);\n events.push({\n eventType: \"contentBlockDelta\",\n payload: {\n contentBlockIndex: blockIndex,\n delta: { type: \"thinking_delta\", thinking: slice },\n },\n });\n }\n\n events.push({\n eventType: \"contentBlockStop\",\n payload: { contentBlockIndex: blockIndex },\n });\n }\n\n // Text block\n const textBlockIndex = reasoning ? 1 : 0;\n\n events.push({\n eventType: \"contentBlockStart\",\n payload: { contentBlockIndex: textBlockIndex, start: {} },\n });\n\n for (let i = 0; i < content.length; i += chunkSize) {\n const slice = content.slice(i, i + chunkSize);\n events.push({\n eventType: \"contentBlockDelta\",\n payload: {\n contentBlockIndex: textBlockIndex,\n delta: { type: \"text_delta\", text: slice },\n },\n });\n }\n\n events.push({\n eventType: \"contentBlockStop\",\n payload: { contentBlockIndex: textBlockIndex },\n });\n\n events.push({\n eventType: \"messageStop\",\n payload: { stopReason: \"end_turn\" },\n });\n\n return events;\n}\n\nexport function buildBedrockStreamToolCallEvents(\n toolCalls: ToolCall[],\n chunkSize: number,\n logger: Logger,\n): Array<{ eventType: string; payload: object }> {\n const events: Array<{ eventType: string; payload: object }> = [];\n\n events.push({\n eventType: \"messageStart\",\n payload: { role: \"assistant\" },\n });\n\n for (let tcIdx = 0; tcIdx < toolCalls.length; tcIdx++) {\n const tc = toolCalls[tcIdx];\n const toolUseId = tc.id || generateToolUseId();\n\n events.push({\n eventType: \"contentBlockStart\",\n payload: {\n contentBlockIndex: tcIdx,\n start: {\n toolUse: { toolUseId, name: tc.name },\n },\n },\n });\n\n let argsStr: string;\n try {\n const parsed = JSON.parse(tc.arguments || \"{}\");\n argsStr = JSON.stringify(parsed);\n } catch {\n logger.warn(\n `Malformed JSON in fixture tool call arguments for \"${tc.name}\": ${tc.arguments}`,\n );\n argsStr = \"{}\";\n }\n\n for (let i = 0; i < argsStr.length; i += chunkSize) {\n const slice = argsStr.slice(i, i + chunkSize);\n events.push({\n eventType: \"contentBlockDelta\",\n payload: {\n contentBlockIndex: tcIdx,\n delta: { type: \"input_json_delta\", inputJSON: slice },\n },\n });\n }\n\n events.push({\n eventType: \"contentBlockStop\",\n payload: { contentBlockIndex: tcIdx },\n });\n }\n\n events.push({\n eventType: \"messageStop\",\n payload: { stopReason: \"tool_use\" },\n });\n\n return events;\n}\n\n// ─── Streaming request handler ──────────────────────────────────────────────\n\nexport async function handleBedrockStream(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n raw: string,\n modelId: string,\n fixtures: Fixture[],\n journal: Journal,\n defaults: HandlerDefaults,\n setCorsHeaders: (res: http.ServerResponse) => void,\n): Promise<void> {\n const { logger } = defaults;\n setCorsHeaders(res);\n\n const urlPath = req.url ?? `/model/${modelId}/invoke-with-response-stream`;\n\n let bedrockReq: BedrockRequest;\n try {\n bedrockReq = JSON.parse(raw) as BedrockRequest;\n } catch {\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({\n error: {\n message: \"Malformed JSON\",\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n if (!bedrockReq.messages || !Array.isArray(bedrockReq.messages)) {\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({\n error: {\n message: \"Invalid request: messages array is required\",\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n const completionReq = bedrockToCompletionRequest(bedrockReq, modelId);\n completionReq._endpointType = \"chat\";\n\n const testId = getTestId(req);\n const fixture = matchFixture(\n fixtures,\n completionReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n );\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n }\n\n if (\n applyChaos(\n res,\n fixture,\n defaults.chaos,\n req.headers,\n journal,\n {\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n },\n defaults.registry,\n defaults.logger,\n )\n )\n return;\n\n if (!fixture) {\n if (defaults.record) {\n const proxied = await proxyAndRecord(\n req,\n res,\n completionReq,\n \"bedrock\",\n urlPath,\n fixtures,\n defaults,\n raw,\n );\n if (proxied) {\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: res.statusCode ?? 200, fixture: null },\n });\n return;\n }\n }\n const strictStatus = defaults.strict ? 503 : 404;\n const strictMessage = defaults.strict\n ? \"Strict mode: no fixture matched\"\n : \"No fixture matched\";\n if (defaults.strict) {\n logger.error(`STRICT: No fixture matched for ${req.method ?? \"POST\"} ${urlPath}`);\n }\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: strictStatus, fixture: null },\n });\n writeErrorResponse(\n res,\n strictStatus,\n JSON.stringify({\n error: {\n message: strictMessage,\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n const response = fixture.response;\n const latency = fixture.latency ?? defaults.latency;\n const chunkSize = Math.max(1, fixture.chunkSize ?? defaults.chunkSize);\n\n // Error response\n if (isErrorResponse(response)) {\n const status = response.status ?? 500;\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status, fixture },\n });\n writeErrorResponse(res, status, JSON.stringify(response));\n return;\n }\n\n // Text response — stream as Event Stream\n if (isTextResponse(response)) {\n const journalEntry = journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 200, fixture },\n });\n const events = buildBedrockStreamTextEvents(response.content, chunkSize, response.reasoning);\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeEventStream(res, events, {\n latency,\n streamingProfile: fixture.streamingProfile,\n signal: interruption?.signal,\n onChunkSent: interruption?.tick,\n });\n if (!completed) {\n if (!res.writableEnded) res.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n }\n interruption?.cleanup();\n return;\n }\n\n // Tool call response — stream as Event Stream\n if (isToolCallResponse(response)) {\n const journalEntry = journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 200, fixture },\n });\n const events = buildBedrockStreamToolCallEvents(response.toolCalls, chunkSize, logger);\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeEventStream(res, events, {\n latency,\n streamingProfile: fixture.streamingProfile,\n signal: interruption?.signal,\n onChunkSent: interruption?.tick,\n });\n if (!completed) {\n if (!res.writableEnded) res.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n }\n interruption?.cleanup();\n return;\n }\n\n // Unknown response type\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 500, fixture },\n });\n writeErrorResponse(\n res,\n 500,\n JSON.stringify({\n error: {\n message: \"Fixture response did not match any known type\",\n type: \"server_error\",\n },\n }),\n );\n}\n"],"mappings":";;;;;;;;;AAmFA,SAAS,mBAAmB,SAAiD;AAC3E,KAAI,OAAO,YAAY,SAAU,QAAO;AACxC,QAAO,QACJ,QAAQ,MAAM,EAAE,SAAS,OAAO,CAChC,KAAK,MAAM,EAAE,QAAQ,GAAG,CACxB,KAAK,GAAG;;AAGb,SAAgB,2BACd,KACA,SACuB;CACvB,MAAM,WAA0B,EAAE;AAGlC,KAAI,IAAI,QAAQ;EACd,MAAM,aACJ,OAAO,IAAI,WAAW,WAClB,IAAI,SACJ,IAAI,OACD,QAAQ,MAAM,EAAE,SAAS,OAAO,CAChC,KAAK,MAAM,EAAE,QAAQ,GAAG,CACxB,KAAK,GAAG;AACjB,MAAI,WACF,UAAS,KAAK;GAAE,MAAM;GAAU,SAAS;GAAY,CAAC;;AAI1D,MAAK,MAAM,OAAO,IAAI,SACpB,KAAI,IAAI,SAAS,QAAQ;AAEvB,MAAI,OAAO,IAAI,YAAY,YAAY,MAAM,QAAQ,IAAI,QAAQ,EAAE;GACjE,MAAM,cAAc,IAAI,QAAQ,QAAQ,MAAM,EAAE,SAAS,cAAc;GACvE,MAAM,aAAa,IAAI,QAAQ,QAAQ,MAAM,EAAE,SAAS,OAAO;AAE/D,OAAI,YAAY,SAAS,GAAG;AAC1B,SAAK,MAAM,MAAM,aAAa;KAC5B,MAAM,gBACJ,OAAO,GAAG,YAAY,WAClB,GAAG,UACH,MAAM,QAAQ,GAAG,QAAQ,GACvB,GAAG,QACA,QAAQ,MAAM,EAAE,SAAS,OAAO,CAChC,KAAK,MAAM,EAAE,QAAQ,GAAG,CACxB,KAAK,GAAG,GACX;AACR,cAAS,KAAK;MACZ,MAAM;MACN,SAAS;MACT,cAAc,GAAG;MAClB,CAAC;;AAEJ,QAAI,WAAW,SAAS,EACtB,UAAS,KAAK;KACZ,MAAM;KACN,SAAS,WAAW,KAAK,MAAM,EAAE,QAAQ,GAAG,CAAC,KAAK,GAAG;KACtD,CAAC;AAEJ;;;AAGJ,WAAS,KAAK;GACZ,MAAM;GACN,SAAS,mBAAmB,IAAI,QAAQ;GACzC,CAAC;YACO,IAAI,SAAS,YACtB,KAAI,OAAO,IAAI,YAAY,SACzB,UAAS,KAAK;EAAE,MAAM;EAAa,SAAS,IAAI;EAAS,CAAC;UACjD,MAAM,QAAQ,IAAI,QAAQ,EAAE;EACrC,MAAM,gBAAgB,IAAI,QAAQ,QAAQ,MAAM,EAAE,SAAS,WAAW;EACtE,MAAM,cAAc,mBAAmB,IAAI,QAAQ;AAEnD,MAAI,cAAc,SAAS,EACzB,UAAS,KAAK;GACZ,MAAM;GACN,SAAS,eAAe;GACxB,YAAY,cAAc,KAAK,OAAO;IACpC,IAAI,EAAE,MAAM,mBAAmB;IAC/B,MAAM;IACN,UAAU;KACR,MAAM,EAAE,QAAQ;KAChB,WAAW,OAAO,EAAE,UAAU,WAAW,EAAE,QAAQ,KAAK,UAAU,EAAE,SAAS,EAAE,CAAC;KACjF;IACF,EAAE;GACJ,CAAC;MAEF,UAAS,KAAK;GAAE,MAAM;GAAa,SAAS,eAAe;GAAM,CAAC;OAGpE,UAAS,KAAK;EAAE,MAAM;EAAa,SAAS;EAAM,CAAC;CAMzD,IAAI;AACJ,KAAI,IAAI,SAAS,IAAI,MAAM,SAAS,EAClC,SAAQ,IAAI,MAAM,KAAK,OAAO;EAC5B,MAAM;EACN,UAAU;GACR,MAAM,EAAE;GACR,aAAa,EAAE;GACf,YAAY,EAAE;GACf;EACF,EAAE;AAGL,QAAO;EACL,OAAO;EACP;EACA,QAAQ;EACR,aAAa,IAAI;EACjB;EACD;;AAKH,SAAS,yBAAyB,SAAiB,OAAe,WAA4B;CAC5F,MAAM,gBAA0B,EAAE;AAClC,KAAI,UACF,eAAc,KAAK;EAAE,MAAM;EAAY,UAAU;EAAW,CAAC;AAE/D,eAAc,KAAK;EAAE,MAAM;EAAQ,MAAM;EAAS,CAAC;AAEnD,QAAO;EACL,IAAI,mBAAmB;EACvB,MAAM;EACN,MAAM;EACN,SAAS;EACT;EACA,aAAa;EACb,eAAe;EACf,OAAO;GAAE,cAAc;GAAG,eAAe;GAAG;EAC7C;;AAGH,SAAS,6BACP,WACA,OACA,QACQ;AACR,QAAO;EACL,IAAI,mBAAmB;EACvB,MAAM;EACN,MAAM;EACN,SAAS,UAAU,KAAK,OAAO;GAC7B,IAAI;AACJ,OAAI;AACF,cAAU,KAAK,MAAM,GAAG,aAAa,KAAK;WACpC;AACN,WAAO,KACL,sDAAsD,GAAG,KAAK,KAAK,GAAG,YACvE;AACD,cAAU,EAAE;;AAEd,UAAO;IACL,MAAM;IACN,IAAI,GAAG,MAAM,mBAAmB;IAChC,MAAM,GAAG;IACT,OAAO;IACR;IACD;EACF;EACA,aAAa;EACb,eAAe;EACf,OAAO;GAAE,cAAc;GAAG,eAAe;GAAG;EAC7C;;AAKH,eAAsB,cACpB,KACA,KACA,KACA,SACA,UACA,SACA,UACA,gBACe;CACf,MAAM,EAAE,WAAW;AACnB,gBAAe,IAAI;CAEnB,MAAM,UAAU,IAAI,OAAO,UAAU,QAAQ;CAE7C,IAAI;AACJ,KAAI;AACF,eAAa,KAAK,MAAM,IAAI;SACtB;AACN,UAAQ,IAAI;GACV,QAAQ,IAAI,UAAU;GACtB,MAAM;GACN,SAAS,eAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IAAE,QAAQ;IAAK,SAAS;IAAM;GACzC,CAAC;AACF,qBACE,KACA,KACA,KAAK,UAAU,EACb,OAAO;GACL,SAAS;GACT,MAAM;GACP,EACF,CAAC,CACH;AACD;;AAGF,KAAI,CAAC,WAAW,YAAY,CAAC,MAAM,QAAQ,WAAW,SAAS,EAAE;AAC/D,UAAQ,IAAI;GACV,QAAQ,IAAI,UAAU;GACtB,MAAM;GACN,SAAS,eAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IAAE,QAAQ;IAAK,SAAS;IAAM;GACzC,CAAC;AACF,qBACE,KACA,KACA,KAAK,UAAU,EACb,OAAO;GACL,SAAS;GACT,MAAM;GACP,EACF,CAAC,CACH;AACD;;CAIF,MAAM,gBAAgB,2BAA2B,YAAY,QAAQ;AACrE,eAAc,gBAAgB;CAE9B,MAAM,SAAS,UAAU,IAAI;CAC7B,MAAM,UAAU,aACd,UACA,eACA,QAAQ,6BAA6B,OAAO,EAC5C,SAAS,iBACV;AAED,KAAI,QACF,SAAQ,2BAA2B,SAAS,UAAU,OAAO;AAG/D,KACE,WACE,KACA,SACA,SAAS,OACT,IAAI,SACJ,SACA;EACE,QAAQ,IAAI,UAAU;EACtB,MAAM;EACN,SAAS,eAAe,IAAI,QAAQ;EACpC,MAAM;EACP,EACD,SAAS,UACT,SAAS,OACV,CAED;AAEF,KAAI,CAAC,SAAS;AACZ,MAAI,SAAS,QAWX;OAVgB,MAAM,eACpB,KACA,KACA,eACA,WACA,SACA,UACA,UACA,IACD,EACY;AACX,YAAQ,IAAI;KACV,QAAQ,IAAI,UAAU;KACtB,MAAM;KACN,SAAS,eAAe,IAAI,QAAQ;KACpC,MAAM;KACN,UAAU;MAAE,QAAQ,IAAI,cAAc;MAAK,SAAS;MAAM;KAC3D,CAAC;AACF;;;EAGJ,MAAM,eAAe,SAAS,SAAS,MAAM;EAC7C,MAAM,gBAAgB,SAAS,SAC3B,oCACA;AACJ,MAAI,SAAS,OACX,QAAO,MAAM,kCAAkC,IAAI,UAAU,OAAO,GAAG,UAAU;AAEnF,UAAQ,IAAI;GACV,QAAQ,IAAI,UAAU;GACtB,MAAM;GACN,SAAS,eAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IAAE,QAAQ;IAAc,SAAS;IAAM;GAClD,CAAC;AACF,qBACE,KACA,cACA,KAAK,UAAU,EACb,OAAO;GACL,SAAS;GACT,MAAM;GACP,EACF,CAAC,CACH;AACD;;CAGF,MAAM,WAAW,QAAQ;AAGzB,KAAI,gBAAgB,SAAS,EAAE;EAC7B,MAAM,SAAS,SAAS,UAAU;AAClC,UAAQ,IAAI;GACV,QAAQ,IAAI,UAAU;GACtB,MAAM;GACN,SAAS,eAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IAAE;IAAQ;IAAS;GAC9B,CAAC;EAEF,MAAM,iBAAiB;GACrB,MAAM;GACN,OAAO;IACL,MAAM,SAAS,MAAM,QAAQ;IAC7B,SAAS,SAAS,MAAM;IACzB;GACF;AACD,qBAAmB,KAAK,QAAQ,KAAK,UAAU,eAAe,CAAC;AAC/D;;AAIF,KAAI,eAAe,SAAS,EAAE;AAC5B,UAAQ,IAAI;GACV,QAAQ,IAAI,UAAU;GACtB,MAAM;GACN,SAAS,eAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IAAE,QAAQ;IAAK;IAAS;GACnC,CAAC;EACF,MAAM,OAAO,yBACX,SAAS,SACT,cAAc,OACd,SAAS,UACV;AACD,MAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,MAAI,IAAI,KAAK,UAAU,KAAK,CAAC;AAC7B;;AAIF,KAAI,mBAAmB,SAAS,EAAE;AAChC,UAAQ,IAAI;GACV,QAAQ,IAAI,UAAU;GACtB,MAAM;GACN,SAAS,eAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IAAE,QAAQ;IAAK;IAAS;GACnC,CAAC;EACF,MAAM,OAAO,6BAA6B,SAAS,WAAW,cAAc,OAAO,OAAO;AAC1F,MAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,MAAI,IAAI,KAAK,UAAU,KAAK,CAAC;AAC7B;;AAIF,SAAQ,IAAI;EACV,QAAQ,IAAI,UAAU;EACtB,MAAM;EACN,SAAS,eAAe,IAAI,QAAQ;EACpC,MAAM;EACN,UAAU;GAAE,QAAQ;GAAK;GAAS;EACnC,CAAC;AACF,oBACE,KACA,KACA,KAAK,UAAU,EACb,OAAO;EACL,SAAS;EACT,MAAM;EACP,EACF,CAAC,CACH;;AAKH,SAAgB,6BACd,SACA,WACA,WAC+C;CAC/C,MAAM,SAAwD,EAAE;AAEhE,QAAO,KAAK;EACV,WAAW;EACX,SAAS,EAAE,MAAM,aAAa;EAC/B,CAAC;AAGF,KAAI,WAAW;EACb,MAAM,aAAa;AACnB,SAAO,KAAK;GACV,WAAW;GACX,SAAS;IAAE,mBAAmB;IAAY,OAAO,EAAE,MAAM,YAAY;IAAE;GACxE,CAAC;AAEF,OAAK,IAAI,IAAI,GAAG,IAAI,UAAU,QAAQ,KAAK,WAAW;GACpD,MAAM,QAAQ,UAAU,MAAM,GAAG,IAAI,UAAU;AAC/C,UAAO,KAAK;IACV,WAAW;IACX,SAAS;KACP,mBAAmB;KACnB,OAAO;MAAE,MAAM;MAAkB,UAAU;MAAO;KACnD;IACF,CAAC;;AAGJ,SAAO,KAAK;GACV,WAAW;GACX,SAAS,EAAE,mBAAmB,YAAY;GAC3C,CAAC;;CAIJ,MAAM,iBAAiB,YAAY,IAAI;AAEvC,QAAO,KAAK;EACV,WAAW;EACX,SAAS;GAAE,mBAAmB;GAAgB,OAAO,EAAE;GAAE;EAC1D,CAAC;AAEF,MAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK,WAAW;EAClD,MAAM,QAAQ,QAAQ,MAAM,GAAG,IAAI,UAAU;AAC7C,SAAO,KAAK;GACV,WAAW;GACX,SAAS;IACP,mBAAmB;IACnB,OAAO;KAAE,MAAM;KAAc,MAAM;KAAO;IAC3C;GACF,CAAC;;AAGJ,QAAO,KAAK;EACV,WAAW;EACX,SAAS,EAAE,mBAAmB,gBAAgB;EAC/C,CAAC;AAEF,QAAO,KAAK;EACV,WAAW;EACX,SAAS,EAAE,YAAY,YAAY;EACpC,CAAC;AAEF,QAAO;;AAGT,SAAgB,iCACd,WACA,WACA,QAC+C;CAC/C,MAAM,SAAwD,EAAE;AAEhE,QAAO,KAAK;EACV,WAAW;EACX,SAAS,EAAE,MAAM,aAAa;EAC/B,CAAC;AAEF,MAAK,IAAI,QAAQ,GAAG,QAAQ,UAAU,QAAQ,SAAS;EACrD,MAAM,KAAK,UAAU;EACrB,MAAM,YAAY,GAAG,MAAM,mBAAmB;AAE9C,SAAO,KAAK;GACV,WAAW;GACX,SAAS;IACP,mBAAmB;IACnB,OAAO,EACL,SAAS;KAAE;KAAW,MAAM,GAAG;KAAM,EACtC;IACF;GACF,CAAC;EAEF,IAAI;AACJ,MAAI;GACF,MAAM,SAAS,KAAK,MAAM,GAAG,aAAa,KAAK;AAC/C,aAAU,KAAK,UAAU,OAAO;UAC1B;AACN,UAAO,KACL,sDAAsD,GAAG,KAAK,KAAK,GAAG,YACvE;AACD,aAAU;;AAGZ,OAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK,WAAW;GAClD,MAAM,QAAQ,QAAQ,MAAM,GAAG,IAAI,UAAU;AAC7C,UAAO,KAAK;IACV,WAAW;IACX,SAAS;KACP,mBAAmB;KACnB,OAAO;MAAE,MAAM;MAAoB,WAAW;MAAO;KACtD;IACF,CAAC;;AAGJ,SAAO,KAAK;GACV,WAAW;GACX,SAAS,EAAE,mBAAmB,OAAO;GACtC,CAAC;;AAGJ,QAAO,KAAK;EACV,WAAW;EACX,SAAS,EAAE,YAAY,YAAY;EACpC,CAAC;AAEF,QAAO;;AAKT,eAAsB,oBACpB,KACA,KACA,KACA,SACA,UACA,SACA,UACA,gBACe;CACf,MAAM,EAAE,WAAW;AACnB,gBAAe,IAAI;CAEnB,MAAM,UAAU,IAAI,OAAO,UAAU,QAAQ;CAE7C,IAAI;AACJ,KAAI;AACF,eAAa,KAAK,MAAM,IAAI;SACtB;AACN,UAAQ,IAAI;GACV,QAAQ,IAAI,UAAU;GACtB,MAAM;GACN,SAAS,eAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IAAE,QAAQ;IAAK,SAAS;IAAM;GACzC,CAAC;AACF,qBACE,KACA,KACA,KAAK,UAAU,EACb,OAAO;GACL,SAAS;GACT,MAAM;GACP,EACF,CAAC,CACH;AACD;;AAGF,KAAI,CAAC,WAAW,YAAY,CAAC,MAAM,QAAQ,WAAW,SAAS,EAAE;AAC/D,UAAQ,IAAI;GACV,QAAQ,IAAI,UAAU;GACtB,MAAM;GACN,SAAS,eAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IAAE,QAAQ;IAAK,SAAS;IAAM;GACzC,CAAC;AACF,qBACE,KACA,KACA,KAAK,UAAU,EACb,OAAO;GACL,SAAS;GACT,MAAM;GACP,EACF,CAAC,CACH;AACD;;CAGF,MAAM,gBAAgB,2BAA2B,YAAY,QAAQ;AACrE,eAAc,gBAAgB;CAE9B,MAAM,SAAS,UAAU,IAAI;CAC7B,MAAM,UAAU,aACd,UACA,eACA,QAAQ,6BAA6B,OAAO,EAC5C,SAAS,iBACV;AAED,KAAI,QACF,SAAQ,2BAA2B,SAAS,UAAU,OAAO;AAG/D,KACE,WACE,KACA,SACA,SAAS,OACT,IAAI,SACJ,SACA;EACE,QAAQ,IAAI,UAAU;EACtB,MAAM;EACN,SAAS,eAAe,IAAI,QAAQ;EACpC,MAAM;EACP,EACD,SAAS,UACT,SAAS,OACV,CAED;AAEF,KAAI,CAAC,SAAS;AACZ,MAAI,SAAS,QAWX;OAVgB,MAAM,eACpB,KACA,KACA,eACA,WACA,SACA,UACA,UACA,IACD,EACY;AACX,YAAQ,IAAI;KACV,QAAQ,IAAI,UAAU;KACtB,MAAM;KACN,SAAS,eAAe,IAAI,QAAQ;KACpC,MAAM;KACN,UAAU;MAAE,QAAQ,IAAI,cAAc;MAAK,SAAS;MAAM;KAC3D,CAAC;AACF;;;EAGJ,MAAM,eAAe,SAAS,SAAS,MAAM;EAC7C,MAAM,gBAAgB,SAAS,SAC3B,oCACA;AACJ,MAAI,SAAS,OACX,QAAO,MAAM,kCAAkC,IAAI,UAAU,OAAO,GAAG,UAAU;AAEnF,UAAQ,IAAI;GACV,QAAQ,IAAI,UAAU;GACtB,MAAM;GACN,SAAS,eAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IAAE,QAAQ;IAAc,SAAS;IAAM;GAClD,CAAC;AACF,qBACE,KACA,cACA,KAAK,UAAU,EACb,OAAO;GACL,SAAS;GACT,MAAM;GACP,EACF,CAAC,CACH;AACD;;CAGF,MAAM,WAAW,QAAQ;CACzB,MAAM,UAAU,QAAQ,WAAW,SAAS;CAC5C,MAAM,YAAY,KAAK,IAAI,GAAG,QAAQ,aAAa,SAAS,UAAU;AAGtE,KAAI,gBAAgB,SAAS,EAAE;EAC7B,MAAM,SAAS,SAAS,UAAU;AAClC,UAAQ,IAAI;GACV,QAAQ,IAAI,UAAU;GACtB,MAAM;GACN,SAAS,eAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IAAE;IAAQ;IAAS;GAC9B,CAAC;AACF,qBAAmB,KAAK,QAAQ,KAAK,UAAU,SAAS,CAAC;AACzD;;AAIF,KAAI,eAAe,SAAS,EAAE;EAC5B,MAAM,eAAe,QAAQ,IAAI;GAC/B,QAAQ,IAAI,UAAU;GACtB,MAAM;GACN,SAAS,eAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IAAE,QAAQ;IAAK;IAAS;GACnC,CAAC;EACF,MAAM,SAAS,6BAA6B,SAAS,SAAS,WAAW,SAAS,UAAU;EAC5F,MAAM,eAAe,yBAAyB,QAAQ;AAOtD,MAAI,CANc,MAAM,iBAAiB,KAAK,QAAQ;GACpD;GACA,kBAAkB,QAAQ;GAC1B,QAAQ,cAAc;GACtB,aAAa,cAAc;GAC5B,CAAC,EACc;AACd,OAAI,CAAC,IAAI,cAAe,KAAI,SAAS;AACrC,gBAAa,SAAS,cAAc;AACpC,gBAAa,SAAS,kBAAkB,cAAc,QAAQ;;AAEhE,gBAAc,SAAS;AACvB;;AAIF,KAAI,mBAAmB,SAAS,EAAE;EAChC,MAAM,eAAe,QAAQ,IAAI;GAC/B,QAAQ,IAAI,UAAU;GACtB,MAAM;GACN,SAAS,eAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IAAE,QAAQ;IAAK;IAAS;GACnC,CAAC;EACF,MAAM,SAAS,iCAAiC,SAAS,WAAW,WAAW,OAAO;EACtF,MAAM,eAAe,yBAAyB,QAAQ;AAOtD,MAAI,CANc,MAAM,iBAAiB,KAAK,QAAQ;GACpD;GACA,kBAAkB,QAAQ;GAC1B,QAAQ,cAAc;GACtB,aAAa,cAAc;GAC5B,CAAC,EACc;AACd,OAAI,CAAC,IAAI,cAAe,KAAI,SAAS;AACrC,gBAAa,SAAS,cAAc;AACpC,gBAAa,SAAS,kBAAkB,cAAc,QAAQ;;AAEhE,gBAAc,SAAS;AACvB;;AAIF,SAAQ,IAAI;EACV,QAAQ,IAAI,UAAU;EACtB,MAAM;EACN,SAAS,eAAe,IAAI,QAAQ;EACpC,MAAM;EACN,UAAU;GAAE,QAAQ;GAAK;GAAS;EACnC,CAAC;AACF,oBACE,KACA,KACA,KAAK,UAAU,EACb,OAAO;EACL,SAAS;EACT,MAAM;EACP,EACF,CAAC,CACH"}
|
|
1
|
+
{"version":3,"file":"bedrock.js","names":[],"sources":["../src/bedrock.ts"],"sourcesContent":["/**\n * AWS Bedrock Claude endpoint support — invoke and invoke-with-response-stream.\n *\n * Handles four Bedrock endpoint families (split across two modules):\n *\n * This file (bedrock.ts):\n * - POST /model/{modelId}/invoke — non-streaming invoke\n * - POST /model/{modelId}/invoke-with-response-stream — binary EventStream streaming\n *\n * bedrock-converse.ts:\n * - POST /model/{modelId}/converse — Converse API (non-streaming)\n * - POST /model/{modelId}/converse-stream — Converse API (EventStream streaming)\n *\n * Translates incoming Bedrock Claude format into the ChatCompletionRequest\n * format used by the fixture router, and converts fixture responses back into\n * the appropriate Bedrock response format (JSON for invoke, AWS Event Stream\n * binary encoding for streaming).\n */\n\nimport type * as http from \"node:http\";\nimport type {\n ChatCompletionRequest,\n ChatMessage,\n Fixture,\n HandlerDefaults,\n ToolCall,\n ToolDefinition,\n} from \"./types.js\";\nimport {\n generateMessageId,\n generateToolUseId,\n isTextResponse,\n isToolCallResponse,\n isErrorResponse,\n flattenHeaders,\n getTestId,\n} from \"./helpers.js\";\nimport { matchFixture } from \"./router.js\";\nimport { writeErrorResponse } from \"./sse-writer.js\";\nimport { writeEventStream } from \"./aws-event-stream.js\";\nimport { createInterruptionSignal } from \"./interruption.js\";\nimport type { Journal } from \"./journal.js\";\nimport type { Logger } from \"./logger.js\";\nimport { applyChaos } from \"./chaos.js\";\nimport { proxyAndRecord } from \"./recorder.js\";\n\n// ─── Bedrock Claude request types ────────────────────────────────────────────\n\ninterface BedrockContentBlock {\n type: \"text\" | \"tool_use\" | \"tool_result\" | \"image\" | \"document\";\n text?: string;\n id?: string;\n name?: string;\n input?: unknown;\n tool_use_id?: string;\n content?: string | BedrockContentBlock[];\n is_error?: boolean;\n}\n\ninterface BedrockMessage {\n role: \"user\" | \"assistant\";\n content: string | BedrockContentBlock[];\n}\n\ninterface BedrockToolDef {\n name: string;\n description?: string;\n input_schema?: object;\n}\n\ninterface BedrockRequest {\n anthropic_version?: string;\n messages: BedrockMessage[];\n system?: string | BedrockContentBlock[];\n tools?: BedrockToolDef[];\n tool_choice?: unknown;\n max_tokens: number;\n temperature?: number;\n [key: string]: unknown;\n}\n\n// ─── Input conversion: Bedrock → ChatCompletionRequest ──────────────────────\n\nfunction extractTextContent(content: string | BedrockContentBlock[]): string {\n if (typeof content === \"string\") return content;\n return content\n .filter((b) => b.type === \"text\")\n .map((b) => b.text ?? \"\")\n .join(\"\");\n}\n\nexport function bedrockToCompletionRequest(\n req: BedrockRequest,\n modelId: string,\n): ChatCompletionRequest {\n const messages: ChatMessage[] = [];\n\n // system field → system message\n if (req.system) {\n const systemText =\n typeof req.system === \"string\"\n ? req.system\n : req.system\n .filter((b) => b.type === \"text\")\n .map((b) => b.text ?? \"\")\n .join(\"\");\n if (systemText) {\n messages.push({ role: \"system\", content: systemText });\n }\n }\n\n for (const msg of req.messages) {\n if (msg.role === \"user\") {\n // Check for tool_result blocks\n if (typeof msg.content !== \"string\" && Array.isArray(msg.content)) {\n const toolResults = msg.content.filter((b) => b.type === \"tool_result\");\n const textBlocks = msg.content.filter((b) => b.type === \"text\");\n\n if (toolResults.length > 0) {\n for (const tr of toolResults) {\n const resultContent =\n typeof tr.content === \"string\"\n ? tr.content\n : Array.isArray(tr.content)\n ? tr.content\n .filter((b) => b.type === \"text\")\n .map((b) => b.text ?? \"\")\n .join(\"\")\n : \"\";\n messages.push({\n role: \"tool\",\n content: resultContent,\n tool_call_id: tr.tool_use_id,\n });\n }\n if (textBlocks.length > 0) {\n messages.push({\n role: \"user\",\n content: textBlocks.map((b) => b.text ?? \"\").join(\"\"),\n });\n }\n continue;\n }\n }\n messages.push({\n role: \"user\",\n content: extractTextContent(msg.content),\n });\n } else if (msg.role === \"assistant\") {\n if (typeof msg.content === \"string\") {\n messages.push({ role: \"assistant\", content: msg.content });\n } else if (Array.isArray(msg.content)) {\n const toolUseBlocks = msg.content.filter((b) => b.type === \"tool_use\");\n const textContent = extractTextContent(msg.content);\n\n if (toolUseBlocks.length > 0) {\n messages.push({\n role: \"assistant\",\n content: textContent || null,\n tool_calls: toolUseBlocks.map((b) => ({\n id: b.id ?? generateToolUseId(),\n type: \"function\" as const,\n function: {\n name: b.name ?? \"\",\n arguments: typeof b.input === \"string\" ? b.input : JSON.stringify(b.input ?? {}),\n },\n })),\n });\n } else {\n messages.push({ role: \"assistant\", content: textContent || null });\n }\n } else {\n messages.push({ role: \"assistant\", content: null });\n }\n }\n }\n\n // Convert tools\n let tools: ToolDefinition[] | undefined;\n if (req.tools && req.tools.length > 0) {\n tools = req.tools.map((t) => ({\n type: \"function\" as const,\n function: {\n name: t.name,\n description: t.description,\n parameters: t.input_schema,\n },\n }));\n }\n\n return {\n model: modelId,\n messages,\n stream: false,\n temperature: req.temperature,\n tools,\n };\n}\n\n// ─── Response builders ──────────────────────────────────────────────────────\n\nfunction buildBedrockTextResponse(content: string, model: string, reasoning?: string): object {\n const contentBlocks: object[] = [];\n if (reasoning) {\n contentBlocks.push({ type: \"thinking\", thinking: reasoning });\n }\n contentBlocks.push({ type: \"text\", text: content });\n\n return {\n id: generateMessageId(),\n type: \"message\",\n role: \"assistant\",\n content: contentBlocks,\n model,\n stop_reason: \"end_turn\",\n stop_sequence: null,\n usage: { input_tokens: 0, output_tokens: 0 },\n };\n}\n\nfunction buildBedrockToolCallResponse(\n toolCalls: ToolCall[],\n model: string,\n logger: Logger,\n): object {\n return {\n id: generateMessageId(),\n type: \"message\",\n role: \"assistant\",\n content: toolCalls.map((tc) => {\n let argsObj: unknown;\n try {\n argsObj = JSON.parse(tc.arguments || \"{}\");\n } catch {\n logger.warn(\n `Malformed JSON in fixture tool call arguments for \"${tc.name}\": ${tc.arguments}`,\n );\n argsObj = {};\n }\n return {\n type: \"tool_use\",\n id: tc.id || generateToolUseId(),\n name: tc.name,\n input: argsObj,\n };\n }),\n model,\n stop_reason: \"tool_use\",\n stop_sequence: null,\n usage: { input_tokens: 0, output_tokens: 0 },\n };\n}\n\n// ─── Request handler ────────────────────────────────────────────────────────\n\nexport async function handleBedrock(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n raw: string,\n modelId: string,\n fixtures: Fixture[],\n journal: Journal,\n defaults: HandlerDefaults,\n setCorsHeaders: (res: http.ServerResponse) => void,\n): Promise<void> {\n const { logger } = defaults;\n setCorsHeaders(res);\n\n const urlPath = req.url ?? `/model/${modelId}/invoke`;\n\n let bedrockReq: BedrockRequest;\n try {\n bedrockReq = JSON.parse(raw) as BedrockRequest;\n } catch {\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({\n error: {\n message: \"Malformed JSON\",\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n if (!bedrockReq.messages || !Array.isArray(bedrockReq.messages)) {\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({\n error: {\n message: \"Invalid request: messages array is required\",\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n // Convert to ChatCompletionRequest for fixture matching\n const completionReq = bedrockToCompletionRequest(bedrockReq, modelId);\n completionReq._endpointType = \"chat\";\n\n const testId = getTestId(req);\n const fixture = matchFixture(\n fixtures,\n completionReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n );\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n }\n\n if (\n applyChaos(\n res,\n fixture,\n defaults.chaos,\n req.headers,\n journal,\n {\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n },\n fixture ? \"fixture\" : \"proxy\",\n defaults.registry,\n defaults.logger,\n )\n )\n return;\n\n if (!fixture) {\n if (defaults.record) {\n const outcome = await proxyAndRecord(\n req,\n res,\n completionReq,\n \"bedrock\",\n urlPath,\n fixtures,\n defaults,\n raw,\n );\n if (outcome !== \"not_configured\") {\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: res.statusCode ?? 200, fixture: null },\n });\n return;\n }\n }\n const strictStatus = defaults.strict ? 503 : 404;\n const strictMessage = defaults.strict\n ? \"Strict mode: no fixture matched\"\n : \"No fixture matched\";\n if (defaults.strict) {\n logger.error(`STRICT: No fixture matched for ${req.method ?? \"POST\"} ${urlPath}`);\n }\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: strictStatus, fixture: null },\n });\n writeErrorResponse(\n res,\n strictStatus,\n JSON.stringify({\n error: {\n message: strictMessage,\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n const response = fixture.response;\n\n // Error response\n if (isErrorResponse(response)) {\n const status = response.status ?? 500;\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status, fixture },\n });\n // Anthropic-style error format (Bedrock uses Claude): { type: \"error\", error: { type, message } }\n const anthropicError = {\n type: \"error\",\n error: {\n type: response.error.type ?? \"api_error\",\n message: response.error.message,\n },\n };\n writeErrorResponse(res, status, JSON.stringify(anthropicError));\n return;\n }\n\n // Text response\n if (isTextResponse(response)) {\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 200, fixture },\n });\n const body = buildBedrockTextResponse(\n response.content,\n completionReq.model,\n response.reasoning,\n );\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n return;\n }\n\n // Tool call response\n if (isToolCallResponse(response)) {\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 200, fixture },\n });\n const body = buildBedrockToolCallResponse(response.toolCalls, completionReq.model, logger);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n return;\n }\n\n // Unknown response type\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 500, fixture },\n });\n writeErrorResponse(\n res,\n 500,\n JSON.stringify({\n error: {\n message: \"Fixture response did not match any known type\",\n type: \"server_error\",\n },\n }),\n );\n}\n\n// ─── Streaming event builders ───────────────────────────────────────────────\n\nexport function buildBedrockStreamTextEvents(\n content: string,\n chunkSize: number,\n reasoning?: string,\n): Array<{ eventType: string; payload: object }> {\n const events: Array<{ eventType: string; payload: object }> = [];\n\n events.push({\n eventType: \"messageStart\",\n payload: { role: \"assistant\" },\n });\n\n // Thinking block (emitted before text when reasoning is present)\n if (reasoning) {\n const blockIndex = 0;\n events.push({\n eventType: \"contentBlockStart\",\n payload: { contentBlockIndex: blockIndex, start: { type: \"thinking\" } },\n });\n\n for (let i = 0; i < reasoning.length; i += chunkSize) {\n const slice = reasoning.slice(i, i + chunkSize);\n events.push({\n eventType: \"contentBlockDelta\",\n payload: {\n contentBlockIndex: blockIndex,\n delta: { type: \"thinking_delta\", thinking: slice },\n },\n });\n }\n\n events.push({\n eventType: \"contentBlockStop\",\n payload: { contentBlockIndex: blockIndex },\n });\n }\n\n // Text block\n const textBlockIndex = reasoning ? 1 : 0;\n\n events.push({\n eventType: \"contentBlockStart\",\n payload: { contentBlockIndex: textBlockIndex, start: {} },\n });\n\n for (let i = 0; i < content.length; i += chunkSize) {\n const slice = content.slice(i, i + chunkSize);\n events.push({\n eventType: \"contentBlockDelta\",\n payload: {\n contentBlockIndex: textBlockIndex,\n delta: { type: \"text_delta\", text: slice },\n },\n });\n }\n\n events.push({\n eventType: \"contentBlockStop\",\n payload: { contentBlockIndex: textBlockIndex },\n });\n\n events.push({\n eventType: \"messageStop\",\n payload: { stopReason: \"end_turn\" },\n });\n\n return events;\n}\n\nexport function buildBedrockStreamToolCallEvents(\n toolCalls: ToolCall[],\n chunkSize: number,\n logger: Logger,\n): Array<{ eventType: string; payload: object }> {\n const events: Array<{ eventType: string; payload: object }> = [];\n\n events.push({\n eventType: \"messageStart\",\n payload: { role: \"assistant\" },\n });\n\n for (let tcIdx = 0; tcIdx < toolCalls.length; tcIdx++) {\n const tc = toolCalls[tcIdx];\n const toolUseId = tc.id || generateToolUseId();\n\n events.push({\n eventType: \"contentBlockStart\",\n payload: {\n contentBlockIndex: tcIdx,\n start: {\n toolUse: { toolUseId, name: tc.name },\n },\n },\n });\n\n let argsStr: string;\n try {\n const parsed = JSON.parse(tc.arguments || \"{}\");\n argsStr = JSON.stringify(parsed);\n } catch {\n logger.warn(\n `Malformed JSON in fixture tool call arguments for \"${tc.name}\": ${tc.arguments}`,\n );\n argsStr = \"{}\";\n }\n\n for (let i = 0; i < argsStr.length; i += chunkSize) {\n const slice = argsStr.slice(i, i + chunkSize);\n events.push({\n eventType: \"contentBlockDelta\",\n payload: {\n contentBlockIndex: tcIdx,\n delta: { type: \"input_json_delta\", inputJSON: slice },\n },\n });\n }\n\n events.push({\n eventType: \"contentBlockStop\",\n payload: { contentBlockIndex: tcIdx },\n });\n }\n\n events.push({\n eventType: \"messageStop\",\n payload: { stopReason: \"tool_use\" },\n });\n\n return events;\n}\n\n// ─── Streaming request handler ──────────────────────────────────────────────\n\nexport async function handleBedrockStream(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n raw: string,\n modelId: string,\n fixtures: Fixture[],\n journal: Journal,\n defaults: HandlerDefaults,\n setCorsHeaders: (res: http.ServerResponse) => void,\n): Promise<void> {\n const { logger } = defaults;\n setCorsHeaders(res);\n\n const urlPath = req.url ?? `/model/${modelId}/invoke-with-response-stream`;\n\n let bedrockReq: BedrockRequest;\n try {\n bedrockReq = JSON.parse(raw) as BedrockRequest;\n } catch {\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({\n error: {\n message: \"Malformed JSON\",\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n if (!bedrockReq.messages || !Array.isArray(bedrockReq.messages)) {\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({\n error: {\n message: \"Invalid request: messages array is required\",\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n const completionReq = bedrockToCompletionRequest(bedrockReq, modelId);\n completionReq._endpointType = \"chat\";\n\n const testId = getTestId(req);\n const fixture = matchFixture(\n fixtures,\n completionReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n );\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n }\n\n if (\n applyChaos(\n res,\n fixture,\n defaults.chaos,\n req.headers,\n journal,\n {\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n },\n fixture ? \"fixture\" : \"proxy\",\n defaults.registry,\n defaults.logger,\n )\n )\n return;\n\n if (!fixture) {\n if (defaults.record) {\n const outcome = await proxyAndRecord(\n req,\n res,\n completionReq,\n \"bedrock\",\n urlPath,\n fixtures,\n defaults,\n raw,\n );\n if (outcome !== \"not_configured\") {\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: res.statusCode ?? 200, fixture: null },\n });\n return;\n }\n }\n const strictStatus = defaults.strict ? 503 : 404;\n const strictMessage = defaults.strict\n ? \"Strict mode: no fixture matched\"\n : \"No fixture matched\";\n if (defaults.strict) {\n logger.error(`STRICT: No fixture matched for ${req.method ?? \"POST\"} ${urlPath}`);\n }\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: strictStatus, fixture: null },\n });\n writeErrorResponse(\n res,\n strictStatus,\n JSON.stringify({\n error: {\n message: strictMessage,\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n const response = fixture.response;\n const latency = fixture.latency ?? defaults.latency;\n const chunkSize = Math.max(1, fixture.chunkSize ?? defaults.chunkSize);\n\n // Error response\n if (isErrorResponse(response)) {\n const status = response.status ?? 500;\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status, fixture },\n });\n writeErrorResponse(res, status, JSON.stringify(response));\n return;\n }\n\n // Text response — stream as Event Stream\n if (isTextResponse(response)) {\n const journalEntry = journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 200, fixture },\n });\n const events = buildBedrockStreamTextEvents(response.content, chunkSize, response.reasoning);\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeEventStream(res, events, {\n latency,\n streamingProfile: fixture.streamingProfile,\n signal: interruption?.signal,\n onChunkSent: interruption?.tick,\n });\n if (!completed) {\n if (!res.writableEnded) res.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n }\n interruption?.cleanup();\n return;\n }\n\n // Tool call response — stream as Event Stream\n if (isToolCallResponse(response)) {\n const journalEntry = journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 200, fixture },\n });\n const events = buildBedrockStreamToolCallEvents(response.toolCalls, chunkSize, logger);\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeEventStream(res, events, {\n latency,\n streamingProfile: fixture.streamingProfile,\n signal: interruption?.signal,\n onChunkSent: interruption?.tick,\n });\n if (!completed) {\n if (!res.writableEnded) res.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n }\n interruption?.cleanup();\n return;\n }\n\n // Unknown response type\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 500, fixture },\n });\n writeErrorResponse(\n res,\n 500,\n JSON.stringify({\n error: {\n message: \"Fixture response did not match any known type\",\n type: \"server_error\",\n },\n }),\n );\n}\n"],"mappings":"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"}
|
package/dist/chaos.cjs
CHANGED
|
@@ -60,11 +60,33 @@ function evaluateChaos(fixture, serverDefaults, rawHeaders, logger) {
|
|
|
60
60
|
/**
|
|
61
61
|
* Apply chaos to a request. Returns true if chaos was applied (caller should
|
|
62
62
|
* return early), false if the request should proceed normally.
|
|
63
|
+
*
|
|
64
|
+
* `source` is required so the invariant "this handler only applies chaos in
|
|
65
|
+
* the <X> phase" is enforced at the type level. A future handler that grows
|
|
66
|
+
* a proxy path MUST pass `"proxy"` explicitly; the default can't drift silently.
|
|
63
67
|
*/
|
|
64
|
-
function applyChaos(res, fixture, serverDefaults, rawHeaders, journal, context, registry, logger) {
|
|
68
|
+
function applyChaos(res, fixture, serverDefaults, rawHeaders, journal, context, source, registry, logger) {
|
|
65
69
|
const action = evaluateChaos(fixture, serverDefaults, rawHeaders, logger);
|
|
66
70
|
if (!action) return false;
|
|
67
|
-
|
|
71
|
+
applyChaosAction(action, res, fixture, journal, context, source, registry);
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Apply a specific (already-rolled) chaos action. Exposed so callers that roll
|
|
76
|
+
* the dice themselves can dispatch without re-rolling — important when the
|
|
77
|
+
* caller wants to branch on the action before committing (e.g. pre-flight vs.
|
|
78
|
+
* post-response phases).
|
|
79
|
+
*
|
|
80
|
+
* `source` is required (not optional) so callers can't silently omit it on
|
|
81
|
+
* one branch and journal an ambiguous entry. Pass `"fixture"` when a fixture
|
|
82
|
+
* matched (or would have) and `"proxy"` when the request was headed for the
|
|
83
|
+
* proxy path.
|
|
84
|
+
*/
|
|
85
|
+
function applyChaosAction(action, res, fixture, journal, context, source, registry) {
|
|
86
|
+
if (registry) registry.incrementCounter("aimock_chaos_triggered_total", {
|
|
87
|
+
action,
|
|
88
|
+
source
|
|
89
|
+
});
|
|
68
90
|
switch (action) {
|
|
69
91
|
case "drop":
|
|
70
92
|
journal.add({
|
|
@@ -72,7 +94,8 @@ function applyChaos(res, fixture, serverDefaults, rawHeaders, journal, context,
|
|
|
72
94
|
response: {
|
|
73
95
|
status: 500,
|
|
74
96
|
fixture,
|
|
75
|
-
chaosAction: "drop"
|
|
97
|
+
chaosAction: "drop",
|
|
98
|
+
source
|
|
76
99
|
}
|
|
77
100
|
});
|
|
78
101
|
require_sse_writer.writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
@@ -80,35 +103,38 @@ function applyChaos(res, fixture, serverDefaults, rawHeaders, journal, context,
|
|
|
80
103
|
type: "server_error",
|
|
81
104
|
code: "chaos_drop"
|
|
82
105
|
} }));
|
|
83
|
-
return
|
|
106
|
+
return;
|
|
84
107
|
case "malformed":
|
|
85
108
|
journal.add({
|
|
86
109
|
...context,
|
|
87
110
|
response: {
|
|
88
111
|
status: 200,
|
|
89
112
|
fixture,
|
|
90
|
-
chaosAction: "malformed"
|
|
113
|
+
chaosAction: "malformed",
|
|
114
|
+
source
|
|
91
115
|
}
|
|
92
116
|
});
|
|
93
117
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
94
118
|
res.end("{malformed json: <<<chaos>>>");
|
|
95
|
-
return
|
|
119
|
+
return;
|
|
96
120
|
case "disconnect":
|
|
97
121
|
journal.add({
|
|
98
122
|
...context,
|
|
99
123
|
response: {
|
|
100
124
|
status: 0,
|
|
101
125
|
fixture,
|
|
102
|
-
chaosAction: "disconnect"
|
|
126
|
+
chaosAction: "disconnect",
|
|
127
|
+
source
|
|
103
128
|
}
|
|
104
129
|
});
|
|
105
130
|
res.destroy();
|
|
106
|
-
return
|
|
107
|
-
default: return
|
|
131
|
+
return;
|
|
132
|
+
default: return;
|
|
108
133
|
}
|
|
109
134
|
}
|
|
110
135
|
|
|
111
136
|
//#endregion
|
|
112
137
|
exports.applyChaos = applyChaos;
|
|
138
|
+
exports.applyChaosAction = applyChaosAction;
|
|
113
139
|
exports.evaluateChaos = evaluateChaos;
|
|
114
140
|
//# sourceMappingURL=chaos.cjs.map
|
package/dist/chaos.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chaos.cjs","names":[],"sources":["../src/chaos.ts"],"sourcesContent":["/**\n * Chaos testing support for LLMock.\n *\n * Provides probabilistic failure injection — requests can be dropped (500),\n * returned with malformed JSON, or have the connection forcibly disconnected.\n *\n * Precedence: per-request headers > fixture-level config > server-level defaults.\n */\n\nimport type * as http from \"node:http\";\nimport type { ChaosAction, ChaosConfig, ChatCompletionRequest, Fixture } from \"./types.js\";\nimport { writeErrorResponse } from \"./sse-writer.js\";\nimport type { Journal } from \"./journal.js\";\nimport type { Logger } from \"./logger.js\";\nimport type { MetricsRegistry } from \"./metrics.js\";\n\n/**\n * Resolve chaos config from headers, fixture, and server defaults.\n * Header values override fixture values, which override server defaults.\n */\nfunction resolveChaosConfig(\n fixture: Fixture | null,\n serverDefaults?: ChaosConfig,\n rawHeaders?: http.IncomingHttpHeaders,\n logger?: Logger,\n): ChaosConfig {\n const base: ChaosConfig = { ...serverDefaults };\n\n // Fixture-level overrides server defaults\n if (fixture?.chaos) {\n if (fixture.chaos.dropRate !== undefined) base.dropRate = fixture.chaos.dropRate;\n if (fixture.chaos.malformedRate !== undefined) base.malformedRate = fixture.chaos.malformedRate;\n if (fixture.chaos.disconnectRate !== undefined)\n base.disconnectRate = fixture.chaos.disconnectRate;\n }\n\n // Header overrides everything\n if (rawHeaders) {\n const dropHeader = rawHeaders[\"x-aimock-chaos-drop\"];\n const malformedHeader = rawHeaders[\"x-aimock-chaos-malformed\"];\n const disconnectHeader = rawHeaders[\"x-aimock-chaos-disconnect\"];\n\n if (typeof dropHeader === \"string\") {\n const val = parseFloat(dropHeader);\n if (isNaN(val)) {\n logger?.warn(`[chaos] x-aimock-chaos-drop: invalid value \"${dropHeader}\", ignoring`);\n } else {\n if (val < 0 || val > 1) {\n logger?.warn(`[chaos] x-aimock-chaos-drop: value ${val} out of range [0,1], clamping`);\n }\n base.dropRate = Math.min(1, Math.max(0, val));\n }\n }\n if (typeof malformedHeader === \"string\") {\n const val = parseFloat(malformedHeader);\n if (isNaN(val)) {\n logger?.warn(\n `[chaos] x-aimock-chaos-malformed: invalid value \"${malformedHeader}\", ignoring`,\n );\n } else {\n if (val < 0 || val > 1) {\n logger?.warn(\n `[chaos] x-aimock-chaos-malformed: value ${val} out of range [0,1], clamping`,\n );\n }\n base.malformedRate = Math.min(1, Math.max(0, val));\n }\n }\n if (typeof disconnectHeader === \"string\") {\n const val = parseFloat(disconnectHeader);\n if (isNaN(val)) {\n logger?.warn(\n `[chaos] x-aimock-chaos-disconnect: invalid value \"${disconnectHeader}\", ignoring`,\n );\n } else {\n if (val < 0 || val > 1) {\n logger?.warn(\n `[chaos] x-aimock-chaos-disconnect: value ${val} out of range [0,1], clamping`,\n );\n }\n base.disconnectRate = Math.min(1, Math.max(0, val));\n }\n }\n }\n\n // Clamp all resolved rates to [0, 1] regardless of source.\n // Header values are already clamped above; this covers fixture-level and server defaults.\n if (base.dropRate !== undefined) base.dropRate = Math.min(1, Math.max(0, base.dropRate));\n if (base.malformedRate !== undefined)\n base.malformedRate = Math.min(1, Math.max(0, base.malformedRate));\n if (base.disconnectRate !== undefined)\n base.disconnectRate = Math.min(1, Math.max(0, base.disconnectRate));\n\n return base;\n}\n\n/**\n * Evaluate chaos config and return the triggered action, or null if none.\n * Checks in order: drop, malformed, disconnect — first hit wins.\n */\nexport function evaluateChaos(\n fixture: Fixture | null,\n serverDefaults?: ChaosConfig,\n rawHeaders?: http.IncomingHttpHeaders,\n logger?: Logger,\n): ChaosAction | null {\n const config = resolveChaosConfig(fixture, serverDefaults, rawHeaders, logger);\n\n if (config.dropRate !== undefined && config.dropRate > 0 && Math.random() < config.dropRate) {\n return \"drop\";\n }\n if (\n config.malformedRate !== undefined &&\n config.malformedRate > 0 &&\n Math.random() < config.malformedRate\n ) {\n return \"malformed\";\n }\n if (\n config.disconnectRate !== undefined &&\n config.disconnectRate > 0 &&\n Math.random() < config.disconnectRate\n ) {\n return \"disconnect\";\n }\n\n return null;\n}\n\ninterface ChaosJournalContext {\n method: string;\n path: string;\n headers: Record<string, string>;\n body: ChatCompletionRequest;\n}\n\n/**\n * Apply chaos to a request. Returns true if chaos was applied (caller should\n * return early), false if the request should proceed normally.\n */\nexport function applyChaos(\n res: http.ServerResponse,\n fixture: Fixture | null,\n serverDefaults: ChaosConfig | undefined,\n rawHeaders: http.IncomingHttpHeaders,\n journal: Journal,\n context: ChaosJournalContext,\n registry?: MetricsRegistry,\n logger?: Logger,\n): boolean {\n const action = evaluateChaos(fixture, serverDefaults, rawHeaders, logger);\n if (!action) return false;\n\n if (registry) {\n registry.incrementCounter(\"aimock_chaos_triggered_total\", { action });\n }\n\n switch (action) {\n case \"drop\": {\n journal.add({\n ...context,\n response: { status: 500, fixture, chaosAction: \"drop\" },\n });\n writeErrorResponse(\n res,\n 500,\n JSON.stringify({\n error: {\n message: \"Chaos: request dropped\",\n type: \"server_error\",\n code: \"chaos_drop\",\n },\n }),\n );\n return true;\n }\n case \"malformed\": {\n journal.add({\n ...context,\n response: { status: 200, fixture, chaosAction: \"malformed\" },\n });\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(\"{malformed json: <<<chaos>>>\");\n return true;\n }\n case \"disconnect\": {\n journal.add({\n ...context,\n response: { status: 0, fixture, chaosAction: \"disconnect\" },\n });\n res.destroy();\n return true;\n }\n default: {\n const _exhaustive: never = action;\n void _exhaustive;\n return false;\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"chaos.cjs","names":[],"sources":["../src/chaos.ts"],"sourcesContent":["/**\n * Chaos testing support for LLMock.\n *\n * Provides probabilistic failure injection — requests can be dropped (500),\n * returned with malformed JSON, or have the connection forcibly disconnected.\n *\n * Precedence: per-request headers > fixture-level config > server-level defaults.\n */\n\nimport type * as http from \"node:http\";\nimport type { ChaosAction, ChaosConfig, ChatCompletionRequest, Fixture } from \"./types.js\";\nimport { writeErrorResponse } from \"./sse-writer.js\";\nimport type { Journal } from \"./journal.js\";\nimport type { Logger } from \"./logger.js\";\nimport type { MetricsRegistry } from \"./metrics.js\";\n\n/**\n * Resolve chaos config from headers, fixture, and server defaults.\n * Header values override fixture values, which override server defaults.\n */\nfunction resolveChaosConfig(\n fixture: Fixture | null,\n serverDefaults?: ChaosConfig,\n rawHeaders?: http.IncomingHttpHeaders,\n logger?: Logger,\n): ChaosConfig {\n const base: ChaosConfig = { ...serverDefaults };\n\n // Fixture-level overrides server defaults\n if (fixture?.chaos) {\n if (fixture.chaos.dropRate !== undefined) base.dropRate = fixture.chaos.dropRate;\n if (fixture.chaos.malformedRate !== undefined) base.malformedRate = fixture.chaos.malformedRate;\n if (fixture.chaos.disconnectRate !== undefined)\n base.disconnectRate = fixture.chaos.disconnectRate;\n }\n\n // Header overrides everything\n if (rawHeaders) {\n const dropHeader = rawHeaders[\"x-aimock-chaos-drop\"];\n const malformedHeader = rawHeaders[\"x-aimock-chaos-malformed\"];\n const disconnectHeader = rawHeaders[\"x-aimock-chaos-disconnect\"];\n\n if (typeof dropHeader === \"string\") {\n const val = parseFloat(dropHeader);\n if (isNaN(val)) {\n logger?.warn(`[chaos] x-aimock-chaos-drop: invalid value \"${dropHeader}\", ignoring`);\n } else {\n if (val < 0 || val > 1) {\n logger?.warn(`[chaos] x-aimock-chaos-drop: value ${val} out of range [0,1], clamping`);\n }\n base.dropRate = Math.min(1, Math.max(0, val));\n }\n }\n if (typeof malformedHeader === \"string\") {\n const val = parseFloat(malformedHeader);\n if (isNaN(val)) {\n logger?.warn(\n `[chaos] x-aimock-chaos-malformed: invalid value \"${malformedHeader}\", ignoring`,\n );\n } else {\n if (val < 0 || val > 1) {\n logger?.warn(\n `[chaos] x-aimock-chaos-malformed: value ${val} out of range [0,1], clamping`,\n );\n }\n base.malformedRate = Math.min(1, Math.max(0, val));\n }\n }\n if (typeof disconnectHeader === \"string\") {\n const val = parseFloat(disconnectHeader);\n if (isNaN(val)) {\n logger?.warn(\n `[chaos] x-aimock-chaos-disconnect: invalid value \"${disconnectHeader}\", ignoring`,\n );\n } else {\n if (val < 0 || val > 1) {\n logger?.warn(\n `[chaos] x-aimock-chaos-disconnect: value ${val} out of range [0,1], clamping`,\n );\n }\n base.disconnectRate = Math.min(1, Math.max(0, val));\n }\n }\n }\n\n // Clamp all resolved rates to [0, 1] regardless of source.\n // Header values are already clamped above; this covers fixture-level and server defaults.\n if (base.dropRate !== undefined) base.dropRate = Math.min(1, Math.max(0, base.dropRate));\n if (base.malformedRate !== undefined)\n base.malformedRate = Math.min(1, Math.max(0, base.malformedRate));\n if (base.disconnectRate !== undefined)\n base.disconnectRate = Math.min(1, Math.max(0, base.disconnectRate));\n\n return base;\n}\n\n/**\n * Evaluate chaos config and return the triggered action, or null if none.\n * Checks in order: drop, malformed, disconnect — first hit wins.\n */\nexport function evaluateChaos(\n fixture: Fixture | null,\n serverDefaults?: ChaosConfig,\n rawHeaders?: http.IncomingHttpHeaders,\n logger?: Logger,\n): ChaosAction | null {\n const config = resolveChaosConfig(fixture, serverDefaults, rawHeaders, logger);\n\n if (config.dropRate !== undefined && config.dropRate > 0 && Math.random() < config.dropRate) {\n return \"drop\";\n }\n if (\n config.malformedRate !== undefined &&\n config.malformedRate > 0 &&\n Math.random() < config.malformedRate\n ) {\n return \"malformed\";\n }\n if (\n config.disconnectRate !== undefined &&\n config.disconnectRate > 0 &&\n Math.random() < config.disconnectRate\n ) {\n return \"disconnect\";\n }\n\n return null;\n}\n\ninterface ChaosJournalContext {\n method: string;\n path: string;\n headers: Record<string, string>;\n body: ChatCompletionRequest | null;\n}\n\n/**\n * Apply chaos to a request. Returns true if chaos was applied (caller should\n * return early), false if the request should proceed normally.\n *\n * `source` is required so the invariant \"this handler only applies chaos in\n * the <X> phase\" is enforced at the type level. A future handler that grows\n * a proxy path MUST pass `\"proxy\"` explicitly; the default can't drift silently.\n */\nexport function applyChaos(\n res: http.ServerResponse,\n fixture: Fixture | null,\n serverDefaults: ChaosConfig | undefined,\n rawHeaders: http.IncomingHttpHeaders,\n journal: Journal,\n context: ChaosJournalContext,\n source: \"fixture\" | \"proxy\",\n registry?: MetricsRegistry,\n logger?: Logger,\n): boolean {\n const action = evaluateChaos(fixture, serverDefaults, rawHeaders, logger);\n if (!action) return false;\n applyChaosAction(action, res, fixture, journal, context, source, registry);\n return true;\n}\n\n/**\n * Apply a specific (already-rolled) chaos action. Exposed so callers that roll\n * the dice themselves can dispatch without re-rolling — important when the\n * caller wants to branch on the action before committing (e.g. pre-flight vs.\n * post-response phases).\n *\n * `source` is required (not optional) so callers can't silently omit it on\n * one branch and journal an ambiguous entry. Pass `\"fixture\"` when a fixture\n * matched (or would have) and `\"proxy\"` when the request was headed for the\n * proxy path.\n */\nexport function applyChaosAction(\n action: ChaosAction,\n res: http.ServerResponse,\n fixture: Fixture | null,\n journal: Journal,\n context: ChaosJournalContext,\n source: \"fixture\" | \"proxy\",\n registry?: MetricsRegistry,\n): void {\n if (registry) {\n registry.incrementCounter(\"aimock_chaos_triggered_total\", { action, source });\n }\n\n switch (action) {\n case \"drop\": {\n journal.add({\n ...context,\n response: { status: 500, fixture, chaosAction: \"drop\", source },\n });\n writeErrorResponse(\n res,\n 500,\n JSON.stringify({\n error: {\n message: \"Chaos: request dropped\",\n type: \"server_error\",\n code: \"chaos_drop\",\n },\n }),\n );\n return;\n }\n case \"malformed\": {\n journal.add({\n ...context,\n response: { status: 200, fixture, chaosAction: \"malformed\", source },\n });\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(\"{malformed json: <<<chaos>>>\");\n return;\n }\n case \"disconnect\": {\n journal.add({\n ...context,\n response: { status: 0, fixture, chaosAction: \"disconnect\", source },\n });\n res.destroy();\n return;\n }\n default: {\n const _exhaustive: never = action;\n void _exhaustive;\n return;\n }\n }\n}\n"],"mappings":"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"}
|
package/dist/chaos.d.cts
CHANGED
|
@@ -15,13 +15,28 @@ interface ChaosJournalContext {
|
|
|
15
15
|
method: string;
|
|
16
16
|
path: string;
|
|
17
17
|
headers: Record<string, string>;
|
|
18
|
-
body: ChatCompletionRequest;
|
|
18
|
+
body: ChatCompletionRequest | null;
|
|
19
19
|
}
|
|
20
20
|
/**
|
|
21
21
|
* Apply chaos to a request. Returns true if chaos was applied (caller should
|
|
22
22
|
* return early), false if the request should proceed normally.
|
|
23
|
+
*
|
|
24
|
+
* `source` is required so the invariant "this handler only applies chaos in
|
|
25
|
+
* the <X> phase" is enforced at the type level. A future handler that grows
|
|
26
|
+
* a proxy path MUST pass `"proxy"` explicitly; the default can't drift silently.
|
|
27
|
+
*/
|
|
28
|
+
declare function applyChaos(res: http.ServerResponse, fixture: Fixture | null, serverDefaults: ChaosConfig | undefined, rawHeaders: http.IncomingHttpHeaders, journal: Journal, context: ChaosJournalContext, source: "fixture" | "proxy", registry?: MetricsRegistry, logger?: Logger): boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Apply a specific (already-rolled) chaos action. Exposed so callers that roll
|
|
31
|
+
* the dice themselves can dispatch without re-rolling — important when the
|
|
32
|
+
* caller wants to branch on the action before committing (e.g. pre-flight vs.
|
|
33
|
+
* post-response phases).
|
|
34
|
+
*
|
|
35
|
+
* `source` is required (not optional) so callers can't silently omit it on
|
|
36
|
+
* one branch and journal an ambiguous entry. Pass `"fixture"` when a fixture
|
|
37
|
+
* matched (or would have) and `"proxy"` when the request was headed for the
|
|
38
|
+
* proxy path.
|
|
23
39
|
*/
|
|
24
|
-
declare function applyChaos(res: http.ServerResponse, fixture: Fixture | null, serverDefaults: ChaosConfig | undefined, rawHeaders: http.IncomingHttpHeaders, journal: Journal, context: ChaosJournalContext, registry?: MetricsRegistry, logger?: Logger): boolean;
|
|
25
40
|
//#endregion
|
|
26
41
|
export { applyChaos, evaluateChaos };
|
|
27
42
|
//# sourceMappingURL=chaos.d.cts.map
|
package/dist/chaos.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chaos.d.cts","names":[],"sources":["../src/chaos.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;AA+HC;;AAKU,iBAhCK,aAAA,CAgCL,OAAA,EA/BA,OA+BA,GAAA,IAAA,EAAA,cAAA,CAAA,EA9BQ,WA8BR,EAAA,UAAA,CAAA,EA7BI,IAAA,CAAK,mBA6BT,EAAA,MAAA,CAAA,EA5BA,MA4BA,CAAA,EA3BR,WA2BQ,GAAA,IAAA;UAHD,mBAAA,CAIF;EAAqB,MAAA,EAAA,MAAA;
|
|
1
|
+
{"version":3,"file":"chaos.d.cts","names":[],"sources":["../src/chaos.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;AA+HC;;AAKU,iBAhCK,aAAA,CAgCL,OAAA,EA/BA,OA+BA,GAAA,IAAA,EAAA,cAAA,CAAA,EA9BQ,WA8BR,EAAA,UAAA,CAAA,EA7BI,IAAA,CAAK,mBA6BT,EAAA,MAAA,CAAA,EA5BA,MA4BA,CAAA,EA3BR,WA2BQ,GAAA,IAAA;UAHD,mBAAA,CAIF;EAAqB,MAAA,EAAA,MAAA;EAWb,IAAA,EAAA,MAAA;EAAU,OAAA,EAZf,MAYe,CAAA,MAAA,EAAA,MAAA,CAAA;MACnB,EAZC,qBAYI,GAAA,IAAA;;;;;;;;;;iBADI,UAAA,MACT,IAAA,CAAK,yBACD,gCACO,qCACJ,IAAA,CAAK,8BACR,kBACA,6DAEE,0BACF"}
|
package/dist/chaos.d.ts
CHANGED
|
@@ -15,13 +15,28 @@ interface ChaosJournalContext {
|
|
|
15
15
|
method: string;
|
|
16
16
|
path: string;
|
|
17
17
|
headers: Record<string, string>;
|
|
18
|
-
body: ChatCompletionRequest;
|
|
18
|
+
body: ChatCompletionRequest | null;
|
|
19
19
|
}
|
|
20
20
|
/**
|
|
21
21
|
* Apply chaos to a request. Returns true if chaos was applied (caller should
|
|
22
22
|
* return early), false if the request should proceed normally.
|
|
23
|
+
*
|
|
24
|
+
* `source` is required so the invariant "this handler only applies chaos in
|
|
25
|
+
* the <X> phase" is enforced at the type level. A future handler that grows
|
|
26
|
+
* a proxy path MUST pass `"proxy"` explicitly; the default can't drift silently.
|
|
27
|
+
*/
|
|
28
|
+
declare function applyChaos(res: http.ServerResponse, fixture: Fixture | null, serverDefaults: ChaosConfig | undefined, rawHeaders: http.IncomingHttpHeaders, journal: Journal, context: ChaosJournalContext, source: "fixture" | "proxy", registry?: MetricsRegistry, logger?: Logger): boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Apply a specific (already-rolled) chaos action. Exposed so callers that roll
|
|
31
|
+
* the dice themselves can dispatch without re-rolling — important when the
|
|
32
|
+
* caller wants to branch on the action before committing (e.g. pre-flight vs.
|
|
33
|
+
* post-response phases).
|
|
34
|
+
*
|
|
35
|
+
* `source` is required (not optional) so callers can't silently omit it on
|
|
36
|
+
* one branch and journal an ambiguous entry. Pass `"fixture"` when a fixture
|
|
37
|
+
* matched (or would have) and `"proxy"` when the request was headed for the
|
|
38
|
+
* proxy path.
|
|
23
39
|
*/
|
|
24
|
-
declare function applyChaos(res: http.ServerResponse, fixture: Fixture | null, serverDefaults: ChaosConfig | undefined, rawHeaders: http.IncomingHttpHeaders, journal: Journal, context: ChaosJournalContext, registry?: MetricsRegistry, logger?: Logger): boolean;
|
|
25
40
|
//#endregion
|
|
26
41
|
export { applyChaos, evaluateChaos };
|
|
27
42
|
//# sourceMappingURL=chaos.d.ts.map
|
package/dist/chaos.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chaos.d.ts","names":[],"sources":["../src/chaos.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;AA+HC;;AAKU,iBAhCK,aAAA,CAgCL,OAAA,EA/BA,OA+BA,GAAA,IAAA,EAAA,cAAA,CAAA,EA9BQ,WA8BR,EAAA,UAAA,CAAA,EA7BI,IAAA,CAAK,mBA6BT,EAAA,MAAA,CAAA,EA5BA,MA4BA,CAAA,EA3BR,WA2BQ,GAAA,IAAA;UAHD,mBAAA,CAIF;EAAqB,MAAA,EAAA,MAAA;
|
|
1
|
+
{"version":3,"file":"chaos.d.ts","names":[],"sources":["../src/chaos.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;AA+HC;;AAKU,iBAhCK,aAAA,CAgCL,OAAA,EA/BA,OA+BA,GAAA,IAAA,EAAA,cAAA,CAAA,EA9BQ,WA8BR,EAAA,UAAA,CAAA,EA7BI,IAAA,CAAK,mBA6BT,EAAA,MAAA,CAAA,EA5BA,MA4BA,CAAA,EA3BR,WA2BQ,GAAA,IAAA;UAHD,mBAAA,CAIF;EAAqB,MAAA,EAAA,MAAA;EAWb,IAAA,EAAA,MAAA;EAAU,OAAA,EAZf,MAYe,CAAA,MAAA,EAAA,MAAA,CAAA;MACnB,EAZC,qBAYI,GAAA,IAAA;;;;;;;;;;iBADI,UAAA,MACT,IAAA,CAAK,yBACD,gCACO,qCACJ,IAAA,CAAK,8BACR,kBACA,6DAEE,0BACF"}
|
package/dist/chaos.js
CHANGED
|
@@ -60,11 +60,33 @@ function evaluateChaos(fixture, serverDefaults, rawHeaders, logger) {
|
|
|
60
60
|
/**
|
|
61
61
|
* Apply chaos to a request. Returns true if chaos was applied (caller should
|
|
62
62
|
* return early), false if the request should proceed normally.
|
|
63
|
+
*
|
|
64
|
+
* `source` is required so the invariant "this handler only applies chaos in
|
|
65
|
+
* the <X> phase" is enforced at the type level. A future handler that grows
|
|
66
|
+
* a proxy path MUST pass `"proxy"` explicitly; the default can't drift silently.
|
|
63
67
|
*/
|
|
64
|
-
function applyChaos(res, fixture, serverDefaults, rawHeaders, journal, context, registry, logger) {
|
|
68
|
+
function applyChaos(res, fixture, serverDefaults, rawHeaders, journal, context, source, registry, logger) {
|
|
65
69
|
const action = evaluateChaos(fixture, serverDefaults, rawHeaders, logger);
|
|
66
70
|
if (!action) return false;
|
|
67
|
-
|
|
71
|
+
applyChaosAction(action, res, fixture, journal, context, source, registry);
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Apply a specific (already-rolled) chaos action. Exposed so callers that roll
|
|
76
|
+
* the dice themselves can dispatch without re-rolling — important when the
|
|
77
|
+
* caller wants to branch on the action before committing (e.g. pre-flight vs.
|
|
78
|
+
* post-response phases).
|
|
79
|
+
*
|
|
80
|
+
* `source` is required (not optional) so callers can't silently omit it on
|
|
81
|
+
* one branch and journal an ambiguous entry. Pass `"fixture"` when a fixture
|
|
82
|
+
* matched (or would have) and `"proxy"` when the request was headed for the
|
|
83
|
+
* proxy path.
|
|
84
|
+
*/
|
|
85
|
+
function applyChaosAction(action, res, fixture, journal, context, source, registry) {
|
|
86
|
+
if (registry) registry.incrementCounter("aimock_chaos_triggered_total", {
|
|
87
|
+
action,
|
|
88
|
+
source
|
|
89
|
+
});
|
|
68
90
|
switch (action) {
|
|
69
91
|
case "drop":
|
|
70
92
|
journal.add({
|
|
@@ -72,7 +94,8 @@ function applyChaos(res, fixture, serverDefaults, rawHeaders, journal, context,
|
|
|
72
94
|
response: {
|
|
73
95
|
status: 500,
|
|
74
96
|
fixture,
|
|
75
|
-
chaosAction: "drop"
|
|
97
|
+
chaosAction: "drop",
|
|
98
|
+
source
|
|
76
99
|
}
|
|
77
100
|
});
|
|
78
101
|
writeErrorResponse(res, 500, JSON.stringify({ error: {
|
|
@@ -80,34 +103,36 @@ function applyChaos(res, fixture, serverDefaults, rawHeaders, journal, context,
|
|
|
80
103
|
type: "server_error",
|
|
81
104
|
code: "chaos_drop"
|
|
82
105
|
} }));
|
|
83
|
-
return
|
|
106
|
+
return;
|
|
84
107
|
case "malformed":
|
|
85
108
|
journal.add({
|
|
86
109
|
...context,
|
|
87
110
|
response: {
|
|
88
111
|
status: 200,
|
|
89
112
|
fixture,
|
|
90
|
-
chaosAction: "malformed"
|
|
113
|
+
chaosAction: "malformed",
|
|
114
|
+
source
|
|
91
115
|
}
|
|
92
116
|
});
|
|
93
117
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
94
118
|
res.end("{malformed json: <<<chaos>>>");
|
|
95
|
-
return
|
|
119
|
+
return;
|
|
96
120
|
case "disconnect":
|
|
97
121
|
journal.add({
|
|
98
122
|
...context,
|
|
99
123
|
response: {
|
|
100
124
|
status: 0,
|
|
101
125
|
fixture,
|
|
102
|
-
chaosAction: "disconnect"
|
|
126
|
+
chaosAction: "disconnect",
|
|
127
|
+
source
|
|
103
128
|
}
|
|
104
129
|
});
|
|
105
130
|
res.destroy();
|
|
106
|
-
return
|
|
107
|
-
default: return
|
|
131
|
+
return;
|
|
132
|
+
default: return;
|
|
108
133
|
}
|
|
109
134
|
}
|
|
110
135
|
|
|
111
136
|
//#endregion
|
|
112
|
-
export { applyChaos, evaluateChaos };
|
|
137
|
+
export { applyChaos, applyChaosAction, evaluateChaos };
|
|
113
138
|
//# sourceMappingURL=chaos.js.map
|
package/dist/chaos.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chaos.js","names":[],"sources":["../src/chaos.ts"],"sourcesContent":["/**\n * Chaos testing support for LLMock.\n *\n * Provides probabilistic failure injection — requests can be dropped (500),\n * returned with malformed JSON, or have the connection forcibly disconnected.\n *\n * Precedence: per-request headers > fixture-level config > server-level defaults.\n */\n\nimport type * as http from \"node:http\";\nimport type { ChaosAction, ChaosConfig, ChatCompletionRequest, Fixture } from \"./types.js\";\nimport { writeErrorResponse } from \"./sse-writer.js\";\nimport type { Journal } from \"./journal.js\";\nimport type { Logger } from \"./logger.js\";\nimport type { MetricsRegistry } from \"./metrics.js\";\n\n/**\n * Resolve chaos config from headers, fixture, and server defaults.\n * Header values override fixture values, which override server defaults.\n */\nfunction resolveChaosConfig(\n fixture: Fixture | null,\n serverDefaults?: ChaosConfig,\n rawHeaders?: http.IncomingHttpHeaders,\n logger?: Logger,\n): ChaosConfig {\n const base: ChaosConfig = { ...serverDefaults };\n\n // Fixture-level overrides server defaults\n if (fixture?.chaos) {\n if (fixture.chaos.dropRate !== undefined) base.dropRate = fixture.chaos.dropRate;\n if (fixture.chaos.malformedRate !== undefined) base.malformedRate = fixture.chaos.malformedRate;\n if (fixture.chaos.disconnectRate !== undefined)\n base.disconnectRate = fixture.chaos.disconnectRate;\n }\n\n // Header overrides everything\n if (rawHeaders) {\n const dropHeader = rawHeaders[\"x-aimock-chaos-drop\"];\n const malformedHeader = rawHeaders[\"x-aimock-chaos-malformed\"];\n const disconnectHeader = rawHeaders[\"x-aimock-chaos-disconnect\"];\n\n if (typeof dropHeader === \"string\") {\n const val = parseFloat(dropHeader);\n if (isNaN(val)) {\n logger?.warn(`[chaos] x-aimock-chaos-drop: invalid value \"${dropHeader}\", ignoring`);\n } else {\n if (val < 0 || val > 1) {\n logger?.warn(`[chaos] x-aimock-chaos-drop: value ${val} out of range [0,1], clamping`);\n }\n base.dropRate = Math.min(1, Math.max(0, val));\n }\n }\n if (typeof malformedHeader === \"string\") {\n const val = parseFloat(malformedHeader);\n if (isNaN(val)) {\n logger?.warn(\n `[chaos] x-aimock-chaos-malformed: invalid value \"${malformedHeader}\", ignoring`,\n );\n } else {\n if (val < 0 || val > 1) {\n logger?.warn(\n `[chaos] x-aimock-chaos-malformed: value ${val} out of range [0,1], clamping`,\n );\n }\n base.malformedRate = Math.min(1, Math.max(0, val));\n }\n }\n if (typeof disconnectHeader === \"string\") {\n const val = parseFloat(disconnectHeader);\n if (isNaN(val)) {\n logger?.warn(\n `[chaos] x-aimock-chaos-disconnect: invalid value \"${disconnectHeader}\", ignoring`,\n );\n } else {\n if (val < 0 || val > 1) {\n logger?.warn(\n `[chaos] x-aimock-chaos-disconnect: value ${val} out of range [0,1], clamping`,\n );\n }\n base.disconnectRate = Math.min(1, Math.max(0, val));\n }\n }\n }\n\n // Clamp all resolved rates to [0, 1] regardless of source.\n // Header values are already clamped above; this covers fixture-level and server defaults.\n if (base.dropRate !== undefined) base.dropRate = Math.min(1, Math.max(0, base.dropRate));\n if (base.malformedRate !== undefined)\n base.malformedRate = Math.min(1, Math.max(0, base.malformedRate));\n if (base.disconnectRate !== undefined)\n base.disconnectRate = Math.min(1, Math.max(0, base.disconnectRate));\n\n return base;\n}\n\n/**\n * Evaluate chaos config and return the triggered action, or null if none.\n * Checks in order: drop, malformed, disconnect — first hit wins.\n */\nexport function evaluateChaos(\n fixture: Fixture | null,\n serverDefaults?: ChaosConfig,\n rawHeaders?: http.IncomingHttpHeaders,\n logger?: Logger,\n): ChaosAction | null {\n const config = resolveChaosConfig(fixture, serverDefaults, rawHeaders, logger);\n\n if (config.dropRate !== undefined && config.dropRate > 0 && Math.random() < config.dropRate) {\n return \"drop\";\n }\n if (\n config.malformedRate !== undefined &&\n config.malformedRate > 0 &&\n Math.random() < config.malformedRate\n ) {\n return \"malformed\";\n }\n if (\n config.disconnectRate !== undefined &&\n config.disconnectRate > 0 &&\n Math.random() < config.disconnectRate\n ) {\n return \"disconnect\";\n }\n\n return null;\n}\n\ninterface ChaosJournalContext {\n method: string;\n path: string;\n headers: Record<string, string>;\n body: ChatCompletionRequest;\n}\n\n/**\n * Apply chaos to a request. Returns true if chaos was applied (caller should\n * return early), false if the request should proceed normally.\n */\nexport function applyChaos(\n res: http.ServerResponse,\n fixture: Fixture | null,\n serverDefaults: ChaosConfig | undefined,\n rawHeaders: http.IncomingHttpHeaders,\n journal: Journal,\n context: ChaosJournalContext,\n registry?: MetricsRegistry,\n logger?: Logger,\n): boolean {\n const action = evaluateChaos(fixture, serverDefaults, rawHeaders, logger);\n if (!action) return false;\n\n if (registry) {\n registry.incrementCounter(\"aimock_chaos_triggered_total\", { action });\n }\n\n switch (action) {\n case \"drop\": {\n journal.add({\n ...context,\n response: { status: 500, fixture, chaosAction: \"drop\" },\n });\n writeErrorResponse(\n res,\n 500,\n JSON.stringify({\n error: {\n message: \"Chaos: request dropped\",\n type: \"server_error\",\n code: \"chaos_drop\",\n },\n }),\n );\n return true;\n }\n case \"malformed\": {\n journal.add({\n ...context,\n response: { status: 200, fixture, chaosAction: \"malformed\" },\n });\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(\"{malformed json: <<<chaos>>>\");\n return true;\n }\n case \"disconnect\": {\n journal.add({\n ...context,\n response: { status: 0, fixture, chaosAction: \"disconnect\" },\n });\n res.destroy();\n return true;\n }\n default: {\n const _exhaustive: never = action;\n void _exhaustive;\n return false;\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"chaos.js","names":[],"sources":["../src/chaos.ts"],"sourcesContent":["/**\n * Chaos testing support for LLMock.\n *\n * Provides probabilistic failure injection — requests can be dropped (500),\n * returned with malformed JSON, or have the connection forcibly disconnected.\n *\n * Precedence: per-request headers > fixture-level config > server-level defaults.\n */\n\nimport type * as http from \"node:http\";\nimport type { ChaosAction, ChaosConfig, ChatCompletionRequest, Fixture } from \"./types.js\";\nimport { writeErrorResponse } from \"./sse-writer.js\";\nimport type { Journal } from \"./journal.js\";\nimport type { Logger } from \"./logger.js\";\nimport type { MetricsRegistry } from \"./metrics.js\";\n\n/**\n * Resolve chaos config from headers, fixture, and server defaults.\n * Header values override fixture values, which override server defaults.\n */\nfunction resolveChaosConfig(\n fixture: Fixture | null,\n serverDefaults?: ChaosConfig,\n rawHeaders?: http.IncomingHttpHeaders,\n logger?: Logger,\n): ChaosConfig {\n const base: ChaosConfig = { ...serverDefaults };\n\n // Fixture-level overrides server defaults\n if (fixture?.chaos) {\n if (fixture.chaos.dropRate !== undefined) base.dropRate = fixture.chaos.dropRate;\n if (fixture.chaos.malformedRate !== undefined) base.malformedRate = fixture.chaos.malformedRate;\n if (fixture.chaos.disconnectRate !== undefined)\n base.disconnectRate = fixture.chaos.disconnectRate;\n }\n\n // Header overrides everything\n if (rawHeaders) {\n const dropHeader = rawHeaders[\"x-aimock-chaos-drop\"];\n const malformedHeader = rawHeaders[\"x-aimock-chaos-malformed\"];\n const disconnectHeader = rawHeaders[\"x-aimock-chaos-disconnect\"];\n\n if (typeof dropHeader === \"string\") {\n const val = parseFloat(dropHeader);\n if (isNaN(val)) {\n logger?.warn(`[chaos] x-aimock-chaos-drop: invalid value \"${dropHeader}\", ignoring`);\n } else {\n if (val < 0 || val > 1) {\n logger?.warn(`[chaos] x-aimock-chaos-drop: value ${val} out of range [0,1], clamping`);\n }\n base.dropRate = Math.min(1, Math.max(0, val));\n }\n }\n if (typeof malformedHeader === \"string\") {\n const val = parseFloat(malformedHeader);\n if (isNaN(val)) {\n logger?.warn(\n `[chaos] x-aimock-chaos-malformed: invalid value \"${malformedHeader}\", ignoring`,\n );\n } else {\n if (val < 0 || val > 1) {\n logger?.warn(\n `[chaos] x-aimock-chaos-malformed: value ${val} out of range [0,1], clamping`,\n );\n }\n base.malformedRate = Math.min(1, Math.max(0, val));\n }\n }\n if (typeof disconnectHeader === \"string\") {\n const val = parseFloat(disconnectHeader);\n if (isNaN(val)) {\n logger?.warn(\n `[chaos] x-aimock-chaos-disconnect: invalid value \"${disconnectHeader}\", ignoring`,\n );\n } else {\n if (val < 0 || val > 1) {\n logger?.warn(\n `[chaos] x-aimock-chaos-disconnect: value ${val} out of range [0,1], clamping`,\n );\n }\n base.disconnectRate = Math.min(1, Math.max(0, val));\n }\n }\n }\n\n // Clamp all resolved rates to [0, 1] regardless of source.\n // Header values are already clamped above; this covers fixture-level and server defaults.\n if (base.dropRate !== undefined) base.dropRate = Math.min(1, Math.max(0, base.dropRate));\n if (base.malformedRate !== undefined)\n base.malformedRate = Math.min(1, Math.max(0, base.malformedRate));\n if (base.disconnectRate !== undefined)\n base.disconnectRate = Math.min(1, Math.max(0, base.disconnectRate));\n\n return base;\n}\n\n/**\n * Evaluate chaos config and return the triggered action, or null if none.\n * Checks in order: drop, malformed, disconnect — first hit wins.\n */\nexport function evaluateChaos(\n fixture: Fixture | null,\n serverDefaults?: ChaosConfig,\n rawHeaders?: http.IncomingHttpHeaders,\n logger?: Logger,\n): ChaosAction | null {\n const config = resolveChaosConfig(fixture, serverDefaults, rawHeaders, logger);\n\n if (config.dropRate !== undefined && config.dropRate > 0 && Math.random() < config.dropRate) {\n return \"drop\";\n }\n if (\n config.malformedRate !== undefined &&\n config.malformedRate > 0 &&\n Math.random() < config.malformedRate\n ) {\n return \"malformed\";\n }\n if (\n config.disconnectRate !== undefined &&\n config.disconnectRate > 0 &&\n Math.random() < config.disconnectRate\n ) {\n return \"disconnect\";\n }\n\n return null;\n}\n\ninterface ChaosJournalContext {\n method: string;\n path: string;\n headers: Record<string, string>;\n body: ChatCompletionRequest | null;\n}\n\n/**\n * Apply chaos to a request. Returns true if chaos was applied (caller should\n * return early), false if the request should proceed normally.\n *\n * `source` is required so the invariant \"this handler only applies chaos in\n * the <X> phase\" is enforced at the type level. A future handler that grows\n * a proxy path MUST pass `\"proxy\"` explicitly; the default can't drift silently.\n */\nexport function applyChaos(\n res: http.ServerResponse,\n fixture: Fixture | null,\n serverDefaults: ChaosConfig | undefined,\n rawHeaders: http.IncomingHttpHeaders,\n journal: Journal,\n context: ChaosJournalContext,\n source: \"fixture\" | \"proxy\",\n registry?: MetricsRegistry,\n logger?: Logger,\n): boolean {\n const action = evaluateChaos(fixture, serverDefaults, rawHeaders, logger);\n if (!action) return false;\n applyChaosAction(action, res, fixture, journal, context, source, registry);\n return true;\n}\n\n/**\n * Apply a specific (already-rolled) chaos action. Exposed so callers that roll\n * the dice themselves can dispatch without re-rolling — important when the\n * caller wants to branch on the action before committing (e.g. pre-flight vs.\n * post-response phases).\n *\n * `source` is required (not optional) so callers can't silently omit it on\n * one branch and journal an ambiguous entry. Pass `\"fixture\"` when a fixture\n * matched (or would have) and `\"proxy\"` when the request was headed for the\n * proxy path.\n */\nexport function applyChaosAction(\n action: ChaosAction,\n res: http.ServerResponse,\n fixture: Fixture | null,\n journal: Journal,\n context: ChaosJournalContext,\n source: \"fixture\" | \"proxy\",\n registry?: MetricsRegistry,\n): void {\n if (registry) {\n registry.incrementCounter(\"aimock_chaos_triggered_total\", { action, source });\n }\n\n switch (action) {\n case \"drop\": {\n journal.add({\n ...context,\n response: { status: 500, fixture, chaosAction: \"drop\", source },\n });\n writeErrorResponse(\n res,\n 500,\n JSON.stringify({\n error: {\n message: \"Chaos: request dropped\",\n type: \"server_error\",\n code: \"chaos_drop\",\n },\n }),\n );\n return;\n }\n case \"malformed\": {\n journal.add({\n ...context,\n response: { status: 200, fixture, chaosAction: \"malformed\", source },\n });\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(\"{malformed json: <<<chaos>>>\");\n return;\n }\n case \"disconnect\": {\n journal.add({\n ...context,\n response: { status: 0, fixture, chaosAction: \"disconnect\", source },\n });\n res.destroy();\n return;\n }\n default: {\n const _exhaustive: never = action;\n void _exhaustive;\n return;\n }\n }\n}\n"],"mappings":"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"}
|
package/dist/cohere.cjs
CHANGED
|
@@ -296,10 +296,10 @@ async function handleCohere(req, res, raw, fixtures, journal, defaults, setCorsH
|
|
|
296
296
|
path: req.url ?? "/v2/chat",
|
|
297
297
|
headers: require_helpers.flattenHeaders(req.headers),
|
|
298
298
|
body: completionReq
|
|
299
|
-
}, defaults.registry, defaults.logger)) return;
|
|
299
|
+
}, fixture ? "fixture" : "proxy", defaults.registry, defaults.logger)) return;
|
|
300
300
|
if (!fixture) {
|
|
301
301
|
if (defaults.record) {
|
|
302
|
-
if (await require_recorder.proxyAndRecord(req, res, completionReq, "cohere", req.url ?? "/v2/chat", fixtures, defaults, raw)) {
|
|
302
|
+
if (await require_recorder.proxyAndRecord(req, res, completionReq, "cohere", req.url ?? "/v2/chat", fixtures, defaults, raw) !== "not_configured") {
|
|
303
303
|
journal.add({
|
|
304
304
|
method: req.method ?? "POST",
|
|
305
305
|
path: req.url ?? "/v2/chat",
|