@copilotkit/aimock 1.19.4 → 1.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/CHANGELOG.md +35 -0
  3. package/README.md +1 -1
  4. package/dist/agui-recorder.cjs +4 -4
  5. package/dist/agui-recorder.cjs.map +1 -1
  6. package/dist/agui-recorder.js +4 -4
  7. package/dist/agui-recorder.js.map +1 -1
  8. package/dist/agui-types.d.ts.map +1 -1
  9. package/dist/bedrock.cjs +28 -3
  10. package/dist/bedrock.cjs.map +1 -1
  11. package/dist/bedrock.d.cts.map +1 -1
  12. package/dist/bedrock.d.ts.map +1 -1
  13. package/dist/bedrock.js +28 -3
  14. package/dist/bedrock.js.map +1 -1
  15. package/dist/cohere.cjs +59 -35
  16. package/dist/cohere.cjs.map +1 -1
  17. package/dist/cohere.d.cts +14 -2
  18. package/dist/cohere.d.cts.map +1 -1
  19. package/dist/cohere.d.ts +14 -2
  20. package/dist/cohere.d.ts.map +1 -1
  21. package/dist/cohere.js +59 -35
  22. package/dist/cohere.js.map +1 -1
  23. package/dist/config-loader.d.ts.map +1 -1
  24. package/dist/fixture-loader.cjs +7 -1
  25. package/dist/fixture-loader.cjs.map +1 -1
  26. package/dist/fixture-loader.d.cts.map +1 -1
  27. package/dist/fixture-loader.d.ts.map +1 -1
  28. package/dist/fixture-loader.js +7 -1
  29. package/dist/fixture-loader.js.map +1 -1
  30. package/dist/gemini.cjs +4 -2
  31. package/dist/gemini.cjs.map +1 -1
  32. package/dist/gemini.d.cts.map +1 -1
  33. package/dist/gemini.d.ts.map +1 -1
  34. package/dist/gemini.js +5 -3
  35. package/dist/gemini.js.map +1 -1
  36. package/dist/messages.cjs +24 -4
  37. package/dist/messages.cjs.map +1 -1
  38. package/dist/messages.d.cts.map +1 -1
  39. package/dist/messages.d.ts.map +1 -1
  40. package/dist/messages.js +24 -4
  41. package/dist/messages.js.map +1 -1
  42. package/dist/moderation.cjs +6 -2
  43. package/dist/moderation.cjs.map +1 -1
  44. package/dist/moderation.d.cts.map +1 -1
  45. package/dist/moderation.d.ts.map +1 -1
  46. package/dist/moderation.js +6 -2
  47. package/dist/moderation.js.map +1 -1
  48. package/dist/ollama.cjs +25 -8
  49. package/dist/ollama.cjs.map +1 -1
  50. package/dist/ollama.d.cts +7 -0
  51. package/dist/ollama.d.cts.map +1 -1
  52. package/dist/ollama.d.ts +7 -0
  53. package/dist/ollama.d.ts.map +1 -1
  54. package/dist/ollama.js +25 -8
  55. package/dist/ollama.js.map +1 -1
  56. package/dist/recorder.cjs +5 -2
  57. package/dist/recorder.cjs.map +1 -1
  58. package/dist/recorder.js +5 -2
  59. package/dist/recorder.js.map +1 -1
  60. package/dist/rerank.cjs +4 -10
  61. package/dist/rerank.cjs.map +1 -1
  62. package/dist/rerank.js +4 -10
  63. package/dist/rerank.js.map +1 -1
  64. package/dist/responses.cjs +3 -1
  65. package/dist/responses.cjs.map +1 -1
  66. package/dist/responses.d.cts.map +1 -1
  67. package/dist/responses.d.ts.map +1 -1
  68. package/dist/responses.js +3 -1
  69. package/dist/responses.js.map +1 -1
  70. package/dist/router.cjs +28 -0
  71. package/dist/router.cjs.map +1 -1
  72. package/dist/router.d.cts +0 -1
  73. package/dist/router.d.cts.map +1 -1
  74. package/dist/router.d.ts +0 -1
  75. package/dist/router.d.ts.map +1 -1
  76. package/dist/router.js +28 -0
  77. package/dist/router.js.map +1 -1
  78. package/dist/search.cjs +7 -1
  79. package/dist/search.cjs.map +1 -1
  80. package/dist/search.js +7 -1
  81. package/dist/search.js.map +1 -1
  82. package/dist/server.cjs +12 -2
  83. package/dist/server.cjs.map +1 -1
  84. package/dist/server.d.cts.map +1 -1
  85. package/dist/server.d.ts.map +1 -1
  86. package/dist/server.js +12 -2
  87. package/dist/server.js.map +1 -1
  88. package/dist/transcription.cjs +7 -6
  89. package/dist/transcription.cjs.map +1 -1
  90. package/dist/transcription.js +7 -6
  91. package/dist/transcription.js.map +1 -1
  92. package/dist/types.d.cts +11 -0
  93. package/dist/types.d.cts.map +1 -1
  94. package/dist/types.d.ts +11 -0
  95. package/dist/types.d.ts.map +1 -1
  96. package/dist/vector-types.d.cts.map +1 -1
  97. package/dist/vector-types.d.ts.map +1 -1
  98. package/dist/ws-gemini-live.cjs +37 -29
  99. package/dist/ws-gemini-live.cjs.map +1 -1
  100. package/dist/ws-gemini-live.d.cts.map +1 -1
  101. package/dist/ws-gemini-live.d.ts.map +1 -1
  102. package/dist/ws-gemini-live.js +37 -29
  103. package/dist/ws-gemini-live.js.map +1 -1
  104. package/dist/ws-realtime.cjs +84 -15
  105. package/dist/ws-realtime.cjs.map +1 -1
  106. package/dist/ws-realtime.d.cts.map +1 -1
  107. package/dist/ws-realtime.d.ts.map +1 -1
  108. package/dist/ws-realtime.js +84 -16
  109. package/dist/ws-realtime.js.map +1 -1
  110. package/package.json +1 -1
  111. package/skills/write-fixtures/SKILL.md +2 -0
