@copilotkit/aimock 1.33.0 → 1.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +30 -0
- package/README.md +6 -1
- package/dist/agui-types.d.ts.map +1 -1
- package/dist/bedrock-converse.cjs +112 -10
- 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 +114 -12
- package/dist/bedrock-converse.js.map +1 -1
- package/dist/bedrock.cjs +143 -6
- 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 +145 -8
- package/dist/bedrock.js.map +1 -1
- package/dist/cohere.cjs +91 -9
- package/dist/cohere.cjs.map +1 -1
- package/dist/cohere.d.cts.map +1 -1
- package/dist/cohere.d.ts.map +1 -1
- package/dist/cohere.js +93 -11
- package/dist/cohere.js.map +1 -1
- package/dist/config-loader.d.ts.map +1 -1
- package/dist/fixture-loader.cjs +121 -20
- package/dist/fixture-loader.cjs.map +1 -1
- package/dist/fixture-loader.d.cts.map +1 -1
- package/dist/fixture-loader.d.ts.map +1 -1
- package/dist/fixture-loader.js +121 -20
- package/dist/fixture-loader.js.map +1 -1
- package/dist/gemini-interactions.cjs +146 -108
- package/dist/gemini-interactions.cjs.map +1 -1
- package/dist/gemini-interactions.d.cts.map +1 -1
- package/dist/gemini-interactions.d.ts.map +1 -1
- package/dist/gemini-interactions.js +147 -109
- package/dist/gemini-interactions.js.map +1 -1
- package/dist/gemini.cjs +72 -7
- package/dist/gemini.cjs.map +1 -1
- package/dist/gemini.d.cts.map +1 -1
- package/dist/gemini.d.ts.map +1 -1
- package/dist/gemini.js +74 -9
- package/dist/gemini.js.map +1 -1
- package/dist/grok-video.cjs +834 -0
- package/dist/grok-video.cjs.map +1 -0
- package/dist/grok-video.d.cts +74 -0
- package/dist/grok-video.d.cts.map +1 -0
- package/dist/grok-video.d.ts +74 -0
- package/dist/grok-video.d.ts.map +1 -0
- package/dist/grok-video.js +829 -0
- package/dist/grok-video.js.map +1 -0
- package/dist/helpers.cjs +181 -2
- package/dist/helpers.cjs.map +1 -1
- package/dist/helpers.d.cts +33 -3
- package/dist/helpers.d.cts.map +1 -1
- package/dist/helpers.d.ts +33 -3
- package/dist/helpers.d.ts.map +1 -1
- package/dist/helpers.js +181 -3
- package/dist/helpers.js.map +1 -1
- package/dist/index.cjs +11 -0
- package/dist/index.d.cts +5 -3
- package/dist/index.d.ts +5 -3
- package/dist/index.js +4 -2
- package/dist/messages.cjs +100 -11
- package/dist/messages.cjs.map +1 -1
- package/dist/messages.d.cts.map +1 -1
- package/dist/messages.d.ts.map +1 -1
- package/dist/messages.js +102 -13
- package/dist/messages.js.map +1 -1
- package/dist/metrics.cjs +11 -1
- package/dist/metrics.cjs.map +1 -1
- package/dist/metrics.d.cts.map +1 -1
- package/dist/metrics.d.ts.map +1 -1
- package/dist/metrics.js +8 -1
- package/dist/metrics.js.map +1 -1
- package/dist/ollama.cjs +77 -7
- 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 +79 -9
- package/dist/ollama.js.map +1 -1
- package/dist/openrouter-video.cjs +19 -138
- package/dist/openrouter-video.cjs.map +1 -1
- package/dist/openrouter-video.d.cts.map +1 -1
- package/dist/openrouter-video.d.ts.map +1 -1
- package/dist/openrouter-video.js +1 -120
- package/dist/openrouter-video.js.map +1 -1
- package/dist/recorder.cjs +12 -9
- package/dist/recorder.cjs.map +1 -1
- package/dist/recorder.js +12 -9
- package/dist/recorder.js.map +1 -1
- package/dist/responses.cjs +137 -59
- package/dist/responses.cjs.map +1 -1
- package/dist/responses.d.cts +2 -2
- package/dist/responses.d.cts.map +1 -1
- package/dist/responses.d.ts +2 -2
- package/dist/responses.d.ts.map +1 -1
- package/dist/responses.js +139 -61
- package/dist/responses.js.map +1 -1
- package/dist/router.cjs +236 -33
- package/dist/router.cjs.map +1 -1
- package/dist/router.d.cts +54 -2
- package/dist/router.d.cts.map +1 -1
- package/dist/router.d.ts +54 -2
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +236 -34
- package/dist/router.js.map +1 -1
- package/dist/server.cjs +163 -21
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +4 -0
- package/dist/server.d.cts.map +1 -1
- package/dist/server.d.ts +4 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +166 -24
- package/dist/server.js.map +1 -1
- package/dist/stream-collapse.cjs +355 -79
- package/dist/stream-collapse.cjs.map +1 -1
- package/dist/stream-collapse.d.cts +27 -4
- package/dist/stream-collapse.d.cts.map +1 -1
- package/dist/stream-collapse.d.ts +27 -4
- package/dist/stream-collapse.d.ts.map +1 -1
- package/dist/stream-collapse.js +355 -79
- package/dist/stream-collapse.js.map +1 -1
- package/dist/types.d.cts +104 -7
- package/dist/types.d.cts.map +1 -1
- package/dist/types.d.ts +104 -7
- package/dist/types.d.ts.map +1 -1
- package/dist/vector-types.d.ts.map +1 -1
- package/dist/veo-video.cjs +794 -0
- package/dist/veo-video.cjs.map +1 -0
- package/dist/veo-video.d.cts +50 -0
- package/dist/veo-video.d.cts.map +1 -0
- package/dist/veo-video.d.ts +50 -0
- package/dist/veo-video.d.ts.map +1 -0
- package/dist/veo-video.js +789 -0
- package/dist/veo-video.js.map +1 -0
- package/dist/video-proxy-shared.cjs +145 -0
- package/dist/video-proxy-shared.cjs.map +1 -0
- package/dist/video-proxy-shared.js +140 -0
- package/dist/video-proxy-shared.js.map +1 -0
- package/dist/ws-gemini-live.cjs +96 -3
- package/dist/ws-gemini-live.cjs.map +1 -1
- package/dist/ws-gemini-live.d.cts.map +1 -1
- package/dist/ws-gemini-live.d.ts.map +1 -1
- package/dist/ws-gemini-live.js +97 -4
- package/dist/ws-gemini-live.js.map +1 -1
- package/dist/ws-realtime.cjs +281 -4
- package/dist/ws-realtime.cjs.map +1 -1
- package/dist/ws-realtime.d.cts.map +1 -1
- package/dist/ws-realtime.d.ts.map +1 -1
- package/dist/ws-realtime.js +282 -5
- package/dist/ws-realtime.js.map +1 -1
- package/dist/ws-responses.cjs +1 -1
- package/dist/ws-responses.cjs.map +1 -1
- package/dist/ws-responses.js +1 -1
- package/dist/ws-responses.js.map +1 -1
- package/fixtures/examples/llm/blocks-tool-first.json +13 -0
- package/package.json +1 -1
- package/skills/write-fixtures/SKILL.md +19 -1
package/dist/ollama.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ollama.js","names":[],"sources":["../src/ollama.ts"],"sourcesContent":["/**\n * Ollama API endpoint support.\n *\n * Translates incoming /api/chat, /api/generate, and /api/embeddings requests\n * into the ChatCompletionRequest format used by the fixture router, and converts\n * fixture responses back into Ollama's NDJSON streaming or non-streaming format.\n *\n * Key differences from OpenAI:\n * - Ollama defaults to stream: true (opposite of OpenAI)\n * - Streaming uses NDJSON, not SSE\n * - Tool call arguments are objects, not JSON strings\n * - Tool calls have no id field\n * - Embeddings return a single `embedding` array, not an array of objects\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 isTextResponse,\n isToolCallResponse,\n isContentWithToolCallsResponse,\n isErrorResponse,\n isEmbeddingResponse,\n serializeErrorResponse,\n generateDeterministicEmbedding,\n flattenHeaders,\n getTestId,\n resolveResponse,\n resolveStrictMode,\n resolveReasoningForModel,\n strictOverrideField,\n getContext,\n strictNoMatchMessage,\n strictNoMatchLogLine,\n} from \"./helpers.js\";\nimport { matchFixtureDiagnostic } from \"./router.js\";\nimport { writeErrorResponse } from \"./sse-writer.js\";\nimport { writeNDJSONStream } from \"./ndjson-writer.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// ─── Ollama request types ────────────────────────────────────────────────────\n\ninterface OllamaMessage {\n role: \"system\" | \"user\" | \"assistant\" | \"tool\";\n content: string;\n tool_calls?: Array<{ function: { name: string; arguments: unknown } }>;\n images?: string[];\n}\n\ninterface OllamaToolDef {\n type: string;\n function: {\n name: string;\n description?: string;\n parameters?: object;\n };\n}\n\ninterface OllamaRequest {\n model: string;\n messages: OllamaMessage[];\n stream?: boolean; // default true!\n options?: { temperature?: number; num_predict?: number };\n tools?: OllamaToolDef[];\n}\n\ninterface OllamaGenerateRequest {\n model: string;\n prompt: string;\n stream?: boolean; // default true!\n options?: { temperature?: number; num_predict?: number };\n system?: string;\n images?: string[];\n}\n\n// ─── Duration fields (zeroed, required on final/non-streaming responses) ────\n\nconst DURATION_FIELDS = {\n done_reason: \"stop\" as const,\n total_duration: 0,\n load_duration: 0,\n prompt_eval_count: 0,\n prompt_eval_duration: 0,\n eval_count: 0,\n eval_duration: 0,\n};\n\n// ─── Input conversion: Ollama → ChatCompletionRequest ────────────────────────\n\nexport function ollamaToCompletionRequest(req: OllamaRequest): ChatCompletionRequest {\n const messages: ChatMessage[] = [];\n\n for (const msg of req.messages) {\n const chatMsg: ChatMessage = {\n role: msg.role as ChatMessage[\"role\"],\n content: msg.content,\n };\n\n // Map inbound tool_calls on assistant messages to the internal format\n if (msg.tool_calls && msg.tool_calls.length > 0) {\n chatMsg.tool_calls = msg.tool_calls.map((tc, i) => ({\n id: `call_${i}`,\n type: \"function\" as const,\n function: {\n name: tc.function.name,\n arguments:\n typeof tc.function.arguments === \"string\"\n ? tc.function.arguments\n : JSON.stringify(tc.function.arguments),\n },\n }));\n }\n\n messages.push(chatMsg);\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.function.name,\n description: t.function.description,\n parameters: t.function.parameters,\n },\n }));\n }\n\n return {\n model: req.model,\n messages,\n stream: req.stream ?? true,\n temperature: req.options?.temperature,\n max_tokens: req.options?.num_predict,\n tools,\n };\n}\n\nfunction ollamaGenerateToCompletionRequest(req: OllamaGenerateRequest): ChatCompletionRequest {\n const messages: ChatMessage[] = [];\n\n // Prepend system message if present\n if (req.system) {\n messages.push({ role: \"system\", content: req.system });\n }\n\n messages.push({ role: \"user\", content: req.prompt });\n\n return {\n model: req.model,\n messages,\n stream: req.stream ?? true,\n temperature: req.options?.temperature,\n max_tokens: req.options?.num_predict,\n };\n}\n\n// ─── Response builders: /api/chat ────────────────────────────────────────────\n\nfunction buildOllamaChatTextChunks(\n content: string,\n model: string,\n chunkSize: number,\n reasoning?: string,\n): object[] {\n const chunks: object[] = [];\n const createdAt = new Date().toISOString();\n\n // Reasoning chunks (before content)\n if (reasoning) {\n for (let i = 0; i < reasoning.length; i += chunkSize) {\n const slice = reasoning.slice(i, i + chunkSize);\n chunks.push({\n model,\n created_at: createdAt,\n message: { role: \"assistant\", content: \"\", reasoning_content: slice },\n done: false,\n });\n }\n }\n\n for (let i = 0; i < content.length; i += chunkSize) {\n const slice = content.slice(i, i + chunkSize);\n chunks.push({\n model,\n created_at: createdAt,\n message: { role: \"assistant\", content: slice },\n done: false,\n });\n }\n\n // Final chunk with done: true and all duration fields\n chunks.push({\n model,\n created_at: createdAt,\n message: { role: \"assistant\", content: \"\" },\n done: true,\n ...DURATION_FIELDS,\n });\n\n return chunks;\n}\n\nfunction buildOllamaChatTextResponse(content: string, model: string, reasoning?: string): object {\n return {\n model,\n created_at: new Date().toISOString(),\n message: {\n role: \"assistant\",\n content,\n ...(reasoning ? { reasoning_content: reasoning } : {}),\n },\n done: true,\n ...DURATION_FIELDS,\n };\n}\n\nfunction buildOllamaChatToolCallChunks(\n toolCalls: ToolCall[],\n model: string,\n chunkSize: number,\n logger: Logger,\n reasoning?: string,\n): object[] {\n const ollamaToolCalls = 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 function: {\n name: tc.name,\n arguments: argsObj,\n },\n };\n });\n\n // Tool calls are sent in a single chunk (no streaming of individual args)\n const chunks: object[] = [];\n const createdAt = new Date().toISOString();\n\n // Reasoning chunks (before tool calls)\n if (reasoning) {\n for (let i = 0; i < reasoning.length; i += chunkSize) {\n const slice = reasoning.slice(i, i + chunkSize);\n chunks.push({\n model,\n created_at: createdAt,\n message: { role: \"assistant\", content: \"\", reasoning_content: slice },\n done: false,\n });\n }\n }\n\n chunks.push({\n model,\n created_at: createdAt,\n message: {\n role: \"assistant\",\n content: \"\",\n tool_calls: ollamaToolCalls,\n },\n done: false,\n });\n\n // Final chunk\n chunks.push({\n model,\n created_at: createdAt,\n message: { role: \"assistant\", content: \"\" },\n done: true,\n ...DURATION_FIELDS,\n });\n\n return chunks;\n}\n\nfunction buildOllamaChatToolCallResponse(\n toolCalls: ToolCall[],\n model: string,\n logger: Logger,\n reasoning?: string,\n): object {\n const ollamaToolCalls = 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 function: {\n name: tc.name,\n arguments: argsObj,\n },\n };\n });\n\n return {\n model,\n created_at: new Date().toISOString(),\n message: {\n role: \"assistant\",\n content: \"\",\n ...(reasoning ? { reasoning_content: reasoning } : {}),\n tool_calls: ollamaToolCalls,\n },\n done: true,\n ...DURATION_FIELDS,\n };\n}\n\n// ─── Response builders: /api/chat — content + tool calls ────────────────────\n\nfunction buildOllamaChatContentWithToolCallsChunks(\n content: string,\n toolCalls: ToolCall[],\n model: string,\n chunkSize: number,\n logger: Logger,\n reasoning?: string,\n): object[] {\n const chunks: object[] = [];\n const createdAt = new Date().toISOString();\n\n // Reasoning chunks (before content)\n if (reasoning) {\n for (let i = 0; i < reasoning.length; i += chunkSize) {\n const slice = reasoning.slice(i, i + chunkSize);\n chunks.push({\n model,\n created_at: createdAt,\n message: { role: \"assistant\", content: \"\", reasoning_content: slice },\n done: false,\n });\n }\n }\n\n // Content chunks first\n for (let i = 0; i < content.length; i += chunkSize) {\n const slice = content.slice(i, i + chunkSize);\n chunks.push({\n model,\n created_at: createdAt,\n message: { role: \"assistant\", content: slice },\n done: false,\n });\n }\n\n // Tool calls in a single chunk (same as tool-call-only path)\n const ollamaToolCalls = 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 function: {\n name: tc.name,\n arguments: argsObj,\n },\n };\n });\n\n chunks.push({\n model,\n created_at: createdAt,\n message: {\n role: \"assistant\",\n content: \"\",\n tool_calls: ollamaToolCalls,\n },\n done: false,\n });\n\n // Final chunk\n chunks.push({\n model,\n created_at: createdAt,\n message: { role: \"assistant\", content: \"\" },\n done: true,\n ...DURATION_FIELDS,\n });\n\n return chunks;\n}\n\nfunction buildOllamaChatContentWithToolCallsResponse(\n content: string,\n toolCalls: ToolCall[],\n model: string,\n logger: Logger,\n reasoning?: string,\n): object {\n const ollamaToolCalls = 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 function: {\n name: tc.name,\n arguments: argsObj,\n },\n };\n });\n\n return {\n model,\n created_at: new Date().toISOString(),\n message: {\n role: \"assistant\",\n content,\n ...(reasoning ? { reasoning_content: reasoning } : {}),\n tool_calls: ollamaToolCalls,\n },\n done: true,\n ...DURATION_FIELDS,\n };\n}\n\n// ─── Response builders: /api/generate ────────────────────────────────────────\n\nfunction buildOllamaGenerateTextChunks(\n content: string,\n model: string,\n chunkSize: number,\n reasoning?: string,\n): object[] {\n const chunks: object[] = [];\n const createdAt = new Date().toISOString();\n\n // Reasoning chunks (before content)\n if (reasoning) {\n for (let i = 0; i < reasoning.length; i += chunkSize) {\n const slice = reasoning.slice(i, i + chunkSize);\n chunks.push({\n model,\n created_at: createdAt,\n response: \"\",\n reasoning_content: slice,\n done: false,\n });\n }\n }\n\n for (let i = 0; i < content.length; i += chunkSize) {\n const slice = content.slice(i, i + chunkSize);\n chunks.push({\n model,\n created_at: createdAt,\n response: slice,\n done: false,\n });\n }\n\n // Final chunk\n chunks.push({\n model,\n created_at: createdAt,\n response: \"\",\n done: true,\n ...DURATION_FIELDS,\n context: [],\n });\n\n return chunks;\n}\n\nfunction buildOllamaGenerateTextResponse(\n content: string,\n model: string,\n reasoning?: string,\n): object {\n return {\n model,\n created_at: new Date().toISOString(),\n response: content,\n ...(reasoning ? { reasoning_content: reasoning } : {}),\n done: true,\n ...DURATION_FIELDS,\n context: [],\n };\n}\n\n// ─── Request handler: /api/chat ──────────────────────────────────────────────\n\nexport async function handleOllama(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n raw: 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 ?? \"/api/chat\";\n\n let ollamaReq: OllamaRequest;\n try {\n ollamaReq = JSON.parse(raw) as OllamaRequest;\n } catch (parseErr) {\n const detail = parseErr instanceof Error ? parseErr.message : \"unknown\";\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 body: ${detail}`,\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n if (!ollamaReq.messages || !Array.isArray(ollamaReq.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 = ollamaToCompletionRequest(ollamaReq);\n completionReq._endpointType = \"chat\";\n completionReq._context = getContext(req);\n\n const testId = getTestId(req);\n const { fixture, skippedBySequenceOrTurn } = matchFixtureDiagnostic(\n fixtures,\n completionReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n );\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);\n } else {\n logger.debug(`No fixture matched for request`);\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 const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);\n if (effectiveStrict) {\n const strictStatus = 503;\n const strictMessage = strictNoMatchMessage(skippedBySequenceOrTurn);\n logger.error(strictNoMatchLogLine(req.method ?? \"POST\", urlPath, skippedBySequenceOrTurn));\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: {\n status: strictStatus,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\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 if (defaults.record) {\n const outcome = await proxyAndRecord(\n req,\n res,\n completionReq,\n \"ollama\",\n urlPath,\n fixtures,\n defaults,\n raw,\n );\n if (outcome === \"handled_by_hook\") return;\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, source: \"proxy\" },\n });\n return;\n }\n }\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: {\n status: 404,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n writeErrorResponse(\n res,\n 404,\n JSON.stringify({\n error: {\n message: \"No fixture matched\",\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n const response = await resolveResponse(fixture, completionReq);\n const latency = fixture.latency ?? defaults.latency;\n const chunkSize = Math.max(1, fixture.chunkSize ?? defaults.chunkSize);\n\n // Ollama defaults to streaming when stream is absent or true\n const streaming = ollamaReq.stream !== false;\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, serializeErrorResponse(response), {\n retryAfter: response.retryAfter,\n });\n return;\n }\n\n // Content + tool calls response (must be checked before text/tool-only branches)\n if (isContentWithToolCallsResponse(response)) {\n if (response.webSearches?.length) {\n logger.warn(\"webSearches in fixture response are not supported for Ollama API -- ignoring\");\n }\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 // Gate reasoning emission on the requested model's capability (aimock#254).\n const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);\n const effReasoning = resolveReasoningForModel(\n response.reasoning,\n completionReq.model,\n effectiveStrict,\n logger,\n );\n if (!streaming) {\n const body = buildOllamaChatContentWithToolCallsResponse(\n response.content,\n response.toolCalls,\n completionReq.model,\n logger,\n effReasoning,\n );\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const chunks = buildOllamaChatContentWithToolCallsChunks(\n response.content,\n response.toolCalls,\n completionReq.model,\n chunkSize,\n logger,\n effReasoning,\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeNDJSONStream(res, chunks, {\n latency,\n streamingProfile: fixture.streamingProfile,\n recordedTimings: fixture.recordedTimings,\n replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,\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 }\n return;\n }\n\n // Text response\n if (isTextResponse(response)) {\n if (response.webSearches?.length) {\n logger.warn(\"webSearches in fixture response are not supported for Ollama API -- ignoring\");\n }\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 // Gate reasoning emission on the requested model's capability (aimock#254).\n const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);\n const effReasoning = resolveReasoningForModel(\n response.reasoning,\n completionReq.model,\n effectiveStrict,\n logger,\n );\n if (!streaming) {\n const body = buildOllamaChatTextResponse(response.content, completionReq.model, effReasoning);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const chunks = buildOllamaChatTextChunks(\n response.content,\n completionReq.model,\n chunkSize,\n effReasoning,\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeNDJSONStream(res, chunks, {\n latency,\n streamingProfile: fixture.streamingProfile,\n recordedTimings: fixture.recordedTimings,\n replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,\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 }\n return;\n }\n\n // Tool call response\n if (isToolCallResponse(response)) {\n if (response.webSearches?.length) {\n logger.warn(\"webSearches in fixture response are not supported for Ollama API — ignoring\");\n }\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 // Gate reasoning emission on the requested model's capability (aimock#254).\n const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);\n const effReasoning = resolveReasoningForModel(\n response.reasoning,\n completionReq.model,\n effectiveStrict,\n logger,\n );\n if (!streaming) {\n const body = buildOllamaChatToolCallResponse(\n response.toolCalls,\n completionReq.model,\n logger,\n effReasoning,\n );\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const chunks = buildOllamaChatToolCallChunks(\n response.toolCalls,\n completionReq.model,\n chunkSize,\n logger,\n effReasoning,\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeNDJSONStream(res, chunks, {\n latency,\n streamingProfile: fixture.streamingProfile,\n recordedTimings: fixture.recordedTimings,\n replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,\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 }\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// ─── Request handler: /api/generate ──────────────────────────────────────────\n\nexport async function handleOllamaGenerate(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n raw: string,\n fixtures: Fixture[],\n journal: Journal,\n defaults: HandlerDefaults,\n setCorsHeaders: (res: http.ServerResponse) => void,\n): Promise<void> {\n setCorsHeaders(res);\n\n const urlPath = req.url ?? \"/api/generate\";\n\n let generateReq: OllamaGenerateRequest;\n try {\n generateReq = JSON.parse(raw) as OllamaGenerateRequest;\n } catch (parseErr) {\n const detail = parseErr instanceof Error ? parseErr.message : \"unknown\";\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 body: ${detail}`,\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n if (!generateReq.prompt || typeof generateReq.prompt !== \"string\") {\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: prompt field is required\",\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n // Convert to ChatCompletionRequest for fixture matching\n const completionReq = ollamaGenerateToCompletionRequest(generateReq);\n completionReq._endpointType = \"chat\";\n completionReq._context = getContext(req);\n\n const testId = getTestId(req);\n const { fixture, skippedBySequenceOrTurn } = matchFixtureDiagnostic(\n fixtures,\n completionReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n );\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n defaults.logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);\n } else {\n defaults.logger.debug(`No fixture matched for request`);\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 const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);\n if (effectiveStrict) {\n const strictStatus = 503;\n const strictMessage = strictNoMatchMessage(skippedBySequenceOrTurn);\n defaults.logger.error(\n strictNoMatchLogLine(req.method ?? \"POST\", urlPath, skippedBySequenceOrTurn),\n );\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: {\n status: strictStatus,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\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 if (defaults.record) {\n const outcome = await proxyAndRecord(\n req,\n res,\n completionReq,\n \"ollama\",\n urlPath,\n fixtures,\n defaults,\n raw,\n );\n if (outcome === \"handled_by_hook\") return;\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, source: \"proxy\" },\n });\n return;\n }\n }\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: {\n status: 404,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n writeErrorResponse(\n res,\n 404,\n JSON.stringify({\n error: {\n message: \"No fixture matched\",\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n const response = await resolveResponse(fixture, completionReq);\n const latency = fixture.latency ?? defaults.latency;\n const chunkSize = Math.max(1, fixture.chunkSize ?? defaults.chunkSize);\n\n // Ollama defaults to streaming when stream is absent or true\n const streaming = generateReq.stream !== false;\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, serializeErrorResponse(response), {\n retryAfter: response.retryAfter,\n });\n return;\n }\n\n // Text response (only type supported for /api/generate)\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 // Gate reasoning emission on the requested model's capability (aimock#254).\n const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);\n const effReasoning = resolveReasoningForModel(\n response.reasoning,\n completionReq.model,\n effectiveStrict,\n defaults.logger,\n );\n if (!streaming) {\n const body = buildOllamaGenerateTextResponse(\n response.content,\n completionReq.model,\n effReasoning,\n );\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const chunks = buildOllamaGenerateTextChunks(\n response.content,\n completionReq.model,\n chunkSize,\n effReasoning,\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeNDJSONStream(res, chunks, {\n latency,\n streamingProfile: fixture.streamingProfile,\n recordedTimings: fixture.recordedTimings,\n replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,\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 }\n return;\n }\n\n // Tool call fixtures matched but not supported on /api/generate\n if (isToolCallResponse(response) || isContentWithToolCallsResponse(response)) {\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 400, fixture },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({\n error: {\n message: \"Tool call fixtures are not supported on /api/generate — use /api/chat instead\",\n type: \"invalid_request_error\",\n },\n }),\n );\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// ─── Ollama embeddings request type ─────────────────────────────────────────\n\ninterface OllamaEmbeddingsRequest {\n model: string;\n prompt?: string;\n input?: string | string[];\n}\n\n// ─── Request handler: /api/embeddings ───────────────────────────────────────\n\nexport async function handleOllamaEmbeddings(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n raw: 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 ?? \"/api/embeddings\";\n\n let embReq: OllamaEmbeddingsRequest;\n try {\n embReq = JSON.parse(raw) as OllamaEmbeddingsRequest;\n } catch (parseErr) {\n const detail = parseErr instanceof Error ? parseErr.message : \"unknown\";\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 body: ${detail}`,\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n // Ollama accepts either \"prompt\" or \"input\" for the text to embed\n const inputText =\n embReq.prompt ??\n (typeof embReq.input === \"string\" ? embReq.input : undefined) ??\n (Array.isArray(embReq.input) ? embReq.input.join(\" \") : undefined);\n\n if (!embReq.model) {\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: model field is required\",\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n if (!inputText) {\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: prompt or input field is required\",\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n // Build synthetic ChatCompletionRequest for the fixture router\n const syntheticReq: ChatCompletionRequest = {\n model: embReq.model,\n messages: [],\n embeddingInput: inputText,\n _endpointType: \"embedding\",\n _context: getContext(req),\n };\n\n const testId = getTestId(req);\n const { fixture, skippedBySequenceOrTurn } = matchFixtureDiagnostic(\n fixtures,\n syntheticReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n );\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);\n } else {\n logger.debug(`No fixture matched for request`);\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: syntheticReq,\n },\n fixture ? \"fixture\" : \"proxy\",\n defaults.registry,\n defaults.logger,\n )\n )\n return;\n\n if (fixture) {\n const response = await resolveResponse(fixture, syntheticReq);\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: syntheticReq,\n response: { status, fixture },\n });\n writeErrorResponse(res, status, serializeErrorResponse(response), {\n retryAfter: response.retryAfter,\n });\n return;\n }\n\n // Embedding response — use the fixture's embedding\n if (isEmbeddingResponse(response)) {\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 200, fixture },\n });\n const body = {\n model: embReq.model,\n embedding: [...response.embedding],\n };\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n return;\n }\n\n // Fixture matched but response type is not compatible with embeddings\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 500, fixture },\n });\n writeErrorResponse(\n res,\n 500,\n JSON.stringify({\n error: {\n message:\n \"Fixture response did not match any known embedding type (must have embedding or error)\",\n type: \"server_error\",\n },\n }),\n );\n return;\n }\n\n // No fixture match — check strict mode first, then try proxy\n const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);\n if (effectiveStrict) {\n const strictMessage = strictNoMatchMessage(skippedBySequenceOrTurn);\n logger.error(strictNoMatchLogLine(req.method ?? \"POST\", urlPath, skippedBySequenceOrTurn));\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: {\n status: 503,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n writeErrorResponse(\n res,\n 503,\n JSON.stringify({\n error: {\n message: strictMessage,\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n if (defaults.record) {\n const outcome = await proxyAndRecord(\n req,\n res,\n syntheticReq,\n \"ollama\",\n urlPath,\n fixtures,\n defaults,\n raw,\n );\n if (outcome === \"handled_by_hook\") return;\n if (outcome !== \"not_configured\") {\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: res.statusCode ?? 200, fixture: null, source: \"proxy\" },\n });\n return;\n }\n }\n\n // No fixture match — generate deterministic embedding from input text\n logger.warn(\n `No embedding fixture matched for \"${inputText.slice(0, 80)}\" — returning deterministic fallback`,\n );\n const embedding = generateDeterministicEmbedding(inputText);\n\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 200, fixture: null },\n });\n\n const body = {\n model: embReq.model,\n embedding,\n };\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ollama.js","names":[],"sources":["../src/ollama.ts"],"sourcesContent":["/**\n * Ollama API endpoint support.\n *\n * Translates incoming /api/chat, /api/generate, and /api/embeddings requests\n * into the ChatCompletionRequest format used by the fixture router, and converts\n * fixture responses back into Ollama's NDJSON streaming or non-streaming format.\n *\n * Key differences from OpenAI:\n * - Ollama defaults to stream: true (opposite of OpenAI)\n * - Streaming uses NDJSON, not SSE\n * - Tool call arguments are objects, not JSON strings\n * - Tool calls have no id field\n * - Embeddings return a single `embedding` array, not an array of objects\n */\n\nimport type * as http from \"node:http\";\nimport type {\n ChatCompletionRequest,\n ChatMessage,\n Fixture,\n FixtureBlock,\n HandlerDefaults,\n ToolCall,\n ToolDefinition,\n} from \"./types.js\";\nimport {\n isTextResponse,\n isToolCallResponse,\n isContentWithToolCallsResponse,\n isErrorResponse,\n isEmbeddingResponse,\n resolveFixtureBlocks,\n serializeErrorResponse,\n generateDeterministicEmbedding,\n flattenHeaders,\n getTestId,\n resolveResponse,\n resolveStrictMode,\n resolveReasoningForModel,\n strictOverrideField,\n getContext,\n strictNoMatchMessage,\n strictNoMatchLogLine,\n} from \"./helpers.js\";\nimport { matchFixtureDiagnostic, recordMatchOptions } from \"./router.js\";\nimport { writeErrorResponse } from \"./sse-writer.js\";\nimport { writeNDJSONStream } from \"./ndjson-writer.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// ─── Ollama request types ────────────────────────────────────────────────────\n\ninterface OllamaMessage {\n role: \"system\" | \"user\" | \"assistant\" | \"tool\";\n content: string;\n tool_calls?: Array<{ function: { name: string; arguments: unknown } }>;\n images?: string[];\n}\n\ninterface OllamaToolDef {\n type: string;\n function: {\n name: string;\n description?: string;\n parameters?: object;\n };\n}\n\ninterface OllamaRequest {\n model: string;\n messages: OllamaMessage[];\n stream?: boolean; // default true!\n options?: { temperature?: number; num_predict?: number };\n tools?: OllamaToolDef[];\n}\n\ninterface OllamaGenerateRequest {\n model: string;\n prompt: string;\n stream?: boolean; // default true!\n options?: { temperature?: number; num_predict?: number };\n system?: string;\n images?: string[];\n}\n\n// ─── Duration fields (zeroed, required on final/non-streaming responses) ────\n\nconst DURATION_FIELDS = {\n done_reason: \"stop\" as const,\n total_duration: 0,\n load_duration: 0,\n prompt_eval_count: 0,\n prompt_eval_duration: 0,\n eval_count: 0,\n eval_duration: 0,\n};\n\n// ─── Input conversion: Ollama → ChatCompletionRequest ────────────────────────\n\nexport function ollamaToCompletionRequest(req: OllamaRequest): ChatCompletionRequest {\n const messages: ChatMessage[] = [];\n\n for (const msg of req.messages) {\n const chatMsg: ChatMessage = {\n role: msg.role as ChatMessage[\"role\"],\n content: msg.content,\n };\n\n // Map inbound tool_calls on assistant messages to the internal format\n if (msg.tool_calls && msg.tool_calls.length > 0) {\n chatMsg.tool_calls = msg.tool_calls.map((tc, i) => ({\n id: `call_${i}`,\n type: \"function\" as const,\n function: {\n name: tc.function.name,\n arguments:\n typeof tc.function.arguments === \"string\"\n ? tc.function.arguments\n : JSON.stringify(tc.function.arguments),\n },\n }));\n }\n\n messages.push(chatMsg);\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.function.name,\n description: t.function.description,\n parameters: t.function.parameters,\n },\n }));\n }\n\n return {\n model: req.model,\n messages,\n stream: req.stream ?? true,\n temperature: req.options?.temperature,\n max_tokens: req.options?.num_predict,\n tools,\n };\n}\n\nfunction ollamaGenerateToCompletionRequest(req: OllamaGenerateRequest): ChatCompletionRequest {\n const messages: ChatMessage[] = [];\n\n // Prepend system message if present\n if (req.system) {\n messages.push({ role: \"system\", content: req.system });\n }\n\n messages.push({ role: \"user\", content: req.prompt });\n\n return {\n model: req.model,\n messages,\n stream: req.stream ?? true,\n temperature: req.options?.temperature,\n max_tokens: req.options?.num_predict,\n };\n}\n\n// ─── Response builders: /api/chat ────────────────────────────────────────────\n\nfunction buildOllamaChatTextChunks(\n content: string,\n model: string,\n chunkSize: number,\n reasoning?: string,\n): object[] {\n const chunks: object[] = [];\n const createdAt = new Date().toISOString();\n\n // Reasoning chunks (before content)\n if (reasoning) {\n for (let i = 0; i < reasoning.length; i += chunkSize) {\n const slice = reasoning.slice(i, i + chunkSize);\n chunks.push({\n model,\n created_at: createdAt,\n message: { role: \"assistant\", content: \"\", reasoning_content: slice },\n done: false,\n });\n }\n }\n\n for (let i = 0; i < content.length; i += chunkSize) {\n const slice = content.slice(i, i + chunkSize);\n chunks.push({\n model,\n created_at: createdAt,\n message: { role: \"assistant\", content: slice },\n done: false,\n });\n }\n\n // Final chunk with done: true and all duration fields\n chunks.push({\n model,\n created_at: createdAt,\n message: { role: \"assistant\", content: \"\" },\n done: true,\n ...DURATION_FIELDS,\n });\n\n return chunks;\n}\n\nfunction buildOllamaChatTextResponse(content: string, model: string, reasoning?: string): object {\n return {\n model,\n created_at: new Date().toISOString(),\n message: {\n role: \"assistant\",\n content,\n ...(reasoning ? { reasoning_content: reasoning } : {}),\n },\n done: true,\n ...DURATION_FIELDS,\n };\n}\n\nfunction buildOllamaChatToolCallChunks(\n toolCalls: ToolCall[],\n model: string,\n chunkSize: number,\n logger: Logger,\n reasoning?: string,\n): object[] {\n const ollamaToolCalls = 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 function: {\n name: tc.name,\n arguments: argsObj,\n },\n };\n });\n\n // Tool calls are sent in a single chunk (no streaming of individual args)\n const chunks: object[] = [];\n const createdAt = new Date().toISOString();\n\n // Reasoning chunks (before tool calls)\n if (reasoning) {\n for (let i = 0; i < reasoning.length; i += chunkSize) {\n const slice = reasoning.slice(i, i + chunkSize);\n chunks.push({\n model,\n created_at: createdAt,\n message: { role: \"assistant\", content: \"\", reasoning_content: slice },\n done: false,\n });\n }\n }\n\n chunks.push({\n model,\n created_at: createdAt,\n message: {\n role: \"assistant\",\n content: \"\",\n tool_calls: ollamaToolCalls,\n },\n done: false,\n });\n\n // Final chunk\n chunks.push({\n model,\n created_at: createdAt,\n message: { role: \"assistant\", content: \"\" },\n done: true,\n ...DURATION_FIELDS,\n });\n\n return chunks;\n}\n\nfunction buildOllamaChatToolCallResponse(\n toolCalls: ToolCall[],\n model: string,\n logger: Logger,\n reasoning?: string,\n): object {\n const ollamaToolCalls = 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 function: {\n name: tc.name,\n arguments: argsObj,\n },\n };\n });\n\n return {\n model,\n created_at: new Date().toISOString(),\n message: {\n role: \"assistant\",\n content: \"\",\n ...(reasoning ? { reasoning_content: reasoning } : {}),\n tool_calls: ollamaToolCalls,\n },\n done: true,\n ...DURATION_FIELDS,\n };\n}\n\n// ─── Response builders: /api/chat — content + tool calls ────────────────────\n\n// Map a fixture tool call into Ollama's wire shape (object arguments, no id).\nfunction toOllamaToolCall(\n tc: ToolCall,\n logger: Logger,\n): { function: { name: string; arguments: unknown } } {\n let argsObj: unknown;\n try {\n argsObj = JSON.parse(tc.arguments || \"{}\");\n } catch {\n logger.warn(`Malformed JSON in fixture tool call arguments for \"${tc.name}\": ${tc.arguments}`);\n argsObj = {};\n }\n return { function: { name: tc.name, arguments: argsObj } };\n}\n\nfunction buildOllamaChatContentWithToolCallsChunks(\n content: string,\n toolCalls: ToolCall[],\n model: string,\n chunkSize: number,\n logger: Logger,\n reasoning?: string,\n blocks?: FixtureBlock[],\n): object[] {\n const chunks: object[] = [];\n const createdAt = new Date().toISOString();\n\n // ── Ordered-blocks path ──────────────────────────────────────────────────\n // When the fixture declares explicit `blocks`, stream NDJSON message chunks\n // following the blocks' ARRAY ORDER: a text block emits a `message.content`\n // delta chunk; a toolCall block emits a chunk carrying `message.tool_calls`.\n // So [toolCall, text] puts the tool_call-bearing chunk before the content\n // chunk. Ollama tool-first ordering is PARTIALLY observable: the chunk order\n // on the wire is honored, but some Ollama clients reassemble content and\n // tool_calls positionally (text first regardless), so downstream order is\n // best-effort. Reasoning chunks (if any) still lead, matching legacy. The\n // legacy single-chunk-all-tools path stays untouched on the else branch.\n if (blocks && blocks.length > 0) {\n const ordered = resolveFixtureBlocks(blocks);\n\n // Reasoning chunks (before everything else), identical to legacy.\n if (reasoning) {\n for (let i = 0; i < reasoning.length; i += chunkSize) {\n const slice = reasoning.slice(i, i + chunkSize);\n chunks.push({\n model,\n created_at: createdAt,\n message: { role: \"assistant\", content: \"\", reasoning_content: slice },\n done: false,\n });\n }\n }\n\n for (const block of ordered) {\n if (block.type === \"text\") {\n for (let i = 0; i < block.text.length; i += chunkSize) {\n const slice = block.text.slice(i, i + chunkSize);\n chunks.push({\n model,\n created_at: createdAt,\n message: { role: \"assistant\", content: slice },\n done: false,\n });\n }\n } else {\n chunks.push({\n model,\n created_at: createdAt,\n message: {\n role: \"assistant\",\n content: \"\",\n tool_calls: [toOllamaToolCall(block, logger)],\n },\n done: false,\n });\n }\n }\n\n // Final chunk — preserved exactly as legacy (done + timing fields).\n chunks.push({\n model,\n created_at: createdAt,\n message: { role: \"assistant\", content: \"\" },\n done: true,\n ...DURATION_FIELDS,\n });\n\n return chunks;\n }\n\n // ── Legacy path (UNCHANGED) ──────────────────────────────────────────────\n // Reasoning chunks (before content)\n if (reasoning) {\n for (let i = 0; i < reasoning.length; i += chunkSize) {\n const slice = reasoning.slice(i, i + chunkSize);\n chunks.push({\n model,\n created_at: createdAt,\n message: { role: \"assistant\", content: \"\", reasoning_content: slice },\n done: false,\n });\n }\n }\n\n // Content chunks first\n for (let i = 0; i < content.length; i += chunkSize) {\n const slice = content.slice(i, i + chunkSize);\n chunks.push({\n model,\n created_at: createdAt,\n message: { role: \"assistant\", content: slice },\n done: false,\n });\n }\n\n // Tool calls in a single chunk (same as tool-call-only path)\n const ollamaToolCalls = 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 function: {\n name: tc.name,\n arguments: argsObj,\n },\n };\n });\n\n chunks.push({\n model,\n created_at: createdAt,\n message: {\n role: \"assistant\",\n content: \"\",\n tool_calls: ollamaToolCalls,\n },\n done: false,\n });\n\n // Final chunk\n chunks.push({\n model,\n created_at: createdAt,\n message: { role: \"assistant\", content: \"\" },\n done: true,\n ...DURATION_FIELDS,\n });\n\n return chunks;\n}\n\n// NOTE (#274): this NON-streaming Ollama builder is intentionally degenerate\n// w.r.t. `blocks` ORDERING. Ollama's non-streaming chat response puts `content`\n// and `tool_calls` in SEPARATE fields on a single `message` object — they are\n// NOT a positionally-observable array, so a tool-first `blocks` fixture cannot\n// be expressed in the wire shape. Block ORDER is therefore a no-op here.\n// The PAYLOAD, however, is still derived from `blocks` when present (the body\n// backfills `content`/`tool_calls` from the blocks — see the comment at the\n// top of the function body); only the relative ordering of those fields is\n// unobservable. (Order-observable surfaces — Claude `content[]`, Gemini\n// `parts[]`, Responses `output[]` — DO honor block order; see those builders.)\nfunction buildOllamaChatContentWithToolCallsResponse(\n content: string,\n toolCalls: ToolCall[],\n model: string,\n logger: Logger,\n reasoning?: string,\n blocks?: FixtureBlock[],\n): object {\n // Blocks-only / blocks-present fixtures: the non-streaming wire shape has no\n // positional array, so order is a no-op here (see NOTE above). But the PAYLOAD\n // must not be dropped: backfill `content` from text blocks (concatenated) and\n // `tool_calls` from toolCall blocks, mirroring what the streaming path derives.\n // Legacy (no-blocks) callers keep byte-identical output.\n if (blocks && blocks.length > 0) {\n const ordered = resolveFixtureBlocks(blocks);\n content = ordered\n .filter((b): b is { type: \"text\"; text: string } => b.type === \"text\")\n .map((b) => b.text)\n .join(\"\");\n toolCalls = ordered\n .filter(\n (b): b is { type: \"toolCall\"; name: string; arguments: string; id?: string } =>\n b.type === \"toolCall\",\n )\n .map((b) => ({ name: b.name, arguments: b.arguments }));\n }\n\n const ollamaToolCalls = 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 function: {\n name: tc.name,\n arguments: argsObj,\n },\n };\n });\n\n return {\n model,\n created_at: new Date().toISOString(),\n message: {\n role: \"assistant\",\n content,\n ...(reasoning ? { reasoning_content: reasoning } : {}),\n tool_calls: ollamaToolCalls,\n },\n done: true,\n ...DURATION_FIELDS,\n };\n}\n\n// ─── Response builders: /api/generate ────────────────────────────────────────\n\nfunction buildOllamaGenerateTextChunks(\n content: string,\n model: string,\n chunkSize: number,\n reasoning?: string,\n): object[] {\n const chunks: object[] = [];\n const createdAt = new Date().toISOString();\n\n // Reasoning chunks (before content)\n if (reasoning) {\n for (let i = 0; i < reasoning.length; i += chunkSize) {\n const slice = reasoning.slice(i, i + chunkSize);\n chunks.push({\n model,\n created_at: createdAt,\n response: \"\",\n reasoning_content: slice,\n done: false,\n });\n }\n }\n\n for (let i = 0; i < content.length; i += chunkSize) {\n const slice = content.slice(i, i + chunkSize);\n chunks.push({\n model,\n created_at: createdAt,\n response: slice,\n done: false,\n });\n }\n\n // Final chunk\n chunks.push({\n model,\n created_at: createdAt,\n response: \"\",\n done: true,\n ...DURATION_FIELDS,\n context: [],\n });\n\n return chunks;\n}\n\nfunction buildOllamaGenerateTextResponse(\n content: string,\n model: string,\n reasoning?: string,\n): object {\n return {\n model,\n created_at: new Date().toISOString(),\n response: content,\n ...(reasoning ? { reasoning_content: reasoning } : {}),\n done: true,\n ...DURATION_FIELDS,\n context: [],\n };\n}\n\n// ─── Request handler: /api/chat ──────────────────────────────────────────────\n\nexport async function handleOllama(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n raw: 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 ?? \"/api/chat\";\n\n let ollamaReq: OllamaRequest;\n try {\n ollamaReq = JSON.parse(raw) as OllamaRequest;\n } catch (parseErr) {\n const detail = parseErr instanceof Error ? parseErr.message : \"unknown\";\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 body: ${detail}`,\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n if (!ollamaReq.messages || !Array.isArray(ollamaReq.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 = ollamaToCompletionRequest(ollamaReq);\n completionReq._endpointType = \"chat\";\n completionReq._context = getContext(req);\n\n const testId = getTestId(req);\n const { fixture, skippedBySequenceOrTurn } = matchFixtureDiagnostic(\n fixtures,\n completionReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n // Record mode proxies on a miss to capture a fresh turn (see record gate\n // below), so keep turnIndex strict to prevent an earlier-turn fixture from\n // shadowing a longer request and skipping the new turn's recording.\n recordMatchOptions(!!defaults.record, defaults.logger),\n );\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);\n } else {\n logger.debug(`No fixture matched for request`);\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 const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);\n if (effectiveStrict) {\n const strictStatus = 503;\n const strictMessage = strictNoMatchMessage(skippedBySequenceOrTurn);\n logger.error(strictNoMatchLogLine(req.method ?? \"POST\", urlPath, skippedBySequenceOrTurn));\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: {\n status: strictStatus,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\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 if (defaults.record) {\n const outcome = await proxyAndRecord(\n req,\n res,\n completionReq,\n \"ollama\",\n urlPath,\n fixtures,\n defaults,\n raw,\n );\n if (outcome === \"handled_by_hook\") return;\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, source: \"proxy\" },\n });\n return;\n }\n }\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: {\n status: 404,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n writeErrorResponse(\n res,\n 404,\n JSON.stringify({\n error: {\n message: \"No fixture matched\",\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n const response = await resolveResponse(fixture, completionReq);\n const latency = fixture.latency ?? defaults.latency;\n const chunkSize = Math.max(1, fixture.chunkSize ?? defaults.chunkSize);\n\n // Ollama defaults to streaming when stream is absent or true\n const streaming = ollamaReq.stream !== false;\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, serializeErrorResponse(response), {\n retryAfter: response.retryAfter,\n });\n return;\n }\n\n // Content + tool calls response (must be checked before text/tool-only branches)\n if (isContentWithToolCallsResponse(response)) {\n if (response.webSearches?.length) {\n logger.warn(\"webSearches in fixture response are not supported for Ollama API -- ignoring\");\n }\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 // Gate reasoning emission on the requested model's capability (aimock#254).\n const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);\n const effReasoning = resolveReasoningForModel(\n response.reasoning,\n completionReq.model,\n effectiveStrict,\n logger,\n );\n if (!streaming) {\n const body = buildOllamaChatContentWithToolCallsResponse(\n response.content ?? \"\",\n response.toolCalls ?? [],\n completionReq.model,\n logger,\n effReasoning,\n response.blocks,\n );\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const chunks = buildOllamaChatContentWithToolCallsChunks(\n response.content ?? \"\",\n response.toolCalls ?? [],\n completionReq.model,\n chunkSize,\n logger,\n effReasoning,\n response.blocks,\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeNDJSONStream(res, chunks, {\n latency,\n streamingProfile: fixture.streamingProfile,\n recordedTimings: fixture.recordedTimings,\n replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,\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 }\n return;\n }\n\n // Text response\n if (isTextResponse(response)) {\n if (response.webSearches?.length) {\n logger.warn(\"webSearches in fixture response are not supported for Ollama API -- ignoring\");\n }\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 // Gate reasoning emission on the requested model's capability (aimock#254).\n const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);\n const effReasoning = resolveReasoningForModel(\n response.reasoning,\n completionReq.model,\n effectiveStrict,\n logger,\n );\n if (!streaming) {\n const body = buildOllamaChatTextResponse(response.content, completionReq.model, effReasoning);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const chunks = buildOllamaChatTextChunks(\n response.content,\n completionReq.model,\n chunkSize,\n effReasoning,\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeNDJSONStream(res, chunks, {\n latency,\n streamingProfile: fixture.streamingProfile,\n recordedTimings: fixture.recordedTimings,\n replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,\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 }\n return;\n }\n\n // Tool call response\n if (isToolCallResponse(response)) {\n if (response.webSearches?.length) {\n logger.warn(\"webSearches in fixture response are not supported for Ollama API — ignoring\");\n }\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 // Gate reasoning emission on the requested model's capability (aimock#254).\n const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);\n const effReasoning = resolveReasoningForModel(\n response.reasoning,\n completionReq.model,\n effectiveStrict,\n logger,\n );\n if (!streaming) {\n const body = buildOllamaChatToolCallResponse(\n response.toolCalls,\n completionReq.model,\n logger,\n effReasoning,\n );\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const chunks = buildOllamaChatToolCallChunks(\n response.toolCalls,\n completionReq.model,\n chunkSize,\n logger,\n effReasoning,\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeNDJSONStream(res, chunks, {\n latency,\n streamingProfile: fixture.streamingProfile,\n recordedTimings: fixture.recordedTimings,\n replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,\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 }\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// ─── Request handler: /api/generate ──────────────────────────────────────────\n\nexport async function handleOllamaGenerate(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n raw: string,\n fixtures: Fixture[],\n journal: Journal,\n defaults: HandlerDefaults,\n setCorsHeaders: (res: http.ServerResponse) => void,\n): Promise<void> {\n setCorsHeaders(res);\n\n const urlPath = req.url ?? \"/api/generate\";\n\n let generateReq: OllamaGenerateRequest;\n try {\n generateReq = JSON.parse(raw) as OllamaGenerateRequest;\n } catch (parseErr) {\n const detail = parseErr instanceof Error ? parseErr.message : \"unknown\";\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 body: ${detail}`,\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n if (!generateReq.prompt || typeof generateReq.prompt !== \"string\") {\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: prompt field is required\",\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n // Convert to ChatCompletionRequest for fixture matching\n const completionReq = ollamaGenerateToCompletionRequest(generateReq);\n completionReq._endpointType = \"chat\";\n completionReq._context = getContext(req);\n\n const testId = getTestId(req);\n const { fixture, skippedBySequenceOrTurn } = matchFixtureDiagnostic(\n fixtures,\n completionReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n // Record mode proxies on a miss to capture a fresh turn (see record gate\n // below), so keep turnIndex strict to prevent an earlier-turn fixture from\n // shadowing a longer request and skipping the new turn's recording.\n recordMatchOptions(!!defaults.record, defaults.logger),\n );\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n defaults.logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);\n } else {\n defaults.logger.debug(`No fixture matched for request`);\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 const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);\n if (effectiveStrict) {\n const strictStatus = 503;\n const strictMessage = strictNoMatchMessage(skippedBySequenceOrTurn);\n defaults.logger.error(\n strictNoMatchLogLine(req.method ?? \"POST\", urlPath, skippedBySequenceOrTurn),\n );\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: {\n status: strictStatus,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\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 if (defaults.record) {\n const outcome = await proxyAndRecord(\n req,\n res,\n completionReq,\n \"ollama\",\n urlPath,\n fixtures,\n defaults,\n raw,\n );\n if (outcome === \"handled_by_hook\") return;\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, source: \"proxy\" },\n });\n return;\n }\n }\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: {\n status: 404,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n writeErrorResponse(\n res,\n 404,\n JSON.stringify({\n error: {\n message: \"No fixture matched\",\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n const response = await resolveResponse(fixture, completionReq);\n const latency = fixture.latency ?? defaults.latency;\n const chunkSize = Math.max(1, fixture.chunkSize ?? defaults.chunkSize);\n\n // Ollama defaults to streaming when stream is absent or true\n const streaming = generateReq.stream !== false;\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, serializeErrorResponse(response), {\n retryAfter: response.retryAfter,\n });\n return;\n }\n\n // Text response (only type supported for /api/generate)\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 // Gate reasoning emission on the requested model's capability (aimock#254).\n const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);\n const effReasoning = resolveReasoningForModel(\n response.reasoning,\n completionReq.model,\n effectiveStrict,\n defaults.logger,\n );\n if (!streaming) {\n const body = buildOllamaGenerateTextResponse(\n response.content,\n completionReq.model,\n effReasoning,\n );\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const chunks = buildOllamaGenerateTextChunks(\n response.content,\n completionReq.model,\n chunkSize,\n effReasoning,\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeNDJSONStream(res, chunks, {\n latency,\n streamingProfile: fixture.streamingProfile,\n recordedTimings: fixture.recordedTimings,\n replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,\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 }\n return;\n }\n\n // Tool call fixtures matched but not supported on /api/generate\n if (isToolCallResponse(response) || isContentWithToolCallsResponse(response)) {\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 400, fixture },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({\n error: {\n message: \"Tool call fixtures are not supported on /api/generate — use /api/chat instead\",\n type: \"invalid_request_error\",\n },\n }),\n );\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// ─── Ollama embeddings request type ─────────────────────────────────────────\n\ninterface OllamaEmbeddingsRequest {\n model: string;\n prompt?: string;\n input?: string | string[];\n}\n\n// ─── Request handler: /api/embeddings ───────────────────────────────────────\n\nexport async function handleOllamaEmbeddings(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n raw: 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 ?? \"/api/embeddings\";\n\n let embReq: OllamaEmbeddingsRequest;\n try {\n embReq = JSON.parse(raw) as OllamaEmbeddingsRequest;\n } catch (parseErr) {\n const detail = parseErr instanceof Error ? parseErr.message : \"unknown\";\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 body: ${detail}`,\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n // Ollama accepts either \"prompt\" or \"input\" for the text to embed\n const inputText =\n embReq.prompt ??\n (typeof embReq.input === \"string\" ? embReq.input : undefined) ??\n (Array.isArray(embReq.input) ? embReq.input.join(\" \") : undefined);\n\n if (!embReq.model) {\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: model field is required\",\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n if (!inputText) {\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: prompt or input field is required\",\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n // Build synthetic ChatCompletionRequest for the fixture router\n const syntheticReq: ChatCompletionRequest = {\n model: embReq.model,\n messages: [],\n embeddingInput: inputText,\n _endpointType: \"embedding\",\n _context: getContext(req),\n };\n\n const testId = getTestId(req);\n const { fixture, skippedBySequenceOrTurn } = matchFixtureDiagnostic(\n fixtures,\n syntheticReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n // Record mode proxies on a miss to capture a fresh turn (see record gate\n // below), so keep turnIndex strict to prevent an earlier-turn fixture from\n // shadowing a longer request and skipping the new turn's recording.\n recordMatchOptions(!!defaults.record, defaults.logger),\n );\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);\n } else {\n logger.debug(`No fixture matched for request`);\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: syntheticReq,\n },\n fixture ? \"fixture\" : \"proxy\",\n defaults.registry,\n defaults.logger,\n )\n )\n return;\n\n if (fixture) {\n const response = await resolveResponse(fixture, syntheticReq);\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: syntheticReq,\n response: { status, fixture },\n });\n writeErrorResponse(res, status, serializeErrorResponse(response), {\n retryAfter: response.retryAfter,\n });\n return;\n }\n\n // Embedding response — use the fixture's embedding\n if (isEmbeddingResponse(response)) {\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 200, fixture },\n });\n const body = {\n model: embReq.model,\n embedding: [...response.embedding],\n };\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n return;\n }\n\n // Fixture matched but response type is not compatible with embeddings\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 500, fixture },\n });\n writeErrorResponse(\n res,\n 500,\n JSON.stringify({\n error: {\n message:\n \"Fixture response did not match any known embedding type (must have embedding or error)\",\n type: \"server_error\",\n },\n }),\n );\n return;\n }\n\n // No fixture match — check strict mode first, then try proxy\n const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);\n if (effectiveStrict) {\n const strictMessage = strictNoMatchMessage(skippedBySequenceOrTurn);\n logger.error(strictNoMatchLogLine(req.method ?? \"POST\", urlPath, skippedBySequenceOrTurn));\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: {\n status: 503,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n writeErrorResponse(\n res,\n 503,\n JSON.stringify({\n error: {\n message: strictMessage,\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n if (defaults.record) {\n const outcome = await proxyAndRecord(\n req,\n res,\n syntheticReq,\n \"ollama\",\n urlPath,\n fixtures,\n defaults,\n raw,\n );\n if (outcome === \"handled_by_hook\") return;\n if (outcome !== \"not_configured\") {\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: res.statusCode ?? 200, fixture: null, source: \"proxy\" },\n });\n return;\n }\n }\n\n // No fixture match — generate deterministic embedding from input text\n logger.warn(\n `No embedding fixture matched for \"${inputText.slice(0, 80)}\" — returning deterministic fallback`,\n );\n const embedding = generateDeterministicEmbedding(inputText);\n\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 200, fixture: null },\n });\n\n const body = {\n model: embReq.model,\n embedding,\n };\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
const require_runtime = require('./_virtual/_rolldown/runtime.cjs');
|
|
2
|
-
const require_constants = require('./constants.cjs');
|
|
3
2
|
const require_helpers = require('./helpers.cjs');
|
|
4
3
|
const require_router = require('./router.cjs');
|
|
5
4
|
const require_sse_writer = require('./sse-writer.cjs');
|
|
@@ -7,6 +6,7 @@ const require_chaos = require('./chaos.cjs');
|
|
|
7
6
|
const require_url = require('./url.cjs');
|
|
8
7
|
const require_recorder = require('./recorder.cjs');
|
|
9
8
|
const require_fal = require('./fal.cjs');
|
|
9
|
+
const require_video_proxy_shared = require('./video-proxy-shared.cjs');
|
|
10
10
|
let node_crypto = require("node:crypto");
|
|
11
11
|
node_crypto = require_runtime.__toESM(node_crypto);
|
|
12
12
|
|
|
@@ -100,61 +100,6 @@ function advanceJob(job) {
|
|
|
100
100
|
else if (job.pollCount >= job.pollsBeforeCompleted) job.status = terminalStatus(job);
|
|
101
101
|
}
|
|
102
102
|
/**
|
|
103
|
-
* First non-empty value of a possibly array-typed, possibly comma-joined
|
|
104
|
-
* header. An empty header value or a leading-comma list (", host") would
|
|
105
|
-
* otherwise yield "" — triggering a spurious rejection warn and discarding
|
|
106
|
-
* valid later entries — so empty segments are skipped.
|
|
107
|
-
*/
|
|
108
|
-
function firstForwardedValue(header) {
|
|
109
|
-
const raw = Array.isArray(header) ? header.join(",") : header;
|
|
110
|
-
if (raw === void 0) return void 0;
|
|
111
|
-
for (const segment of raw.split(",")) {
|
|
112
|
-
const trimmed = segment.trim();
|
|
113
|
-
if (trimmed) return trimmed;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
const FORWARDED_HOST_RE = /^[a-zA-Z0-9._-]+(:\d+)?$/;
|
|
117
|
-
const FORWARDED_HOST_IPV6_RE = /^\[[0-9a-fA-F:.]+\](:\d+)?$/;
|
|
118
|
-
function requestBase(req, logger) {
|
|
119
|
-
const candidate = firstForwardedValue(req.headers["x-forwarded-proto"])?.toLowerCase();
|
|
120
|
-
const proto = candidate === "http" || candidate === "https" ? candidate : "http";
|
|
121
|
-
const fwdHost = firstForwardedValue(req.headers["x-forwarded-host"]);
|
|
122
|
-
const rawHost = req.headers.host;
|
|
123
|
-
let host = rawHost !== void 0 && (FORWARDED_HOST_RE.test(rawHost) || FORWARDED_HOST_IPV6_RE.test(rawHost)) ? rawHost : "localhost";
|
|
124
|
-
if (fwdHost !== void 0) if (FORWARDED_HOST_RE.test(fwdHost) || FORWARDED_HOST_IPV6_RE.test(fwdHost)) host = fwdHost;
|
|
125
|
-
else logger.warn(`x-forwarded-host value rejected, falling back to Host header: ${JSON.stringify(fwdHost.slice(0, 100))}`);
|
|
126
|
-
return `${proto}://${host}`;
|
|
127
|
-
}
|
|
128
|
-
/**
|
|
129
|
-
* Query-string suffix embedding the request's testId into generated URLs
|
|
130
|
-
* (polling_url, unsigned_urls). The @openrouter/sdk fetches these URLs with
|
|
131
|
-
* standard Authorization but no aimock-specific headers (no x-test-id) — so
|
|
132
|
-
* the testId must travel in the URL for getTestId's `?testId=` fallback to
|
|
133
|
-
* resolve the right job scope. The default testId is omitted to keep
|
|
134
|
-
* single-tenant URLs clean.
|
|
135
|
-
*/
|
|
136
|
-
function testIdSuffix(testId, sep) {
|
|
137
|
-
return testId === require_constants.DEFAULT_TEST_ID ? "" : `${sep}testId=${encodeURIComponent(testId)}`;
|
|
138
|
-
}
|
|
139
|
-
/** Default upstream timeout for the live lifecycle proxy (matches recorder.ts). */
|
|
140
|
-
const DEFAULT_UPSTREAM_TIMEOUT_MS = 3e4;
|
|
141
|
-
/**
|
|
142
|
-
* Abort signal for the SMALL-JSON upstream fetches on this surface (submit,
|
|
143
|
-
* status poll, models listing), honoring `record.upstreamTimeoutMs` with the
|
|
144
|
-
* same clamp conventions as recorder.ts (non-finite / non-positive values
|
|
145
|
-
* fall back to the 30s default). Note the nuance (documented on
|
|
146
|
-
* RecordConfig.upstreamTimeoutMs): these envelope-sized fetches use the value
|
|
147
|
-
* as a TOTAL deadline via AbortSignal.timeout — indistinguishable from a
|
|
148
|
-
* socket-idle timeout for small bodies. The byte-bearing content fetches use
|
|
149
|
-
* `fetchHeadersWithTimeout` + `readBodyIdle` instead, which implement true
|
|
150
|
-
* idle semantics. An abort rejects the fetch and surfaces through the
|
|
151
|
-
* caller's existing failure path (502 proxy_error on submit/poll, models
|
|
152
|
-
* synthesis fallback).
|
|
153
|
-
*/
|
|
154
|
-
function upstreamTimeoutSignal(record) {
|
|
155
|
-
return AbortSignal.timeout(require_recorder.clampTimeout(record?.upstreamTimeoutMs, DEFAULT_UPSTREAM_TIMEOUT_MS));
|
|
156
|
-
}
|
|
157
|
-
/**
|
|
158
103
|
* Fetch a byte-bearing upstream URL with `record.upstreamTimeoutMs` gating
|
|
159
104
|
* only the HEADERS: the abort timer is cleared the moment the response head
|
|
160
105
|
* arrives, so a long — but steadily progressing — body download is never
|
|
@@ -162,7 +107,7 @@ function upstreamTimeoutSignal(record) {
|
|
|
162
107
|
* `readBodyIdle` (between-chunk idle semantics).
|
|
163
108
|
*/
|
|
164
109
|
async function fetchHeadersWithTimeout(url, headers, record) {
|
|
165
|
-
const timeoutMs = require_recorder.clampTimeout(record?.upstreamTimeoutMs, DEFAULT_UPSTREAM_TIMEOUT_MS);
|
|
110
|
+
const timeoutMs = require_recorder.clampTimeout(record?.upstreamTimeoutMs, require_video_proxy_shared.DEFAULT_UPSTREAM_TIMEOUT_MS);
|
|
166
111
|
const controller = new AbortController();
|
|
167
112
|
const timer = setTimeout(() => controller.abort(/* @__PURE__ */ new Error(`Upstream response headers timed out after ${timeoutMs}ms`)), timeoutMs);
|
|
168
113
|
try {
|
|
@@ -175,50 +120,6 @@ async function fetchHeadersWithTimeout(url, headers, record) {
|
|
|
175
120
|
}
|
|
176
121
|
}
|
|
177
122
|
/**
|
|
178
|
-
* Buffer a fetch Response body with IDLE-based timeout semantics: a timer
|
|
179
|
-
* clamped from `record.bodyTimeoutMs` (30s default — same clamp as
|
|
180
|
-
* recorder.ts) is re-armed for every chunk, so a steadily-dripping body of
|
|
181
|
-
* any total duration completes and only a silent mid-body stall rejects.
|
|
182
|
-
* When `cap` > 0 the byte count is enforced DURING the read: on exceed the
|
|
183
|
-
* stream is cancelled and `{ overCap: true }` is returned with NOTHING
|
|
184
|
-
* oversized retained in memory — the cap is a memory guard as much as a
|
|
185
|
-
* disk guard.
|
|
186
|
-
*/
|
|
187
|
-
async function readBodyIdle(res, record, cap = 0) {
|
|
188
|
-
const body = res.body;
|
|
189
|
-
if (!body) return {
|
|
190
|
-
overCap: false,
|
|
191
|
-
buf: Buffer.alloc(0)
|
|
192
|
-
};
|
|
193
|
-
const idleMs = require_recorder.clampTimeout(record?.bodyTimeoutMs, DEFAULT_UPSTREAM_TIMEOUT_MS);
|
|
194
|
-
const reader = body.getReader();
|
|
195
|
-
const chunks = [];
|
|
196
|
-
let total = 0;
|
|
197
|
-
try {
|
|
198
|
-
for (;;) {
|
|
199
|
-
let idleTimer;
|
|
200
|
-
const readPromise = reader.read();
|
|
201
|
-
readPromise.catch(() => {});
|
|
202
|
-
const result = await Promise.race([readPromise, new Promise((_, reject) => {
|
|
203
|
-
idleTimer = setTimeout(() => reject(/* @__PURE__ */ new Error(`Upstream response body idle for ${idleMs}ms`)), idleMs);
|
|
204
|
-
})]).finally(() => clearTimeout(idleTimer));
|
|
205
|
-
if (result.done) break;
|
|
206
|
-
total += result.value.byteLength;
|
|
207
|
-
if (cap > 0 && total > cap) return {
|
|
208
|
-
overCap: true,
|
|
209
|
-
bytesRead: total
|
|
210
|
-
};
|
|
211
|
-
chunks.push(Buffer.from(result.value));
|
|
212
|
-
}
|
|
213
|
-
} finally {
|
|
214
|
-
reader.cancel().catch(() => {});
|
|
215
|
-
}
|
|
216
|
-
return {
|
|
217
|
-
overCap: false,
|
|
218
|
-
buf: Buffer.concat(chunks)
|
|
219
|
-
};
|
|
220
|
-
}
|
|
221
|
-
/**
|
|
222
123
|
* Cap on an upstream ERROR body (401/403 relays) buffered by the content
|
|
223
124
|
* proxy. Error bodies are envelope-sized JSON in practice — 64 KB is generous
|
|
224
125
|
* — and unlike the video bytes they are buffered in full before the relay, so
|
|
@@ -226,26 +127,6 @@ async function readBodyIdle(res, record, cap = 0) {
|
|
|
226
127
|
*/
|
|
227
128
|
const OPENROUTER_VIDEO_ERROR_BODY_CAP = 64 * 1024;
|
|
228
129
|
/**
|
|
229
|
-
* Cap on the small-JSON upstream envelope bodies (submit, status poll, models
|
|
230
|
-
* listing) buffered before parse/relay. Envelopes are KB-scale in practice —
|
|
231
|
-
* 1 MB is generous headroom even for a large models listing — and, like the
|
|
232
|
-
* error-body cap above, they are buffered in full, so an unbounded `text()`
|
|
233
|
-
* would be a memory hole on a hostile upstream.
|
|
234
|
-
*/
|
|
235
|
-
const OPENROUTER_VIDEO_ENVELOPE_BODY_CAP = 1024 * 1024;
|
|
236
|
-
/**
|
|
237
|
-
* Bounded read of a small-JSON upstream envelope body (submit, status poll,
|
|
238
|
-
* models listing): readBodyIdle's idle semantics plus the envelope cap. An
|
|
239
|
-
* over-cap body is a hard failure — the throw surfaces through each caller's
|
|
240
|
-
* existing failure path (502 proxy_error on submit/poll, models synthesis
|
|
241
|
-
* fallback) without retaining anything oversized in memory.
|
|
242
|
-
*/
|
|
243
|
-
async function readEnvelopeText(res, record) {
|
|
244
|
-
const read = await readBodyIdle(res, record, OPENROUTER_VIDEO_ENVELOPE_BODY_CAP);
|
|
245
|
-
if (read.overCap) throw new Error(`Upstream envelope body exceeded ${OPENROUTER_VIDEO_ENVELOPE_BODY_CAP} bytes (read aborted at ${read.bytesRead})`);
|
|
246
|
-
return read.buf.toString("utf8");
|
|
247
|
-
}
|
|
248
|
-
/**
|
|
249
130
|
* Rewrite an upstream poll body for relay to the client. Every field is
|
|
250
131
|
* relayed verbatim EXCEPT the ones that would let the client escape the mock
|
|
251
132
|
* or learn upstream identifiers:
|
|
@@ -261,13 +142,13 @@ async function readEnvelopeText(res, record) {
|
|
|
261
142
|
*/
|
|
262
143
|
function rewriteRecordPollBody(args) {
|
|
263
144
|
const { upstreamBody, job, req, testId, logger } = args;
|
|
264
|
-
const base = requestBase(req, logger);
|
|
145
|
+
const base = require_video_proxy_shared.requestBase(req, logger);
|
|
265
146
|
const body = {
|
|
266
147
|
...upstreamBody,
|
|
267
148
|
id: job.jobId
|
|
268
149
|
};
|
|
269
|
-
if (upstreamBody.polling_url !== void 0) body.polling_url = `${base}/api/v1/videos/${job.jobId}${testIdSuffix(testId, "?")}`;
|
|
270
|
-
if (Array.isArray(upstreamBody.unsigned_urls)) body.unsigned_urls = upstreamBody.unsigned_urls.map((_url, i) => `${base}/api/v1/videos/${job.jobId}/content?index=${i}${testIdSuffix(testId, "&")}`);
|
|
150
|
+
if (upstreamBody.polling_url !== void 0) body.polling_url = `${base}/api/v1/videos/${job.jobId}${require_video_proxy_shared.testIdSuffix(testId, "?")}`;
|
|
151
|
+
if (Array.isArray(upstreamBody.unsigned_urls)) body.unsigned_urls = upstreamBody.unsigned_urls.map((_url, i) => `${base}/api/v1/videos/${job.jobId}/content?index=${i}${require_video_proxy_shared.testIdSuffix(testId, "&")}`);
|
|
271
152
|
else if (upstreamBody.unsigned_urls !== void 0) {
|
|
272
153
|
delete body.unsigned_urls;
|
|
273
154
|
if (!job.nonArrayUrlsWarned) {
|
|
@@ -389,7 +270,7 @@ async function handleOpenRouterVideoStatus(req, res, jobId, fixtures, journal, d
|
|
|
389
270
|
status: job.status
|
|
390
271
|
};
|
|
391
272
|
if (job.status === "completed") {
|
|
392
|
-
body.unsigned_urls = [`${requestBase(req, defaults.logger)}/api/v1/videos/${job.jobId}/content?index=0${testIdSuffix(testId, "&")}`];
|
|
273
|
+
body.unsigned_urls = [`${require_video_proxy_shared.requestBase(req, defaults.logger)}/api/v1/videos/${job.jobId}/content?index=0${require_video_proxy_shared.testIdSuffix(testId, "&")}`];
|
|
393
274
|
body.usage = { cost: job.video.cost ?? 0 };
|
|
394
275
|
} else if (job.status === "failed") {
|
|
395
276
|
if (job.video.error === "" && !job.emptyErrorWarned) {
|
|
@@ -657,7 +538,7 @@ async function proxyOpenRouterVideoRecordContent(args) {
|
|
|
657
538
|
if (contentRes.status === 401 || contentRes.status === 403) {
|
|
658
539
|
let errBody;
|
|
659
540
|
try {
|
|
660
|
-
const read = await readBodyIdle(contentRes, record, OPENROUTER_VIDEO_ERROR_BODY_CAP);
|
|
541
|
+
const read = await require_video_proxy_shared.readBodyIdle(contentRes, record, OPENROUTER_VIDEO_ERROR_BODY_CAP);
|
|
661
542
|
if (read.overCap) {
|
|
662
543
|
logger.warn(`Upstream ${contentRes.status} error body for job ${job.upstreamJobId} exceeded ${OPENROUTER_VIDEO_ERROR_BODY_CAP} bytes — relaying the status with an empty body`);
|
|
663
544
|
errBody = Buffer.alloc(0);
|
|
@@ -687,7 +568,7 @@ async function proxyOpenRouterVideoRecordContent(args) {
|
|
|
687
568
|
if (!contentRes.ok) {
|
|
688
569
|
let sample = "";
|
|
689
570
|
try {
|
|
690
|
-
const read = await readBodyIdle(contentRes, record, OPENROUTER_VIDEO_ERROR_BODY_CAP);
|
|
571
|
+
const read = await require_video_proxy_shared.readBodyIdle(contentRes, record, OPENROUTER_VIDEO_ERROR_BODY_CAP);
|
|
691
572
|
if (!read.overCap) sample = read.buf.toString("utf8").slice(0, 200);
|
|
692
573
|
} catch {}
|
|
693
574
|
proxyError(`Content ${contentRes.status}${sample ? `: ${sample}` : ""}`);
|
|
@@ -716,7 +597,7 @@ async function proxyOpenRouterVideoRecordContent(args) {
|
|
|
716
597
|
res.end();
|
|
717
598
|
return;
|
|
718
599
|
}
|
|
719
|
-
const idleMs = require_recorder.clampTimeout(record.bodyTimeoutMs, DEFAULT_UPSTREAM_TIMEOUT_MS);
|
|
600
|
+
const idleMs = require_recorder.clampTimeout(record.bodyTimeoutMs, require_video_proxy_shared.DEFAULT_UPSTREAM_TIMEOUT_MS);
|
|
720
601
|
const reader = body.getReader();
|
|
721
602
|
try {
|
|
722
603
|
for (;;) {
|
|
@@ -816,9 +697,9 @@ async function handleOpenRouterVideoModels(req, res, fixtures, journal, defaults
|
|
|
816
697
|
const target = require_url.resolveUpstreamUrl(upstreamBase, "/api/v1/videos/models");
|
|
817
698
|
const upstreamRes = await fetch(target, {
|
|
818
699
|
headers: require_recorder.buildForwardHeaders(req),
|
|
819
|
-
signal: upstreamTimeoutSignal(record)
|
|
700
|
+
signal: require_video_proxy_shared.upstreamTimeoutSignal(record)
|
|
820
701
|
});
|
|
821
|
-
const text = await readEnvelopeText(upstreamRes, record);
|
|
702
|
+
const text = await require_video_proxy_shared.readEnvelopeText(upstreamRes, record);
|
|
822
703
|
if (!upstreamRes.ok) throw new Error(`Models ${upstreamRes.status}: ${text.slice(0, 200)}`);
|
|
823
704
|
relay = {
|
|
824
705
|
text,
|
|
@@ -1095,7 +976,7 @@ async function handleOpenRouterVideoCreate(req, res, raw, fixtures, journal, def
|
|
|
1095
976
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1096
977
|
res.end(JSON.stringify({
|
|
1097
978
|
id: jobId,
|
|
1098
|
-
polling_url: `${requestBase(req, defaults.logger)}/api/v1/videos/${jobId}${testIdSuffix(testId, "?")}`,
|
|
979
|
+
polling_url: `${require_video_proxy_shared.requestBase(req, defaults.logger)}/api/v1/videos/${jobId}${require_video_proxy_shared.testIdSuffix(testId, "?")}`,
|
|
1099
980
|
status: "pending"
|
|
1100
981
|
}));
|
|
1101
982
|
}
|
|
@@ -1154,12 +1035,12 @@ async function proxyOpenRouterVideoSubmit(args) {
|
|
|
1154
1035
|
method: "POST",
|
|
1155
1036
|
headers: require_recorder.buildForwardHeaders(req),
|
|
1156
1037
|
body: raw,
|
|
1157
|
-
signal: upstreamTimeoutSignal(record)
|
|
1038
|
+
signal: require_video_proxy_shared.upstreamTimeoutSignal(record)
|
|
1158
1039
|
});
|
|
1159
1040
|
fetched = {
|
|
1160
1041
|
status: upstreamRes.status,
|
|
1161
1042
|
contentType: upstreamRes.headers.get("content-type"),
|
|
1162
|
-
text: await readEnvelopeText(upstreamRes, record)
|
|
1043
|
+
text: await require_video_proxy_shared.readEnvelopeText(upstreamRes, record)
|
|
1163
1044
|
};
|
|
1164
1045
|
} catch (err) {
|
|
1165
1046
|
return proxyError(err instanceof Error ? err.message : "Unknown proxy error");
|
|
@@ -1236,7 +1117,7 @@ async function proxyOpenRouterVideoSubmit(args) {
|
|
|
1236
1117
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1237
1118
|
res.end(JSON.stringify({
|
|
1238
1119
|
id: jobId,
|
|
1239
|
-
polling_url: `${requestBase(req, defaults.logger)}/api/v1/videos/${jobId}${testIdSuffix(testId, "?")}`,
|
|
1120
|
+
polling_url: `${require_video_proxy_shared.requestBase(req, defaults.logger)}/api/v1/videos/${jobId}${require_video_proxy_shared.testIdSuffix(testId, "?")}`,
|
|
1240
1121
|
status: "pending"
|
|
1241
1122
|
}));
|
|
1242
1123
|
return "handled";
|
|
@@ -1312,12 +1193,12 @@ async function proxyOpenRouterVideoRecordPoll(args) {
|
|
|
1312
1193
|
try {
|
|
1313
1194
|
const upstreamRes = await fetch(job.upstreamPollingUrl, {
|
|
1314
1195
|
headers: require_recorder.buildForwardHeaders(req),
|
|
1315
|
-
signal: upstreamTimeoutSignal(record)
|
|
1196
|
+
signal: require_video_proxy_shared.upstreamTimeoutSignal(record)
|
|
1316
1197
|
});
|
|
1317
1198
|
fetched = {
|
|
1318
1199
|
status: upstreamRes.status,
|
|
1319
1200
|
contentType: upstreamRes.headers.get("content-type"),
|
|
1320
|
-
text: await readEnvelopeText(upstreamRes, record)
|
|
1201
|
+
text: await require_video_proxy_shared.readEnvelopeText(upstreamRes, record)
|
|
1321
1202
|
};
|
|
1322
1203
|
} catch (err) {
|
|
1323
1204
|
proxyError(err instanceof Error ? err.message : "Unknown proxy error");
|
|
@@ -1536,7 +1417,7 @@ async function captureOpenRouterVideoRecordFixture(args) {
|
|
|
1536
1417
|
if (!contentRes.ok) {
|
|
1537
1418
|
let sample = "";
|
|
1538
1419
|
try {
|
|
1539
|
-
const read = await readBodyIdle(contentRes, record, OPENROUTER_VIDEO_ERROR_BODY_CAP);
|
|
1420
|
+
const read = await require_video_proxy_shared.readBodyIdle(contentRes, record, OPENROUTER_VIDEO_ERROR_BODY_CAP);
|
|
1540
1421
|
if (!read.overCap) sample = read.buf.toString("utf8").slice(0, 200);
|
|
1541
1422
|
} catch {}
|
|
1542
1423
|
throw new Error(`Content ${contentRes.status}${sample ? `: ${sample}` : ""}`);
|
|
@@ -1547,7 +1428,7 @@ async function captureOpenRouterVideoRecordFixture(args) {
|
|
|
1547
1428
|
logger.warn(`Captured video for job ${job.upstreamJobId} declares Content-Length ${declaredLength} — over maxContentBytes (${cap}); skipping the download; fixture persisted WITHOUT b64 (content serves the placeholder MP4)`);
|
|
1548
1429
|
warnings.push(`Declared content length (${declaredLength} bytes) exceeded maxContentBytes (${cap}) — download skipped, b64 omitted`);
|
|
1549
1430
|
} else {
|
|
1550
|
-
const read = await readBodyIdle(contentRes, record, cap);
|
|
1431
|
+
const read = await require_video_proxy_shared.readBodyIdle(contentRes, record, cap);
|
|
1551
1432
|
if (read.overCap) {
|
|
1552
1433
|
logger.warn(`Captured video for job ${job.upstreamJobId} streamed past maxContentBytes (${cap}) — download aborted at ${read.bytesRead} bytes; fixture persisted WITHOUT b64 (content serves the placeholder MP4)`);
|
|
1553
1434
|
warnings.push(`Captured video exceeded maxContentBytes (${cap}) — download aborted, b64 omitted`);
|