@copilotkit/aimock 1.26.1 → 1.27.1
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 +29 -0
- package/dist/agui-handler.cjs +36 -3
- package/dist/agui-handler.cjs.map +1 -1
- package/dist/agui-handler.d.cts +9 -1
- package/dist/agui-handler.d.cts.map +1 -1
- package/dist/agui-handler.d.ts +9 -1
- package/dist/agui-handler.d.ts.map +1 -1
- package/dist/agui-handler.js +36 -4
- package/dist/agui-handler.js.map +1 -1
- package/dist/agui-mock.cjs +14 -0
- package/dist/agui-mock.cjs.map +1 -1
- package/dist/agui-mock.d.cts +1 -0
- package/dist/agui-mock.d.cts.map +1 -1
- package/dist/agui-mock.d.ts +1 -0
- package/dist/agui-mock.d.ts.map +1 -1
- package/dist/agui-mock.js +14 -0
- package/dist/agui-mock.js.map +1 -1
- package/dist/agui-recorder.cjs +49 -21
- package/dist/agui-recorder.cjs.map +1 -1
- package/dist/agui-recorder.d.cts +0 -1
- package/dist/agui-recorder.d.cts.map +1 -1
- package/dist/agui-recorder.d.ts +0 -1
- package/dist/agui-recorder.d.ts.map +1 -1
- package/dist/agui-recorder.js +50 -22
- package/dist/agui-recorder.js.map +1 -1
- package/dist/agui-stub.cjs +1 -0
- package/dist/agui-stub.d.cts +3 -3
- package/dist/agui-stub.d.ts +3 -3
- package/dist/agui-stub.js +2 -2
- package/dist/agui-types.d.cts +10 -2
- package/dist/agui-types.d.cts.map +1 -1
- package/dist/agui-types.d.ts +10 -2
- package/dist/agui-types.d.ts.map +1 -1
- package/dist/config-loader.cjs +25 -11
- package/dist/config-loader.cjs.map +1 -1
- package/dist/config-loader.d.cts +1 -0
- package/dist/config-loader.d.cts.map +1 -1
- package/dist/config-loader.d.ts +1 -0
- package/dist/config-loader.d.ts.map +1 -1
- package/dist/config-loader.js +25 -11
- package/dist/config-loader.js.map +1 -1
- package/dist/elevenlabs-audio.cjs +2 -2
- package/dist/elevenlabs-audio.cjs.map +1 -1
- package/dist/elevenlabs-audio.js +2 -2
- package/dist/elevenlabs-audio.js.map +1 -1
- package/dist/fal-audio.cjs +32 -8
- package/dist/fal-audio.cjs.map +1 -1
- package/dist/fal-audio.js +32 -8
- package/dist/fal-audio.js.map +1 -1
- package/dist/gemini-interactions.cjs +61 -6
- package/dist/gemini-interactions.cjs.map +1 -1
- package/dist/gemini-interactions.d.cts +18 -1
- package/dist/gemini-interactions.d.cts.map +1 -1
- package/dist/gemini-interactions.d.ts +18 -1
- package/dist/gemini-interactions.d.ts.map +1 -1
- package/dist/gemini-interactions.js +61 -6
- package/dist/gemini-interactions.js.map +1 -1
- package/dist/helpers.cjs +7 -7
- package/dist/helpers.cjs.map +1 -1
- package/dist/helpers.d.cts +4 -1
- package/dist/helpers.d.cts.map +1 -1
- package/dist/helpers.d.ts +4 -1
- package/dist/helpers.d.ts.map +1 -1
- package/dist/helpers.js +7 -7
- package/dist/helpers.js.map +1 -1
- package/dist/index.cjs +1 -0
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2 -2
- package/dist/recorder.cjs +3 -3
- package/dist/recorder.cjs.map +1 -1
- package/dist/recorder.d.cts.map +1 -1
- package/dist/recorder.d.ts.map +1 -1
- package/dist/recorder.js +3 -3
- package/dist/recorder.js.map +1 -1
- package/dist/router.cjs +2 -7
- package/dist/router.cjs.map +1 -1
- package/dist/router.js +2 -7
- package/dist/router.js.map +1 -1
- package/dist/transcription.cjs +3 -1
- package/dist/transcription.cjs.map +1 -1
- package/dist/transcription.d.cts.map +1 -1
- package/dist/transcription.d.ts.map +1 -1
- package/dist/transcription.js +3 -1
- package/dist/transcription.js.map +1 -1
- package/dist/ws-gemini-live.cjs +28 -14
- 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 +28 -14
- package/dist/ws-gemini-live.js.map +1 -1
- package/dist/ws-realtime.cjs +64 -41
- 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 +64 -41
- package/dist/ws-realtime.js.map +1 -1
- package/package.json +1 -1
package/dist/fal-audio.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fal-audio.js","names":[],"sources":["../src/fal-audio.ts"],"sourcesContent":["import type http from \"node:http\";\nimport crypto from \"node:crypto\";\nimport type {\n AudioResponse,\n ChatCompletionRequest,\n Fixture,\n HandlerDefaults,\n RawJSONResponse,\n} from \"./types.js\";\nimport {\n isAudioResponse,\n isErrorResponse,\n isJSONResponse,\n serializeErrorResponse,\n flattenHeaders,\n FORMAT_TO_CONTENT_TYPE,\n getContext,\n getTestId,\n resolveResponse,\n resolveStrictMode,\n strictOverrideField,\n} from \"./helpers.js\";\nimport { writeErrorResponse } from \"./sse-writer.js\";\nimport { matchFixture } from \"./router.js\";\nimport { buildFixtureMatch, persistFixture, proxyAndRecord } from \"./recorder.js\";\nimport { buildFalForwardHeaders, walkFalQueue } from \"./fal.js\";\nimport type { Journal } from \"./journal.js\";\nimport { applyChaos } from \"./chaos.js\";\n\n// ─── FalJobMap with TTL and size bound ───────────────────────────────────\n\nconst FAL_JOB_MAX_ENTRIES = 10_000;\nconst FAL_JOB_TTL_MS = 3_600_000; // 1 hour\n\ninterface FalJob {\n requestId: string;\n modelId: string;\n status: \"IN_QUEUE\" | \"IN_PROGRESS\" | \"COMPLETED\";\n result: Record<string, unknown> | null;\n}\n\ninterface FalJobEntry {\n job: FalJob;\n createdAt: number;\n}\n\n/**\n * A Map wrapper for fal.ai queue jobs that enforces a maximum size and per-entry TTL.\n * Entries older than FAL_JOB_TTL_MS are lazily evicted on `get`.\n * When the map exceeds FAL_JOB_MAX_ENTRIES on `set`, the oldest entries\n * are removed to stay within bounds.\n * A background sweep runs every 60 s to proactively evict expired entries.\n */\nexport class FalJobMap {\n private readonly entries = new Map<string, FalJobEntry>();\n private sweepTimer: ReturnType<typeof setInterval> | null = null;\n\n constructor() {\n this.startSweep();\n }\n\n /** Start the proactive TTL sweep (every 60 s). */\n startSweep(): void {\n if (this.sweepTimer) return;\n this.sweepTimer = setInterval(() => {\n const now = Date.now();\n for (const [key, entry] of this.entries) {\n if (now - entry.createdAt > FAL_JOB_TTL_MS) {\n this.entries.delete(key);\n }\n }\n }, 60_000);\n // Allow the process to exit even if the timer is still active\n if (this.sweepTimer && typeof this.sweepTimer === \"object\" && \"unref\" in this.sweepTimer) {\n this.sweepTimer.unref();\n }\n }\n\n /** Stop the proactive TTL sweep. */\n stopSweep(): void {\n if (this.sweepTimer) {\n clearInterval(this.sweepTimer);\n this.sweepTimer = null;\n }\n }\n\n get(key: string): FalJob | undefined {\n const entry = this.entries.get(key);\n if (!entry) return undefined;\n if (Date.now() - entry.createdAt > FAL_JOB_TTL_MS) {\n this.entries.delete(key);\n return undefined;\n }\n return entry.job;\n }\n\n set(key: string, job: FalJob): void {\n this.entries.set(key, { job, createdAt: Date.now() });\n // Evict oldest entries if over capacity\n if (this.entries.size > FAL_JOB_MAX_ENTRIES) {\n const excess = this.entries.size - FAL_JOB_MAX_ENTRIES;\n const iter = this.entries.keys();\n for (let i = 0; i < excess; i++) {\n const next = iter.next();\n if (!next.done) this.entries.delete(next.value);\n }\n }\n }\n\n delete(key: string): boolean {\n return this.entries.delete(key);\n }\n\n clear(): void {\n this.entries.clear();\n }\n\n destroy(): void {\n this.stopSweep();\n this.entries.clear();\n }\n\n get size(): number {\n return this.entries.size;\n }\n}\n\n// Module-level singleton — exported so server.ts can clear it during reset\nexport const falJobs = new FalJobMap();\n\n// ─── Audio response translation ──────────────────────────────────────────\n\nexport function audioToFalFile(response: AudioResponse): Record<string, unknown> {\n let contentType: string;\n let data: string;\n\n if (typeof response.audio === \"string\") {\n data = response.audio;\n contentType = FORMAT_TO_CONTENT_TYPE[response.format ?? \"mp3\"] ?? \"audio/mpeg\";\n } else {\n data = response.audio.b64Json;\n contentType = response.audio.contentType ?? \"audio/mpeg\";\n }\n\n const ext =\n response.format ??\n (contentType !== \"audio/mpeg\"\n ? (Object.entries(FORMAT_TO_CONTENT_TYPE).find(([, v]) => v === contentType)?.[0] ?? \"mp3\")\n : \"mp3\");\n\n const fileSize =\n Math.ceil((data.length * 3) / 4) - (data.endsWith(\"==\") ? 2 : data.endsWith(\"=\") ? 1 : 0);\n\n return {\n audio: {\n url: `https://mock.fal.media/files/generated_audio.${ext}`,\n content_type: contentType,\n file_name: `generated_audio.${ext}`,\n file_size: fileSize,\n },\n };\n}\n\n// ─── Route patterns ──────────────────────────────────────────────────────\n\nconst QUEUE_SUBMIT_RE = /^\\/fal\\/queue\\/submit\\/(.+)$/;\nconst QUEUE_STATUS_RE = /^\\/fal\\/queue\\/requests\\/([^/]+)\\/status$/;\nconst QUEUE_RESULT_RE = /^\\/fal\\/queue\\/requests\\/([^/]+)$/;\nconst QUEUE_CANCEL_RE = /^\\/fal\\/queue\\/requests\\/([^/]+)\\/cancel$/;\nconst SYNC_RUN_RE = /^\\/fal\\/run\\/(.+)$/;\n\n// ─── Handler ─────────────────────────────────────────────────────────────\n\nexport async function handleFalQueue(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n body: string,\n pathname: string,\n fixtures: Fixture[],\n defaults: HandlerDefaults,\n journal: Journal,\n): Promise<void> {\n const testId = getTestId(req);\n const matchCounts = journal.getFixtureMatchCountsForTest(testId);\n\n // ── Queue Submit ───────────────────────────────────────────────────\n const submitMatch = QUEUE_SUBMIT_RE.exec(pathname);\n if (submitMatch && req.method === \"POST\") {\n const modelId = submitMatch[1];\n return handleQueueSubmit(\n req,\n res,\n body,\n pathname,\n modelId,\n testId,\n fixtures,\n defaults,\n matchCounts,\n journal,\n );\n }\n\n // ── Queue Status ───────────────────────────────────────────────────\n const statusMatch = QUEUE_STATUS_RE.exec(pathname);\n if (statusMatch) {\n const requestId = statusMatch[1];\n return handleQueueStatus(req, res, pathname, requestId, testId, journal);\n }\n\n // ── Queue Cancel ───────────────────────────────────────────────────\n const cancelMatch = QUEUE_CANCEL_RE.exec(pathname);\n if (cancelMatch) {\n const requestId = cancelMatch[1];\n return handleQueueCancel(req, res, pathname, requestId, testId, journal);\n }\n\n // ── Queue Result ───────────────────────────────────────────────────\n const resultMatch = QUEUE_RESULT_RE.exec(pathname);\n if (resultMatch) {\n const requestId = resultMatch[1];\n return handleQueueResult(req, res, pathname, requestId, testId, journal);\n }\n\n // ── Synchronous Run ────────────────────────────────────────────────\n const runMatch = SYNC_RUN_RE.exec(pathname);\n if (runMatch && req.method === \"POST\") {\n const modelId = runMatch[1];\n return handleSyncRun(\n req,\n res,\n body,\n pathname,\n modelId,\n fixtures,\n defaults,\n matchCounts,\n journal,\n );\n }\n\n // Unknown fal path\n const errorBody = { error: { message: \"Unknown fal.ai endpoint\", type: \"not_found\" } };\n journal.add({\n method: req.method ?? \"GET\",\n path: pathname,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 404, fixture: null },\n });\n res.writeHead(404, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(errorBody));\n}\n\n// ─── Sub-handlers ────────────────────────────────────────────────────────\n\nasync function handleQueueSubmit(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n body: string,\n pathname: string,\n modelId: string,\n testId: string,\n fixtures: Fixture[],\n defaults: HandlerDefaults,\n matchCounts: Map<Fixture, number>,\n journal: Journal,\n): Promise<void> {\n let parsed: Record<string, unknown> = {};\n if (body.trim()) {\n try {\n parsed = JSON.parse(body) as Record<string, unknown>;\n } catch (parseErr) {\n const detail = parseErr instanceof Error ? parseErr.message : \"unknown\";\n journal.add({\n method: req.method ?? \"POST\",\n path: pathname,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n error: { message: `Malformed JSON: ${detail}`, type: \"invalid_request_error\" },\n }),\n );\n return;\n }\n }\n\n const prompt =\n (typeof parsed.prompt === \"string\" ? parsed.prompt : null) ??\n (typeof parsed.text === \"string\" ? parsed.text : null) ??\n \"\";\n\n const syntheticReq: ChatCompletionRequest = {\n model: modelId,\n messages: [{ role: \"user\", content: prompt }],\n _endpointType: \"fal-audio\",\n _context: getContext(req),\n };\n\n const fixture = matchFixture(fixtures, syntheticReq, matchCounts, defaults.requestTransform);\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n }\n\n if (\n applyChaos(\n res,\n fixture,\n defaults.chaos,\n req.headers,\n journal,\n {\n method: req.method ?? \"POST\",\n path: pathname,\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 effectiveStrict = resolveStrictMode(defaults.strict, req.headers);\n if (effectiveStrict) {\n journal.add({\n method: req.method ?? \"POST\",\n path: pathname,\n headers: {},\n body: syntheticReq,\n response: {\n status: 503,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n res.writeHead(503, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n error: {\n message: \"Strict mode: no fixture matched\",\n type: \"invalid_request_error\",\n code: \"no_fixture_match\",\n },\n }),\n );\n return;\n }\n if (defaults.record) {\n const handled = await tryRecordAudioQueueWalk({\n req,\n res,\n syntheticReq,\n modelId,\n pathname,\n body,\n fixtures,\n defaults,\n testId,\n journal,\n });\n if (handled) return;\n }\n\n journal.add({\n method: req.method ?? \"POST\",\n path: pathname,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: {\n status: 404,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n res.writeHead(404, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n error: {\n message: \"No fixture matched\",\n type: \"invalid_request_error\",\n code: \"no_fixture_match\",\n },\n }),\n );\n return;\n }\n\n const response = await resolveResponse(fixture, syntheticReq);\n\n if (isErrorResponse(response)) {\n const status = response.status ?? 500;\n journal.add({\n method: req.method ?? \"POST\",\n path: pathname,\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 // Two valid recorded shapes for fal audio queues:\n // - AudioResponse: legacy authored fixtures with raw base64 audio that we\n // wrap into the fal `{ audio: { url, ... } }` envelope on demand.\n // - RawJSONResponse: queue-walk recordings that stored the final envelope\n // upstream returned (already in fal's `{ audio: { url, ... } }` shape).\n let result: Record<string, unknown>;\n if (isAudioResponse(response)) {\n result = audioToFalFile(response);\n } else if (isJSONResponse(response)) {\n const json = (response as RawJSONResponse).json;\n if (!json || typeof json !== \"object\") {\n journal.add({\n method: req.method ?? \"POST\",\n path: pathname,\n headers: {},\n body: syntheticReq,\n response: { status: 500, fixture },\n });\n res.writeHead(500, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n error: {\n message: \"Recorded fal audio fixture has non-object json\",\n type: \"server_error\",\n },\n }),\n );\n return;\n }\n result = json as Record<string, unknown>;\n } else {\n journal.add({\n method: req.method ?? \"POST\",\n path: pathname,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 500, fixture },\n });\n res.writeHead(500, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n error: { message: \"Fixture response is not an audio type\", type: \"server_error\" },\n }),\n );\n return;\n }\n\n const requestId = crypto.randomUUID();\n\n const job: FalJob = {\n requestId,\n modelId,\n status: \"COMPLETED\",\n result,\n };\n\n const stateKey = `${testId}:${requestId}`;\n falJobs.set(stateKey, job);\n\n journal.add({\n method: req.method ?? \"POST\",\n path: pathname,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 200, fixture },\n });\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n request_id: requestId,\n response_url: `https://queue.fal.run/${modelId}/requests/${requestId}/response`,\n status_url: `https://queue.fal.run/${modelId}/requests/${requestId}/status`,\n cancel_url: `https://queue.fal.run/${modelId}/requests/${requestId}/cancel`,\n queue_position: 0,\n }),\n );\n}\n\n/**\n * Walk the upstream queue (submit → poll status → get result), persist the\n * FINAL result body as a fal-audio fixture, then synthesise the local envelope\n * and seed `falJobs` so the same recording run's status/result polls work. The\n * legacy `proxyAndRecord` shortcut wrote the IN_QUEUE envelope as the fixture,\n * which broke replay (the SDK polls until COMPLETED then expects the result\n * body, not the envelope).\n *\n * Returns `true` if the request has been handled (response written and\n * journaled); `false` if recording wasn't configured for this provider and the\n * caller should fall through to strict/404.\n */\nasync function tryRecordAudioQueueWalk(args: {\n req: http.IncomingMessage;\n res: http.ServerResponse;\n syntheticReq: ChatCompletionRequest;\n modelId: string;\n pathname: string;\n body: string;\n fixtures: Fixture[];\n defaults: HandlerDefaults;\n testId: string;\n journal: Journal;\n}): Promise<boolean> {\n const { req, res, syntheticReq, modelId, pathname, body, fixtures, defaults, testId, journal } =\n args;\n\n const record = defaults.record;\n if (!record) return false;\n const upstreamBase = record.providers.fal;\n if (!upstreamBase) {\n // Fall back to the generic proxy so non-queue-shaped audio endpoints (e.g.\n // direct audio bytes, when someone misconfigures) still get a chance to\n // record, mirroring prior behavior.\n const outcome = await proxyAndRecord(\n req,\n res,\n syntheticReq,\n \"fal\",\n pathname,\n fixtures,\n defaults,\n body,\n );\n if (outcome === \"handled_by_hook\") return true;\n if (outcome === \"relayed\") {\n journal.add({\n method: req.method ?? \"POST\",\n path: pathname,\n headers: {},\n body: syntheticReq,\n response: { status: res.statusCode ?? 200, fixture: null, source: \"proxy\" },\n });\n return true;\n }\n return false;\n }\n\n defaults.logger.warn(\n `NO FIXTURE MATCH — walking legacy fal audio queue at ${upstreamBase}${pathname}`,\n );\n\n let finalBody: unknown;\n try {\n finalBody = await walkFalQueue({\n upstreamBase,\n submitPath: pathname,\n body,\n headers: buildFalForwardHeaders(req),\n pollIntervalMs: record.fal?.pollIntervalMs,\n timeoutMs: record.fal?.timeoutMs,\n // Legacy aimock-style paths, not the model-prefixed fal.ai layout.\n fallbackStatusPath: (id) => `/fal/queue/requests/${id}/status`,\n fallbackResultPath: (id) => `/fal/queue/requests/${id}`,\n });\n } catch (err) {\n const msg = err instanceof Error ? err.message : \"Unknown queue-walk error\";\n defaults.logger.error(`fal-audio queue-walk proxy failed: ${msg}`);\n journal.add({\n method: req.method ?? \"POST\",\n path: pathname,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 502, fixture: null, source: \"proxy\" },\n });\n res.writeHead(502, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n error: { message: `Proxy to upstream failed: ${msg}`, type: \"proxy_error\" },\n }),\n );\n return true;\n }\n\n if (!finalBody || typeof finalBody !== \"object\") {\n defaults.logger.error(\"fal-audio queue-walk produced non-object result\");\n journal.add({\n method: req.method ?? \"POST\",\n path: pathname,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 502, fixture: null, source: \"proxy\" },\n });\n res.writeHead(502, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n error: { message: \"Upstream result body was not a JSON object\", type: \"proxy_error\" },\n }),\n );\n return true;\n }\n\n const matchRequest = defaults.requestTransform\n ? defaults.requestTransform(syntheticReq)\n : syntheticReq;\n const fixture: Fixture = {\n match: buildFixtureMatch(matchRequest, record),\n response: { json: finalBody, status: 200 },\n };\n persistFixture({\n record,\n providerKey: \"fal\",\n testId,\n fixture,\n fixtures,\n logger: defaults.logger,\n });\n\n const requestId = crypto.randomUUID();\n const job: FalJob = {\n requestId,\n modelId,\n status: \"COMPLETED\",\n result: finalBody as Record<string, unknown>,\n };\n falJobs.set(`${testId}:${requestId}`, job);\n\n journal.add({\n method: req.method ?? \"POST\",\n path: pathname,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 200, fixture: null, source: \"proxy\" },\n });\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n request_id: requestId,\n response_url: `https://queue.fal.run/${modelId}/requests/${requestId}/response`,\n status_url: `https://queue.fal.run/${modelId}/requests/${requestId}/status`,\n cancel_url: `https://queue.fal.run/${modelId}/requests/${requestId}/cancel`,\n queue_position: 0,\n }),\n );\n return true;\n}\n\nfunction handleQueueStatus(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n pathname: string,\n requestId: string,\n testId: string,\n journal: Journal,\n): void {\n const stateKey = `${testId}:${requestId}`;\n const job = falJobs.get(stateKey);\n\n if (!job) {\n journal.add({\n method: req.method ?? \"GET\",\n path: pathname,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 404, fixture: null },\n });\n res.writeHead(404, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n error: { message: `Request ${requestId} not found`, type: \"not_found\" },\n }),\n );\n return;\n }\n\n journal.add({\n method: req.method ?? \"GET\",\n path: pathname,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 200, fixture: null },\n });\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n status: job.status,\n request_id: job.requestId,\n response_url: `https://queue.fal.run/${job.modelId}/requests/${requestId}/response`,\n }),\n );\n}\n\nfunction handleQueueResult(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n pathname: string,\n requestId: string,\n testId: string,\n journal: Journal,\n): void {\n const stateKey = `${testId}:${requestId}`;\n const job = falJobs.get(stateKey);\n\n if (!job) {\n journal.add({\n method: req.method ?? \"GET\",\n path: pathname,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 404, fixture: null },\n });\n res.writeHead(404, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n error: { message: `Request ${requestId} not found`, type: \"not_found\" },\n }),\n );\n return;\n }\n\n if (job.result === null) {\n journal.add({\n method: req.method ?? \"GET\",\n path: pathname,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 409, fixture: null },\n });\n writeErrorResponse(\n res,\n 409,\n JSON.stringify({\n error: { message: \"Job result not yet available\", type: \"not_ready\" },\n }),\n );\n return;\n }\n\n journal.add({\n method: req.method ?? \"GET\",\n path: pathname,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 200, fixture: null },\n });\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(job.result));\n}\n\nfunction handleQueueCancel(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n pathname: string,\n requestId: string,\n testId: string,\n journal: Journal,\n): void {\n const stateKey = `${testId}:${requestId}`;\n const job = falJobs.get(stateKey);\n\n if (!job) {\n journal.add({\n method: req.method ?? \"DELETE\",\n path: pathname,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 404, fixture: null },\n });\n res.writeHead(404, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ status: \"NOT_FOUND\" }));\n return;\n }\n\n // Since we complete immediately, cancellation always returns ALREADY_COMPLETED\n journal.add({\n method: req.method ?? \"DELETE\",\n path: pathname,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ status: \"ALREADY_COMPLETED\" }));\n}\n\nasync function handleSyncRun(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n body: string,\n pathname: string,\n modelId: string,\n fixtures: Fixture[],\n defaults: HandlerDefaults,\n matchCounts: Map<Fixture, number>,\n journal: Journal,\n): Promise<void> {\n let parsed: Record<string, unknown> = {};\n if (body.trim()) {\n try {\n parsed = JSON.parse(body) as Record<string, unknown>;\n } catch (parseErr) {\n const detail = parseErr instanceof Error ? parseErr.message : \"unknown\";\n journal.add({\n method: req.method ?? \"POST\",\n path: pathname,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n error: { message: `Malformed JSON: ${detail}`, type: \"invalid_request_error\" },\n }),\n );\n return;\n }\n }\n\n const prompt =\n (typeof parsed.prompt === \"string\" ? parsed.prompt : null) ??\n (typeof parsed.text === \"string\" ? parsed.text : null) ??\n \"\";\n\n const syntheticReq: ChatCompletionRequest = {\n model: modelId,\n messages: [{ role: \"user\", content: prompt }],\n _endpointType: \"fal-audio\",\n _context: getContext(req),\n };\n\n const fixture = matchFixture(fixtures, syntheticReq, matchCounts, defaults.requestTransform);\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, getTestId(req));\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: pathname,\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 effectiveStrict = resolveStrictMode(defaults.strict, req.headers);\n if (effectiveStrict) {\n journal.add({\n method: req.method ?? \"POST\",\n path: pathname,\n headers: {},\n body: syntheticReq,\n response: {\n status: 503,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n res.writeHead(503, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n error: {\n message: \"Strict mode: no fixture matched\",\n type: \"invalid_request_error\",\n code: \"no_fixture_match\",\n },\n }),\n );\n return;\n }\n if (defaults.record) {\n const outcome = await proxyAndRecord(\n req,\n res,\n syntheticReq,\n \"fal\",\n pathname,\n fixtures,\n defaults,\n body,\n );\n if (outcome === \"handled_by_hook\") return;\n if (outcome !== \"not_configured\") {\n journal.add({\n method: req.method ?? \"POST\",\n path: pathname,\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 journal.add({\n method: req.method ?? \"POST\",\n path: pathname,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: {\n status: 404,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n res.writeHead(404, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n error: {\n message: \"No fixture matched\",\n type: \"invalid_request_error\",\n code: \"no_fixture_match\",\n },\n }),\n );\n return;\n }\n\n const response = await resolveResponse(fixture, syntheticReq);\n\n if (isErrorResponse(response)) {\n const status = response.status ?? 500;\n journal.add({\n method: req.method ?? \"POST\",\n path: pathname,\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 if (!isAudioResponse(response)) {\n journal.add({\n method: req.method ?? \"POST\",\n path: pathname,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 500, fixture },\n });\n res.writeHead(500, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n error: { message: \"Fixture response is not an audio type\", type: \"server_error\" },\n }),\n );\n return;\n }\n\n const result = audioToFalFile(response);\n\n journal.add({\n method: req.method ?? \"POST\",\n path: pathname,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 200, fixture },\n });\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(result));\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"fal-audio.js","names":[],"sources":["../src/fal-audio.ts"],"sourcesContent":["import type http from \"node:http\";\nimport crypto from \"node:crypto\";\nimport type {\n AudioResponse,\n ChatCompletionRequest,\n Fixture,\n HandlerDefaults,\n RawJSONResponse,\n} from \"./types.js\";\nimport {\n isAudioResponse,\n isErrorResponse,\n isJSONResponse,\n serializeErrorResponse,\n flattenHeaders,\n FORMAT_TO_CONTENT_TYPE,\n getContext,\n getTestId,\n resolveResponse,\n resolveStrictMode,\n strictOverrideField,\n} from \"./helpers.js\";\nimport { writeErrorResponse } from \"./sse-writer.js\";\nimport { matchFixture } from \"./router.js\";\nimport { buildFixtureMatch, persistFixture, proxyAndRecord } from \"./recorder.js\";\nimport { buildFalForwardHeaders, walkFalQueue } from \"./fal.js\";\nimport type { Journal } from \"./journal.js\";\nimport { applyChaos } from \"./chaos.js\";\n\n// ─── FalJobMap with TTL and size bound ───────────────────────────────────\n\nconst FAL_JOB_MAX_ENTRIES = 10_000;\nconst FAL_JOB_TTL_MS = 3_600_000; // 1 hour\n\ninterface FalJob {\n requestId: string;\n modelId: string;\n status: \"IN_QUEUE\" | \"IN_PROGRESS\" | \"COMPLETED\";\n result: Record<string, unknown> | null;\n}\n\ninterface FalJobEntry {\n job: FalJob;\n createdAt: number;\n}\n\n/**\n * A Map wrapper for fal.ai queue jobs that enforces a maximum size and per-entry TTL.\n * Entries older than FAL_JOB_TTL_MS are lazily evicted on `get`.\n * When the map exceeds FAL_JOB_MAX_ENTRIES on `set`, the oldest entries\n * are removed to stay within bounds.\n * A background sweep runs every 60 s to proactively evict expired entries.\n */\nexport class FalJobMap {\n private readonly entries = new Map<string, FalJobEntry>();\n private sweepTimer: ReturnType<typeof setInterval> | null = null;\n\n constructor() {\n this.startSweep();\n }\n\n /** Start the proactive TTL sweep (every 60 s). */\n startSweep(): void {\n if (this.sweepTimer) return;\n this.sweepTimer = setInterval(() => {\n const now = Date.now();\n for (const [key, entry] of this.entries) {\n if (now - entry.createdAt > FAL_JOB_TTL_MS) {\n this.entries.delete(key);\n }\n }\n }, 60_000);\n // Allow the process to exit even if the timer is still active\n if (this.sweepTimer && typeof this.sweepTimer === \"object\" && \"unref\" in this.sweepTimer) {\n this.sweepTimer.unref();\n }\n }\n\n /** Stop the proactive TTL sweep. */\n stopSweep(): void {\n if (this.sweepTimer) {\n clearInterval(this.sweepTimer);\n this.sweepTimer = null;\n }\n }\n\n get(key: string): FalJob | undefined {\n const entry = this.entries.get(key);\n if (!entry) return undefined;\n if (Date.now() - entry.createdAt > FAL_JOB_TTL_MS) {\n this.entries.delete(key);\n return undefined;\n }\n return entry.job;\n }\n\n set(key: string, job: FalJob): void {\n this.entries.set(key, { job, createdAt: Date.now() });\n // Evict oldest entries if over capacity\n if (this.entries.size > FAL_JOB_MAX_ENTRIES) {\n const excess = this.entries.size - FAL_JOB_MAX_ENTRIES;\n const iter = this.entries.keys();\n for (let i = 0; i < excess; i++) {\n const next = iter.next();\n if (!next.done) this.entries.delete(next.value);\n }\n }\n }\n\n delete(key: string): boolean {\n return this.entries.delete(key);\n }\n\n clear(): void {\n this.entries.clear();\n }\n\n destroy(): void {\n this.stopSweep();\n this.entries.clear();\n }\n\n get size(): number {\n return this.entries.size;\n }\n}\n\n// Module-level singleton — exported so server.ts can clear it during reset\nexport const falJobs = new FalJobMap();\n\n// ─── Audio response translation ──────────────────────────────────────────\n\nexport function audioToFalFile(response: AudioResponse): Record<string, unknown> {\n let contentType: string;\n let data: string;\n\n if (typeof response.audio === \"string\") {\n data = response.audio;\n contentType = FORMAT_TO_CONTENT_TYPE[response.format ?? \"mp3\"] ?? \"audio/mpeg\";\n } else {\n data = response.audio.b64Json;\n contentType = response.audio.contentType ?? \"audio/mpeg\";\n }\n\n const ext =\n response.format ??\n (contentType !== \"audio/mpeg\"\n ? (Object.entries(FORMAT_TO_CONTENT_TYPE).find(([, v]) => v === contentType)?.[0] ?? \"mp3\")\n : \"mp3\");\n\n const fileSize =\n Math.ceil((data.length * 3) / 4) - (data.endsWith(\"==\") ? 2 : data.endsWith(\"=\") ? 1 : 0);\n\n return {\n audio: {\n url: `https://mock.fal.media/files/generated_audio.${ext}`,\n content_type: contentType,\n file_name: `generated_audio.${ext}`,\n file_size: fileSize,\n },\n };\n}\n\n// ─── Route patterns ──────────────────────────────────────────────────────\n\nconst QUEUE_SUBMIT_RE = /^\\/fal\\/queue\\/submit\\/(.+)$/;\nconst QUEUE_STATUS_RE = /^\\/fal\\/queue\\/requests\\/([^/]+)\\/status$/;\nconst QUEUE_RESULT_RE = /^\\/fal\\/queue\\/requests\\/([^/]+)$/;\nconst QUEUE_CANCEL_RE = /^\\/fal\\/queue\\/requests\\/([^/]+)\\/cancel$/;\nconst SYNC_RUN_RE = /^\\/fal\\/run\\/(.+)$/;\n\n// ─── Handler ─────────────────────────────────────────────────────────────\n\nexport async function handleFalQueue(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n body: string,\n pathname: string,\n fixtures: Fixture[],\n defaults: HandlerDefaults,\n journal: Journal,\n): Promise<void> {\n const testId = getTestId(req);\n const matchCounts = journal.getFixtureMatchCountsForTest(testId);\n\n // ── Queue Submit ───────────────────────────────────────────────────\n const submitMatch = QUEUE_SUBMIT_RE.exec(pathname);\n if (submitMatch && req.method === \"POST\") {\n const modelId = submitMatch[1];\n return handleQueueSubmit(\n req,\n res,\n body,\n pathname,\n modelId,\n testId,\n fixtures,\n defaults,\n matchCounts,\n journal,\n );\n }\n\n // ── Queue Status ───────────────────────────────────────────────────\n const statusMatch = QUEUE_STATUS_RE.exec(pathname);\n if (statusMatch) {\n const requestId = statusMatch[1];\n return handleQueueStatus(req, res, pathname, requestId, testId, journal);\n }\n\n // ── Queue Cancel ───────────────────────────────────────────────────\n const cancelMatch = QUEUE_CANCEL_RE.exec(pathname);\n if (cancelMatch) {\n const requestId = cancelMatch[1];\n return handleQueueCancel(req, res, pathname, requestId, testId, journal);\n }\n\n // ── Queue Result ───────────────────────────────────────────────────\n const resultMatch = QUEUE_RESULT_RE.exec(pathname);\n if (resultMatch) {\n const requestId = resultMatch[1];\n return handleQueueResult(req, res, pathname, requestId, testId, journal);\n }\n\n // ── Synchronous Run ────────────────────────────────────────────────\n const runMatch = SYNC_RUN_RE.exec(pathname);\n if (runMatch && req.method === \"POST\") {\n const modelId = runMatch[1];\n return handleSyncRun(\n req,\n res,\n body,\n pathname,\n modelId,\n fixtures,\n defaults,\n matchCounts,\n journal,\n );\n }\n\n // Unknown fal path\n const errorBody = { error: { message: \"Unknown fal.ai endpoint\", type: \"not_found\" } };\n journal.add({\n method: req.method ?? \"GET\",\n path: pathname,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 404, fixture: null },\n });\n res.writeHead(404, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(errorBody));\n}\n\n// ─── Sub-handlers ────────────────────────────────────────────────────────\n\nasync function handleQueueSubmit(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n body: string,\n pathname: string,\n modelId: string,\n testId: string,\n fixtures: Fixture[],\n defaults: HandlerDefaults,\n matchCounts: Map<Fixture, number>,\n journal: Journal,\n): Promise<void> {\n let parsed: Record<string, unknown> = {};\n if (body.trim()) {\n try {\n parsed = JSON.parse(body) as Record<string, unknown>;\n } catch (parseErr) {\n const detail = parseErr instanceof Error ? parseErr.message : \"unknown\";\n journal.add({\n method: req.method ?? \"POST\",\n path: pathname,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n error: { message: `Malformed JSON: ${detail}`, type: \"invalid_request_error\" },\n }),\n );\n return;\n }\n }\n\n const prompt =\n (typeof parsed.prompt === \"string\" ? parsed.prompt : null) ??\n (typeof parsed.text === \"string\" ? parsed.text : null) ??\n \"\";\n\n const syntheticReq: ChatCompletionRequest = {\n model: modelId,\n messages: [{ role: \"user\", content: prompt }],\n _endpointType: \"fal-audio\",\n _context: getContext(req),\n };\n\n const fixture = matchFixture(fixtures, syntheticReq, matchCounts, defaults.requestTransform);\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n }\n\n if (\n applyChaos(\n res,\n fixture,\n defaults.chaos,\n req.headers,\n journal,\n {\n method: req.method ?? \"POST\",\n path: pathname,\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 effectiveStrict = resolveStrictMode(defaults.strict, req.headers);\n if (effectiveStrict) {\n journal.add({\n method: req.method ?? \"POST\",\n path: pathname,\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 res.writeHead(503, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n error: {\n message: \"Strict mode: no fixture matched\",\n type: \"invalid_request_error\",\n code: \"no_fixture_match\",\n },\n }),\n );\n return;\n }\n if (defaults.record) {\n const handled = await tryRecordAudioQueueWalk({\n req,\n res,\n syntheticReq,\n modelId,\n pathname,\n body,\n fixtures,\n defaults,\n testId,\n journal,\n });\n if (handled) return;\n }\n\n journal.add({\n method: req.method ?? \"POST\",\n path: pathname,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: {\n status: 404,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n res.writeHead(404, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n error: {\n message: \"No fixture matched\",\n type: \"invalid_request_error\",\n code: \"no_fixture_match\",\n },\n }),\n );\n return;\n }\n\n const response = await resolveResponse(fixture, syntheticReq);\n\n if (isErrorResponse(response)) {\n const status = response.status ?? 500;\n journal.add({\n method: req.method ?? \"POST\",\n path: pathname,\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 // Two valid recorded shapes for fal audio queues:\n // - AudioResponse: legacy authored fixtures with raw base64 audio that we\n // wrap into the fal `{ audio: { url, ... } }` envelope on demand.\n // - RawJSONResponse: queue-walk recordings that stored the final envelope\n // upstream returned (already in fal's `{ audio: { url, ... } }` shape).\n let result: Record<string, unknown>;\n if (isAudioResponse(response)) {\n result = audioToFalFile(response);\n } else if (isJSONResponse(response)) {\n const json = (response as RawJSONResponse).json;\n if (!json || typeof json !== \"object\") {\n journal.add({\n method: req.method ?? \"POST\",\n path: pathname,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 500, fixture },\n });\n res.writeHead(500, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n error: {\n message: \"Recorded fal audio fixture has non-object json\",\n type: \"server_error\",\n },\n }),\n );\n return;\n }\n result = json as Record<string, unknown>;\n } else {\n journal.add({\n method: req.method ?? \"POST\",\n path: pathname,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 500, fixture },\n });\n res.writeHead(500, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n error: { message: \"Fixture response is not an audio type\", type: \"server_error\" },\n }),\n );\n return;\n }\n\n const requestId = crypto.randomUUID();\n\n const job: FalJob = {\n requestId,\n modelId,\n status: \"COMPLETED\",\n result,\n };\n\n const stateKey = `${testId}:${requestId}`;\n falJobs.set(stateKey, job);\n\n journal.add({\n method: req.method ?? \"POST\",\n path: pathname,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 200, fixture },\n });\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n request_id: requestId,\n response_url: `https://queue.fal.run/${modelId}/requests/${requestId}/response`,\n status_url: `https://queue.fal.run/${modelId}/requests/${requestId}/status`,\n cancel_url: `https://queue.fal.run/${modelId}/requests/${requestId}/cancel`,\n queue_position: 0,\n }),\n );\n}\n\n/**\n * Walk the upstream queue (submit → poll status → get result), persist the\n * FINAL result body as a fal-audio fixture, then synthesise the local envelope\n * and seed `falJobs` so the same recording run's status/result polls work. The\n * legacy `proxyAndRecord` shortcut wrote the IN_QUEUE envelope as the fixture,\n * which broke replay (the SDK polls until COMPLETED then expects the result\n * body, not the envelope).\n *\n * Returns `true` if the request has been handled (response written and\n * journaled); `false` if recording wasn't configured for this provider and the\n * caller should fall through to strict/404.\n */\nasync function tryRecordAudioQueueWalk(args: {\n req: http.IncomingMessage;\n res: http.ServerResponse;\n syntheticReq: ChatCompletionRequest;\n modelId: string;\n pathname: string;\n body: string;\n fixtures: Fixture[];\n defaults: HandlerDefaults;\n testId: string;\n journal: Journal;\n}): Promise<boolean> {\n const { req, res, syntheticReq, modelId, pathname, body, fixtures, defaults, testId, journal } =\n args;\n\n const record = defaults.record;\n if (!record) return false;\n const upstreamBase = record.providers.fal;\n if (!upstreamBase) {\n // Fall back to the generic proxy so non-queue-shaped audio endpoints (e.g.\n // direct audio bytes, when someone misconfigures) still get a chance to\n // record, mirroring prior behavior.\n const outcome = await proxyAndRecord(\n req,\n res,\n syntheticReq,\n \"fal\",\n pathname,\n fixtures,\n defaults,\n body,\n );\n if (outcome === \"handled_by_hook\") return true;\n if (outcome === \"relayed\") {\n journal.add({\n method: req.method ?? \"POST\",\n path: pathname,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: res.statusCode ?? 200, fixture: null, source: \"proxy\" },\n });\n return true;\n }\n return false;\n }\n\n defaults.logger.warn(\n `NO FIXTURE MATCH — walking legacy fal audio queue at ${upstreamBase}${pathname}`,\n );\n\n let finalBody: unknown;\n try {\n finalBody = await walkFalQueue({\n upstreamBase,\n submitPath: pathname,\n body,\n headers: buildFalForwardHeaders(req),\n pollIntervalMs: record.fal?.pollIntervalMs,\n timeoutMs: record.fal?.timeoutMs,\n // Legacy aimock-style paths, not the model-prefixed fal.ai layout.\n fallbackStatusPath: (id) => `/fal/queue/requests/${id}/status`,\n fallbackResultPath: (id) => `/fal/queue/requests/${id}`,\n });\n } catch (err) {\n const msg = err instanceof Error ? err.message : \"Unknown queue-walk error\";\n defaults.logger.error(`fal-audio queue-walk proxy failed: ${msg}`);\n journal.add({\n method: req.method ?? \"POST\",\n path: pathname,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 502, fixture: null, source: \"proxy\" },\n });\n res.writeHead(502, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n error: { message: `Proxy to upstream failed: ${msg}`, type: \"proxy_error\" },\n }),\n );\n return true;\n }\n\n if (!finalBody || typeof finalBody !== \"object\") {\n defaults.logger.error(\"fal-audio queue-walk produced non-object result\");\n journal.add({\n method: req.method ?? \"POST\",\n path: pathname,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 502, fixture: null, source: \"proxy\" },\n });\n res.writeHead(502, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n error: { message: \"Upstream result body was not a JSON object\", type: \"proxy_error\" },\n }),\n );\n return true;\n }\n\n const matchRequest = defaults.requestTransform\n ? defaults.requestTransform(syntheticReq)\n : syntheticReq;\n const fixture: Fixture = {\n match: buildFixtureMatch(matchRequest, record),\n response: { json: finalBody, status: 200 },\n };\n persistFixture({\n record,\n providerKey: \"fal\",\n testId,\n fixture,\n fixtures,\n logger: defaults.logger,\n });\n\n const requestId = crypto.randomUUID();\n const job: FalJob = {\n requestId,\n modelId,\n status: \"COMPLETED\",\n result: finalBody as Record<string, unknown>,\n };\n falJobs.set(`${testId}:${requestId}`, job);\n\n journal.add({\n method: req.method ?? \"POST\",\n path: pathname,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 200, fixture: null, source: \"proxy\" },\n });\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n request_id: requestId,\n response_url: `https://queue.fal.run/${modelId}/requests/${requestId}/response`,\n status_url: `https://queue.fal.run/${modelId}/requests/${requestId}/status`,\n cancel_url: `https://queue.fal.run/${modelId}/requests/${requestId}/cancel`,\n queue_position: 0,\n }),\n );\n return true;\n}\n\nfunction handleQueueStatus(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n pathname: string,\n requestId: string,\n testId: string,\n journal: Journal,\n): void {\n const stateKey = `${testId}:${requestId}`;\n const job = falJobs.get(stateKey);\n\n if (!job) {\n journal.add({\n method: req.method ?? \"GET\",\n path: pathname,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 404, fixture: null },\n });\n res.writeHead(404, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n error: { message: `Request ${requestId} not found`, type: \"not_found\" },\n }),\n );\n return;\n }\n\n journal.add({\n method: req.method ?? \"GET\",\n path: pathname,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 200, fixture: null },\n });\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n status: job.status,\n request_id: job.requestId,\n response_url: `https://queue.fal.run/${job.modelId}/requests/${requestId}/response`,\n }),\n );\n}\n\nfunction handleQueueResult(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n pathname: string,\n requestId: string,\n testId: string,\n journal: Journal,\n): void {\n const stateKey = `${testId}:${requestId}`;\n const job = falJobs.get(stateKey);\n\n if (!job) {\n journal.add({\n method: req.method ?? \"GET\",\n path: pathname,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 404, fixture: null },\n });\n res.writeHead(404, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n error: { message: `Request ${requestId} not found`, type: \"not_found\" },\n }),\n );\n return;\n }\n\n if (job.result === null) {\n journal.add({\n method: req.method ?? \"GET\",\n path: pathname,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 409, fixture: null },\n });\n writeErrorResponse(\n res,\n 409,\n JSON.stringify({\n error: { message: \"Job result not yet available\", type: \"not_ready\" },\n }),\n );\n return;\n }\n\n journal.add({\n method: req.method ?? \"GET\",\n path: pathname,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 200, fixture: null },\n });\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(job.result));\n}\n\nfunction handleQueueCancel(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n pathname: string,\n requestId: string,\n testId: string,\n journal: Journal,\n): void {\n const stateKey = `${testId}:${requestId}`;\n const job = falJobs.get(stateKey);\n\n if (!job) {\n journal.add({\n method: req.method ?? \"DELETE\",\n path: pathname,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 404, fixture: null },\n });\n res.writeHead(404, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ status: \"NOT_FOUND\" }));\n return;\n }\n\n // Since we complete immediately, cancellation always returns ALREADY_COMPLETED\n journal.add({\n method: req.method ?? \"DELETE\",\n path: pathname,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ status: \"ALREADY_COMPLETED\" }));\n}\n\nasync function handleSyncRun(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n body: string,\n pathname: string,\n modelId: string,\n fixtures: Fixture[],\n defaults: HandlerDefaults,\n matchCounts: Map<Fixture, number>,\n journal: Journal,\n): Promise<void> {\n let parsed: Record<string, unknown> = {};\n if (body.trim()) {\n try {\n parsed = JSON.parse(body) as Record<string, unknown>;\n } catch (parseErr) {\n const detail = parseErr instanceof Error ? parseErr.message : \"unknown\";\n journal.add({\n method: req.method ?? \"POST\",\n path: pathname,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n error: { message: `Malformed JSON: ${detail}`, type: \"invalid_request_error\" },\n }),\n );\n return;\n }\n }\n\n const prompt =\n (typeof parsed.prompt === \"string\" ? parsed.prompt : null) ??\n (typeof parsed.text === \"string\" ? parsed.text : null) ??\n \"\";\n\n // _endpointType is intentionally \"fal-audio\" — the same value used by\n // handleQueueSubmit. Both the synchronous /fal/run/ and asynchronous\n // /fal/queue/submit/ paths serve the same fal audio fixtures, so they\n // share a single endpoint type for fixture matching purposes.\n const syntheticReq: ChatCompletionRequest = {\n model: modelId,\n messages: [{ role: \"user\", content: prompt }],\n _endpointType: \"fal-audio\",\n _context: getContext(req),\n };\n\n const fixture = matchFixture(fixtures, syntheticReq, matchCounts, defaults.requestTransform);\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, getTestId(req));\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: pathname,\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 effectiveStrict = resolveStrictMode(defaults.strict, req.headers);\n if (effectiveStrict) {\n journal.add({\n method: req.method ?? \"POST\",\n path: pathname,\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 res.writeHead(503, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n error: {\n message: \"Strict mode: no fixture matched\",\n type: \"invalid_request_error\",\n code: \"no_fixture_match\",\n },\n }),\n );\n return;\n }\n if (defaults.record) {\n const outcome = await proxyAndRecord(\n req,\n res,\n syntheticReq,\n \"fal\",\n pathname,\n fixtures,\n defaults,\n body,\n );\n if (outcome === \"handled_by_hook\") return;\n if (outcome !== \"not_configured\") {\n journal.add({\n method: req.method ?? \"POST\",\n path: pathname,\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 journal.add({\n method: req.method ?? \"POST\",\n path: pathname,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: {\n status: 404,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n res.writeHead(404, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n error: {\n message: \"No fixture matched\",\n type: \"invalid_request_error\",\n code: \"no_fixture_match\",\n },\n }),\n );\n return;\n }\n\n const response = await resolveResponse(fixture, syntheticReq);\n\n if (isErrorResponse(response)) {\n const status = response.status ?? 500;\n journal.add({\n method: req.method ?? \"POST\",\n path: pathname,\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 // Two valid recorded shapes for fal audio sync runs:\n // - AudioResponse: authored fixtures with raw base64 audio that we wrap into\n // the fal `{ audio: { url, ... } }` envelope on demand.\n // - RawJSONResponse: queue-walk recordings that stored the final fal envelope\n // upstream returned (already in fal's `{ audio: { url, ... } }` shape).\n let result: Record<string, unknown>;\n let resultStatus = 200;\n if (isAudioResponse(response)) {\n result = audioToFalFile(response);\n } else if (isJSONResponse(response)) {\n resultStatus = (response as RawJSONResponse).status ?? 200;\n const json = (response as RawJSONResponse).json;\n if (!json || typeof json !== \"object\") {\n journal.add({\n method: req.method ?? \"POST\",\n path: pathname,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 500, fixture },\n });\n res.writeHead(500, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n error: {\n message: \"Recorded fal audio fixture has non-object json\",\n type: \"server_error\",\n },\n }),\n );\n return;\n }\n result = json as Record<string, unknown>;\n } else {\n journal.add({\n method: req.method ?? \"POST\",\n path: pathname,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 500, fixture },\n });\n res.writeHead(500, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n error: { message: \"Fixture response is not an audio type\", type: \"server_error\" },\n }),\n );\n return;\n }\n\n journal.add({\n method: req.method ?? \"POST\",\n path: pathname,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: resultStatus, fixture },\n });\n res.writeHead(resultStatus, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(result));\n}\n"],"mappings":"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"}
|
|
@@ -6,6 +6,22 @@ const require_chaos = require('./chaos.cjs');
|
|
|
6
6
|
const require_recorder = require('./recorder.cjs');
|
|
7
7
|
|
|
8
8
|
//#region src/gemini-interactions.ts
|
|
9
|
+
/** Step types whose payload is a tool/agent result keyed by call_id. */
|
|
10
|
+
const STEP_RESULT_TYPES = new Set([
|
|
11
|
+
"function_result",
|
|
12
|
+
"code_execution_result",
|
|
13
|
+
"url_context_result",
|
|
14
|
+
"google_search_result",
|
|
15
|
+
"google_maps_result",
|
|
16
|
+
"mcp_server_tool_result",
|
|
17
|
+
"file_search_result"
|
|
18
|
+
]);
|
|
19
|
+
/** All recognized top-level Step types (used as the Step[] discriminator). */
|
|
20
|
+
const STEP_TYPES = new Set([
|
|
21
|
+
"user_input",
|
|
22
|
+
"model_output",
|
|
23
|
+
...STEP_RESULT_TYPES
|
|
24
|
+
]);
|
|
9
25
|
function geminiInteractionsToCompletionRequest(req) {
|
|
10
26
|
const messages = [];
|
|
11
27
|
const model = req.model ?? "gemini-2.5-flash";
|
|
@@ -20,6 +36,7 @@ function geminiInteractionsToCompletionRequest(req) {
|
|
|
20
36
|
});
|
|
21
37
|
else if (Array.isArray(req.input)) {
|
|
22
38
|
const firstItem = req.input[0];
|
|
39
|
+
const isStepArray = !!firstItem && !("role" in firstItem) && typeof firstItem.type === "string" && STEP_TYPES.has(firstItem.type);
|
|
23
40
|
if (firstItem && "role" in firstItem) for (const turn of req.input) {
|
|
24
41
|
const role = turn.role === "model" ? "assistant" : turn.role;
|
|
25
42
|
const blocks = turn.content ?? turn.parts;
|
|
@@ -71,7 +88,42 @@ function geminiInteractionsToCompletionRequest(req) {
|
|
|
71
88
|
});
|
|
72
89
|
}
|
|
73
90
|
}
|
|
74
|
-
else {
|
|
91
|
+
else if (isStepArray) {
|
|
92
|
+
for (const step of req.input) if (step.type === "user_input") {
|
|
93
|
+
const text = (step.content ?? []).filter((p) => p.type === "text").map((p) => p.text ?? "").join("");
|
|
94
|
+
messages.push({
|
|
95
|
+
role: "user",
|
|
96
|
+
content: text
|
|
97
|
+
});
|
|
98
|
+
} else if (step.type === "model_output") {
|
|
99
|
+
const blocks = step.content ?? [];
|
|
100
|
+
const funcCallParts = blocks.filter((p) => p.type === "function_call");
|
|
101
|
+
const textContent = blocks.filter((p) => p.type === "text").map((p) => p.text ?? "").join("");
|
|
102
|
+
if (funcCallParts.length > 0) messages.push({
|
|
103
|
+
role: "assistant",
|
|
104
|
+
content: textContent || null,
|
|
105
|
+
tool_calls: funcCallParts.map((p) => ({
|
|
106
|
+
id: p.id ?? p.call_id ?? require_helpers.generateToolCallId(),
|
|
107
|
+
type: "function",
|
|
108
|
+
function: {
|
|
109
|
+
name: p.name ?? "",
|
|
110
|
+
arguments: JSON.stringify(p.arguments ?? {})
|
|
111
|
+
}
|
|
112
|
+
}))
|
|
113
|
+
});
|
|
114
|
+
else messages.push({
|
|
115
|
+
role: "assistant",
|
|
116
|
+
content: textContent
|
|
117
|
+
});
|
|
118
|
+
} else if (STEP_RESULT_TYPES.has(step.type)) {
|
|
119
|
+
const resultValue = step.result ?? step.output;
|
|
120
|
+
messages.push({
|
|
121
|
+
role: "tool",
|
|
122
|
+
content: typeof resultValue === "string" ? resultValue : JSON.stringify(resultValue ?? ""),
|
|
123
|
+
tool_call_id: step.call_id ?? step.id ?? ""
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
} else {
|
|
75
127
|
const text = req.input.filter((p) => p.type === "text").map((p) => p.text ?? "").join("");
|
|
76
128
|
messages.push({
|
|
77
129
|
role: "user",
|
|
@@ -114,12 +166,12 @@ function interactionsUsage(overrides) {
|
|
|
114
166
|
total_output_tokens: 0,
|
|
115
167
|
total_tokens: 0
|
|
116
168
|
};
|
|
117
|
-
const input = overrides.usage.input_tokens ?? overrides.usage.prompt_tokens ?? 0;
|
|
118
|
-
const output = overrides.usage.output_tokens ?? overrides.usage.completion_tokens ?? 0;
|
|
169
|
+
const input = overrides.usage.input_tokens ?? overrides.usage.prompt_tokens ?? overrides.usage.promptTokenCount ?? 0;
|
|
170
|
+
const output = overrides.usage.output_tokens ?? overrides.usage.completion_tokens ?? overrides.usage.candidatesTokenCount ?? 0;
|
|
119
171
|
return {
|
|
120
172
|
total_input_tokens: input,
|
|
121
173
|
total_output_tokens: output,
|
|
122
|
-
total_tokens: input + output
|
|
174
|
+
total_tokens: overrides.usage.total_tokens ?? overrides.usage.totalTokenCount ?? input + output
|
|
123
175
|
};
|
|
124
176
|
}
|
|
125
177
|
function buildInteractionsTextResponse(content, model, interactionId, overrides) {
|
|
@@ -411,9 +463,11 @@ async function writeGeminiInteractionsSSEStream(res, events, optionsOrLatency) {
|
|
|
411
463
|
if (signal?.aborted) return false;
|
|
412
464
|
if (res.writableEnded) return true;
|
|
413
465
|
res.write(`data: ${JSON.stringify(event)}\n\n`);
|
|
414
|
-
|
|
466
|
+
if (event.event_type === "content.delta") {
|
|
467
|
+
onChunkSent?.();
|
|
468
|
+
chunkIndex++;
|
|
469
|
+
}
|
|
415
470
|
if (signal?.aborted) return false;
|
|
416
|
-
chunkIndex++;
|
|
417
471
|
}
|
|
418
472
|
if (!res.writableEnded) res.end();
|
|
419
473
|
return true;
|
|
@@ -598,6 +652,7 @@ async function handleGeminiInteractions(req, res, raw, fixtures, journal, defaul
|
|
|
598
652
|
return;
|
|
599
653
|
}
|
|
600
654
|
if (require_helpers.isToolCallResponse(response)) {
|
|
655
|
+
if (response.webSearches?.length) logger.warn("webSearches in fixture response are not supported for Gemini Interactions API — ignoring");
|
|
601
656
|
const overrides = require_helpers.extractOverrides(response);
|
|
602
657
|
const journalEntry = journal.add({
|
|
603
658
|
method: req.method ?? "POST",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gemini-interactions.cjs","names":["generateToolCallId","calculateDelay","delay","flattenHeaders","getContext","getTestId","matchFixture","applyChaos","resolveStrictMode","strictOverrideField","proxyAndRecord","resolveResponse","isErrorResponse","isContentWithToolCallsResponse","extractOverrides","createInterruptionSignal","isTextResponse","isToolCallResponse"],"sources":["../src/gemini-interactions.ts"],"sourcesContent":["/**\n * Google Gemini Interactions API support.\n *\n * Translates incoming Interactions requests into the ChatCompletionRequest\n * format used by the fixture router, and converts fixture responses back\n * into the Gemini Interactions format — either a single JSON response or\n * an SSE stream with event_type-based framing.\n */\n\nimport type * as http from \"node:http\";\nimport type {\n ChatCompletionRequest,\n ChatMessage,\n Fixture,\n HandlerDefaults,\n RecordedTimings,\n ResponseOverrides,\n StreamingProfile,\n ToolCall,\n ToolDefinition,\n} from \"./types.js\";\nimport {\n isTextResponse,\n isToolCallResponse,\n isContentWithToolCallsResponse,\n isErrorResponse,\n extractOverrides,\n generateToolCallId,\n flattenHeaders,\n getTestId,\n getContext,\n resolveResponse,\n resolveStrictMode,\n strictOverrideField,\n} from \"./helpers.js\";\nimport { matchFixture } from \"./router.js\";\nimport { writeErrorResponse, delay, calculateDelay } from \"./sse-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// ─── Interactions request types ────────────────────────────────────────────\n\ninterface InteractionsContentBlock {\n type: string;\n text?: string;\n name?: string;\n call_id?: string;\n id?: string;\n arguments?: Record<string, unknown>;\n output?: unknown;\n result?: unknown;\n}\n\ninterface InteractionsTurn {\n role: string;\n content?: InteractionsContentBlock[];\n parts?: InteractionsContentBlock[];\n}\n\ninterface InteractionsFunctionTool {\n type: \"function\";\n name: string;\n description?: string;\n parameters?: object;\n}\n\ninterface InteractionsRequest {\n model?: string;\n input?: string | InteractionsTurn[] | InteractionsContentBlock[];\n system_instruction?: string;\n tools?: InteractionsFunctionTool[];\n generation_config?: {\n temperature?: number;\n max_output_tokens?: number;\n [key: string]: unknown;\n };\n stream?: boolean;\n previous_interaction_id?: string;\n [key: string]: unknown;\n}\n\n// ─── Input conversion: Interactions → ChatCompletionRequest ───────────────\n\nexport function geminiInteractionsToCompletionRequest(\n req: InteractionsRequest,\n): ChatCompletionRequest {\n const messages: ChatMessage[] = [];\n const model = req.model ?? \"gemini-2.5-flash\";\n\n // system_instruction → system message\n if (req.system_instruction) {\n messages.push({ role: \"system\", content: req.system_instruction });\n }\n\n // Parse input\n if (req.input !== undefined) {\n if (typeof req.input === \"string\") {\n // Simple string input → single user message\n messages.push({ role: \"user\", content: req.input });\n } else if (Array.isArray(req.input)) {\n // Could be Turn[] or Content[]\n const firstItem = req.input[0];\n if (firstItem && \"role\" in firstItem) {\n // Turn[] format\n for (const turn of req.input as InteractionsTurn[]) {\n const role = turn.role === \"model\" ? \"assistant\" : turn.role;\n const blocks = turn.content ?? turn.parts;\n if (!blocks || blocks.length === 0) {\n if (role === \"user\" || role === \"assistant\") {\n messages.push({ role: role as \"user\" | \"assistant\", content: \"\" });\n }\n continue;\n }\n\n // Check for function_call or function_result parts\n const funcCallParts = blocks.filter((p) => p.type === \"function_call\");\n const funcResultParts = blocks.filter((p) => p.type === \"function_result\");\n const textParts = blocks.filter((p) => p.type === \"text\");\n\n if (funcCallParts.length > 0) {\n // Assistant tool call message\n const textContent = textParts.map((p) => p.text ?? \"\").join(\"\");\n messages.push({\n role: \"assistant\",\n content: textContent || null,\n tool_calls: funcCallParts.map((p) => ({\n id: p.id ?? p.call_id ?? generateToolCallId(),\n type: \"function\" as const,\n function: {\n name: p.name ?? \"\",\n arguments: JSON.stringify(p.arguments ?? {}),\n },\n })),\n });\n } else if (funcResultParts.length > 0) {\n // Tool response messages\n for (const part of funcResultParts) {\n const resultValue = part.result ?? part.output;\n messages.push({\n role: \"tool\",\n content:\n typeof resultValue === \"string\" ? resultValue : JSON.stringify(resultValue ?? \"\"),\n tool_call_id: part.call_id ?? part.id ?? \"\",\n });\n }\n // Any text parts alongside → separate user message\n if (textParts.length > 0) {\n const text = textParts.map((p) => p.text ?? \"\").join(\"\");\n if (text) {\n messages.push({ role: \"user\", content: text });\n }\n }\n } else {\n // Text-only turn\n const text = textParts.map((p) => p.text ?? \"\").join(\"\");\n if (role === \"user\" || role === \"assistant\" || role === \"system\") {\n messages.push({\n role: role as \"user\" | \"assistant\" | \"system\",\n content: text,\n });\n }\n }\n }\n } else {\n // Content[] format — single user message with content blocks\n const textParts = (req.input as InteractionsContentBlock[]).filter(\n (p) => p.type === \"text\",\n );\n const text = textParts.map((p) => p.text ?? \"\").join(\"\");\n messages.push({ role: \"user\", content: text || \"\" });\n }\n }\n }\n\n // Convert tools\n let tools: ToolDefinition[] | undefined;\n if (req.tools && req.tools.length > 0) {\n const funcTools = req.tools.filter((t) => t.type === \"function\");\n if (funcTools.length > 0) {\n tools = funcTools.map((t) => ({\n type: \"function\" as const,\n function: {\n name: t.name,\n description: t.description,\n parameters: t.parameters,\n },\n }));\n }\n }\n\n return {\n model,\n messages,\n stream: req.stream !== false, // default true\n temperature: req.generation_config?.temperature,\n max_tokens: req.generation_config?.max_output_tokens,\n tools,\n };\n}\n\n// ─── Interaction ID generation ────────────────────────────────────────────\n\nlet interactionCounter = 0;\n\nexport function resetInteractionCounter(): void {\n interactionCounter = 0;\n}\n\nfunction nextInteractionId(): string {\n return `aimock-int-${interactionCounter++}`;\n}\n\n// ─── Usage helpers ────────────────────────────────────────────────────────\n\nfunction interactionsUsage(overrides?: ResponseOverrides): {\n total_input_tokens: number;\n total_output_tokens: number;\n total_tokens: number;\n} {\n if (!overrides?.usage) return { total_input_tokens: 0, total_output_tokens: 0, total_tokens: 0 };\n const input = overrides.usage.input_tokens ?? overrides.usage.prompt_tokens ?? 0;\n const output = overrides.usage.output_tokens ?? overrides.usage.completion_tokens ?? 0;\n return {\n total_input_tokens: input,\n total_output_tokens: output,\n total_tokens: input + output,\n };\n}\n\n// ─── Response building: fixture → Interactions format ─────────────────────\n\nexport function buildInteractionsTextResponse(\n content: string,\n model: string,\n interactionId: string,\n overrides?: ResponseOverrides,\n): object {\n return {\n id: interactionId,\n status: \"completed\",\n model: overrides?.model ?? model,\n role: \"model\",\n outputs: [{ type: \"text\", text: content }],\n usage: interactionsUsage(overrides),\n };\n}\n\nexport function buildInteractionsToolCallResponse(\n toolCalls: ToolCall[],\n model: string,\n interactionId: string,\n logger: Logger,\n overrides?: ResponseOverrides,\n): object {\n return {\n id: interactionId,\n status: \"requires_action\",\n model: overrides?.model ?? model,\n role: \"model\",\n outputs: toolCalls.map((tc) => {\n let argsObj: unknown;\n try {\n argsObj = JSON.parse(tc.arguments || \"{}\");\n } catch {\n logger.warn(\n `Malformed JSON in fixture tool call arguments for \"${tc.name}\": ${tc.arguments}`,\n );\n argsObj = {};\n }\n return {\n type: \"function_call\",\n id: tc.id || generateToolCallId(),\n name: tc.name,\n arguments: argsObj,\n };\n }),\n usage: interactionsUsage(overrides),\n };\n}\n\nexport function buildInteractionsContentWithToolCallsResponse(\n content: string,\n toolCalls: ToolCall[],\n model: string,\n interactionId: string,\n logger: Logger,\n overrides?: ResponseOverrides,\n): object {\n const outputs: object[] = [{ type: \"text\", text: content }];\n for (const tc of toolCalls) {\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 outputs.push({\n type: \"function_call\",\n id: tc.id || generateToolCallId(),\n name: tc.name,\n arguments: argsObj,\n });\n }\n\n return {\n id: interactionId,\n status: \"requires_action\",\n model: overrides?.model ?? model,\n role: \"model\",\n outputs,\n usage: interactionsUsage(overrides),\n };\n}\n\nfunction buildInteractionsErrorResponse(message: string, code?: string): object {\n return {\n error: {\n code: code ?? \"INVALID_ARGUMENT\",\n message,\n },\n };\n}\n\n// ─── SSE event builders ──────────────────────────────────────────────────\n\ninterface InteractionsSSEEvent {\n event_type: string;\n [key: string]: unknown;\n}\n\nlet eventIdCounter = 0;\n\nexport function resetEventIdCounter(): void {\n eventIdCounter = 0;\n}\n\nfunction nextEventId(): string {\n return `evt_${++eventIdCounter}`;\n}\n\nexport function buildInteractionsTextSSEEvents(\n content: string,\n interactionId: string,\n chunkSize: number,\n overrides?: ResponseOverrides,\n): InteractionsSSEEvent[] {\n const events: InteractionsSSEEvent[] = [];\n\n // interaction.start\n events.push({\n event_type: \"interaction.start\",\n interaction: { id: interactionId, status: \"in_progress\" },\n event_id: nextEventId(),\n });\n\n // content.start\n events.push({\n event_type: \"content.start\",\n index: 0,\n content: { type: \"text\" },\n event_id: nextEventId(),\n });\n\n // content.delta(s)\n if (content.length === 0) {\n events.push({\n event_type: \"content.delta\",\n index: 0,\n delta: { type: \"text\", text: \"\" },\n event_id: nextEventId(),\n });\n } else {\n for (let i = 0; i < content.length; i += chunkSize) {\n const slice = content.slice(i, i + chunkSize);\n events.push({\n event_type: \"content.delta\",\n index: 0,\n delta: { type: \"text\", text: slice },\n event_id: nextEventId(),\n });\n }\n }\n\n // content.stop\n events.push({\n event_type: \"content.stop\",\n index: 0,\n event_id: nextEventId(),\n });\n\n // interaction.complete\n events.push({\n event_type: \"interaction.complete\",\n interaction: {\n id: interactionId,\n status: \"completed\",\n usage: interactionsUsage(overrides),\n },\n event_id: nextEventId(),\n });\n\n return events;\n}\n\nexport function buildInteractionsToolCallSSEEvents(\n toolCalls: ToolCall[],\n interactionId: string,\n logger: Logger,\n overrides?: ResponseOverrides,\n): InteractionsSSEEvent[] {\n const events: InteractionsSSEEvent[] = [];\n\n // interaction.start\n events.push({\n event_type: \"interaction.start\",\n interaction: { id: interactionId, status: \"in_progress\" },\n event_id: nextEventId(),\n });\n\n // Each tool call gets its own content.start/delta/stop bracket\n for (let idx = 0; idx < toolCalls.length; idx++) {\n const tc = toolCalls[idx];\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\n events.push({\n event_type: \"content.start\",\n index: idx,\n content: { type: \"function_call\" },\n event_id: nextEventId(),\n });\n\n events.push({\n event_type: \"content.delta\",\n index: idx,\n delta: {\n type: \"function_call\",\n id: tc.id || generateToolCallId(),\n name: tc.name,\n arguments: argsObj,\n },\n event_id: nextEventId(),\n });\n\n events.push({\n event_type: \"content.stop\",\n index: idx,\n event_id: nextEventId(),\n });\n }\n\n // interaction.complete\n events.push({\n event_type: \"interaction.complete\",\n interaction: {\n id: interactionId,\n status: \"requires_action\",\n usage: interactionsUsage(overrides),\n },\n event_id: nextEventId(),\n });\n\n return events;\n}\n\nexport function buildInteractionsContentWithToolCallsSSEEvents(\n content: string,\n toolCalls: ToolCall[],\n interactionId: string,\n chunkSize: number,\n logger: Logger,\n overrides?: ResponseOverrides,\n): InteractionsSSEEvent[] {\n const events: InteractionsSSEEvent[] = [];\n\n // interaction.start\n events.push({\n event_type: \"interaction.start\",\n interaction: { id: interactionId, status: \"in_progress\" },\n event_id: nextEventId(),\n });\n\n // Text content at index 0\n events.push({\n event_type: \"content.start\",\n index: 0,\n content: { type: \"text\" },\n event_id: nextEventId(),\n });\n\n if (content.length === 0) {\n events.push({\n event_type: \"content.delta\",\n index: 0,\n delta: { type: \"text\", text: \"\" },\n event_id: nextEventId(),\n });\n } else {\n for (let i = 0; i < content.length; i += chunkSize) {\n const slice = content.slice(i, i + chunkSize);\n events.push({\n event_type: \"content.delta\",\n index: 0,\n delta: { type: \"text\", text: slice },\n event_id: nextEventId(),\n });\n }\n }\n\n events.push({\n event_type: \"content.stop\",\n index: 0,\n event_id: nextEventId(),\n });\n\n // Tool calls at index 1+\n for (let i = 0; i < toolCalls.length; i++) {\n const tc = toolCalls[i];\n const idx = i + 1; // offset by 1 because text is index 0\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\n events.push({\n event_type: \"content.start\",\n index: idx,\n content: { type: \"function_call\" },\n event_id: nextEventId(),\n });\n\n events.push({\n event_type: \"content.delta\",\n index: idx,\n delta: {\n type: \"function_call\",\n id: tc.id || generateToolCallId(),\n name: tc.name,\n arguments: argsObj,\n },\n event_id: nextEventId(),\n });\n\n events.push({\n event_type: \"content.stop\",\n index: idx,\n event_id: nextEventId(),\n });\n }\n\n // interaction.complete\n events.push({\n event_type: \"interaction.complete\",\n interaction: {\n id: interactionId,\n status: \"requires_action\",\n usage: interactionsUsage(overrides),\n },\n event_id: nextEventId(),\n });\n\n return events;\n}\n\n// ─── SSE writer for Interactions streaming ────────────────────────────────\n\ninterface InteractionsStreamOptions {\n latency?: number;\n streamingProfile?: StreamingProfile;\n recordedTimings?: RecordedTimings;\n replaySpeed?: number;\n signal?: AbortSignal;\n onChunkSent?: () => void;\n}\n\nexport async function writeGeminiInteractionsSSEStream(\n res: http.ServerResponse,\n events: InteractionsSSEEvent[],\n optionsOrLatency?: number | InteractionsStreamOptions,\n): Promise<boolean> {\n const opts: InteractionsStreamOptions =\n typeof optionsOrLatency === \"number\" ? { latency: optionsOrLatency } : (optionsOrLatency ?? {});\n const latency = opts.latency ?? 0;\n const profile = opts.streamingProfile;\n const { recordedTimings, replaySpeed } = opts;\n const signal = opts.signal;\n const onChunkSent = opts.onChunkSent;\n\n if (res.writableEnded) return true;\n res.setHeader(\"Content-Type\", \"text/event-stream\");\n res.setHeader(\"Cache-Control\", \"no-cache\");\n res.setHeader(\"Connection\", \"keep-alive\");\n\n let chunkIndex = 0;\n for (const event of events) {\n const chunkDelay = calculateDelay(chunkIndex, profile, latency, recordedTimings, replaySpeed);\n if (chunkDelay > 0) await delay(chunkDelay, signal);\n if (signal?.aborted) return false;\n if (res.writableEnded) return true;\n // Data-only SSE (no event: prefix, no [DONE])\n res.write(`data: ${JSON.stringify(event)}\\n\\n`);\n onChunkSent?.();\n if (signal?.aborted) return false;\n chunkIndex++;\n }\n\n if (!res.writableEnded) {\n res.end();\n }\n return true;\n}\n\n// ─── Request handler ──────────────────────────────────────────────────────\n\nexport async function handleGeminiInteractions(\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 ?? \"/v1beta/interactions\";\n\n let interactionsReq: InteractionsRequest;\n try {\n interactionsReq = JSON.parse(raw) as InteractionsRequest;\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 buildInteractionsErrorResponse(`Malformed JSON body: ${detail}`, \"INVALID_ARGUMENT\"),\n ),\n );\n return;\n }\n\n // Convert to ChatCompletionRequest for fixture matching\n const completionReq = geminiInteractionsToCompletionRequest(interactionsReq);\n completionReq._endpointType = \"chat\";\n completionReq._context = getContext(req);\n\n const streaming = interactionsReq.stream !== false; // default true\n const model = completionReq.model;\n\n const testId = getTestId(req);\n const fixture = matchFixture(\n fixtures,\n completionReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n );\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n }\n\n if (\n applyChaos(\n res,\n fixture,\n defaults.chaos,\n req.headers,\n journal,\n {\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n },\n fixture ? \"fixture\" : \"proxy\",\n defaults.registry,\n defaults.logger,\n )\n )\n return;\n\n if (!fixture) {\n const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);\n if (effectiveStrict) {\n const strictStatus = 503;\n const strictMessage = \"Strict mode: no fixture matched\";\n logger.error(`STRICT: No fixture matched for ${req.method ?? \"POST\"} ${urlPath}`);\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(buildInteractionsErrorResponse(strictMessage, \"UNAVAILABLE\")),\n );\n return;\n }\n if (defaults.record) {\n const outcome = await proxyAndRecord(\n req,\n res,\n completionReq,\n \"gemini-interactions\",\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: {\n status: res.statusCode ?? 200,\n fixture: null,\n source: \"proxy\",\n },\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(buildInteractionsErrorResponse(\"No fixture matched\", \"NOT_FOUND\")),\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 const replaySpeed = fixture.replaySpeed ?? defaults.replaySpeed;\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(\n res,\n status,\n JSON.stringify(\n buildInteractionsErrorResponse(response.error.message, response.error.type ?? \"ERROR\"),\n ),\n { retryAfter: response.retryAfter },\n );\n return;\n }\n\n const interactionId = nextInteractionId();\n\n // Content + tool calls response\n if (isContentWithToolCallsResponse(response)) {\n if (response.webSearches?.length) {\n logger.warn(\n \"webSearches in fixture response are not supported for Gemini Interactions API — ignoring\",\n );\n }\n const overrides = extractOverrides(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 if (!streaming) {\n const body = buildInteractionsContentWithToolCallsResponse(\n response.content,\n response.toolCalls,\n model,\n interactionId,\n logger,\n overrides,\n );\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const events = buildInteractionsContentWithToolCallsSSEEvents(\n response.content,\n response.toolCalls,\n interactionId,\n chunkSize,\n logger,\n overrides,\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeGeminiInteractionsSSEStream(res, events, {\n latency,\n streamingProfile: fixture.streamingProfile,\n recordedTimings: fixture.recordedTimings,\n 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(\n \"webSearches in fixture response are not supported for Gemini Interactions API — ignoring\",\n );\n }\n const overrides = extractOverrides(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 if (!streaming) {\n const body = buildInteractionsTextResponse(response.content, model, interactionId, overrides);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const events = buildInteractionsTextSSEEvents(\n response.content,\n interactionId,\n chunkSize,\n overrides,\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeGeminiInteractionsSSEStream(res, events, {\n latency,\n streamingProfile: fixture.streamingProfile,\n recordedTimings: fixture.recordedTimings,\n 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 const overrides = extractOverrides(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 if (!streaming) {\n const body = buildInteractionsToolCallResponse(\n response.toolCalls,\n model,\n interactionId,\n logger,\n overrides,\n );\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const events = buildInteractionsToolCallSSEEvents(\n response.toolCalls,\n interactionId,\n logger,\n overrides,\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeGeminiInteractionsSSEStream(res, events, {\n latency,\n streamingProfile: fixture.streamingProfile,\n recordedTimings: fixture.recordedTimings,\n 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 buildInteractionsErrorResponse(\"Fixture response did not match any known type\", \"INTERNAL\"),\n ),\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"gemini-interactions.cjs","names":["generateToolCallId","calculateDelay","delay","flattenHeaders","getContext","getTestId","matchFixture","applyChaos","resolveStrictMode","strictOverrideField","proxyAndRecord","resolveResponse","isErrorResponse","isContentWithToolCallsResponse","extractOverrides","createInterruptionSignal","isTextResponse","isToolCallResponse"],"sources":["../src/gemini-interactions.ts"],"sourcesContent":["/**\n * Google Gemini Interactions API support.\n *\n * Translates incoming Interactions requests into the ChatCompletionRequest\n * format used by the fixture router, and converts fixture responses back\n * into the Gemini Interactions format — either a single JSON response or\n * an SSE stream with event_type-based framing.\n */\n\nimport type * as http from \"node:http\";\nimport type {\n ChatCompletionRequest,\n ChatMessage,\n Fixture,\n HandlerDefaults,\n RecordedTimings,\n ResponseOverrides,\n StreamingProfile,\n ToolCall,\n ToolDefinition,\n} from \"./types.js\";\nimport {\n isTextResponse,\n isToolCallResponse,\n isContentWithToolCallsResponse,\n isErrorResponse,\n extractOverrides,\n generateToolCallId,\n flattenHeaders,\n getTestId,\n getContext,\n resolveResponse,\n resolveStrictMode,\n strictOverrideField,\n} from \"./helpers.js\";\nimport { matchFixture } from \"./router.js\";\nimport { writeErrorResponse, delay, calculateDelay } from \"./sse-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// ─── Interactions request types ────────────────────────────────────────────\n\ninterface InteractionsContentBlock {\n type: string;\n text?: string;\n name?: string;\n call_id?: string;\n id?: string;\n arguments?: Record<string, unknown>;\n output?: unknown;\n result?: unknown;\n}\n\ninterface InteractionsTurn {\n role: string;\n content?: InteractionsContentBlock[];\n parts?: InteractionsContentBlock[];\n}\n\n/**\n * Top-level Step envelope accepted by the live Gemini Interactions API.\n * The SDK's TypeScript union does not include Step[], but the wire contract\n * does — clients following the live API send these at the top level of `input`.\n * Discriminated by `type`; no `role` field (distinguishes from Turn[]).\n */\ninterface InteractionsStep {\n type: string;\n content?: InteractionsContentBlock[];\n call_id?: string;\n id?: string;\n name?: string;\n result?: unknown;\n output?: unknown;\n is_error?: boolean;\n signature?: string;\n}\n\n/** Step types whose payload is a tool/agent result keyed by call_id. */\nconst STEP_RESULT_TYPES = new Set<string>([\n \"function_result\",\n \"code_execution_result\",\n \"url_context_result\",\n \"google_search_result\",\n \"google_maps_result\",\n \"mcp_server_tool_result\",\n \"file_search_result\",\n]);\n\n/** All recognized top-level Step types (used as the Step[] discriminator). */\nconst STEP_TYPES = new Set<string>([\"user_input\", \"model_output\", ...STEP_RESULT_TYPES]);\n\ninterface InteractionsFunctionTool {\n type: \"function\";\n name: string;\n description?: string;\n parameters?: object;\n}\n\ninterface InteractionsRequest {\n model?: string;\n input?: string | InteractionsTurn[] | InteractionsStep[] | InteractionsContentBlock[];\n system_instruction?: string;\n tools?: InteractionsFunctionTool[];\n generation_config?: {\n temperature?: number;\n max_output_tokens?: number;\n [key: string]: unknown;\n };\n stream?: boolean;\n previous_interaction_id?: string;\n [key: string]: unknown;\n}\n\n// ─── Input conversion: Interactions → ChatCompletionRequest ───────────────\n\nexport function geminiInteractionsToCompletionRequest(\n req: InteractionsRequest,\n): ChatCompletionRequest {\n const messages: ChatMessage[] = [];\n const model = req.model ?? \"gemini-2.5-flash\";\n\n // system_instruction → system message\n if (req.system_instruction) {\n messages.push({ role: \"system\", content: req.system_instruction });\n }\n\n // Parse input\n if (req.input !== undefined) {\n if (typeof req.input === \"string\") {\n // Simple string input → single user message\n messages.push({ role: \"user\", content: req.input });\n } else if (Array.isArray(req.input)) {\n // Could be Turn[], Step[], or Content[]\n const firstItem = req.input[0] as\n | InteractionsTurn\n | InteractionsStep\n | InteractionsContentBlock\n | undefined;\n const isStepArray =\n !!firstItem &&\n !(\"role\" in firstItem) &&\n typeof firstItem.type === \"string\" &&\n STEP_TYPES.has(firstItem.type);\n\n if (firstItem && \"role\" in firstItem) {\n // Turn[] format\n for (const turn of req.input as InteractionsTurn[]) {\n const role = turn.role === \"model\" ? \"assistant\" : turn.role;\n const blocks = turn.content ?? turn.parts;\n if (!blocks || blocks.length === 0) {\n if (role === \"user\" || role === \"assistant\") {\n messages.push({ role: role as \"user\" | \"assistant\", content: \"\" });\n }\n continue;\n }\n\n // Check for function_call or function_result parts\n const funcCallParts = blocks.filter((p) => p.type === \"function_call\");\n const funcResultParts = blocks.filter((p) => p.type === \"function_result\");\n const textParts = blocks.filter((p) => p.type === \"text\");\n\n if (funcCallParts.length > 0) {\n // Assistant tool call message\n const textContent = textParts.map((p) => p.text ?? \"\").join(\"\");\n messages.push({\n role: \"assistant\",\n content: textContent || null,\n tool_calls: funcCallParts.map((p) => ({\n id: p.id ?? p.call_id ?? generateToolCallId(),\n type: \"function\" as const,\n function: {\n name: p.name ?? \"\",\n arguments: JSON.stringify(p.arguments ?? {}),\n },\n })),\n });\n } else if (funcResultParts.length > 0) {\n // Tool response messages\n for (const part of funcResultParts) {\n const resultValue = part.result ?? part.output;\n messages.push({\n role: \"tool\",\n content:\n typeof resultValue === \"string\" ? resultValue : JSON.stringify(resultValue ?? \"\"),\n tool_call_id: part.call_id ?? part.id ?? \"\",\n });\n }\n // Any text parts alongside → separate user message\n if (textParts.length > 0) {\n const text = textParts.map((p) => p.text ?? \"\").join(\"\");\n if (text) {\n messages.push({ role: \"user\", content: text });\n }\n }\n } else {\n // Text-only turn\n const text = textParts.map((p) => p.text ?? \"\").join(\"\");\n if (role === \"user\" || role === \"assistant\" || role === \"system\") {\n messages.push({\n role: role as \"user\" | \"assistant\" | \"system\",\n content: text,\n });\n }\n }\n }\n } else if (isStepArray) {\n // Step[] format — the wire contract Google's /v1beta/interactions accepts.\n for (const step of req.input as InteractionsStep[]) {\n if (step.type === \"user_input\") {\n const text = (step.content ?? [])\n .filter((p) => p.type === \"text\")\n .map((p) => p.text ?? \"\")\n .join(\"\");\n messages.push({ role: \"user\", content: text });\n } else if (step.type === \"model_output\") {\n const blocks = step.content ?? [];\n const funcCallParts = blocks.filter((p) => p.type === \"function_call\");\n const textParts = blocks.filter((p) => p.type === \"text\");\n const textContent = textParts.map((p) => p.text ?? \"\").join(\"\");\n\n if (funcCallParts.length > 0) {\n messages.push({\n role: \"assistant\",\n content: textContent || null,\n tool_calls: funcCallParts.map((p) => ({\n id: p.id ?? p.call_id ?? generateToolCallId(),\n type: \"function\" as const,\n function: {\n name: p.name ?? \"\",\n arguments: JSON.stringify(p.arguments ?? {}),\n },\n })),\n });\n } else {\n messages.push({ role: \"assistant\", content: textContent });\n }\n } else if (STEP_RESULT_TYPES.has(step.type)) {\n const resultValue = step.result ?? step.output;\n messages.push({\n role: \"tool\",\n content:\n typeof resultValue === \"string\" ? resultValue : JSON.stringify(resultValue ?? \"\"),\n tool_call_id: step.call_id ?? step.id ?? \"\",\n });\n }\n }\n } else {\n // Content[] format — single user message with content blocks\n const textParts = (req.input as InteractionsContentBlock[]).filter(\n (p) => p.type === \"text\",\n );\n const text = textParts.map((p) => p.text ?? \"\").join(\"\");\n messages.push({ role: \"user\", content: text || \"\" });\n }\n }\n }\n\n // Convert tools\n let tools: ToolDefinition[] | undefined;\n if (req.tools && req.tools.length > 0) {\n const funcTools = req.tools.filter((t) => t.type === \"function\");\n if (funcTools.length > 0) {\n tools = funcTools.map((t) => ({\n type: \"function\" as const,\n function: {\n name: t.name,\n description: t.description,\n parameters: t.parameters,\n },\n }));\n }\n }\n\n return {\n model,\n messages,\n stream: req.stream !== false, // default true\n temperature: req.generation_config?.temperature,\n max_tokens: req.generation_config?.max_output_tokens,\n tools,\n };\n}\n\n// ─── Interaction ID generation ────────────────────────────────────────────\n\nlet interactionCounter = 0;\n\nexport function resetInteractionCounter(): void {\n interactionCounter = 0;\n}\n\nfunction nextInteractionId(): string {\n return `aimock-int-${interactionCounter++}`;\n}\n\n// ─── Usage helpers ────────────────────────────────────────────────────────\n\nfunction interactionsUsage(overrides?: ResponseOverrides): {\n total_input_tokens: number;\n total_output_tokens: number;\n total_tokens: number;\n} {\n if (!overrides?.usage) return { total_input_tokens: 0, total_output_tokens: 0, total_tokens: 0 };\n const input =\n overrides.usage.input_tokens ??\n overrides.usage.prompt_tokens ??\n overrides.usage.promptTokenCount ??\n 0;\n const output =\n overrides.usage.output_tokens ??\n overrides.usage.completion_tokens ??\n overrides.usage.candidatesTokenCount ??\n 0;\n const total = overrides.usage.total_tokens ?? overrides.usage.totalTokenCount ?? input + output;\n return {\n total_input_tokens: input,\n total_output_tokens: output,\n total_tokens: total,\n };\n}\n\n// ─── Response building: fixture → Interactions format ─────────────────────\n\nexport function buildInteractionsTextResponse(\n content: string,\n model: string,\n interactionId: string,\n overrides?: ResponseOverrides,\n): object {\n return {\n id: interactionId,\n status: \"completed\",\n model: overrides?.model ?? model,\n role: \"model\",\n outputs: [{ type: \"text\", text: content }],\n usage: interactionsUsage(overrides),\n };\n}\n\nexport function buildInteractionsToolCallResponse(\n toolCalls: ToolCall[],\n model: string,\n interactionId: string,\n logger: Logger,\n overrides?: ResponseOverrides,\n): object {\n return {\n id: interactionId,\n status: \"requires_action\",\n model: overrides?.model ?? model,\n role: \"model\",\n outputs: toolCalls.map((tc) => {\n let argsObj: unknown;\n try {\n argsObj = JSON.parse(tc.arguments || \"{}\");\n } catch {\n logger.warn(\n `Malformed JSON in fixture tool call arguments for \"${tc.name}\": ${tc.arguments}`,\n );\n argsObj = {};\n }\n return {\n type: \"function_call\",\n id: tc.id || generateToolCallId(),\n name: tc.name,\n arguments: argsObj,\n };\n }),\n usage: interactionsUsage(overrides),\n };\n}\n\nexport function buildInteractionsContentWithToolCallsResponse(\n content: string,\n toolCalls: ToolCall[],\n model: string,\n interactionId: string,\n logger: Logger,\n overrides?: ResponseOverrides,\n): object {\n const outputs: object[] = [{ type: \"text\", text: content }];\n for (const tc of toolCalls) {\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 outputs.push({\n type: \"function_call\",\n id: tc.id || generateToolCallId(),\n name: tc.name,\n arguments: argsObj,\n });\n }\n\n return {\n id: interactionId,\n status: \"requires_action\",\n model: overrides?.model ?? model,\n role: \"model\",\n outputs,\n usage: interactionsUsage(overrides),\n };\n}\n\nfunction buildInteractionsErrorResponse(message: string, code?: string): object {\n return {\n error: {\n code: code ?? \"INVALID_ARGUMENT\",\n message,\n },\n };\n}\n\n// ─── SSE event builders ──────────────────────────────────────────────────\n\ninterface InteractionsSSEEvent {\n event_type: string;\n [key: string]: unknown;\n}\n\nlet eventIdCounter = 0;\n\nexport function resetEventIdCounter(): void {\n eventIdCounter = 0;\n}\n\nfunction nextEventId(): string {\n return `evt_${++eventIdCounter}`;\n}\n\nexport function buildInteractionsTextSSEEvents(\n content: string,\n interactionId: string,\n chunkSize: number,\n overrides?: ResponseOverrides,\n): InteractionsSSEEvent[] {\n const events: InteractionsSSEEvent[] = [];\n\n // interaction.start\n events.push({\n event_type: \"interaction.start\",\n interaction: { id: interactionId, status: \"in_progress\" },\n event_id: nextEventId(),\n });\n\n // content.start\n events.push({\n event_type: \"content.start\",\n index: 0,\n content: { type: \"text\" },\n event_id: nextEventId(),\n });\n\n // content.delta(s)\n if (content.length === 0) {\n events.push({\n event_type: \"content.delta\",\n index: 0,\n delta: { type: \"text\", text: \"\" },\n event_id: nextEventId(),\n });\n } else {\n for (let i = 0; i < content.length; i += chunkSize) {\n const slice = content.slice(i, i + chunkSize);\n events.push({\n event_type: \"content.delta\",\n index: 0,\n delta: { type: \"text\", text: slice },\n event_id: nextEventId(),\n });\n }\n }\n\n // content.stop\n events.push({\n event_type: \"content.stop\",\n index: 0,\n event_id: nextEventId(),\n });\n\n // interaction.complete\n events.push({\n event_type: \"interaction.complete\",\n interaction: {\n id: interactionId,\n status: \"completed\",\n usage: interactionsUsage(overrides),\n },\n event_id: nextEventId(),\n });\n\n return events;\n}\n\nexport function buildInteractionsToolCallSSEEvents(\n toolCalls: ToolCall[],\n interactionId: string,\n logger: Logger,\n overrides?: ResponseOverrides,\n): InteractionsSSEEvent[] {\n const events: InteractionsSSEEvent[] = [];\n\n // interaction.start\n events.push({\n event_type: \"interaction.start\",\n interaction: { id: interactionId, status: \"in_progress\" },\n event_id: nextEventId(),\n });\n\n // Each tool call gets its own content.start/delta/stop bracket\n for (let idx = 0; idx < toolCalls.length; idx++) {\n const tc = toolCalls[idx];\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\n events.push({\n event_type: \"content.start\",\n index: idx,\n content: { type: \"function_call\" },\n event_id: nextEventId(),\n });\n\n events.push({\n event_type: \"content.delta\",\n index: idx,\n delta: {\n type: \"function_call\",\n id: tc.id || generateToolCallId(),\n name: tc.name,\n arguments: argsObj,\n },\n event_id: nextEventId(),\n });\n\n events.push({\n event_type: \"content.stop\",\n index: idx,\n event_id: nextEventId(),\n });\n }\n\n // interaction.complete\n events.push({\n event_type: \"interaction.complete\",\n interaction: {\n id: interactionId,\n status: \"requires_action\",\n usage: interactionsUsage(overrides),\n },\n event_id: nextEventId(),\n });\n\n return events;\n}\n\nexport function buildInteractionsContentWithToolCallsSSEEvents(\n content: string,\n toolCalls: ToolCall[],\n interactionId: string,\n chunkSize: number,\n logger: Logger,\n overrides?: ResponseOverrides,\n): InteractionsSSEEvent[] {\n const events: InteractionsSSEEvent[] = [];\n\n // interaction.start\n events.push({\n event_type: \"interaction.start\",\n interaction: { id: interactionId, status: \"in_progress\" },\n event_id: nextEventId(),\n });\n\n // Text content at index 0\n events.push({\n event_type: \"content.start\",\n index: 0,\n content: { type: \"text\" },\n event_id: nextEventId(),\n });\n\n if (content.length === 0) {\n events.push({\n event_type: \"content.delta\",\n index: 0,\n delta: { type: \"text\", text: \"\" },\n event_id: nextEventId(),\n });\n } else {\n for (let i = 0; i < content.length; i += chunkSize) {\n const slice = content.slice(i, i + chunkSize);\n events.push({\n event_type: \"content.delta\",\n index: 0,\n delta: { type: \"text\", text: slice },\n event_id: nextEventId(),\n });\n }\n }\n\n events.push({\n event_type: \"content.stop\",\n index: 0,\n event_id: nextEventId(),\n });\n\n // Tool calls at index 1+\n for (let i = 0; i < toolCalls.length; i++) {\n const tc = toolCalls[i];\n const idx = i + 1; // offset by 1 because text is index 0\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\n events.push({\n event_type: \"content.start\",\n index: idx,\n content: { type: \"function_call\" },\n event_id: nextEventId(),\n });\n\n events.push({\n event_type: \"content.delta\",\n index: idx,\n delta: {\n type: \"function_call\",\n id: tc.id || generateToolCallId(),\n name: tc.name,\n arguments: argsObj,\n },\n event_id: nextEventId(),\n });\n\n events.push({\n event_type: \"content.stop\",\n index: idx,\n event_id: nextEventId(),\n });\n }\n\n // interaction.complete\n events.push({\n event_type: \"interaction.complete\",\n interaction: {\n id: interactionId,\n status: \"requires_action\",\n usage: interactionsUsage(overrides),\n },\n event_id: nextEventId(),\n });\n\n return events;\n}\n\n// ─── SSE writer for Interactions streaming ────────────────────────────────\n\ninterface InteractionsStreamOptions {\n latency?: number;\n streamingProfile?: StreamingProfile;\n recordedTimings?: RecordedTimings;\n replaySpeed?: number;\n signal?: AbortSignal;\n onChunkSent?: () => void;\n}\n\nexport async function writeGeminiInteractionsSSEStream(\n res: http.ServerResponse,\n events: InteractionsSSEEvent[],\n optionsOrLatency?: number | InteractionsStreamOptions,\n): Promise<boolean> {\n const opts: InteractionsStreamOptions =\n typeof optionsOrLatency === \"number\" ? { latency: optionsOrLatency } : (optionsOrLatency ?? {});\n const latency = opts.latency ?? 0;\n const profile = opts.streamingProfile;\n const { recordedTimings, replaySpeed } = opts;\n const signal = opts.signal;\n const onChunkSent = opts.onChunkSent;\n\n if (res.writableEnded) return true;\n res.setHeader(\"Content-Type\", \"text/event-stream\");\n res.setHeader(\"Cache-Control\", \"no-cache\");\n res.setHeader(\"Connection\", \"keep-alive\");\n\n let chunkIndex = 0;\n for (const event of events) {\n const chunkDelay = calculateDelay(chunkIndex, profile, latency, recordedTimings, replaySpeed);\n if (chunkDelay > 0) await delay(chunkDelay, signal);\n if (signal?.aborted) return false;\n if (res.writableEnded) return true;\n // Data-only SSE (no event: prefix, no [DONE])\n res.write(`data: ${JSON.stringify(event)}\\n\\n`);\n // Only count content deltas for truncateAfterChunks — framing events\n // (interaction.start, content.start, content.stop, interaction.complete)\n // should not consume chunk budget or trigger the chunk-sent callback.\n if (event.event_type === \"content.delta\") {\n onChunkSent?.();\n chunkIndex++;\n }\n if (signal?.aborted) return false;\n }\n\n if (!res.writableEnded) {\n res.end();\n }\n return true;\n}\n\n// ─── Request handler ──────────────────────────────────────────────────────\n\nexport async function handleGeminiInteractions(\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 ?? \"/v1beta/interactions\";\n\n let interactionsReq: InteractionsRequest;\n try {\n interactionsReq = JSON.parse(raw) as InteractionsRequest;\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 buildInteractionsErrorResponse(`Malformed JSON body: ${detail}`, \"INVALID_ARGUMENT\"),\n ),\n );\n return;\n }\n\n // Convert to ChatCompletionRequest for fixture matching\n const completionReq = geminiInteractionsToCompletionRequest(interactionsReq);\n // Keep \"chat\" rather than \"gemini-interactions\" — the router's endpoint\n // compatibility filter (router.ts) treats \"chat\" as a pass-through that\n // matches any unendpointed fixture. Switching to \"gemini-interactions\"\n // would make the request fall into the multimedia guard branch, preventing\n // generic chat fixtures from matching and breaking existing users. The\n // recorder would also start emitting `endpoint: \"gemini-interactions\"` in\n // recorded fixtures, creating a one-way compatibility break.\n completionReq._endpointType = \"chat\";\n completionReq._context = getContext(req);\n\n const streaming = interactionsReq.stream !== false; // default true\n const model = completionReq.model;\n\n const testId = getTestId(req);\n const fixture = matchFixture(\n fixtures,\n completionReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n );\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n }\n\n if (\n applyChaos(\n res,\n fixture,\n defaults.chaos,\n req.headers,\n journal,\n {\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n },\n fixture ? \"fixture\" : \"proxy\",\n defaults.registry,\n defaults.logger,\n )\n )\n return;\n\n if (!fixture) {\n const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);\n if (effectiveStrict) {\n const strictStatus = 503;\n const strictMessage = \"Strict mode: no fixture matched\";\n logger.error(`STRICT: No fixture matched for ${req.method ?? \"POST\"} ${urlPath}`);\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(buildInteractionsErrorResponse(strictMessage, \"UNAVAILABLE\")),\n );\n return;\n }\n if (defaults.record) {\n const outcome = await proxyAndRecord(\n req,\n res,\n completionReq,\n \"gemini-interactions\",\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: {\n status: res.statusCode ?? 200,\n fixture: null,\n source: \"proxy\",\n },\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(buildInteractionsErrorResponse(\"No fixture matched\", \"NOT_FOUND\")),\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 const replaySpeed = fixture.replaySpeed ?? defaults.replaySpeed;\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(\n res,\n status,\n JSON.stringify(\n buildInteractionsErrorResponse(response.error.message, response.error.type ?? \"ERROR\"),\n ),\n { retryAfter: response.retryAfter },\n );\n return;\n }\n\n const interactionId = nextInteractionId();\n\n // Content + tool calls response\n if (isContentWithToolCallsResponse(response)) {\n if (response.webSearches?.length) {\n logger.warn(\n \"webSearches in fixture response are not supported for Gemini Interactions API — ignoring\",\n );\n }\n const overrides = extractOverrides(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 if (!streaming) {\n const body = buildInteractionsContentWithToolCallsResponse(\n response.content,\n response.toolCalls,\n model,\n interactionId,\n logger,\n overrides,\n );\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const events = buildInteractionsContentWithToolCallsSSEEvents(\n response.content,\n response.toolCalls,\n interactionId,\n chunkSize,\n logger,\n overrides,\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeGeminiInteractionsSSEStream(res, events, {\n latency,\n streamingProfile: fixture.streamingProfile,\n recordedTimings: fixture.recordedTimings,\n 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(\n \"webSearches in fixture response are not supported for Gemini Interactions API — ignoring\",\n );\n }\n const overrides = extractOverrides(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 if (!streaming) {\n const body = buildInteractionsTextResponse(response.content, model, interactionId, overrides);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const events = buildInteractionsTextSSEEvents(\n response.content,\n interactionId,\n chunkSize,\n overrides,\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeGeminiInteractionsSSEStream(res, events, {\n latency,\n streamingProfile: fixture.streamingProfile,\n recordedTimings: fixture.recordedTimings,\n 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(\n \"webSearches in fixture response are not supported for Gemini Interactions API — ignoring\",\n );\n }\n const overrides = extractOverrides(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 if (!streaming) {\n const body = buildInteractionsToolCallResponse(\n response.toolCalls,\n model,\n interactionId,\n logger,\n overrides,\n );\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const events = buildInteractionsToolCallSSEEvents(\n response.toolCalls,\n interactionId,\n logger,\n overrides,\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeGeminiInteractionsSSEStream(res, events, {\n latency,\n streamingProfile: fixture.streamingProfile,\n recordedTimings: fixture.recordedTimings,\n 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 buildInteractionsErrorResponse(\"Fixture response did not match any known type\", \"INTERNAL\"),\n ),\n );\n}\n"],"mappings":"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"}
|
|
@@ -19,6 +19,23 @@ interface InteractionsTurn {
|
|
|
19
19
|
content?: InteractionsContentBlock[];
|
|
20
20
|
parts?: InteractionsContentBlock[];
|
|
21
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Top-level Step envelope accepted by the live Gemini Interactions API.
|
|
24
|
+
* The SDK's TypeScript union does not include Step[], but the wire contract
|
|
25
|
+
* does — clients following the live API send these at the top level of `input`.
|
|
26
|
+
* Discriminated by `type`; no `role` field (distinguishes from Turn[]).
|
|
27
|
+
*/
|
|
28
|
+
interface InteractionsStep {
|
|
29
|
+
type: string;
|
|
30
|
+
content?: InteractionsContentBlock[];
|
|
31
|
+
call_id?: string;
|
|
32
|
+
id?: string;
|
|
33
|
+
name?: string;
|
|
34
|
+
result?: unknown;
|
|
35
|
+
output?: unknown;
|
|
36
|
+
is_error?: boolean;
|
|
37
|
+
signature?: string;
|
|
38
|
+
}
|
|
22
39
|
interface InteractionsFunctionTool {
|
|
23
40
|
type: "function";
|
|
24
41
|
name: string;
|
|
@@ -27,7 +44,7 @@ interface InteractionsFunctionTool {
|
|
|
27
44
|
}
|
|
28
45
|
interface InteractionsRequest {
|
|
29
46
|
model?: string;
|
|
30
|
-
input?: string | InteractionsTurn[] | InteractionsContentBlock[];
|
|
47
|
+
input?: string | InteractionsTurn[] | InteractionsStep[] | InteractionsContentBlock[];
|
|
31
48
|
system_instruction?: string;
|
|
32
49
|
tools?: InteractionsFunctionTool[];
|
|
33
50
|
generation_config?: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gemini-interactions.d.cts","names":[],"sources":["../src/gemini-interactions.ts"],"sourcesContent":[],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"gemini-interactions.d.cts","names":[],"sources":["../src/gemini-interactions.ts"],"sourcesContent":[],"mappings":";;;;;;AAsEoC,UAzB1B,wBAAA,CAiDwB;EAOxB,IAAA,EAAA,MAAA;EAAmB,IAAA,CAAA,EAAA,MAAA;MAEV,CAAA,EAAA,MAAA;SAAqB,CAAA,EAAA,MAAA;KAAqB,EAAA,MAAA;WAEnD,CAAA,EAtDI,MAsDJ,CAAA,MAAA,EAAA,OAAA,CAAA;EAAwB,MAAA,CAAA,EAAA,OAAA;EAalB,MAAA,CAAA,EAAA,OAAA;;UA9DN,gBAAA,CA+DH;MACJ,EAAA,MAAA;EAAqB,OAAA,CAAA,EA9DZ,wBA8DY,EAAA;EAimBF,KAAA,CAAA,EA9pBZ,wBA8pBoC,EAAA;;;;;;;;UArpBpC,gBAAA,CA6pBP;EAAO,IAAA,EAAA,MAAA;YA3pBE;;;;;;;;;UAwBF,wBAAA;;;;;;UAOA,mBAAA;;mBAES,qBAAqB,qBAAqB;;UAEnD;;;;;;;;;;iBAaM,qCAAA,MACT,sBACJ;iBAimBmB,wBAAA,MACf,MAAA,CAAK,sBACL,MAAA,CAAK,uCAEA,oBACD,mBACC,uCACY,MAAA,CAAK,0BAC1B"}
|
|
@@ -19,6 +19,23 @@ interface InteractionsTurn {
|
|
|
19
19
|
content?: InteractionsContentBlock[];
|
|
20
20
|
parts?: InteractionsContentBlock[];
|
|
21
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Top-level Step envelope accepted by the live Gemini Interactions API.
|
|
24
|
+
* The SDK's TypeScript union does not include Step[], but the wire contract
|
|
25
|
+
* does — clients following the live API send these at the top level of `input`.
|
|
26
|
+
* Discriminated by `type`; no `role` field (distinguishes from Turn[]).
|
|
27
|
+
*/
|
|
28
|
+
interface InteractionsStep {
|
|
29
|
+
type: string;
|
|
30
|
+
content?: InteractionsContentBlock[];
|
|
31
|
+
call_id?: string;
|
|
32
|
+
id?: string;
|
|
33
|
+
name?: string;
|
|
34
|
+
result?: unknown;
|
|
35
|
+
output?: unknown;
|
|
36
|
+
is_error?: boolean;
|
|
37
|
+
signature?: string;
|
|
38
|
+
}
|
|
22
39
|
interface InteractionsFunctionTool {
|
|
23
40
|
type: "function";
|
|
24
41
|
name: string;
|
|
@@ -27,7 +44,7 @@ interface InteractionsFunctionTool {
|
|
|
27
44
|
}
|
|
28
45
|
interface InteractionsRequest {
|
|
29
46
|
model?: string;
|
|
30
|
-
input?: string | InteractionsTurn[] | InteractionsContentBlock[];
|
|
47
|
+
input?: string | InteractionsTurn[] | InteractionsStep[] | InteractionsContentBlock[];
|
|
31
48
|
system_instruction?: string;
|
|
32
49
|
tools?: InteractionsFunctionTool[];
|
|
33
50
|
generation_config?: {
|