@@ -1 +1 @@
1
- {"version":3,"file":"ws-realtime.cjs","names":["generateToolCallId","generateId","DEFAULT_TEST_ID","matchFixture","resolveResponse","isErrorResponse","isTextResponse","createInterruptionSignal","delay","isToolCallResponse"],"sources":["../src/ws-realtime.ts"],"sourcesContent":["/**\n * WebSocket handler for OpenAI Realtime API.\n *\n * Accepts Realtime API messages (session.update, conversation.item.create,\n * response.create) over WebSocket and sends back Realtime API events as\n * individual WebSocket text frames.\n */\n\nimport type { ChatCompletionRequest, ChatMessage, Fixture } from \"./types.js\";\nimport { matchFixture } from \"./router.js\";\nimport {\n generateId,\n generateToolCallId,\n isTextResponse,\n isToolCallResponse,\n isErrorResponse,\n resolveResponse,\n} from \"./helpers.js\";\nimport { createInterruptionSignal } from \"./interruption.js\";\nimport { delay } from \"./sse-writer.js\";\nimport { DEFAULT_TEST_ID, type Journal } from \"./journal.js\";\nimport type { Logger } from \"./logger.js\";\nimport type { WebSocketConnection } from \"./ws-framing.js\";\n\n// ─── Realtime protocol types ────────────────────────────────────────────────\n\ninterface RealtimeItem {\n type: \"message\" | \"function_call\" | \"function_call_output\";\n id?: string;\n role?: \"user\" | \"assistant\" | \"system\";\n content?: Array<{ type: string; text?: string }>;\n name?: string;\n call_id?: string;\n arguments?: string;\n output?: string;\n}\n\ninterface SessionConfig {\n model: string;\n modalities: string[];\n instructions: string;\n tools: unknown[];\n voice: string | null;\n input_audio_format: string | null;\n output_audio_format: string | null;\n turn_detection: unknown | null;\n temperature: number;\n}\n\ninterface RealtimeMessage {\n type: string;\n event_id?: string;\n session?: Partial<SessionConfig>;\n item?: RealtimeItem;\n response?: {\n modalities?: string[];\n instructions?: string;\n [key: string]: unknown;\n };\n}\n\n// ─── Conversion helpers ─────────────────────────────────────────────────────\n\nexport function realtimeItemsToMessages(\n items: RealtimeItem[],\n instructions?: string,\n logger?: Logger,\n): ChatMessage[] {\n const messages: ChatMessage[] = [];\n\n if (instructions) {\n messages.push({ role: \"system\", content: instructions });\n }\n\n for (const item of items) {\n if (item.type === \"message\") {\n const text = item.content?.[0]?.text ?? \"\";\n const role =\n item.role === \"assistant\" ? \"assistant\" : item.role === \"system\" ? \"system\" : \"user\";\n messages.push({ role, content: text });\n } else if (item.type === \"function_call\") {\n if (!item.name) {\n logger?.warn(\"Realtime function_call item missing 'name'\");\n }\n messages.push({\n role: \"assistant\",\n content: null,\n tool_calls: [\n {\n id: item.call_id ?? generateToolCallId(),\n type: \"function\",\n function: {\n name: item.name ?? \"\",\n arguments: item.arguments ?? \"\",\n },\n },\n ],\n });\n } else if (item.type === \"function_call_output\") {\n if (!item.output) {\n logger?.warn(\"Realtime function_call_output item missing 'output'\");\n }\n messages.push({\n role: \"tool\",\n content: item.output ?? \"\",\n tool_call_id: item.call_id,\n });\n }\n }\n\n return messages;\n}\n\n// ─── Event builders ─────────────────────────────────────────────────────────\n\nfunction evt(type: string, extra: Record<string, unknown> = {}): string {\n return JSON.stringify({ type, event_id: generateId(\"evt\"), ...extra });\n}\n\nfunction buildErrorRealtimeEvent(\n message: string,\n type = \"invalid_request_error\",\n code?: string,\n): string {\n return evt(\"error\", { error: { message, type, code } });\n}\n\n// ─── Main handler ───────────────────────────────────────────────────────────\n\nexport function handleWebSocketRealtime(\n ws: WebSocketConnection,\n fixtures: Fixture[],\n journal: Journal,\n defaults: {\n latency: number;\n chunkSize: number;\n model: string;\n logger: Logger;\n strict?: boolean;\n requestTransform?: (req: ChatCompletionRequest) => ChatCompletionRequest;\n testId?: string;\n },\n): void {\n const { logger } = defaults;\n const sessionId = generateId(\"sess\");\n\n const session: SessionConfig = {\n model: defaults.model,\n modalities: [\"text\"],\n instructions: \"\",\n tools: [],\n voice: null,\n input_audio_format: null,\n output_audio_format: null,\n turn_detection: null,\n temperature: 0.8,\n };\n\n const conversationItems: RealtimeItem[] = [];\n\n // Send session.created immediately on connect\n ws.send(evt(\"session.created\", { session: { id: sessionId, ...session } }));\n\n // Serialize message processing to prevent event interleaving\n let pending = Promise.resolve();\n ws.on(\"message\", (raw: string) => {\n pending = pending.then(() =>\n processMessage(raw, ws, fixtures, journal, defaults, session, conversationItems).catch(\n (err: unknown) => {\n const msg = err instanceof Error ? err.message : \"Internal error\";\n logger.error(`WebSocket realtime error: ${msg}`);\n try {\n ws.send(buildErrorRealtimeEvent(msg, \"server_error\"));\n } catch {\n // Connection already gone — original error already logged above\n }\n },\n ),\n );\n });\n}\n\nasync function processMessage(\n raw: string,\n ws: WebSocketConnection,\n fixtures: Fixture[],\n journal: Journal,\n defaults: {\n latency: number;\n chunkSize: number;\n model: string;\n logger: Logger;\n strict?: boolean;\n requestTransform?: (req: ChatCompletionRequest) => ChatCompletionRequest;\n testId?: string;\n },\n session: SessionConfig,\n conversationItems: RealtimeItem[],\n): Promise<void> {\n let parsed: RealtimeMessage;\n try {\n parsed = JSON.parse(raw) as RealtimeMessage;\n } catch {\n ws.send(buildErrorRealtimeEvent(\"Malformed JSON\", \"invalid_request_error\", \"invalid_json\"));\n return;\n }\n\n const msgType = parsed.type;\n\n // ── session.update ────────────────────────────────────────────────────\n if (msgType === \"session.update\") {\n if (parsed.session) {\n if (parsed.session.instructions !== undefined) {\n session.instructions = parsed.session.instructions;\n }\n if (parsed.session.tools !== undefined) {\n session.tools = parsed.session.tools;\n }\n if (parsed.session.modalities !== undefined) {\n session.modalities = parsed.session.modalities;\n }\n if (parsed.session.model !== undefined) {\n session.model = parsed.session.model;\n }\n if (parsed.session.temperature !== undefined) {\n session.temperature = parsed.session.temperature;\n }\n }\n ws.send(evt(\"session.updated\", { session: { ...session } }));\n return;\n }\n\n // ── conversation.item.create ──────────────────────────────────────────\n if (msgType === \"conversation.item.create\") {\n if (!parsed.item) {\n ws.send(\n buildErrorRealtimeEvent(\n \"Missing 'item' in conversation.item.create\",\n \"invalid_request_error\",\n ),\n );\n return;\n }\n const item = parsed.item;\n if (!item.id) {\n item.id = generateId(\"item\");\n }\n conversationItems.push(item);\n ws.send(evt(\"conversation.item.created\", { item }));\n return;\n }\n\n // ── response.create ───────────────────────────────────────────────────\n if (msgType === \"response.create\") {\n await handleResponseCreate(ws, fixtures, journal, defaults, session, conversationItems);\n return;\n }\n\n // Unknown message type — ignore silently (matches OpenAI behavior)\n}\n\nasync function handleResponseCreate(\n ws: WebSocketConnection,\n fixtures: Fixture[],\n journal: Journal,\n defaults: {\n latency: number;\n chunkSize: number;\n model: string;\n logger: Logger;\n strict?: boolean;\n requestTransform?: (req: ChatCompletionRequest) => ChatCompletionRequest;\n testId?: string;\n },\n session: SessionConfig,\n conversationItems: RealtimeItem[],\n): Promise<void> {\n const instructions = session.instructions || undefined;\n const messages = realtimeItemsToMessages(conversationItems, instructions, defaults.logger);\n\n const completionReq: ChatCompletionRequest = {\n model: session.model,\n messages,\n };\n\n const testId = defaults.testId ?? DEFAULT_TEST_ID;\n const fixture = matchFixture(\n fixtures,\n completionReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n );\n const responseId = generateId(\"resp\");\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n }\n\n if (!fixture) {\n if (defaults.strict) {\n defaults.logger.warn(`STRICT: No fixture matched for WebSocket message`);\n ws.close(1008, \"Strict mode: no fixture matched\");\n return;\n }\n journal.add({\n method: \"WS\",\n path: \"/v1/realtime\",\n headers: {},\n body: completionReq,\n response: { status: 404, fixture: null },\n });\n // Send response.created with failed status then response.done with error\n ws.send(\n evt(\"response.created\", {\n response: { id: responseId, status: \"failed\", output: [] },\n }),\n );\n ws.send(\n evt(\"response.done\", {\n response: {\n id: responseId,\n status: \"failed\",\n output: [],\n status_details: {\n type: \"error\",\n error: {\n message: \"No fixture matched\",\n type: \"invalid_request_error\",\n code: \"no_fixture_match\",\n },\n },\n },\n }),\n );\n return;\n }\n\n const response = await resolveResponse(fixture, completionReq);\n const latency = fixture.latency ?? defaults.latency;\n const chunkSize = Math.max(1, fixture.chunkSize ?? defaults.chunkSize);\n\n // ── Error fixture ───────────────────────────────────────────────────\n if (isErrorResponse(response)) {\n const status = response.status ?? 500;\n journal.add({\n method: \"WS\",\n path: \"/v1/realtime\",\n headers: {},\n body: completionReq,\n response: { status, fixture },\n });\n ws.send(\n evt(\"response.created\", {\n response: { id: responseId, status: \"failed\", output: [] },\n }),\n );\n ws.send(\n evt(\"response.done\", {\n response: {\n id: responseId,\n status: \"failed\",\n output: [],\n status_details: {\n type: \"error\",\n error: {\n message: response.error.message,\n type: response.error.type,\n code: response.error.code,\n },\n },\n },\n }),\n );\n return;\n }\n\n // ── Text response ───────────────────────────────────────────────────\n if (isTextResponse(response)) {\n const journalEntry = journal.add({\n method: \"WS\",\n path: \"/v1/realtime\",\n headers: {},\n body: completionReq,\n response: { status: 200, fixture },\n });\n\n const itemId = generateId(\"item\");\n const contentIndex = 0;\n const outputIndex = 0;\n\n const outputItem = {\n id: itemId,\n type: \"message\",\n role: \"assistant\",\n content: [{ type: \"text\", text: response.content }],\n };\n\n // response.created\n ws.send(\n evt(\"response.created\", {\n response: { id: responseId, status: \"in_progress\", output: [] },\n }),\n );\n\n // response.output_item.added\n ws.send(\n evt(\"response.output_item.added\", {\n response_id: responseId,\n output_index: outputIndex,\n item: { id: itemId, type: \"message\", role: \"assistant\", content: [] },\n }),\n );\n\n // response.content_part.added\n ws.send(\n evt(\"response.content_part.added\", {\n response_id: responseId,\n item_id: itemId,\n output_index: outputIndex,\n content_index: contentIndex,\n part: { type: \"text\", text: \"\" },\n }),\n );\n\n // response.text.delta (chunked)\n const content = response.content;\n const interruption = createInterruptionSignal(fixture);\n let interrupted = false;\n\n for (let i = 0; i < content.length; i += chunkSize) {\n if (ws.isClosed) break;\n if (latency > 0) await delay(latency, interruption?.signal);\n if (interruption?.signal.aborted) {\n interrupted = true;\n break;\n }\n if (ws.isClosed) break;\n const chunk = content.slice(i, i + chunkSize);\n ws.send(\n evt(\"response.text.delta\", {\n response_id: responseId,\n item_id: itemId,\n output_index: outputIndex,\n content_index: contentIndex,\n delta: chunk,\n }),\n );\n interruption?.tick();\n if (interruption?.signal.aborted) {\n interrupted = true;\n break;\n }\n }\n\n if (interrupted) {\n ws.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n interruption?.cleanup();\n return;\n }\n\n interruption?.cleanup();\n\n if (ws.isClosed) return;\n\n // response.text.done\n ws.send(\n evt(\"response.text.done\", {\n response_id: responseId,\n item_id: itemId,\n output_index: outputIndex,\n content_index: contentIndex,\n text: content,\n }),\n );\n\n // response.content_part.done\n ws.send(\n evt(\"response.content_part.done\", {\n response_id: responseId,\n item_id: itemId,\n output_index: outputIndex,\n content_index: contentIndex,\n part: { type: \"text\", text: content },\n }),\n );\n\n // response.output_item.done\n ws.send(\n evt(\"response.output_item.done\", {\n response_id: responseId,\n output_index: outputIndex,\n item: outputItem,\n }),\n );\n\n // response.done\n ws.send(\n evt(\"response.done\", {\n response: { id: responseId, status: \"completed\", output: [outputItem] },\n }),\n );\n\n // Accumulate assistant response into conversation for multi-turn\n conversationItems.push({\n type: \"message\",\n id: itemId,\n role: \"assistant\",\n content: [{ type: \"text\", text: content }],\n });\n return;\n }\n\n // ── Tool call response ──────────────────────────────────────────────\n if (isToolCallResponse(response)) {\n const journalEntry = journal.add({\n method: \"WS\",\n path: \"/v1/realtime\",\n headers: {},\n body: completionReq,\n response: { status: 200, fixture },\n });\n\n // response.created\n ws.send(\n evt(\"response.created\", {\n response: { id: responseId, status: \"in_progress\", output: [] },\n }),\n );\n\n const outputItems: unknown[] = [];\n const interruption = createInterruptionSignal(fixture);\n let interrupted = false;\n\n for (let tcIdx = 0; tcIdx < response.toolCalls.length; tcIdx++) {\n const tc = response.toolCalls[tcIdx];\n const callId = tc.id ?? generateToolCallId();\n const itemId = generateId(\"item\");\n\n const outputItem = {\n id: itemId,\n type: \"function_call\",\n call_id: callId,\n name: tc.name,\n arguments: tc.arguments,\n };\n\n // response.output_item.added\n ws.send(\n evt(\"response.output_item.added\", {\n response_id: responseId,\n output_index: tcIdx,\n item: {\n id: itemId,\n type: \"function_call\",\n call_id: callId,\n name: tc.name,\n arguments: \"\",\n },\n }),\n );\n\n // response.function_call_arguments.delta (chunked)\n const args = tc.arguments;\n for (let i = 0; i < args.length; i += chunkSize) {\n if (ws.isClosed) break;\n if (latency > 0) await delay(latency, interruption?.signal);\n if (interruption?.signal.aborted) {\n interrupted = true;\n break;\n }\n if (ws.isClosed) break;\n const chunk = args.slice(i, i + chunkSize);\n ws.send(\n evt(\"response.function_call_arguments.delta\", {\n response_id: responseId,\n item_id: itemId,\n output_index: tcIdx,\n call_id: callId,\n delta: chunk,\n }),\n );\n interruption?.tick();\n if (interruption?.signal.aborted) {\n interrupted = true;\n break;\n }\n }\n\n if (interrupted) break;\n\n // response.function_call_arguments.done\n ws.send(\n evt(\"response.function_call_arguments.done\", {\n response_id: responseId,\n item_id: itemId,\n output_index: tcIdx,\n call_id: callId,\n arguments: args,\n }),\n );\n\n // response.output_item.done\n ws.send(\n evt(\"response.output_item.done\", {\n response_id: responseId,\n output_index: tcIdx,\n item: outputItem,\n }),\n );\n\n outputItems.push(outputItem);\n }\n\n if (interrupted) {\n ws.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n interruption?.cleanup();\n return;\n }\n\n interruption?.cleanup();\n\n if (ws.isClosed) return;\n\n // response.done\n ws.send(\n evt(\"response.done\", {\n response: { id: responseId, status: \"completed\", output: outputItems },\n }),\n );\n\n // Accumulate assistant tool calls into conversation for multi-turn\n // Reuse outputItems (which already have the correct call_id) to avoid generating divergent IDs\n for (const item of outputItems) {\n conversationItems.push(item as RealtimeItem);\n }\n return;\n }\n\n // Unknown response type\n journal.add({\n method: \"WS\",\n path: \"/v1/realtime\",\n headers: {},\n body: completionReq,\n response: { status: 500, fixture },\n });\n ws.send(buildErrorRealtimeEvent(\"Fixture response did not match any known type\", \"server_error\"));\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ws-realtime.cjs","names":["generateToolCallId","DEFAULT_TEST_ID","matchFixture","resolveResponse","isErrorResponse","isTextResponse","createInterruptionSignal","delay","isToolCallResponse"],"sources":["../src/ws-realtime.ts"],"sourcesContent":["/**\n * WebSocket handler for OpenAI Realtime API.\n *\n * Accepts Realtime API messages (session.update, conversation.item.create,\n * response.create) over WebSocket and sends back Realtime API events as\n * individual WebSocket text frames.\n */\n\nimport { randomBytes } from \"node:crypto\";\nimport type { ChatCompletionRequest, ChatMessage, Fixture } from \"./types.js\";\nimport { matchFixture } from \"./router.js\";\nimport {\n generateToolCallId,\n isTextResponse,\n isToolCallResponse,\n isErrorResponse,\n resolveResponse,\n} from \"./helpers.js\";\nimport { createInterruptionSignal } from \"./interruption.js\";\nimport { delay } from \"./sse-writer.js\";\nimport { DEFAULT_TEST_ID, type Journal } from \"./journal.js\";\nimport type { Logger } from \"./logger.js\";\nimport type { WebSocketConnection } from \"./ws-framing.js\";\n\n/** Generate a Realtime-API-style ID with underscore separator (e.g. event_xxx, item_xxx). */\nfunction realtimeId(prefix: string): string {\n return `${prefix}_${randomBytes(12).toString(\"base64url\")}`;\n}\n\n// ─── Realtime protocol types ────────────────────────────────────────────────\n\ninterface RealtimeItem {\n type: \"message\" | \"function_call\" | \"function_call_output\";\n id?: string;\n role?: \"user\" | \"assistant\" | \"system\";\n content?: Array<{ type: string; text?: string }>;\n name?: string;\n call_id?: string;\n arguments?: string;\n output?: string;\n}\n\ninterface SessionConfig {\n model: string;\n modalities: string[];\n instructions: string;\n tools: unknown[];\n voice: string | null;\n input_audio_format: string | null;\n output_audio_format: string | null;\n turn_detection: unknown | null;\n temperature: number;\n}\n\ninterface RealtimeMessage {\n type: string;\n event_id?: string;\n session?: Partial<SessionConfig>;\n item?: RealtimeItem;\n response?: {\n modalities?: string[];\n instructions?: string;\n [key: string]: unknown;\n };\n}\n\n// ─── Conversion helpers ─────────────────────────────────────────────────────\n\nexport function realtimeItemsToMessages(\n items: RealtimeItem[],\n instructions?: string,\n logger?: Logger,\n): ChatMessage[] {\n const messages: ChatMessage[] = [];\n\n if (instructions) {\n messages.push({ role: \"system\", content: instructions });\n }\n\n for (const item of items) {\n if (item.type === \"message\") {\n const text = item.content?.[0]?.text ?? \"\";\n const role =\n item.role === \"assistant\" ? \"assistant\" : item.role === \"system\" ? \"system\" : \"user\";\n messages.push({ role, content: text });\n } else if (item.type === \"function_call\") {\n if (!item.name) {\n logger?.warn(\"Realtime function_call item missing 'name'\");\n }\n messages.push({\n role: \"assistant\",\n content: null,\n tool_calls: [\n {\n id: item.call_id ?? generateToolCallId(),\n type: \"function\",\n function: {\n name: item.name ?? \"\",\n arguments: item.arguments ?? \"\",\n },\n },\n ],\n });\n } else if (item.type === \"function_call_output\") {\n if (!item.output) {\n logger?.warn(\"Realtime function_call_output item missing 'output'\");\n }\n messages.push({\n role: \"tool\",\n content: item.output ?? \"\",\n tool_call_id: item.call_id,\n });\n }\n }\n\n return messages;\n}\n\n// ─── Event builders ─────────────────────────────────────────────────────────\n\nfunction evt(type: string, extra: Record<string, unknown> = {}): string {\n return JSON.stringify({ type, event_id: realtimeId(\"event\"), ...extra });\n}\n\nfunction buildErrorRealtimeEvent(\n message: string,\n type = \"invalid_request_error\",\n code?: string,\n): string {\n return evt(\"error\", { error: { message, type, code } });\n}\n\n// ─── Main handler ───────────────────────────────────────────────────────────\n\nexport function handleWebSocketRealtime(\n ws: WebSocketConnection,\n fixtures: Fixture[],\n journal: Journal,\n defaults: {\n latency: number;\n chunkSize: number;\n model: string;\n logger: Logger;\n strict?: boolean;\n requestTransform?: (req: ChatCompletionRequest) => ChatCompletionRequest;\n testId?: string;\n },\n): void {\n const { logger } = defaults;\n const sessionId = realtimeId(\"sess\");\n\n const session: SessionConfig = {\n model: defaults.model,\n modalities: [\"text\"],\n instructions: \"\",\n tools: [],\n voice: null,\n input_audio_format: null,\n output_audio_format: null,\n turn_detection: null,\n temperature: 0.8,\n };\n\n const conversationItems: RealtimeItem[] = [];\n\n // Send session.created immediately on connect\n ws.send(\n evt(\"session.created\", {\n session: {\n id: sessionId,\n object: \"realtime.session\",\n ...session,\n expires_at: Math.floor(Date.now() / 1000) + 3600,\n max_response_output_tokens: \"inf\",\n input_audio_transcription: null,\n tool_choice: \"auto\",\n },\n }),\n );\n\n // Serialize message processing to prevent event interleaving\n let pending = Promise.resolve();\n ws.on(\"message\", (raw: string) => {\n pending = pending.then(() =>\n processMessage(raw, ws, fixtures, journal, defaults, session, conversationItems).catch(\n (err: unknown) => {\n const msg = err instanceof Error ? err.message : \"Internal error\";\n logger.error(`WebSocket realtime error: ${msg}`);\n try {\n ws.send(buildErrorRealtimeEvent(msg, \"server_error\"));\n } catch {\n // Connection already gone — original error already logged above\n }\n },\n ),\n );\n });\n}\n\nasync function processMessage(\n raw: string,\n ws: WebSocketConnection,\n fixtures: Fixture[],\n journal: Journal,\n defaults: {\n latency: number;\n chunkSize: number;\n model: string;\n logger: Logger;\n strict?: boolean;\n requestTransform?: (req: ChatCompletionRequest) => ChatCompletionRequest;\n testId?: string;\n },\n session: SessionConfig,\n conversationItems: RealtimeItem[],\n): Promise<void> {\n let parsed: RealtimeMessage;\n try {\n parsed = JSON.parse(raw) as RealtimeMessage;\n } catch {\n ws.send(buildErrorRealtimeEvent(\"Malformed JSON\", \"invalid_request_error\", \"invalid_json\"));\n return;\n }\n\n const msgType = parsed.type;\n\n // ── session.update ────────────────────────────────────────────────────\n if (msgType === \"session.update\") {\n if (parsed.session) {\n if (parsed.session.instructions !== undefined) {\n session.instructions = parsed.session.instructions;\n }\n if (parsed.session.tools !== undefined) {\n session.tools = parsed.session.tools;\n }\n if (parsed.session.modalities !== undefined) {\n session.modalities = parsed.session.modalities;\n }\n if (parsed.session.model !== undefined) {\n session.model = parsed.session.model;\n }\n if (parsed.session.temperature !== undefined) {\n session.temperature = parsed.session.temperature;\n }\n }\n ws.send(\n evt(\"session.updated\", {\n session: {\n ...session,\n object: \"realtime.session\",\n expires_at: Math.floor(Date.now() / 1000) + 3600,\n max_response_output_tokens: \"inf\",\n input_audio_transcription: null,\n tool_choice: \"auto\",\n },\n }),\n );\n return;\n }\n\n // ── conversation.item.create ──────────────────────────────────────────\n if (msgType === \"conversation.item.create\") {\n if (!parsed.item) {\n ws.send(\n buildErrorRealtimeEvent(\n \"Missing 'item' in conversation.item.create\",\n \"invalid_request_error\",\n ),\n );\n return;\n }\n const item = parsed.item;\n if (!item.id) {\n item.id = realtimeId(\"item\");\n }\n const previousId =\n conversationItems.length > 0\n ? (conversationItems[conversationItems.length - 1].id ?? null)\n : null;\n conversationItems.push(item);\n ws.send(evt(\"conversation.item.created\", { previous_item_id: previousId, item }));\n return;\n }\n\n // ── response.create ───────────────────────────────────────────────────\n if (msgType === \"response.create\") {\n await handleResponseCreate(ws, fixtures, journal, defaults, session, conversationItems);\n return;\n }\n\n // Unknown message type — ignore silently (matches OpenAI behavior)\n}\n\nasync function handleResponseCreate(\n ws: WebSocketConnection,\n fixtures: Fixture[],\n journal: Journal,\n defaults: {\n latency: number;\n chunkSize: number;\n model: string;\n logger: Logger;\n strict?: boolean;\n requestTransform?: (req: ChatCompletionRequest) => ChatCompletionRequest;\n testId?: string;\n },\n session: SessionConfig,\n conversationItems: RealtimeItem[],\n): Promise<void> {\n const instructions = session.instructions || undefined;\n const messages = realtimeItemsToMessages(conversationItems, instructions, defaults.logger);\n\n const completionReq: ChatCompletionRequest = {\n model: session.model,\n messages,\n };\n\n const testId = defaults.testId ?? DEFAULT_TEST_ID;\n const fixture = matchFixture(\n fixtures,\n completionReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n );\n const responseId = realtimeId(\"resp\");\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n }\n\n if (!fixture) {\n if (defaults.strict) {\n defaults.logger.warn(`STRICT: No fixture matched for WebSocket message`);\n ws.close(1008, \"Strict mode: no fixture matched\");\n return;\n }\n journal.add({\n method: \"WS\",\n path: \"/v1/realtime\",\n headers: {},\n body: completionReq,\n response: { status: 404, fixture: null },\n });\n // Send response.created with failed status then response.done with error\n ws.send(\n evt(\"response.created\", {\n response: {\n id: responseId,\n object: \"realtime.response\",\n status: \"failed\",\n status_details: null,\n output: [],\n usage: null,\n },\n }),\n );\n ws.send(\n evt(\"response.done\", {\n response: {\n id: responseId,\n object: \"realtime.response\",\n status: \"failed\",\n output: [],\n status_details: {\n type: \"error\",\n error: {\n message: \"No fixture matched\",\n type: \"invalid_request_error\",\n code: \"no_fixture_match\",\n },\n },\n usage: { total_tokens: 0, input_tokens: 0, output_tokens: 0 },\n },\n }),\n );\n return;\n }\n\n const response = await resolveResponse(fixture, completionReq);\n const latency = fixture.latency ?? defaults.latency;\n const chunkSize = Math.max(1, fixture.chunkSize ?? defaults.chunkSize);\n\n // ── Error fixture ───────────────────────────────────────────────────\n if (isErrorResponse(response)) {\n const status = response.status ?? 500;\n journal.add({\n method: \"WS\",\n path: \"/v1/realtime\",\n headers: {},\n body: completionReq,\n response: { status, fixture },\n });\n ws.send(\n evt(\"response.created\", {\n response: {\n id: responseId,\n object: \"realtime.response\",\n status: \"failed\",\n status_details: null,\n output: [],\n usage: null,\n },\n }),\n );\n ws.send(\n evt(\"response.done\", {\n response: {\n id: responseId,\n object: \"realtime.response\",\n status: \"failed\",\n output: [],\n status_details: {\n type: \"error\",\n error: {\n message: response.error.message,\n type: response.error.type,\n code: response.error.code,\n },\n },\n usage: { total_tokens: 0, input_tokens: 0, output_tokens: 0 },\n },\n }),\n );\n return;\n }\n\n // ── Text response ───────────────────────────────────────────────────\n if (isTextResponse(response)) {\n const journalEntry = journal.add({\n method: \"WS\",\n path: \"/v1/realtime\",\n headers: {},\n body: completionReq,\n response: { status: 200, fixture },\n });\n\n const itemId = realtimeId(\"item\");\n const contentIndex = 0;\n const outputIndex = 0;\n\n const outputItem = {\n id: itemId,\n type: \"message\",\n role: \"assistant\",\n status: \"completed\",\n content: [{ type: \"text\", text: response.content }],\n };\n\n // response.created\n ws.send(\n evt(\"response.created\", {\n response: {\n id: responseId,\n object: \"realtime.response\",\n status: \"in_progress\",\n status_details: null,\n output: [],\n usage: null,\n },\n }),\n );\n\n // response.output_item.added\n ws.send(\n evt(\"response.output_item.added\", {\n response_id: responseId,\n output_index: outputIndex,\n item: {\n id: itemId,\n type: \"message\",\n role: \"assistant\",\n status: \"in_progress\",\n content: [],\n },\n }),\n );\n\n // response.content_part.added\n ws.send(\n evt(\"response.content_part.added\", {\n response_id: responseId,\n item_id: itemId,\n output_index: outputIndex,\n content_index: contentIndex,\n part: { type: \"text\", text: \"\" },\n }),\n );\n\n // response.text.delta (chunked)\n const content = response.content;\n const interruption = createInterruptionSignal(fixture);\n let interrupted = false;\n\n for (let i = 0; i < content.length; i += chunkSize) {\n if (ws.isClosed) break;\n if (latency > 0) await delay(latency, interruption?.signal);\n if (interruption?.signal.aborted) {\n interrupted = true;\n break;\n }\n if (ws.isClosed) break;\n const chunk = content.slice(i, i + chunkSize);\n ws.send(\n evt(\"response.text.delta\", {\n response_id: responseId,\n item_id: itemId,\n output_index: outputIndex,\n content_index: contentIndex,\n delta: chunk,\n }),\n );\n interruption?.tick();\n if (interruption?.signal.aborted) {\n interrupted = true;\n break;\n }\n }\n\n if (interrupted) {\n ws.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n interruption?.cleanup();\n return;\n }\n\n interruption?.cleanup();\n\n if (ws.isClosed) return;\n\n // response.text.done\n ws.send(\n evt(\"response.text.done\", {\n response_id: responseId,\n item_id: itemId,\n output_index: outputIndex,\n content_index: contentIndex,\n text: content,\n }),\n );\n\n // response.content_part.done\n ws.send(\n evt(\"response.content_part.done\", {\n response_id: responseId,\n item_id: itemId,\n output_index: outputIndex,\n content_index: contentIndex,\n part: { type: \"text\", text: content },\n }),\n );\n\n // response.output_item.done\n ws.send(\n evt(\"response.output_item.done\", {\n response_id: responseId,\n output_index: outputIndex,\n item: outputItem,\n }),\n );\n\n // response.done\n ws.send(\n evt(\"response.done\", {\n response: {\n id: responseId,\n object: \"realtime.response\",\n status: \"completed\",\n output: [outputItem],\n usage: { total_tokens: 0, input_tokens: 0, output_tokens: 0 },\n },\n }),\n );\n\n // Accumulate assistant response into conversation for multi-turn\n conversationItems.push({\n type: \"message\",\n id: itemId,\n role: \"assistant\",\n content: [{ type: \"text\", text: content }],\n });\n return;\n }\n\n // ── Tool call response ──────────────────────────────────────────────\n if (isToolCallResponse(response)) {\n const journalEntry = journal.add({\n method: \"WS\",\n path: \"/v1/realtime\",\n headers: {},\n body: completionReq,\n response: { status: 200, fixture },\n });\n\n // response.created\n ws.send(\n evt(\"response.created\", {\n response: {\n id: responseId,\n object: \"realtime.response\",\n status: \"in_progress\",\n status_details: null,\n output: [],\n usage: null,\n },\n }),\n );\n\n const outputItems: unknown[] = [];\n const interruption = createInterruptionSignal(fixture);\n let interrupted = false;\n\n for (let tcIdx = 0; tcIdx < response.toolCalls.length; tcIdx++) {\n const tc = response.toolCalls[tcIdx];\n const callId = tc.id ?? generateToolCallId();\n const itemId = realtimeId(\"item\");\n\n const outputItem = {\n id: itemId,\n type: \"function_call\",\n status: \"completed\",\n call_id: callId,\n name: tc.name,\n arguments: tc.arguments,\n };\n\n // response.output_item.added\n ws.send(\n evt(\"response.output_item.added\", {\n response_id: responseId,\n output_index: tcIdx,\n item: {\n id: itemId,\n type: \"function_call\",\n status: \"in_progress\",\n call_id: callId,\n name: tc.name,\n arguments: \"\",\n },\n }),\n );\n\n // response.function_call_arguments.delta (chunked)\n const args = tc.arguments;\n for (let i = 0; i < args.length; i += chunkSize) {\n if (ws.isClosed) break;\n if (latency > 0) await delay(latency, interruption?.signal);\n if (interruption?.signal.aborted) {\n interrupted = true;\n break;\n }\n if (ws.isClosed) break;\n const chunk = args.slice(i, i + chunkSize);\n ws.send(\n evt(\"response.function_call_arguments.delta\", {\n response_id: responseId,\n item_id: itemId,\n output_index: tcIdx,\n call_id: callId,\n delta: chunk,\n }),\n );\n interruption?.tick();\n if (interruption?.signal.aborted) {\n interrupted = true;\n break;\n }\n }\n\n if (interrupted) break;\n\n // response.function_call_arguments.done\n ws.send(\n evt(\"response.function_call_arguments.done\", {\n response_id: responseId,\n item_id: itemId,\n output_index: tcIdx,\n call_id: callId,\n arguments: args,\n }),\n );\n\n // response.output_item.done\n ws.send(\n evt(\"response.output_item.done\", {\n response_id: responseId,\n output_index: tcIdx,\n item: outputItem,\n }),\n );\n\n outputItems.push(outputItem);\n }\n\n if (interrupted) {\n ws.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n interruption?.cleanup();\n return;\n }\n\n interruption?.cleanup();\n\n if (ws.isClosed) return;\n\n // response.done\n ws.send(\n evt(\"response.done\", {\n response: {\n id: responseId,\n object: \"realtime.response\",\n status: \"completed\",\n output: outputItems,\n usage: { total_tokens: 0, input_tokens: 0, output_tokens: 0 },\n },\n }),\n );\n\n // Accumulate assistant tool calls into conversation for multi-turn\n // Reuse outputItems (which already have the correct call_id) to avoid generating divergent IDs\n for (const item of outputItems) {\n conversationItems.push(item as RealtimeItem);\n }\n return;\n }\n\n // Unknown response type\n journal.add({\n method: \"WS\",\n path: \"/v1/realtime\",\n headers: {},\n body: completionReq,\n response: { status: 500, fixture },\n });\n ws.send(buildErrorRealtimeEvent(\"Fixture response did not match any known type\", \"server_error\"));\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"ws-realtime.d.cts","names":[],"sources":["../src/ws-realtime.ts"],"sourcesContent":[],"mappings":";;;;;;;iBAiIgB,uBAAA,KACV,+BACM,oBACD;;;;UAKC;;2BAEiB,0BAA0B"}
1
+ {"version":3,"file":"ws-realtime.d.cts","names":[],"sources":["../src/ws-realtime.ts"],"sourcesContent":[],"mappings":";;;;;;;iBAsIgB,uBAAA,KACV,+BACM,oBACD;;;;UAKC;;2BAEiB,0BAA0B"}
@@ -1 +1 @@
1
- {"version":3,"file":"ws-realtime.d.ts","names":[],"sources":["../src/ws-realtime.ts"],"sourcesContent":[],"mappings":";;;;;;;iBAiIgB,uBAAA,KACV,+BACM,oBACD;;;;UAKC;;2BAEiB,0BAA0B"}
1
+ {"version":3,"file":"ws-realtime.d.ts","names":[],"sources":["../src/ws-realtime.ts"],"sourcesContent":[],"mappings":";;;;;;;iBAsIgB,uBAAA,KACV,+BACM,oBACD;;;;UAKC;;2BAEiB,0BAA0B"}
@@ -1,10 +1,22 @@
1
- import { generateId, generateToolCallId, isErrorResponse, isTextResponse, isToolCallResponse, resolveResponse } from "./helpers.js";
1
+ import { generateToolCallId, isErrorResponse, isTextResponse, isToolCallResponse, resolveResponse } from "./helpers.js";
2
2
  import { DEFAULT_TEST_ID } from "./journal.js";
3
3
  import { matchFixture } from "./router.js";
4
4
  import { delay } from "./sse-writer.js";
5
5
  import { createInterruptionSignal } from "./interruption.js";
6
+ import { randomBytes } from "node:crypto";
6
7
 
7
8
  //#region src/ws-realtime.ts
9
+ /**
10
+ * WebSocket handler for OpenAI Realtime API.
11
+ *
12
+ * Accepts Realtime API messages (session.update, conversation.item.create,
13
+ * response.create) over WebSocket and sends back Realtime API events as
14
+ * individual WebSocket text frames.
15
+ */
16
+ /** Generate a Realtime-API-style ID with underscore separator (e.g. event_xxx, item_xxx). */
17
+ function realtimeId(prefix) {
18
+ return `${prefix}_${randomBytes(12).toString("base64url")}`;
19
+ }
8
20
  function realtimeItemsToMessages(items, instructions, logger) {
9
21
  const messages = [];
10
22
  if (instructions) messages.push({
@@ -45,7 +57,7 @@ function realtimeItemsToMessages(items, instructions, logger) {
45
57
  function evt(type, extra = {}) {
46
58
  return JSON.stringify({
47
59
  type,
48
- event_id: generateId("evt"),
60
+ event_id: realtimeId("event"),
49
61
  ...extra
50
62
  });
51
63
  }
@@ -58,7 +70,7 @@ function buildErrorRealtimeEvent(message, type = "invalid_request_error", code)
58
70
  }
59
71
  function handleWebSocketRealtime(ws, fixtures, journal, defaults) {
60
72
  const { logger } = defaults;
61
- const sessionId = generateId("sess");
73
+ const sessionId = realtimeId("sess");
62
74
  const session = {
63
75
  model: defaults.model,
64
76
  modalities: ["text"],
@@ -73,7 +85,12 @@ function handleWebSocketRealtime(ws, fixtures, journal, defaults) {
73
85
  const conversationItems = [];
74
86
  ws.send(evt("session.created", { session: {
75
87
  id: sessionId,
76
- ...session
88
+ object: "realtime.session",
89
+ ...session,
90
+ expires_at: Math.floor(Date.now() / 1e3) + 3600,
91
+ max_response_output_tokens: "inf",
92
+ input_audio_transcription: null,
93
+ tool_choice: "auto"
77
94
  } }));
78
95
  let pending = Promise.resolve();
79
96
  ws.on("message", (raw) => {
@@ -103,7 +120,14 @@ async function processMessage(raw, ws, fixtures, journal, defaults, session, con
103
120
  if (parsed.session.model !== void 0) session.model = parsed.session.model;
104
121
  if (parsed.session.temperature !== void 0) session.temperature = parsed.session.temperature;
105
122
  }
106
- ws.send(evt("session.updated", { session: { ...session } }));
123
+ ws.send(evt("session.updated", { session: {
124
+ ...session,
125
+ object: "realtime.session",
126
+ expires_at: Math.floor(Date.now() / 1e3) + 3600,
127
+ max_response_output_tokens: "inf",
128
+ input_audio_transcription: null,
129
+ tool_choice: "auto"
130
+ } }));
107
131
  return;
108
132
  }
109
133
  if (msgType === "conversation.item.create") {
@@ -112,9 +136,13 @@ async function processMessage(raw, ws, fixtures, journal, defaults, session, con
112
136
  return;
113
137
  }
114
138
  const item = parsed.item;
115
- if (!item.id) item.id = generateId("item");
139
+ if (!item.id) item.id = realtimeId("item");
140
+ const previousId = conversationItems.length > 0 ? conversationItems[conversationItems.length - 1].id ?? null : null;
116
141
  conversationItems.push(item);
117
- ws.send(evt("conversation.item.created", { item }));
142
+ ws.send(evt("conversation.item.created", {
143
+ previous_item_id: previousId,
144
+ item
145
+ }));
118
146
  return;
119
147
  }
120
148
  if (msgType === "response.create") {
@@ -130,7 +158,7 @@ async function handleResponseCreate(ws, fixtures, journal, defaults, session, co
130
158
  };
131
159
  const testId = defaults.testId ?? DEFAULT_TEST_ID;
132
160
  const fixture = matchFixture(fixtures, completionReq, journal.getFixtureMatchCountsForTest(testId), defaults.requestTransform);
133
- const responseId = generateId("resp");
161
+ const responseId = realtimeId("resp");
134
162
  if (fixture) journal.incrementFixtureMatchCount(fixture, fixtures, testId);
135
163
  if (!fixture) {
136
164
  if (defaults.strict) {
@@ -150,11 +178,15 @@ async function handleResponseCreate(ws, fixtures, journal, defaults, session, co
150
178
  });
151
179
  ws.send(evt("response.created", { response: {
152
180
  id: responseId,
181
+ object: "realtime.response",
153
182
  status: "failed",
154
- output: []
183
+ status_details: null,
184
+ output: [],
185
+ usage: null
155
186
  } }));
156
187
  ws.send(evt("response.done", { response: {
157
188
  id: responseId,
189
+ object: "realtime.response",
158
190
  status: "failed",
159
191
  output: [],
160
192
  status_details: {
@@ -164,6 +196,11 @@ async function handleResponseCreate(ws, fixtures, journal, defaults, session, co
164
196
  type: "invalid_request_error",
165
197
  code: "no_fixture_match"
166
198
  }
199
+ },
200
+ usage: {
201
+ total_tokens: 0,
202
+ input_tokens: 0,
203
+ output_tokens: 0
167
204
  }
168
205
  } }));
169
206
  return;
@@ -185,11 +222,15 @@ async function handleResponseCreate(ws, fixtures, journal, defaults, session, co
185
222
  });
186
223
  ws.send(evt("response.created", { response: {
187
224
  id: responseId,
225
+ object: "realtime.response",
188
226
  status: "failed",
189
- output: []
227
+ status_details: null,
228
+ output: [],
229
+ usage: null
190
230
  } }));
191
231
  ws.send(evt("response.done", { response: {
192
232
  id: responseId,
233
+ object: "realtime.response",
193
234
  status: "failed",
194
235
  output: [],
195
236
  status_details: {
@@ -199,6 +240,11 @@ async function handleResponseCreate(ws, fixtures, journal, defaults, session, co
199
240
  type: response.error.type,
200
241
  code: response.error.code
201
242
  }
243
+ },
244
+ usage: {
245
+ total_tokens: 0,
246
+ input_tokens: 0,
247
+ output_tokens: 0
202
248
  }
203
249
  } }));
204
250
  return;
@@ -214,13 +260,14 @@ async function handleResponseCreate(ws, fixtures, journal, defaults, session, co
214
260
  fixture
215
261
  }
216
262
  });
217
- const itemId = generateId("item");
263
+ const itemId = realtimeId("item");
218
264
  const contentIndex = 0;
219
265
  const outputIndex = 0;
220
266
  const outputItem = {
221
267
  id: itemId,
222
268
  type: "message",
223
269
  role: "assistant",
270
+ status: "completed",
224
271
  content: [{
225
272
  type: "text",
226
273
  text: response.content
@@ -228,8 +275,11 @@ async function handleResponseCreate(ws, fixtures, journal, defaults, session, co
228
275
  };
229
276
  ws.send(evt("response.created", { response: {
230
277
  id: responseId,
278
+ object: "realtime.response",
231
279
  status: "in_progress",
232
- output: []
280
+ status_details: null,
281
+ output: [],
282
+ usage: null
233
283
  } }));
234
284
  ws.send(evt("response.output_item.added", {
235
285
  response_id: responseId,
@@ -238,6 +288,7 @@ async function handleResponseCreate(ws, fixtures, journal, defaults, session, co
238
288
  id: itemId,
239
289
  type: "message",
240
290
  role: "assistant",
291
+ status: "in_progress",
241
292
  content: []
242
293
  }
243
294
  }));
@@ -309,8 +360,14 @@ async function handleResponseCreate(ws, fixtures, journal, defaults, session, co
309
360
  }));
310
361
  ws.send(evt("response.done", { response: {
311
362
  id: responseId,
363
+ object: "realtime.response",
312
364
  status: "completed",
313
- output: [outputItem]
365
+ output: [outputItem],
366
+ usage: {
367
+ total_tokens: 0,
368
+ input_tokens: 0,
369
+ output_tokens: 0
370
+ }
314
371
  } }));
315
372
  conversationItems.push({
316
373
  type: "message",
@@ -336,8 +393,11 @@ async function handleResponseCreate(ws, fixtures, journal, defaults, session, co
336
393
  });
337
394
  ws.send(evt("response.created", { response: {
338
395
  id: responseId,
396
+ object: "realtime.response",
339
397
  status: "in_progress",
340
- output: []
398
+ status_details: null,
399
+ output: [],
400
+ usage: null
341
401
  } }));
342
402
  const outputItems = [];
343
403
  const interruption = createInterruptionSignal(fixture);
@@ -345,10 +405,11 @@ async function handleResponseCreate(ws, fixtures, journal, defaults, session, co
345
405
  for (let tcIdx = 0; tcIdx < response.toolCalls.length; tcIdx++) {
346
406
  const tc = response.toolCalls[tcIdx];
347
407
  const callId = tc.id ?? generateToolCallId();
348
- const itemId = generateId("item");
408
+ const itemId = realtimeId("item");
349
409
  const outputItem = {
350
410
  id: itemId,
351
411
  type: "function_call",
412
+ status: "completed",
352
413
  call_id: callId,
353
414
  name: tc.name,
354
415
  arguments: tc.arguments
@@ -359,6 +420,7 @@ async function handleResponseCreate(ws, fixtures, journal, defaults, session, co
359
420
  item: {
360
421
  id: itemId,
361
422
  type: "function_call",
423
+ status: "in_progress",
362
424
  call_id: callId,
363
425
  name: tc.name,
364
426
  arguments: ""
@@ -413,8 +475,14 @@ async function handleResponseCreate(ws, fixtures, journal, defaults, session, co
413
475
  if (ws.isClosed) return;
414
476
  ws.send(evt("response.done", { response: {
415
477
  id: responseId,
478
+ object: "realtime.response",
416
479
  status: "completed",
417
- output: outputItems
480
+ output: outputItems,
481
+ usage: {
482
+ total_tokens: 0,
483
+ input_tokens: 0,
484
+ output_tokens: 0
485
+ }
418
486
  } }));
419
487
  for (const item of outputItems) conversationItems.push(item);
420
488
  return;
@@ -1 +1 @@
1
- {"version":3,"file":"ws-realtime.js","names":[],"sources":["../src/ws-realtime.ts"],"sourcesContent":["/**\n * WebSocket handler for OpenAI Realtime API.\n *\n * Accepts Realtime API messages (session.update, conversation.item.create,\n * response.create) over WebSocket and sends back Realtime API events as\n * individual WebSocket text frames.\n */\n\nimport type { ChatCompletionRequest, ChatMessage, Fixture } from \"./types.js\";\nimport { matchFixture } from \"./router.js\";\nimport {\n generateId,\n generateToolCallId,\n isTextResponse,\n isToolCallResponse,\n isErrorResponse,\n resolveResponse,\n} from \"./helpers.js\";\nimport { createInterruptionSignal } from \"./interruption.js\";\nimport { delay } from \"./sse-writer.js\";\nimport { DEFAULT_TEST_ID, type Journal } from \"./journal.js\";\nimport type { Logger } from \"./logger.js\";\nimport type { WebSocketConnection } from \"./ws-framing.js\";\n\n// ─── Realtime protocol types ────────────────────────────────────────────────\n\ninterface RealtimeItem {\n type: \"message\" | \"function_call\" | \"function_call_output\";\n id?: string;\n role?: \"user\" | \"assistant\" | \"system\";\n content?: Array<{ type: string; text?: string }>;\n name?: string;\n call_id?: string;\n arguments?: string;\n output?: string;\n}\n\ninterface SessionConfig {\n model: string;\n modalities: string[];\n instructions: string;\n tools: unknown[];\n voice: string | null;\n input_audio_format: string | null;\n output_audio_format: string | null;\n turn_detection: unknown | null;\n temperature: number;\n}\n\ninterface RealtimeMessage {\n type: string;\n event_id?: string;\n session?: Partial<SessionConfig>;\n item?: RealtimeItem;\n response?: {\n modalities?: string[];\n instructions?: string;\n [key: string]: unknown;\n };\n}\n\n// ─── Conversion helpers ─────────────────────────────────────────────────────\n\nexport function realtimeItemsToMessages(\n items: RealtimeItem[],\n instructions?: string,\n logger?: Logger,\n): ChatMessage[] {\n const messages: ChatMessage[] = [];\n\n if (instructions) {\n messages.push({ role: \"system\", content: instructions });\n }\n\n for (const item of items) {\n if (item.type === \"message\") {\n const text = item.content?.[0]?.text ?? \"\";\n const role =\n item.role === \"assistant\" ? \"assistant\" : item.role === \"system\" ? \"system\" : \"user\";\n messages.push({ role, content: text });\n } else if (item.type === \"function_call\") {\n if (!item.name) {\n logger?.warn(\"Realtime function_call item missing 'name'\");\n }\n messages.push({\n role: \"assistant\",\n content: null,\n tool_calls: [\n {\n id: item.call_id ?? generateToolCallId(),\n type: \"function\",\n function: {\n name: item.name ?? \"\",\n arguments: item.arguments ?? \"\",\n },\n },\n ],\n });\n } else if (item.type === \"function_call_output\") {\n if (!item.output) {\n logger?.warn(\"Realtime function_call_output item missing 'output'\");\n }\n messages.push({\n role: \"tool\",\n content: item.output ?? \"\",\n tool_call_id: item.call_id,\n });\n }\n }\n\n return messages;\n}\n\n// ─── Event builders ─────────────────────────────────────────────────────────\n\nfunction evt(type: string, extra: Record<string, unknown> = {}): string {\n return JSON.stringify({ type, event_id: generateId(\"evt\"), ...extra });\n}\n\nfunction buildErrorRealtimeEvent(\n message: string,\n type = \"invalid_request_error\",\n code?: string,\n): string {\n return evt(\"error\", { error: { message, type, code } });\n}\n\n// ─── Main handler ───────────────────────────────────────────────────────────\n\nexport function handleWebSocketRealtime(\n ws: WebSocketConnection,\n fixtures: Fixture[],\n journal: Journal,\n defaults: {\n latency: number;\n chunkSize: number;\n model: string;\n logger: Logger;\n strict?: boolean;\n requestTransform?: (req: ChatCompletionRequest) => ChatCompletionRequest;\n testId?: string;\n },\n): void {\n const { logger } = defaults;\n const sessionId = generateId(\"sess\");\n\n const session: SessionConfig = {\n model: defaults.model,\n modalities: [\"text\"],\n instructions: \"\",\n tools: [],\n voice: null,\n input_audio_format: null,\n output_audio_format: null,\n turn_detection: null,\n temperature: 0.8,\n };\n\n const conversationItems: RealtimeItem[] = [];\n\n // Send session.created immediately on connect\n ws.send(evt(\"session.created\", { session: { id: sessionId, ...session } }));\n\n // Serialize message processing to prevent event interleaving\n let pending = Promise.resolve();\n ws.on(\"message\", (raw: string) => {\n pending = pending.then(() =>\n processMessage(raw, ws, fixtures, journal, defaults, session, conversationItems).catch(\n (err: unknown) => {\n const msg = err instanceof Error ? err.message : \"Internal error\";\n logger.error(`WebSocket realtime error: ${msg}`);\n try {\n ws.send(buildErrorRealtimeEvent(msg, \"server_error\"));\n } catch {\n // Connection already gone — original error already logged above\n }\n },\n ),\n );\n });\n}\n\nasync function processMessage(\n raw: string,\n ws: WebSocketConnection,\n fixtures: Fixture[],\n journal: Journal,\n defaults: {\n latency: number;\n chunkSize: number;\n model: string;\n logger: Logger;\n strict?: boolean;\n requestTransform?: (req: ChatCompletionRequest) => ChatCompletionRequest;\n testId?: string;\n },\n session: SessionConfig,\n conversationItems: RealtimeItem[],\n): Promise<void> {\n let parsed: RealtimeMessage;\n try {\n parsed = JSON.parse(raw) as RealtimeMessage;\n } catch {\n ws.send(buildErrorRealtimeEvent(\"Malformed JSON\", \"invalid_request_error\", \"invalid_json\"));\n return;\n }\n\n const msgType = parsed.type;\n\n // ── session.update ────────────────────────────────────────────────────\n if (msgType === \"session.update\") {\n if (parsed.session) {\n if (parsed.session.instructions !== undefined) {\n session.instructions = parsed.session.instructions;\n }\n if (parsed.session.tools !== undefined) {\n session.tools = parsed.session.tools;\n }\n if (parsed.session.modalities !== undefined) {\n session.modalities = parsed.session.modalities;\n }\n if (parsed.session.model !== undefined) {\n session.model = parsed.session.model;\n }\n if (parsed.session.temperature !== undefined) {\n session.temperature = parsed.session.temperature;\n }\n }\n ws.send(evt(\"session.updated\", { session: { ...session } }));\n return;\n }\n\n // ── conversation.item.create ──────────────────────────────────────────\n if (msgType === \"conversation.item.create\") {\n if (!parsed.item) {\n ws.send(\n buildErrorRealtimeEvent(\n \"Missing 'item' in conversation.item.create\",\n \"invalid_request_error\",\n ),\n );\n return;\n }\n const item = parsed.item;\n if (!item.id) {\n item.id = generateId(\"item\");\n }\n conversationItems.push(item);\n ws.send(evt(\"conversation.item.created\", { item }));\n return;\n }\n\n // ── response.create ───────────────────────────────────────────────────\n if (msgType === \"response.create\") {\n await handleResponseCreate(ws, fixtures, journal, defaults, session, conversationItems);\n return;\n }\n\n // Unknown message type — ignore silently (matches OpenAI behavior)\n}\n\nasync function handleResponseCreate(\n ws: WebSocketConnection,\n fixtures: Fixture[],\n journal: Journal,\n defaults: {\n latency: number;\n chunkSize: number;\n model: string;\n logger: Logger;\n strict?: boolean;\n requestTransform?: (req: ChatCompletionRequest) => ChatCompletionRequest;\n testId?: string;\n },\n session: SessionConfig,\n conversationItems: RealtimeItem[],\n): Promise<void> {\n const instructions = session.instructions || undefined;\n const messages = realtimeItemsToMessages(conversationItems, instructions, defaults.logger);\n\n const completionReq: ChatCompletionRequest = {\n model: session.model,\n messages,\n };\n\n const testId = defaults.testId ?? DEFAULT_TEST_ID;\n const fixture = matchFixture(\n fixtures,\n completionReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n );\n const responseId = generateId(\"resp\");\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n }\n\n if (!fixture) {\n if (defaults.strict) {\n defaults.logger.warn(`STRICT: No fixture matched for WebSocket message`);\n ws.close(1008, \"Strict mode: no fixture matched\");\n return;\n }\n journal.add({\n method: \"WS\",\n path: \"/v1/realtime\",\n headers: {},\n body: completionReq,\n response: { status: 404, fixture: null },\n });\n // Send response.created with failed status then response.done with error\n ws.send(\n evt(\"response.created\", {\n response: { id: responseId, status: \"failed\", output: [] },\n }),\n );\n ws.send(\n evt(\"response.done\", {\n response: {\n id: responseId,\n status: \"failed\",\n output: [],\n status_details: {\n type: \"error\",\n error: {\n message: \"No fixture matched\",\n type: \"invalid_request_error\",\n code: \"no_fixture_match\",\n },\n },\n },\n }),\n );\n return;\n }\n\n const response = await resolveResponse(fixture, completionReq);\n const latency = fixture.latency ?? defaults.latency;\n const chunkSize = Math.max(1, fixture.chunkSize ?? defaults.chunkSize);\n\n // ── Error fixture ───────────────────────────────────────────────────\n if (isErrorResponse(response)) {\n const status = response.status ?? 500;\n journal.add({\n method: \"WS\",\n path: \"/v1/realtime\",\n headers: {},\n body: completionReq,\n response: { status, fixture },\n });\n ws.send(\n evt(\"response.created\", {\n response: { id: responseId, status: \"failed\", output: [] },\n }),\n );\n ws.send(\n evt(\"response.done\", {\n response: {\n id: responseId,\n status: \"failed\",\n output: [],\n status_details: {\n type: \"error\",\n error: {\n message: response.error.message,\n type: response.error.type,\n code: response.error.code,\n },\n },\n },\n }),\n );\n return;\n }\n\n // ── Text response ───────────────────────────────────────────────────\n if (isTextResponse(response)) {\n const journalEntry = journal.add({\n method: \"WS\",\n path: \"/v1/realtime\",\n headers: {},\n body: completionReq,\n response: { status: 200, fixture },\n });\n\n const itemId = generateId(\"item\");\n const contentIndex = 0;\n const outputIndex = 0;\n\n const outputItem = {\n id: itemId,\n type: \"message\",\n role: \"assistant\",\n content: [{ type: \"text\", text: response.content }],\n };\n\n // response.created\n ws.send(\n evt(\"response.created\", {\n response: { id: responseId, status: \"in_progress\", output: [] },\n }),\n );\n\n // response.output_item.added\n ws.send(\n evt(\"response.output_item.added\", {\n response_id: responseId,\n output_index: outputIndex,\n item: { id: itemId, type: \"message\", role: \"assistant\", content: [] },\n }),\n );\n\n // response.content_part.added\n ws.send(\n evt(\"response.content_part.added\", {\n response_id: responseId,\n item_id: itemId,\n output_index: outputIndex,\n content_index: contentIndex,\n part: { type: \"text\", text: \"\" },\n }),\n );\n\n // response.text.delta (chunked)\n const content = response.content;\n const interruption = createInterruptionSignal(fixture);\n let interrupted = false;\n\n for (let i = 0; i < content.length; i += chunkSize) {\n if (ws.isClosed) break;\n if (latency > 0) await delay(latency, interruption?.signal);\n if (interruption?.signal.aborted) {\n interrupted = true;\n break;\n }\n if (ws.isClosed) break;\n const chunk = content.slice(i, i + chunkSize);\n ws.send(\n evt(\"response.text.delta\", {\n response_id: responseId,\n item_id: itemId,\n output_index: outputIndex,\n content_index: contentIndex,\n delta: chunk,\n }),\n );\n interruption?.tick();\n if (interruption?.signal.aborted) {\n interrupted = true;\n break;\n }\n }\n\n if (interrupted) {\n ws.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n interruption?.cleanup();\n return;\n }\n\n interruption?.cleanup();\n\n if (ws.isClosed) return;\n\n // response.text.done\n ws.send(\n evt(\"response.text.done\", {\n response_id: responseId,\n item_id: itemId,\n output_index: outputIndex,\n content_index: contentIndex,\n text: content,\n }),\n );\n\n // response.content_part.done\n ws.send(\n evt(\"response.content_part.done\", {\n response_id: responseId,\n item_id: itemId,\n output_index: outputIndex,\n content_index: contentIndex,\n part: { type: \"text\", text: content },\n }),\n );\n\n // response.output_item.done\n ws.send(\n evt(\"response.output_item.done\", {\n response_id: responseId,\n output_index: outputIndex,\n item: outputItem,\n }),\n );\n\n // response.done\n ws.send(\n evt(\"response.done\", {\n response: { id: responseId, status: \"completed\", output: [outputItem] },\n }),\n );\n\n // Accumulate assistant response into conversation for multi-turn\n conversationItems.push({\n type: \"message\",\n id: itemId,\n role: \"assistant\",\n content: [{ type: \"text\", text: content }],\n });\n return;\n }\n\n // ── Tool call response ──────────────────────────────────────────────\n if (isToolCallResponse(response)) {\n const journalEntry = journal.add({\n method: \"WS\",\n path: \"/v1/realtime\",\n headers: {},\n body: completionReq,\n response: { status: 200, fixture },\n });\n\n // response.created\n ws.send(\n evt(\"response.created\", {\n response: { id: responseId, status: \"in_progress\", output: [] },\n }),\n );\n\n const outputItems: unknown[] = [];\n const interruption = createInterruptionSignal(fixture);\n let interrupted = false;\n\n for (let tcIdx = 0; tcIdx < response.toolCalls.length; tcIdx++) {\n const tc = response.toolCalls[tcIdx];\n const callId = tc.id ?? generateToolCallId();\n const itemId = generateId(\"item\");\n\n const outputItem = {\n id: itemId,\n type: \"function_call\",\n call_id: callId,\n name: tc.name,\n arguments: tc.arguments,\n };\n\n // response.output_item.added\n ws.send(\n evt(\"response.output_item.added\", {\n response_id: responseId,\n output_index: tcIdx,\n item: {\n id: itemId,\n type: \"function_call\",\n call_id: callId,\n name: tc.name,\n arguments: \"\",\n },\n }),\n );\n\n // response.function_call_arguments.delta (chunked)\n const args = tc.arguments;\n for (let i = 0; i < args.length; i += chunkSize) {\n if (ws.isClosed) break;\n if (latency > 0) await delay(latency, interruption?.signal);\n if (interruption?.signal.aborted) {\n interrupted = true;\n break;\n }\n if (ws.isClosed) break;\n const chunk = args.slice(i, i + chunkSize);\n ws.send(\n evt(\"response.function_call_arguments.delta\", {\n response_id: responseId,\n item_id: itemId,\n output_index: tcIdx,\n call_id: callId,\n delta: chunk,\n }),\n );\n interruption?.tick();\n if (interruption?.signal.aborted) {\n interrupted = true;\n break;\n }\n }\n\n if (interrupted) break;\n\n // response.function_call_arguments.done\n ws.send(\n evt(\"response.function_call_arguments.done\", {\n response_id: responseId,\n item_id: itemId,\n output_index: tcIdx,\n call_id: callId,\n arguments: args,\n }),\n );\n\n // response.output_item.done\n ws.send(\n evt(\"response.output_item.done\", {\n response_id: responseId,\n output_index: tcIdx,\n item: outputItem,\n }),\n );\n\n outputItems.push(outputItem);\n }\n\n if (interrupted) {\n ws.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n interruption?.cleanup();\n return;\n }\n\n interruption?.cleanup();\n\n if (ws.isClosed) return;\n\n // response.done\n ws.send(\n evt(\"response.done\", {\n response: { id: responseId, status: \"completed\", output: outputItems },\n }),\n );\n\n // Accumulate assistant tool calls into conversation for multi-turn\n // Reuse outputItems (which already have the correct call_id) to avoid generating divergent IDs\n for (const item of outputItems) {\n conversationItems.push(item as RealtimeItem);\n }\n return;\n }\n\n // Unknown response type\n journal.add({\n method: \"WS\",\n path: \"/v1/realtime\",\n headers: {},\n body: completionReq,\n response: { status: 500, fixture },\n });\n ws.send(buildErrorRealtimeEvent(\"Fixture response did not match any known type\", \"server_error\"));\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ws-realtime.js","names":[],"sources":["../src/ws-realtime.ts"],"sourcesContent":["/**\n * WebSocket handler for OpenAI Realtime API.\n *\n * Accepts Realtime API messages (session.update, conversation.item.create,\n * response.create) over WebSocket and sends back Realtime API events as\n * individual WebSocket text frames.\n */\n\nimport { randomBytes } from \"node:crypto\";\nimport type { ChatCompletionRequest, ChatMessage, Fixture } from \"./types.js\";\nimport { matchFixture } from \"./router.js\";\nimport {\n generateToolCallId,\n isTextResponse,\n isToolCallResponse,\n isErrorResponse,\n resolveResponse,\n} from \"./helpers.js\";\nimport { createInterruptionSignal } from \"./interruption.js\";\nimport { delay } from \"./sse-writer.js\";\nimport { DEFAULT_TEST_ID, type Journal } from \"./journal.js\";\nimport type { Logger } from \"./logger.js\";\nimport type { WebSocketConnection } from \"./ws-framing.js\";\n\n/** Generate a Realtime-API-style ID with underscore separator (e.g. event_xxx, item_xxx). */\nfunction realtimeId(prefix: string): string {\n return `${prefix}_${randomBytes(12).toString(\"base64url\")}`;\n}\n\n// ─── Realtime protocol types ────────────────────────────────────────────────\n\ninterface RealtimeItem {\n type: \"message\" | \"function_call\" | \"function_call_output\";\n id?: string;\n role?: \"user\" | \"assistant\" | \"system\";\n content?: Array<{ type: string; text?: string }>;\n name?: string;\n call_id?: string;\n arguments?: string;\n output?: string;\n}\n\ninterface SessionConfig {\n model: string;\n modalities: string[];\n instructions: string;\n tools: unknown[];\n voice: string | null;\n input_audio_format: string | null;\n output_audio_format: string | null;\n turn_detection: unknown | null;\n temperature: number;\n}\n\ninterface RealtimeMessage {\n type: string;\n event_id?: string;\n session?: Partial<SessionConfig>;\n item?: RealtimeItem;\n response?: {\n modalities?: string[];\n instructions?: string;\n [key: string]: unknown;\n };\n}\n\n// ─── Conversion helpers ─────────────────────────────────────────────────────\n\nexport function realtimeItemsToMessages(\n items: RealtimeItem[],\n instructions?: string,\n logger?: Logger,\n): ChatMessage[] {\n const messages: ChatMessage[] = [];\n\n if (instructions) {\n messages.push({ role: \"system\", content: instructions });\n }\n\n for (const item of items) {\n if (item.type === \"message\") {\n const text = item.content?.[0]?.text ?? \"\";\n const role =\n item.role === \"assistant\" ? \"assistant\" : item.role === \"system\" ? \"system\" : \"user\";\n messages.push({ role, content: text });\n } else if (item.type === \"function_call\") {\n if (!item.name) {\n logger?.warn(\"Realtime function_call item missing 'name'\");\n }\n messages.push({\n role: \"assistant\",\n content: null,\n tool_calls: [\n {\n id: item.call_id ?? generateToolCallId(),\n type: \"function\",\n function: {\n name: item.name ?? \"\",\n arguments: item.arguments ?? \"\",\n },\n },\n ],\n });\n } else if (item.type === \"function_call_output\") {\n if (!item.output) {\n logger?.warn(\"Realtime function_call_output item missing 'output'\");\n }\n messages.push({\n role: \"tool\",\n content: item.output ?? \"\",\n tool_call_id: item.call_id,\n });\n }\n }\n\n return messages;\n}\n\n// ─── Event builders ─────────────────────────────────────────────────────────\n\nfunction evt(type: string, extra: Record<string, unknown> = {}): string {\n return JSON.stringify({ type, event_id: realtimeId(\"event\"), ...extra });\n}\n\nfunction buildErrorRealtimeEvent(\n message: string,\n type = \"invalid_request_error\",\n code?: string,\n): string {\n return evt(\"error\", { error: { message, type, code } });\n}\n\n// ─── Main handler ───────────────────────────────────────────────────────────\n\nexport function handleWebSocketRealtime(\n ws: WebSocketConnection,\n fixtures: Fixture[],\n journal: Journal,\n defaults: {\n latency: number;\n chunkSize: number;\n model: string;\n logger: Logger;\n strict?: boolean;\n requestTransform?: (req: ChatCompletionRequest) => ChatCompletionRequest;\n testId?: string;\n },\n): void {\n const { logger } = defaults;\n const sessionId = realtimeId(\"sess\");\n\n const session: SessionConfig = {\n model: defaults.model,\n modalities: [\"text\"],\n instructions: \"\",\n tools: [],\n voice: null,\n input_audio_format: null,\n output_audio_format: null,\n turn_detection: null,\n temperature: 0.8,\n };\n\n const conversationItems: RealtimeItem[] = [];\n\n // Send session.created immediately on connect\n ws.send(\n evt(\"session.created\", {\n session: {\n id: sessionId,\n object: \"realtime.session\",\n ...session,\n expires_at: Math.floor(Date.now() / 1000) + 3600,\n max_response_output_tokens: \"inf\",\n input_audio_transcription: null,\n tool_choice: \"auto\",\n },\n }),\n );\n\n // Serialize message processing to prevent event interleaving\n let pending = Promise.resolve();\n ws.on(\"message\", (raw: string) => {\n pending = pending.then(() =>\n processMessage(raw, ws, fixtures, journal, defaults, session, conversationItems).catch(\n (err: unknown) => {\n const msg = err instanceof Error ? err.message : \"Internal error\";\n logger.error(`WebSocket realtime error: ${msg}`);\n try {\n ws.send(buildErrorRealtimeEvent(msg, \"server_error\"));\n } catch {\n // Connection already gone — original error already logged above\n }\n },\n ),\n );\n });\n}\n\nasync function processMessage(\n raw: string,\n ws: WebSocketConnection,\n fixtures: Fixture[],\n journal: Journal,\n defaults: {\n latency: number;\n chunkSize: number;\n model: string;\n logger: Logger;\n strict?: boolean;\n requestTransform?: (req: ChatCompletionRequest) => ChatCompletionRequest;\n testId?: string;\n },\n session: SessionConfig,\n conversationItems: RealtimeItem[],\n): Promise<void> {\n let parsed: RealtimeMessage;\n try {\n parsed = JSON.parse(raw) as RealtimeMessage;\n } catch {\n ws.send(buildErrorRealtimeEvent(\"Malformed JSON\", \"invalid_request_error\", \"invalid_json\"));\n return;\n }\n\n const msgType = parsed.type;\n\n // ── session.update ────────────────────────────────────────────────────\n if (msgType === \"session.update\") {\n if (parsed.session) {\n if (parsed.session.instructions !== undefined) {\n session.instructions = parsed.session.instructions;\n }\n if (parsed.session.tools !== undefined) {\n session.tools = parsed.session.tools;\n }\n if (parsed.session.modalities !== undefined) {\n session.modalities = parsed.session.modalities;\n }\n if (parsed.session.model !== undefined) {\n session.model = parsed.session.model;\n }\n if (parsed.session.temperature !== undefined) {\n session.temperature = parsed.session.temperature;\n }\n }\n ws.send(\n evt(\"session.updated\", {\n session: {\n ...session,\n object: \"realtime.session\",\n expires_at: Math.floor(Date.now() / 1000) + 3600,\n max_response_output_tokens: \"inf\",\n input_audio_transcription: null,\n tool_choice: \"auto\",\n },\n }),\n );\n return;\n }\n\n // ── conversation.item.create ──────────────────────────────────────────\n if (msgType === \"conversation.item.create\") {\n if (!parsed.item) {\n ws.send(\n buildErrorRealtimeEvent(\n \"Missing 'item' in conversation.item.create\",\n \"invalid_request_error\",\n ),\n );\n return;\n }\n const item = parsed.item;\n if (!item.id) {\n item.id = realtimeId(\"item\");\n }\n const previousId =\n conversationItems.length > 0\n ? (conversationItems[conversationItems.length - 1].id ?? null)\n : null;\n conversationItems.push(item);\n ws.send(evt(\"conversation.item.created\", { previous_item_id: previousId, item }));\n return;\n }\n\n // ── response.create ───────────────────────────────────────────────────\n if (msgType === \"response.create\") {\n await handleResponseCreate(ws, fixtures, journal, defaults, session, conversationItems);\n return;\n }\n\n // Unknown message type — ignore silently (matches OpenAI behavior)\n}\n\nasync function handleResponseCreate(\n ws: WebSocketConnection,\n fixtures: Fixture[],\n journal: Journal,\n defaults: {\n latency: number;\n chunkSize: number;\n model: string;\n logger: Logger;\n strict?: boolean;\n requestTransform?: (req: ChatCompletionRequest) => ChatCompletionRequest;\n testId?: string;\n },\n session: SessionConfig,\n conversationItems: RealtimeItem[],\n): Promise<void> {\n const instructions = session.instructions || undefined;\n const messages = realtimeItemsToMessages(conversationItems, instructions, defaults.logger);\n\n const completionReq: ChatCompletionRequest = {\n model: session.model,\n messages,\n };\n\n const testId = defaults.testId ?? DEFAULT_TEST_ID;\n const fixture = matchFixture(\n fixtures,\n completionReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n );\n const responseId = realtimeId(\"resp\");\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n }\n\n if (!fixture) {\n if (defaults.strict) {\n defaults.logger.warn(`STRICT: No fixture matched for WebSocket message`);\n ws.close(1008, \"Strict mode: no fixture matched\");\n return;\n }\n journal.add({\n method: \"WS\",\n path: \"/v1/realtime\",\n headers: {},\n body: completionReq,\n response: { status: 404, fixture: null },\n });\n // Send response.created with failed status then response.done with error\n ws.send(\n evt(\"response.created\", {\n response: {\n id: responseId,\n object: \"realtime.response\",\n status: \"failed\",\n status_details: null,\n output: [],\n usage: null,\n },\n }),\n );\n ws.send(\n evt(\"response.done\", {\n response: {\n id: responseId,\n object: \"realtime.response\",\n status: \"failed\",\n output: [],\n status_details: {\n type: \"error\",\n error: {\n message: \"No fixture matched\",\n type: \"invalid_request_error\",\n code: \"no_fixture_match\",\n },\n },\n usage: { total_tokens: 0, input_tokens: 0, output_tokens: 0 },\n },\n }),\n );\n return;\n }\n\n const response = await resolveResponse(fixture, completionReq);\n const latency = fixture.latency ?? defaults.latency;\n const chunkSize = Math.max(1, fixture.chunkSize ?? defaults.chunkSize);\n\n // ── Error fixture ───────────────────────────────────────────────────\n if (isErrorResponse(response)) {\n const status = response.status ?? 500;\n journal.add({\n method: \"WS\",\n path: \"/v1/realtime\",\n headers: {},\n body: completionReq,\n response: { status, fixture },\n });\n ws.send(\n evt(\"response.created\", {\n response: {\n id: responseId,\n object: \"realtime.response\",\n status: \"failed\",\n status_details: null,\n output: [],\n usage: null,\n },\n }),\n );\n ws.send(\n evt(\"response.done\", {\n response: {\n id: responseId,\n object: \"realtime.response\",\n status: \"failed\",\n output: [],\n status_details: {\n type: \"error\",\n error: {\n message: response.error.message,\n type: response.error.type,\n code: response.error.code,\n },\n },\n usage: { total_tokens: 0, input_tokens: 0, output_tokens: 0 },\n },\n }),\n );\n return;\n }\n\n // ── Text response ───────────────────────────────────────────────────\n if (isTextResponse(response)) {\n const journalEntry = journal.add({\n method: \"WS\",\n path: \"/v1/realtime\",\n headers: {},\n body: completionReq,\n response: { status: 200, fixture },\n });\n\n const itemId = realtimeId(\"item\");\n const contentIndex = 0;\n const outputIndex = 0;\n\n const outputItem = {\n id: itemId,\n type: \"message\",\n role: \"assistant\",\n status: \"completed\",\n content: [{ type: \"text\", text: response.content }],\n };\n\n // response.created\n ws.send(\n evt(\"response.created\", {\n response: {\n id: responseId,\n object: \"realtime.response\",\n status: \"in_progress\",\n status_details: null,\n output: [],\n usage: null,\n },\n }),\n );\n\n // response.output_item.added\n ws.send(\n evt(\"response.output_item.added\", {\n response_id: responseId,\n output_index: outputIndex,\n item: {\n id: itemId,\n type: \"message\",\n role: \"assistant\",\n status: \"in_progress\",\n content: [],\n },\n }),\n );\n\n // response.content_part.added\n ws.send(\n evt(\"response.content_part.added\", {\n response_id: responseId,\n item_id: itemId,\n output_index: outputIndex,\n content_index: contentIndex,\n part: { type: \"text\", text: \"\" },\n }),\n );\n\n // response.text.delta (chunked)\n const content = response.content;\n const interruption = createInterruptionSignal(fixture);\n let interrupted = false;\n\n for (let i = 0; i < content.length; i += chunkSize) {\n if (ws.isClosed) break;\n if (latency > 0) await delay(latency, interruption?.signal);\n if (interruption?.signal.aborted) {\n interrupted = true;\n break;\n }\n if (ws.isClosed) break;\n const chunk = content.slice(i, i + chunkSize);\n ws.send(\n evt(\"response.text.delta\", {\n response_id: responseId,\n item_id: itemId,\n output_index: outputIndex,\n content_index: contentIndex,\n delta: chunk,\n }),\n );\n interruption?.tick();\n if (interruption?.signal.aborted) {\n interrupted = true;\n break;\n }\n }\n\n if (interrupted) {\n ws.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n interruption?.cleanup();\n return;\n }\n\n interruption?.cleanup();\n\n if (ws.isClosed) return;\n\n // response.text.done\n ws.send(\n evt(\"response.text.done\", {\n response_id: responseId,\n item_id: itemId,\n output_index: outputIndex,\n content_index: contentIndex,\n text: content,\n }),\n );\n\n // response.content_part.done\n ws.send(\n evt(\"response.content_part.done\", {\n response_id: responseId,\n item_id: itemId,\n output_index: outputIndex,\n content_index: contentIndex,\n part: { type: \"text\", text: content },\n }),\n );\n\n // response.output_item.done\n ws.send(\n evt(\"response.output_item.done\", {\n response_id: responseId,\n output_index: outputIndex,\n item: outputItem,\n }),\n );\n\n // response.done\n ws.send(\n evt(\"response.done\", {\n response: {\n id: responseId,\n object: \"realtime.response\",\n status: \"completed\",\n output: [outputItem],\n usage: { total_tokens: 0, input_tokens: 0, output_tokens: 0 },\n },\n }),\n );\n\n // Accumulate assistant response into conversation for multi-turn\n conversationItems.push({\n type: \"message\",\n id: itemId,\n role: \"assistant\",\n content: [{ type: \"text\", text: content }],\n });\n return;\n }\n\n // ── Tool call response ──────────────────────────────────────────────\n if (isToolCallResponse(response)) {\n const journalEntry = journal.add({\n method: \"WS\",\n path: \"/v1/realtime\",\n headers: {},\n body: completionReq,\n response: { status: 200, fixture },\n });\n\n // response.created\n ws.send(\n evt(\"response.created\", {\n response: {\n id: responseId,\n object: \"realtime.response\",\n status: \"in_progress\",\n status_details: null,\n output: [],\n usage: null,\n },\n }),\n );\n\n const outputItems: unknown[] = [];\n const interruption = createInterruptionSignal(fixture);\n let interrupted = false;\n\n for (let tcIdx = 0; tcIdx < response.toolCalls.length; tcIdx++) {\n const tc = response.toolCalls[tcIdx];\n const callId = tc.id ?? generateToolCallId();\n const itemId = realtimeId(\"item\");\n\n const outputItem = {\n id: itemId,\n type: \"function_call\",\n status: \"completed\",\n call_id: callId,\n name: tc.name,\n arguments: tc.arguments,\n };\n\n // response.output_item.added\n ws.send(\n evt(\"response.output_item.added\", {\n response_id: responseId,\n output_index: tcIdx,\n item: {\n id: itemId,\n type: \"function_call\",\n status: \"in_progress\",\n call_id: callId,\n name: tc.name,\n arguments: \"\",\n },\n }),\n );\n\n // response.function_call_arguments.delta (chunked)\n const args = tc.arguments;\n for (let i = 0; i < args.length; i += chunkSize) {\n if (ws.isClosed) break;\n if (latency > 0) await delay(latency, interruption?.signal);\n if (interruption?.signal.aborted) {\n interrupted = true;\n break;\n }\n if (ws.isClosed) break;\n const chunk = args.slice(i, i + chunkSize);\n ws.send(\n evt(\"response.function_call_arguments.delta\", {\n response_id: responseId,\n item_id: itemId,\n output_index: tcIdx,\n call_id: callId,\n delta: chunk,\n }),\n );\n interruption?.tick();\n if (interruption?.signal.aborted) {\n interrupted = true;\n break;\n }\n }\n\n if (interrupted) break;\n\n // response.function_call_arguments.done\n ws.send(\n evt(\"response.function_call_arguments.done\", {\n response_id: responseId,\n item_id: itemId,\n output_index: tcIdx,\n call_id: callId,\n arguments: args,\n }),\n );\n\n // response.output_item.done\n ws.send(\n evt(\"response.output_item.done\", {\n response_id: responseId,\n output_index: tcIdx,\n item: outputItem,\n }),\n );\n\n outputItems.push(outputItem);\n }\n\n if (interrupted) {\n ws.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n interruption?.cleanup();\n return;\n }\n\n interruption?.cleanup();\n\n if (ws.isClosed) return;\n\n // response.done\n ws.send(\n evt(\"response.done\", {\n response: {\n id: responseId,\n object: \"realtime.response\",\n status: \"completed\",\n output: outputItems,\n usage: { total_tokens: 0, input_tokens: 0, output_tokens: 0 },\n },\n }),\n );\n\n // Accumulate assistant tool calls into conversation for multi-turn\n // Reuse outputItems (which already have the correct call_id) to avoid generating divergent IDs\n for (const item of outputItems) {\n conversationItems.push(item as RealtimeItem);\n }\n return;\n }\n\n // Unknown response type\n journal.add({\n method: \"WS\",\n path: \"/v1/realtime\",\n headers: {},\n body: completionReq,\n response: { status: 500, fixture },\n });\n ws.send(buildErrorRealtimeEvent(\"Fixture response did not match any known type\", \"server_error\"));\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@copilotkit/aimock",
3
- "version": "1.19.4",
3
+ "version": "1.20.0",
4
4
  "description": "Mock infrastructure for AI application testing — LLM APIs, image generation, text-to-speech, transcription, audio generation, video generation, MCP tools, A2A agents, AG-UI event streams, vector databases, search, rerank, and moderation. One package, one port, zero dependencies.",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -23,6 +23,8 @@ aimock is a zero-dependency mock infrastructure for AI apps. Fixture-driven. Mul
23
23
  | ---------------- | ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
24
24
  | `userMessage` | `string` | Substring of last `role: "user"` message text |
25
25
  | `userMessage` | `RegExp` | Pattern test on last `role: "user"` message text |
26
+ | `systemMessage` | `string` | Substring of the concatenated text of every `role: "system"` message in the request. Use to gate a fixture on host-supplied context (persona, agent-context entries) so changes to that context cause the fixture to fall through instead of returning a stale baked response |
27
+ | `systemMessage` | `RegExp` | Pattern test on the concatenated system-message text |
26
28
  | `inputText` | `string` | Substring of embedding input text (concatenated if multiple inputs) |
27
29
  | `inputText` | `RegExp` | Pattern test on embedding input text |
28
30
  | `toolName` | `string` | Exact match on any tool in request's `tools[]` array (by `function.name`) |