@copilotkit/aimock 1.20.0 → 1.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +47 -0
- package/README.md +1 -0
- package/dist/a2a-mock.cjs +1 -1
- package/dist/a2a-mock.cjs.map +1 -1
- package/dist/a2a-mock.d.cts.map +1 -1
- package/dist/a2a-mock.d.ts.map +1 -1
- package/dist/a2a-mock.js +1 -1
- package/dist/a2a-mock.js.map +1 -1
- package/dist/agui-recorder.cjs +25 -12
- package/dist/agui-recorder.cjs.map +1 -1
- package/dist/agui-recorder.js +25 -12
- package/dist/agui-recorder.js.map +1 -1
- package/dist/agui-types.d.cts.map +1 -1
- package/dist/bedrock-converse.cjs +18 -12
- package/dist/bedrock-converse.cjs.map +1 -1
- package/dist/bedrock-converse.d.cts.map +1 -1
- package/dist/bedrock-converse.d.ts.map +1 -1
- package/dist/bedrock-converse.js +19 -13
- package/dist/bedrock-converse.js.map +1 -1
- package/dist/bedrock.cjs +18 -12
- package/dist/bedrock.cjs.map +1 -1
- package/dist/bedrock.d.cts.map +1 -1
- package/dist/bedrock.d.ts.map +1 -1
- package/dist/bedrock.js +19 -13
- package/dist/bedrock.js.map +1 -1
- package/dist/cli.cjs +1 -1
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1 -1
- package/dist/cli.js.map +1 -1
- package/dist/cohere.cjs +9 -6
- package/dist/cohere.cjs.map +1 -1
- package/dist/cohere.d.cts.map +1 -1
- package/dist/cohere.d.ts.map +1 -1
- package/dist/cohere.js +10 -7
- package/dist/cohere.js.map +1 -1
- package/dist/config-loader.d.cts.map +1 -1
- package/dist/elevenlabs-audio.cjs +8 -5
- package/dist/elevenlabs-audio.cjs.map +1 -1
- package/dist/elevenlabs-audio.d.cts.map +1 -1
- package/dist/elevenlabs-audio.d.ts.map +1 -1
- package/dist/elevenlabs-audio.js +9 -6
- package/dist/elevenlabs-audio.js.map +1 -1
- package/dist/embeddings.cjs +6 -4
- package/dist/embeddings.cjs.map +1 -1
- package/dist/embeddings.d.cts.map +1 -1
- package/dist/embeddings.d.ts.map +1 -1
- package/dist/embeddings.js +7 -5
- package/dist/embeddings.js.map +1 -1
- package/dist/fal-audio.cjs +16 -10
- package/dist/fal-audio.cjs.map +1 -1
- package/dist/fal-audio.d.cts.map +1 -1
- package/dist/fal-audio.d.ts.map +1 -1
- package/dist/fal-audio.js +17 -11
- package/dist/fal-audio.js.map +1 -1
- package/dist/fal.cjs +5 -3
- package/dist/fal.cjs.map +1 -1
- package/dist/fal.d.cts.map +1 -1
- package/dist/fal.d.ts.map +1 -1
- package/dist/fal.js +6 -4
- package/dist/fal.js.map +1 -1
- package/dist/fixture-loader.cjs +19 -5
- package/dist/fixture-loader.cjs.map +1 -1
- package/dist/fixture-loader.js +19 -5
- package/dist/fixture-loader.js.map +1 -1
- package/dist/gemini-interactions.cjs +10 -7
- package/dist/gemini-interactions.cjs.map +1 -1
- package/dist/gemini-interactions.d.cts.map +1 -1
- package/dist/gemini-interactions.d.ts.map +1 -1
- package/dist/gemini-interactions.js +11 -8
- package/dist/gemini-interactions.js.map +1 -1
- package/dist/gemini.cjs +10 -7
- package/dist/gemini.cjs.map +1 -1
- package/dist/gemini.d.cts.map +1 -1
- package/dist/gemini.d.ts.map +1 -1
- package/dist/gemini.js +11 -8
- package/dist/gemini.js.map +1 -1
- package/dist/helpers.cjs +31 -0
- package/dist/helpers.cjs.map +1 -1
- package/dist/helpers.d.cts +1 -0
- package/dist/helpers.d.cts.map +1 -1
- package/dist/helpers.d.ts +1 -0
- package/dist/helpers.d.ts.map +1 -1
- package/dist/helpers.js +30 -1
- package/dist/helpers.js.map +1 -1
- package/dist/images.cjs +8 -5
- package/dist/images.cjs.map +1 -1
- package/dist/images.d.cts.map +1 -1
- package/dist/images.d.ts.map +1 -1
- package/dist/images.js +9 -6
- package/dist/images.js.map +1 -1
- package/dist/mcp-mock.cjs +1 -1
- package/dist/mcp-mock.cjs.map +1 -1
- package/dist/mcp-mock.d.cts.map +1 -1
- package/dist/mcp-mock.d.ts.map +1 -1
- package/dist/mcp-mock.js +1 -1
- package/dist/mcp-mock.js.map +1 -1
- package/dist/messages.cjs +9 -6
- package/dist/messages.cjs.map +1 -1
- package/dist/messages.d.cts.map +1 -1
- package/dist/messages.d.ts.map +1 -1
- package/dist/messages.js +10 -7
- package/dist/messages.js.map +1 -1
- package/dist/moderation.cjs +3 -2
- package/dist/moderation.cjs.map +1 -1
- package/dist/moderation.js +3 -2
- package/dist/moderation.js.map +1 -1
- package/dist/ollama.cjs +18 -12
- package/dist/ollama.cjs.map +1 -1
- package/dist/ollama.d.cts.map +1 -1
- package/dist/ollama.d.ts.map +1 -1
- package/dist/ollama.js +19 -13
- package/dist/ollama.js.map +1 -1
- package/dist/recorder.cjs +82 -38
- package/dist/recorder.cjs.map +1 -1
- package/dist/recorder.d.cts +3 -2
- package/dist/recorder.d.cts.map +1 -1
- package/dist/recorder.d.ts +3 -2
- package/dist/recorder.d.ts.map +1 -1
- package/dist/recorder.js +82 -38
- package/dist/recorder.js.map +1 -1
- package/dist/rerank.cjs +3 -2
- package/dist/rerank.cjs.map +1 -1
- package/dist/rerank.js +3 -2
- package/dist/rerank.js.map +1 -1
- package/dist/responses.cjs +9 -6
- package/dist/responses.cjs.map +1 -1
- package/dist/responses.d.cts.map +1 -1
- package/dist/responses.d.ts.map +1 -1
- package/dist/responses.js +10 -7
- package/dist/responses.js.map +1 -1
- package/dist/router.cjs +18 -5
- package/dist/router.cjs.map +1 -1
- package/dist/router.js +18 -5
- package/dist/router.js.map +1 -1
- package/dist/search.cjs +3 -2
- package/dist/search.cjs.map +1 -1
- package/dist/search.js +3 -2
- package/dist/search.js.map +1 -1
- package/dist/server.cjs +135 -73
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +136 -74
- package/dist/server.js.map +1 -1
- package/dist/speech.cjs +8 -5
- package/dist/speech.cjs.map +1 -1
- package/dist/speech.d.cts.map +1 -1
- package/dist/speech.d.ts.map +1 -1
- package/dist/speech.js +9 -6
- package/dist/speech.js.map +1 -1
- package/dist/stream-collapse.cjs +51 -21
- package/dist/stream-collapse.cjs.map +1 -1
- package/dist/stream-collapse.d.cts +1 -0
- package/dist/stream-collapse.d.cts.map +1 -1
- package/dist/stream-collapse.d.ts +1 -0
- package/dist/stream-collapse.d.ts.map +1 -1
- package/dist/stream-collapse.js +51 -21
- package/dist/stream-collapse.js.map +1 -1
- package/dist/transcription.cjs +5 -3
- 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 +6 -4
- package/dist/transcription.js.map +1 -1
- package/dist/types.d.cts +21 -9
- package/dist/types.d.cts.map +1 -1
- package/dist/types.d.ts +21 -9
- package/dist/types.d.ts.map +1 -1
- package/dist/vector-mock.cjs +10 -8
- package/dist/vector-mock.cjs.map +1 -1
- package/dist/vector-mock.d.cts.map +1 -1
- package/dist/vector-mock.d.ts.map +1 -1
- package/dist/vector-mock.js +10 -8
- package/dist/vector-mock.js.map +1 -1
- package/dist/vector-types.d.cts.map +1 -1
- package/dist/video.cjs +8 -5
- package/dist/video.cjs.map +1 -1
- package/dist/video.d.cts.map +1 -1
- package/dist/video.d.ts.map +1 -1
- package/dist/video.js +9 -6
- package/dist/video.js.map +1 -1
- package/dist/ws-gemini-live.cjs +6 -4
- package/dist/ws-gemini-live.cjs.map +1 -1
- package/dist/ws-gemini-live.d.cts +2 -0
- package/dist/ws-gemini-live.d.cts.map +1 -1
- package/dist/ws-gemini-live.d.ts +2 -0
- package/dist/ws-gemini-live.d.ts.map +1 -1
- package/dist/ws-gemini-live.js +7 -5
- package/dist/ws-gemini-live.js.map +1 -1
- package/dist/ws-realtime.cjs +6 -4
- package/dist/ws-realtime.cjs.map +1 -1
- package/dist/ws-realtime.d.cts +2 -0
- package/dist/ws-realtime.d.cts.map +1 -1
- package/dist/ws-realtime.d.ts +2 -0
- package/dist/ws-realtime.d.ts.map +1 -1
- package/dist/ws-realtime.js +7 -5
- package/dist/ws-realtime.js.map +1 -1
- package/dist/ws-responses.cjs +6 -4
- package/dist/ws-responses.cjs.map +1 -1
- package/dist/ws-responses.d.cts +2 -0
- package/dist/ws-responses.d.cts.map +1 -1
- package/dist/ws-responses.d.ts +2 -0
- package/dist/ws-responses.d.ts.map +1 -1
- package/dist/ws-responses.js +7 -5
- package/dist/ws-responses.js.map +1 -1
- package/package.json +1 -1
- package/skills/write-fixtures/SKILL.md +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,52 @@
|
|
|
1
1
|
# @copilotkit/aimock
|
|
2
2
|
|
|
3
|
+
## [Unreleased]
|
|
4
|
+
|
|
5
|
+
## [1.22.0] - 2026-05-11
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **Per-request strict mode via `X-AIMock-Strict` header** — overrides the server-wide `--strict` flag per request (`true`/`1` = strict, `false`/`0` = lenient). When strict: fixture miss returns 503; when lenient: fixture miss proxies to real provider. Follows the `X-AIMock-Chaos-*` precedence pattern. Journal entries record `strictOverride` when the header overrides the server default. Enables the same aimock instance to serve both deterministic test probes and live demo traffic simultaneously.
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- **Progressive relay for NDJSON and Bedrock binary event streams** — Ollama NDJSON and Bedrock binary event streams were fully buffered before relay, triggering downstream idle timeouts; now relayed progressively as chunks arrive
|
|
14
|
+
- **JSON.parse error detail in bare catch blocks** — capture and surface parse-error detail in all bare catch blocks across 25+ provider/WebSocket/stream-collapse handlers instead of swallowing context
|
|
15
|
+
- **Unguarded stream write/end calls** — wrap stream write/end in try/catch (recorder.ts, agui-recorder.ts) to prevent unhandled exceptions on client disconnect
|
|
16
|
+
- **Response termination for headers-already-sent paths** — add response termination in error paths where headers were already sent (server.ts, a2a-mock.ts, mcp-mock.ts), preventing connection hangs
|
|
17
|
+
- **Vector-mock double body consumption** — fix route passthrough consuming the request body twice, causing empty-body forwarding
|
|
18
|
+
- **Drift detection compared only first event per type** — `compareSSESequences` now compares ALL events per type, not just the first, catching previously invisible divergences
|
|
19
|
+
- **Ollama drift tests used broken async describe.skipIf** — replaced with synchronous env-var gate so tests are correctly skipped or executed
|
|
20
|
+
- **12 unrestored spy/mock leaks and misleading assertions** — fix spy/mock leaks across test files and correct assertions that passed for the wrong reasons
|
|
21
|
+
- Proxy relay hardcoded POST method — now forwards the original HTTP method
|
|
22
|
+
- Response timeout timer leak — cleared after successful upstream completion
|
|
23
|
+
- Client disconnect handler race — checks `writableFinished` before destroying upstream request
|
|
24
|
+
- `onHookBypassed` and `beforeWriteResponse` callbacks not wrapped in try/catch
|
|
25
|
+
- Audio error relay sent non-2xx responses with audio content-type instead of application/json
|
|
26
|
+
- Snapshot-mode fixture writes not atomic — concurrent requests could corrupt the file
|
|
27
|
+
- Undefined `toolCall` name/arguments silently dropped during fixture save
|
|
28
|
+
- Video detection heuristic false-positives on LLM provider responses with `{id, status}` shape
|
|
29
|
+
- One-shot error fixture splice during iteration (deferred via microtask)
|
|
30
|
+
- Azure model injection catch swallowed non-SyntaxError exceptions
|
|
31
|
+
- fal request body lost on passthrough (double `readBody` consumption)
|
|
32
|
+
- fal queue handler dropped PUT request body
|
|
33
|
+
- Recorder test: tmpDir leak on strict-mode reassignment, global fetch dependency, fragile fixturePath cleanup, duplicate helpers, spy leak on assertion failure
|
|
34
|
+
|
|
35
|
+
### Added
|
|
36
|
+
|
|
37
|
+
- Fixture-level chaos evaluation for non-completions endpoints (ElevenLabs, fal)
|
|
38
|
+
|
|
39
|
+
### Changed
|
|
40
|
+
|
|
41
|
+
- **Anti-buffering headers on all progressive stream relay paths** — standard headers (Cache-Control, Connection, X-Accel-Buffering) added to all progressive stream relay paths to prevent intermediate proxy buffering
|
|
42
|
+
- **Stream-collapse returns firstDroppedSample** — stream-collapse functions now return the first dropped sample for forensic debugging of collapsed streams
|
|
43
|
+
|
|
44
|
+
## [1.21.0] - 2026-05-11
|
|
45
|
+
|
|
46
|
+
### Added
|
|
47
|
+
|
|
48
|
+
- **`match.systemMessage` accepts `string[]`** — array form requires ALL substrings to be present in the joined system-message text (AND semantics). Use this when the gate must combine multiple non-adjacent tokens that may appear in any order — e.g., a host that serialises agent-context entries into a system message whose entry order is not stable, but where a fixture should only match when every default value is present (`["\"value\": \"Atai\"", "[\"Viewed the pricing page\",\"Watched the product demo video\"]"]`). Single-string and `RegExp` forms continue to work unchanged. JSON form accepts `string | string[]`; programmatic form accepts `string | string[] | RegExp`.
|
|
49
|
+
|
|
3
50
|
## [1.20.0] - 2026-05-11
|
|
4
51
|
|
|
5
52
|
### Fixed
|
package/README.md
CHANGED
|
@@ -53,6 +53,7 @@ Run them all on one port with `npx @copilotkit/aimock --config aimock.json`, or
|
|
|
53
53
|
- **Multimedia APIs** — [image generation](https://aimock.copilotkit.dev/images) (DALL-E, Imagen), [text-to-speech](https://aimock.copilotkit.dev/speech), [audio transcription](https://aimock.copilotkit.dev/transcription), [video generation](https://aimock.copilotkit.dev/video)
|
|
54
54
|
- **[MCP](https://aimock.copilotkit.dev/mcp-mock) / [A2A](https://aimock.copilotkit.dev/a2a-mock) / [AG-UI](https://aimock.copilotkit.dev/agui-mock) / [Vector](https://aimock.copilotkit.dev/vector-mock)** — Mock every protocol your AI agents use
|
|
55
55
|
- **[Chaos Testing](https://aimock.copilotkit.dev/chaos-testing)** — 500 errors, malformed JSON, mid-stream disconnects at any probability
|
|
56
|
+
- **Per-Request Strict Mode** — `X-AIMock-Strict` header overrides the server-level `--strict` flag per request (`true`/`1` = strict, `false`/`0` = lenient)
|
|
56
57
|
- **[Drift Detection](https://aimock.copilotkit.dev/drift-detection)** — Daily CI validation against real APIs
|
|
57
58
|
- **[Streaming Physics](https://aimock.copilotkit.dev/streaming-physics)** — Configurable `ttft`, `tps`, and `jitter`
|
|
58
59
|
- **[WebSocket APIs](https://aimock.copilotkit.dev/websocket)** — OpenAI Realtime, Responses WS, Gemini Live
|
package/dist/a2a-mock.cjs
CHANGED
package/dist/a2a-mock.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"a2a-mock.cjs","names":["createJsonRpcDispatcher","createA2AMethods","buildAgentCard","readBody","flattenHeaders","http","extractText","findStreamingMatch","generateId","TERMINAL_STATES"],"sources":["../src/a2a-mock.ts"],"sourcesContent":["import * as http from \"node:http\";\nimport type { Mountable } from \"./types.js\";\nimport type { Journal } from \"./journal.js\";\nimport type { MetricsRegistry } from \"./metrics.js\";\nimport type {\n A2AAgentDefinition,\n A2AArtifact,\n A2AMockOptions,\n A2APart,\n A2AStreamEvent,\n A2ATask,\n} from \"./a2a-types.js\";\nimport type { PatternEntry } from \"./a2a-handler.js\";\nimport {\n buildAgentCard,\n createA2AMethods,\n extractText,\n findStreamingMatch,\n TERMINAL_STATES,\n} from \"./a2a-handler.js\";\nimport { createJsonRpcDispatcher } from \"./jsonrpc.js\";\nimport { generateId, flattenHeaders, readBody } from \"./helpers.js\";\n\nexport class A2AMock implements Mountable {\n private agents: Map<string, { def: A2AAgentDefinition; patterns: PatternEntry[] }> = new Map();\n private tasks: Map<string, A2ATask> = new Map();\n private server: http.Server | null = null;\n private journal: Journal | null = null;\n private registry: MetricsRegistry | null = null;\n private options: A2AMockOptions;\n private baseUrl = \"\";\n private dispatcher: ReturnType<typeof createJsonRpcDispatcher>;\n\n constructor(options?: A2AMockOptions) {\n this.options = options ?? {};\n this.dispatcher = this.buildDispatcher();\n }\n\n private buildDispatcher() {\n const methods = createA2AMethods(this.agents, this.tasks);\n return createJsonRpcDispatcher({ methods });\n }\n\n // ---- Agent registration ----\n\n registerAgent(def: A2AAgentDefinition): this {\n this.agents.set(def.name, { def, patterns: [] });\n return this;\n }\n\n // ---- Pattern registration ----\n\n onMessage(agentName: string, pattern: string | RegExp, parts: A2APart[]): this {\n const agent = this.agents.get(agentName);\n if (!agent) {\n throw new Error(`Agent \"${agentName}\" not registered`);\n }\n agent.patterns.push({ kind: \"message\", pattern, agentName, parts });\n return this;\n }\n\n onTask(agentName: string, pattern: string | RegExp, artifacts: A2AArtifact[]): this {\n const agent = this.agents.get(agentName);\n if (!agent) {\n throw new Error(`Agent \"${agentName}\" not registered`);\n }\n agent.patterns.push({ kind: \"task\", pattern, agentName, artifacts });\n return this;\n }\n\n onStreamingTask(\n agentName: string,\n pattern: string | RegExp,\n events: A2AStreamEvent[],\n delayMs?: number,\n ): this {\n const agent = this.agents.get(agentName);\n if (!agent) {\n throw new Error(`Agent \"${agentName}\" not registered`);\n }\n agent.patterns.push({ kind: \"streamingTask\", pattern, agentName, events, delayMs });\n return this;\n }\n\n // ---- Mountable interface ----\n\n async handleRequest(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n pathname: string,\n ): Promise<boolean> {\n // Agent card endpoint\n if (req.method === \"GET\" && pathname === \"/.well-known/agent-card.json\") {\n if (this.registry) {\n this.registry.incrementCounter(\"aimock_a2a_requests_total\", { method: \"GetAgentCard\" });\n }\n const card = buildAgentCard(this.agents, this.baseUrl);\n res.writeHead(200, {\n \"Content-Type\": \"application/json\",\n \"A2A-Version\": \"1.0\",\n });\n res.end(JSON.stringify(card));\n return true;\n }\n\n // JSON-RPC endpoint\n if (req.method === \"POST\" && (pathname === \"/\" || pathname === \"\")) {\n const body = await readBody(req);\n\n // Check for SendStreamingMessage before dispatching\n let parsed: unknown;\n try {\n parsed = JSON.parse(body);\n } catch {\n res.writeHead(200, {\n \"Content-Type\": \"application/json\",\n \"A2A-Version\": \"1.0\",\n });\n res.end(\n JSON.stringify({\n jsonrpc: \"2.0\",\n id: null,\n error: { code: -32700, message: \"Parse error\" },\n }),\n );\n return true;\n }\n\n // Record A2A method metric\n if (this.registry) {\n const rpcMethod =\n typeof parsed === \"object\" && parsed !== null && \"method\" in parsed\n ? String((parsed as Record<string, unknown>).method)\n : \"unknown\";\n this.registry.incrementCounter(\"aimock_a2a_requests_total\", { method: rpcMethod });\n }\n\n if (isStreamingRequest(parsed)) {\n await this.handleStreamingMessage(parsed as Record<string, unknown>, req, res);\n return true;\n }\n\n // Regular JSON-RPC dispatch\n // Add A2A-Version header before dispatching\n res.setHeader(\"A2A-Version\", \"1.0\");\n\n await this.dispatcher(req, res, body);\n\n // Journal the request after the handler completes\n if (this.journal) {\n this.journal.add({\n method: req.method ?? \"POST\",\n path: pathname,\n headers: flattenHeaders(req.headers),\n body: null,\n service: \"a2a\",\n response: { status: res.statusCode, fixture: null },\n });\n }\n\n return true;\n }\n\n return false;\n }\n\n health(): { status: string; agents: number; tasks: number } {\n return {\n status: \"ok\",\n agents: this.agents.size,\n tasks: this.tasks.size,\n };\n }\n\n setJournal(journal: Journal): void {\n this.journal = journal;\n }\n\n setRegistry(registry: MetricsRegistry): void {\n this.registry = registry;\n }\n\n // ---- Standalone mode ----\n\n async start(): Promise<string> {\n if (this.server) {\n throw new Error(\"A2AMock server already started\");\n }\n\n const host = this.options.host ?? \"127.0.0.1\";\n const port = this.options.port ?? 0;\n\n return new Promise<string>((resolve, reject) => {\n const srv = http.createServer(async (req, res) => {\n const url = new URL(req.url ?? \"/\", `http://${req.headers.host ?? \"localhost\"}`);\n await this.handleRequest(req, res, url.pathname).catch((err) => {\n console.error(\"A2AMock request error:\", err);\n if (!res.headersSent) {\n res.writeHead(500);\n res.end(\"Internal server error\");\n }\n });\n });\n\n srv.on(\"error\", reject);\n\n srv.listen(port, host, () => {\n const addr = srv.address();\n if (typeof addr === \"object\" && addr !== null) {\n this.baseUrl = `http://${host}:${addr.port}`;\n }\n this.server = srv;\n resolve(this.baseUrl);\n });\n });\n }\n\n async stop(): Promise<void> {\n if (!this.server) {\n throw new Error(\"A2AMock server not started\");\n }\n const srv = this.server;\n await new Promise<void>((resolve, reject) => {\n srv.close((err: Error | undefined) => (err ? reject(err) : resolve()));\n });\n this.server = null;\n }\n\n get url(): string {\n if (!this.server) {\n throw new Error(\"A2AMock server not started\");\n }\n return this.baseUrl;\n }\n\n // ---- Reset ----\n\n reset(): this {\n this.agents.clear();\n this.tasks.clear();\n return this;\n }\n\n // ---- Internal: set base URL when mounted ----\n\n setBaseUrl(url: string): void {\n this.baseUrl = url;\n }\n\n // ---- Private: streaming handler ----\n\n private async handleStreamingMessage(\n parsed: Record<string, unknown>,\n req: http.IncomingMessage,\n res: http.ServerResponse,\n ): Promise<void> {\n const params = parsed.params as Record<string, unknown> | undefined;\n const id = parsed.id as string | number;\n const text = extractText(params);\n const entry = findStreamingMatch(text, this.agents);\n\n if (!entry) {\n res.writeHead(200, {\n \"Content-Type\": \"application/json\",\n \"A2A-Version\": \"1.0\",\n });\n res.end(\n JSON.stringify({\n jsonrpc: \"2.0\",\n id,\n error: { code: -32000, message: \"No matching pattern for message\" },\n }),\n );\n return;\n }\n\n // Create task for the streaming response\n const taskId = generateId(\"task\");\n const contextId = generateId(\"ctx\");\n const userParts: A2APart[] = params?.message\n ? (((params.message as Record<string, unknown>).parts as A2APart[]) ?? [{ text }])\n : [{ text }];\n\n const task: A2ATask = {\n id: taskId,\n contextId,\n status: { state: \"TASK_STATE_WORKING\", timestamp: new Date().toISOString() },\n artifacts: [],\n history: [\n {\n messageId: generateId(\"msg\"),\n role: \"ROLE_USER\",\n parts: userParts,\n },\n ],\n };\n this.tasks.set(taskId, task);\n\n // Write SSE response\n res.writeHead(200, {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n \"A2A-Version\": \"1.0\",\n });\n\n const delayMs = entry.delayMs ?? 0;\n\n for (const event of entry.events) {\n if (delayMs > 0) {\n await delay(delayMs);\n }\n\n let resultPayload: Record<string, unknown>;\n\n if (event.type === \"status\") {\n task.status = { state: event.state, timestamp: new Date().toISOString() };\n resultPayload = {\n task: {\n id: task.id,\n contextId: task.contextId,\n status: task.status,\n },\n };\n } else {\n // artifact event\n const artifact = {\n parts: event.parts,\n name: event.name,\n append: event.append,\n lastChunk: event.lastChunk,\n };\n task.artifacts.push({ parts: event.parts, name: event.name });\n resultPayload = {\n task: {\n id: task.id,\n contextId: task.contextId,\n status: task.status,\n },\n artifact,\n };\n }\n\n const envelope = JSON.stringify({\n jsonrpc: \"2.0\",\n id,\n result: resultPayload,\n });\n\n res.write(`data: ${envelope}\\n\\n`);\n }\n\n // Final completion — only set COMPLETED if the task is not already in a terminal state\n if (!TERMINAL_STATES.has(task.status.state)) {\n task.status = { state: \"TASK_STATE_COMPLETED\", timestamp: new Date().toISOString() };\n }\n\n res.end();\n\n // Journal\n if (this.journal) {\n this.journal.add({\n method: \"POST\",\n path: \"/\",\n headers: flattenHeaders(req.headers),\n body: null,\n service: \"a2a\",\n response: { status: res.statusCode, fixture: null },\n });\n }\n }\n}\n\n// ---- Helpers ----\n\nfunction isStreamingRequest(parsed: unknown): boolean {\n if (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) return false;\n const obj = parsed as Record<string, unknown>;\n return obj.method === \"SendStreamingMessage\";\n}\n\nfunction delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n"],"mappings":";;;;;;;;AAuBA,IAAa,UAAb,MAA0C;CACxC,AAAQ,yBAA6E,IAAI,KAAK;CAC9F,AAAQ,wBAA8B,IAAI,KAAK;CAC/C,AAAQ,SAA6B;CACrC,AAAQ,UAA0B;CAClC,AAAQ,WAAmC;CAC3C,AAAQ;CACR,AAAQ,UAAU;CAClB,AAAQ;CAER,YAAY,SAA0B;AACpC,OAAK,UAAU,WAAW,EAAE;AAC5B,OAAK,aAAa,KAAK,iBAAiB;;CAG1C,AAAQ,kBAAkB;AAExB,SAAOA,wCAAwB,EAAE,SADjBC,qCAAiB,KAAK,QAAQ,KAAK,MAAM,EACf,CAAC;;CAK7C,cAAc,KAA+B;AAC3C,OAAK,OAAO,IAAI,IAAI,MAAM;GAAE;GAAK,UAAU,EAAE;GAAE,CAAC;AAChD,SAAO;;CAKT,UAAU,WAAmB,SAA0B,OAAwB;EAC7E,MAAM,QAAQ,KAAK,OAAO,IAAI,UAAU;AACxC,MAAI,CAAC,MACH,OAAM,IAAI,MAAM,UAAU,UAAU,kBAAkB;AAExD,QAAM,SAAS,KAAK;GAAE,MAAM;GAAW;GAAS;GAAW;GAAO,CAAC;AACnE,SAAO;;CAGT,OAAO,WAAmB,SAA0B,WAAgC;EAClF,MAAM,QAAQ,KAAK,OAAO,IAAI,UAAU;AACxC,MAAI,CAAC,MACH,OAAM,IAAI,MAAM,UAAU,UAAU,kBAAkB;AAExD,QAAM,SAAS,KAAK;GAAE,MAAM;GAAQ;GAAS;GAAW;GAAW,CAAC;AACpE,SAAO;;CAGT,gBACE,WACA,SACA,QACA,SACM;EACN,MAAM,QAAQ,KAAK,OAAO,IAAI,UAAU;AACxC,MAAI,CAAC,MACH,OAAM,IAAI,MAAM,UAAU,UAAU,kBAAkB;AAExD,QAAM,SAAS,KAAK;GAAE,MAAM;GAAiB;GAAS;GAAW;GAAQ;GAAS,CAAC;AACnF,SAAO;;CAKT,MAAM,cACJ,KACA,KACA,UACkB;AAElB,MAAI,IAAI,WAAW,SAAS,aAAa,gCAAgC;AACvE,OAAI,KAAK,SACP,MAAK,SAAS,iBAAiB,6BAA6B,EAAE,QAAQ,gBAAgB,CAAC;GAEzF,MAAM,OAAOC,mCAAe,KAAK,QAAQ,KAAK,QAAQ;AACtD,OAAI,UAAU,KAAK;IACjB,gBAAgB;IAChB,eAAe;IAChB,CAAC;AACF,OAAI,IAAI,KAAK,UAAU,KAAK,CAAC;AAC7B,UAAO;;AAIT,MAAI,IAAI,WAAW,WAAW,aAAa,OAAO,aAAa,KAAK;GAClE,MAAM,OAAO,MAAMC,yBAAS,IAAI;GAGhC,IAAI;AACJ,OAAI;AACF,aAAS,KAAK,MAAM,KAAK;WACnB;AACN,QAAI,UAAU,KAAK;KACjB,gBAAgB;KAChB,eAAe;KAChB,CAAC;AACF,QAAI,IACF,KAAK,UAAU;KACb,SAAS;KACT,IAAI;KACJ,OAAO;MAAE,MAAM;MAAQ,SAAS;MAAe;KAChD,CAAC,CACH;AACD,WAAO;;AAIT,OAAI,KAAK,UAAU;IACjB,MAAM,YACJ,OAAO,WAAW,YAAY,WAAW,QAAQ,YAAY,SACzD,OAAQ,OAAmC,OAAO,GAClD;AACN,SAAK,SAAS,iBAAiB,6BAA6B,EAAE,QAAQ,WAAW,CAAC;;AAGpF,OAAI,mBAAmB,OAAO,EAAE;AAC9B,UAAM,KAAK,uBAAuB,QAAmC,KAAK,IAAI;AAC9E,WAAO;;AAKT,OAAI,UAAU,eAAe,MAAM;AAEnC,SAAM,KAAK,WAAW,KAAK,KAAK,KAAK;AAGrC,OAAI,KAAK,QACP,MAAK,QAAQ,IAAI;IACf,QAAQ,IAAI,UAAU;IACtB,MAAM;IACN,SAASC,+BAAe,IAAI,QAAQ;IACpC,MAAM;IACN,SAAS;IACT,UAAU;KAAE,QAAQ,IAAI;KAAY,SAAS;KAAM;IACpD,CAAC;AAGJ,UAAO;;AAGT,SAAO;;CAGT,SAA4D;AAC1D,SAAO;GACL,QAAQ;GACR,QAAQ,KAAK,OAAO;GACpB,OAAO,KAAK,MAAM;GACnB;;CAGH,WAAW,SAAwB;AACjC,OAAK,UAAU;;CAGjB,YAAY,UAAiC;AAC3C,OAAK,WAAW;;CAKlB,MAAM,QAAyB;AAC7B,MAAI,KAAK,OACP,OAAM,IAAI,MAAM,iCAAiC;EAGnD,MAAM,OAAO,KAAK,QAAQ,QAAQ;EAClC,MAAM,OAAO,KAAK,QAAQ,QAAQ;AAElC,SAAO,IAAI,SAAiB,SAAS,WAAW;GAC9C,MAAM,MAAMC,UAAK,aAAa,OAAO,KAAK,QAAQ;IAChD,MAAM,MAAM,IAAI,IAAI,IAAI,OAAO,KAAK,UAAU,IAAI,QAAQ,QAAQ,cAAc;AAChF,UAAM,KAAK,cAAc,KAAK,KAAK,IAAI,SAAS,CAAC,OAAO,QAAQ;AAC9D,aAAQ,MAAM,0BAA0B,IAAI;AAC5C,SAAI,CAAC,IAAI,aAAa;AACpB,UAAI,UAAU,IAAI;AAClB,UAAI,IAAI,wBAAwB;;MAElC;KACF;AAEF,OAAI,GAAG,SAAS,OAAO;AAEvB,OAAI,OAAO,MAAM,YAAY;IAC3B,MAAM,OAAO,IAAI,SAAS;AAC1B,QAAI,OAAO,SAAS,YAAY,SAAS,KACvC,MAAK,UAAU,UAAU,KAAK,GAAG,KAAK;AAExC,SAAK,SAAS;AACd,YAAQ,KAAK,QAAQ;KACrB;IACF;;CAGJ,MAAM,OAAsB;AAC1B,MAAI,CAAC,KAAK,OACR,OAAM,IAAI,MAAM,6BAA6B;EAE/C,MAAM,MAAM,KAAK;AACjB,QAAM,IAAI,SAAe,SAAS,WAAW;AAC3C,OAAI,OAAO,QAA4B,MAAM,OAAO,IAAI,GAAG,SAAS,CAAE;IACtE;AACF,OAAK,SAAS;;CAGhB,IAAI,MAAc;AAChB,MAAI,CAAC,KAAK,OACR,OAAM,IAAI,MAAM,6BAA6B;AAE/C,SAAO,KAAK;;CAKd,QAAc;AACZ,OAAK,OAAO,OAAO;AACnB,OAAK,MAAM,OAAO;AAClB,SAAO;;CAKT,WAAW,KAAmB;AAC5B,OAAK,UAAU;;CAKjB,MAAc,uBACZ,QACA,KACA,KACe;EACf,MAAM,SAAS,OAAO;EACtB,MAAM,KAAK,OAAO;EAClB,MAAM,OAAOC,gCAAY,OAAO;EAChC,MAAM,QAAQC,uCAAmB,MAAM,KAAK,OAAO;AAEnD,MAAI,CAAC,OAAO;AACV,OAAI,UAAU,KAAK;IACjB,gBAAgB;IAChB,eAAe;IAChB,CAAC;AACF,OAAI,IACF,KAAK,UAAU;IACb,SAAS;IACT;IACA,OAAO;KAAE,MAAM;KAAQ,SAAS;KAAmC;IACpE,CAAC,CACH;AACD;;EAIF,MAAM,SAASC,2BAAW,OAAO;EACjC,MAAM,YAAYA,2BAAW,MAAM;EACnC,MAAM,YAAuB,QAAQ,UAC9B,OAAO,QAAoC,SAAuB,CAAC,EAAE,MAAM,CAAC,GAC/E,CAAC,EAAE,MAAM,CAAC;EAEd,MAAM,OAAgB;GACpB,IAAI;GACJ;GACA,QAAQ;IAAE,OAAO;IAAsB,4BAAW,IAAI,MAAM,EAAC,aAAa;IAAE;GAC5E,WAAW,EAAE;GACb,SAAS,CACP;IACE,WAAWA,2BAAW,MAAM;IAC5B,MAAM;IACN,OAAO;IACR,CACF;GACF;AACD,OAAK,MAAM,IAAI,QAAQ,KAAK;AAG5B,MAAI,UAAU,KAAK;GACjB,gBAAgB;GAChB,iBAAiB;GACjB,YAAY;GACZ,eAAe;GAChB,CAAC;EAEF,MAAM,UAAU,MAAM,WAAW;AAEjC,OAAK,MAAM,SAAS,MAAM,QAAQ;AAChC,OAAI,UAAU,EACZ,OAAM,MAAM,QAAQ;GAGtB,IAAI;AAEJ,OAAI,MAAM,SAAS,UAAU;AAC3B,SAAK,SAAS;KAAE,OAAO,MAAM;KAAO,4BAAW,IAAI,MAAM,EAAC,aAAa;KAAE;AACzE,oBAAgB,EACd,MAAM;KACJ,IAAI,KAAK;KACT,WAAW,KAAK;KAChB,QAAQ,KAAK;KACd,EACF;UACI;IAEL,MAAM,WAAW;KACf,OAAO,MAAM;KACb,MAAM,MAAM;KACZ,QAAQ,MAAM;KACd,WAAW,MAAM;KAClB;AACD,SAAK,UAAU,KAAK;KAAE,OAAO,MAAM;KAAO,MAAM,MAAM;KAAM,CAAC;AAC7D,oBAAgB;KACd,MAAM;MACJ,IAAI,KAAK;MACT,WAAW,KAAK;MAChB,QAAQ,KAAK;MACd;KACD;KACD;;GAGH,MAAM,WAAW,KAAK,UAAU;IAC9B,SAAS;IACT;IACA,QAAQ;IACT,CAAC;AAEF,OAAI,MAAM,SAAS,SAAS,MAAM;;AAIpC,MAAI,CAACC,oCAAgB,IAAI,KAAK,OAAO,MAAM,CACzC,MAAK,SAAS;GAAE,OAAO;GAAwB,4BAAW,IAAI,MAAM,EAAC,aAAa;GAAE;AAGtF,MAAI,KAAK;AAGT,MAAI,KAAK,QACP,MAAK,QAAQ,IAAI;GACf,QAAQ;GACR,MAAM;GACN,SAASL,+BAAe,IAAI,QAAQ;GACpC,MAAM;GACN,SAAS;GACT,UAAU;IAAE,QAAQ,IAAI;IAAY,SAAS;IAAM;GACpD,CAAC;;;AAOR,SAAS,mBAAmB,QAA0B;AACpD,KAAI,OAAO,WAAW,YAAY,WAAW,QAAQ,MAAM,QAAQ,OAAO,CAAE,QAAO;AAEnF,QADY,OACD,WAAW;;AAGxB,SAAS,MAAM,IAA2B;AACxC,QAAO,IAAI,SAAS,YAAY,WAAW,SAAS,GAAG,CAAC"}
|
|
1
|
+
{"version":3,"file":"a2a-mock.cjs","names":["createJsonRpcDispatcher","createA2AMethods","buildAgentCard","readBody","flattenHeaders","http","extractText","findStreamingMatch","generateId","TERMINAL_STATES"],"sources":["../src/a2a-mock.ts"],"sourcesContent":["import * as http from \"node:http\";\nimport type { Mountable } from \"./types.js\";\nimport type { Journal } from \"./journal.js\";\nimport type { MetricsRegistry } from \"./metrics.js\";\nimport type {\n A2AAgentDefinition,\n A2AArtifact,\n A2AMockOptions,\n A2APart,\n A2AStreamEvent,\n A2ATask,\n} from \"./a2a-types.js\";\nimport type { PatternEntry } from \"./a2a-handler.js\";\nimport {\n buildAgentCard,\n createA2AMethods,\n extractText,\n findStreamingMatch,\n TERMINAL_STATES,\n} from \"./a2a-handler.js\";\nimport { createJsonRpcDispatcher } from \"./jsonrpc.js\";\nimport { generateId, flattenHeaders, readBody } from \"./helpers.js\";\n\nexport class A2AMock implements Mountable {\n private agents: Map<string, { def: A2AAgentDefinition; patterns: PatternEntry[] }> = new Map();\n private tasks: Map<string, A2ATask> = new Map();\n private server: http.Server | null = null;\n private journal: Journal | null = null;\n private registry: MetricsRegistry | null = null;\n private options: A2AMockOptions;\n private baseUrl = \"\";\n private dispatcher: ReturnType<typeof createJsonRpcDispatcher>;\n\n constructor(options?: A2AMockOptions) {\n this.options = options ?? {};\n this.dispatcher = this.buildDispatcher();\n }\n\n private buildDispatcher() {\n const methods = createA2AMethods(this.agents, this.tasks);\n return createJsonRpcDispatcher({ methods });\n }\n\n // ---- Agent registration ----\n\n registerAgent(def: A2AAgentDefinition): this {\n this.agents.set(def.name, { def, patterns: [] });\n return this;\n }\n\n // ---- Pattern registration ----\n\n onMessage(agentName: string, pattern: string | RegExp, parts: A2APart[]): this {\n const agent = this.agents.get(agentName);\n if (!agent) {\n throw new Error(`Agent \"${agentName}\" not registered`);\n }\n agent.patterns.push({ kind: \"message\", pattern, agentName, parts });\n return this;\n }\n\n onTask(agentName: string, pattern: string | RegExp, artifacts: A2AArtifact[]): this {\n const agent = this.agents.get(agentName);\n if (!agent) {\n throw new Error(`Agent \"${agentName}\" not registered`);\n }\n agent.patterns.push({ kind: \"task\", pattern, agentName, artifacts });\n return this;\n }\n\n onStreamingTask(\n agentName: string,\n pattern: string | RegExp,\n events: A2AStreamEvent[],\n delayMs?: number,\n ): this {\n const agent = this.agents.get(agentName);\n if (!agent) {\n throw new Error(`Agent \"${agentName}\" not registered`);\n }\n agent.patterns.push({ kind: \"streamingTask\", pattern, agentName, events, delayMs });\n return this;\n }\n\n // ---- Mountable interface ----\n\n async handleRequest(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n pathname: string,\n ): Promise<boolean> {\n // Agent card endpoint\n if (req.method === \"GET\" && pathname === \"/.well-known/agent-card.json\") {\n if (this.registry) {\n this.registry.incrementCounter(\"aimock_a2a_requests_total\", { method: \"GetAgentCard\" });\n }\n const card = buildAgentCard(this.agents, this.baseUrl);\n res.writeHead(200, {\n \"Content-Type\": \"application/json\",\n \"A2A-Version\": \"1.0\",\n });\n res.end(JSON.stringify(card));\n return true;\n }\n\n // JSON-RPC endpoint\n if (req.method === \"POST\" && (pathname === \"/\" || pathname === \"\")) {\n const body = await readBody(req);\n\n // Check for SendStreamingMessage before dispatching\n let parsed: unknown;\n try {\n parsed = JSON.parse(body);\n } catch {\n res.writeHead(200, {\n \"Content-Type\": \"application/json\",\n \"A2A-Version\": \"1.0\",\n });\n res.end(\n JSON.stringify({\n jsonrpc: \"2.0\",\n id: null,\n error: { code: -32700, message: \"Parse error\" },\n }),\n );\n return true;\n }\n\n // Record A2A method metric\n if (this.registry) {\n const rpcMethod =\n typeof parsed === \"object\" && parsed !== null && \"method\" in parsed\n ? String((parsed as Record<string, unknown>).method)\n : \"unknown\";\n this.registry.incrementCounter(\"aimock_a2a_requests_total\", { method: rpcMethod });\n }\n\n if (isStreamingRequest(parsed)) {\n await this.handleStreamingMessage(parsed as Record<string, unknown>, req, res);\n return true;\n }\n\n // Regular JSON-RPC dispatch\n // Add A2A-Version header before dispatching\n res.setHeader(\"A2A-Version\", \"1.0\");\n\n await this.dispatcher(req, res, body);\n\n // Journal the request after the handler completes\n if (this.journal) {\n this.journal.add({\n method: req.method ?? \"POST\",\n path: pathname,\n headers: flattenHeaders(req.headers),\n body: null,\n service: \"a2a\",\n response: { status: res.statusCode, fixture: null },\n });\n }\n\n return true;\n }\n\n return false;\n }\n\n health(): { status: string; agents: number; tasks: number } {\n return {\n status: \"ok\",\n agents: this.agents.size,\n tasks: this.tasks.size,\n };\n }\n\n setJournal(journal: Journal): void {\n this.journal = journal;\n }\n\n setRegistry(registry: MetricsRegistry): void {\n this.registry = registry;\n }\n\n // ---- Standalone mode ----\n\n async start(): Promise<string> {\n if (this.server) {\n throw new Error(\"A2AMock server already started\");\n }\n\n const host = this.options.host ?? \"127.0.0.1\";\n const port = this.options.port ?? 0;\n\n return new Promise<string>((resolve, reject) => {\n const srv = http.createServer(async (req, res) => {\n const url = new URL(req.url ?? \"/\", `http://${req.headers.host ?? \"localhost\"}`);\n await this.handleRequest(req, res, url.pathname).catch((err) => {\n console.error(\"A2AMock request error:\", err);\n if (!res.headersSent) {\n res.writeHead(500);\n res.end(\"Internal server error\");\n } else if (!res.writableEnded) {\n res.end();\n }\n });\n });\n\n srv.on(\"error\", reject);\n\n srv.listen(port, host, () => {\n const addr = srv.address();\n if (typeof addr === \"object\" && addr !== null) {\n this.baseUrl = `http://${host}:${addr.port}`;\n }\n this.server = srv;\n resolve(this.baseUrl);\n });\n });\n }\n\n async stop(): Promise<void> {\n if (!this.server) {\n throw new Error(\"A2AMock server not started\");\n }\n const srv = this.server;\n await new Promise<void>((resolve, reject) => {\n srv.close((err: Error | undefined) => (err ? reject(err) : resolve()));\n });\n this.server = null;\n }\n\n get url(): string {\n if (!this.server) {\n throw new Error(\"A2AMock server not started\");\n }\n return this.baseUrl;\n }\n\n // ---- Reset ----\n\n reset(): this {\n this.agents.clear();\n this.tasks.clear();\n return this;\n }\n\n // ---- Internal: set base URL when mounted ----\n\n setBaseUrl(url: string): void {\n this.baseUrl = url;\n }\n\n // ---- Private: streaming handler ----\n\n private async handleStreamingMessage(\n parsed: Record<string, unknown>,\n req: http.IncomingMessage,\n res: http.ServerResponse,\n ): Promise<void> {\n const params = parsed.params as Record<string, unknown> | undefined;\n const id = parsed.id as string | number;\n const text = extractText(params);\n const entry = findStreamingMatch(text, this.agents);\n\n if (!entry) {\n res.writeHead(200, {\n \"Content-Type\": \"application/json\",\n \"A2A-Version\": \"1.0\",\n });\n res.end(\n JSON.stringify({\n jsonrpc: \"2.0\",\n id,\n error: { code: -32000, message: \"No matching pattern for message\" },\n }),\n );\n return;\n }\n\n // Create task for the streaming response\n const taskId = generateId(\"task\");\n const contextId = generateId(\"ctx\");\n const userParts: A2APart[] = params?.message\n ? (((params.message as Record<string, unknown>).parts as A2APart[]) ?? [{ text }])\n : [{ text }];\n\n const task: A2ATask = {\n id: taskId,\n contextId,\n status: { state: \"TASK_STATE_WORKING\", timestamp: new Date().toISOString() },\n artifacts: [],\n history: [\n {\n messageId: generateId(\"msg\"),\n role: \"ROLE_USER\",\n parts: userParts,\n },\n ],\n };\n this.tasks.set(taskId, task);\n\n // Write SSE response\n res.writeHead(200, {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n \"A2A-Version\": \"1.0\",\n });\n\n const delayMs = entry.delayMs ?? 0;\n\n for (const event of entry.events) {\n if (delayMs > 0) {\n await delay(delayMs);\n }\n\n let resultPayload: Record<string, unknown>;\n\n if (event.type === \"status\") {\n task.status = { state: event.state, timestamp: new Date().toISOString() };\n resultPayload = {\n task: {\n id: task.id,\n contextId: task.contextId,\n status: task.status,\n },\n };\n } else {\n // artifact event\n const artifact = {\n parts: event.parts,\n name: event.name,\n append: event.append,\n lastChunk: event.lastChunk,\n };\n task.artifacts.push({ parts: event.parts, name: event.name });\n resultPayload = {\n task: {\n id: task.id,\n contextId: task.contextId,\n status: task.status,\n },\n artifact,\n };\n }\n\n const envelope = JSON.stringify({\n jsonrpc: \"2.0\",\n id,\n result: resultPayload,\n });\n\n res.write(`data: ${envelope}\\n\\n`);\n }\n\n // Final completion — only set COMPLETED if the task is not already in a terminal state\n if (!TERMINAL_STATES.has(task.status.state)) {\n task.status = { state: \"TASK_STATE_COMPLETED\", timestamp: new Date().toISOString() };\n }\n\n res.end();\n\n // Journal\n if (this.journal) {\n this.journal.add({\n method: \"POST\",\n path: \"/\",\n headers: flattenHeaders(req.headers),\n body: null,\n service: \"a2a\",\n response: { status: res.statusCode, fixture: null },\n });\n }\n }\n}\n\n// ---- Helpers ----\n\nfunction isStreamingRequest(parsed: unknown): boolean {\n if (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) return false;\n const obj = parsed as Record<string, unknown>;\n return obj.method === \"SendStreamingMessage\";\n}\n\nfunction delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n"],"mappings":"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"}
|
package/dist/a2a-mock.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"a2a-mock.d.cts","names":[],"sources":["../src/a2a-mock.ts"],"sourcesContent":[],"mappings":";;;;;;;cAuBa,OAAA,YAAmB;;EAAnB,QAAA,KAAQ;EAAA,QAAA,MAAA;UAUG,OAAA;UAYH,QAAA;UAO4B,OAAA;UAAe,OAAA;UASlB,UAAA;aAAmB,CAAA,OAAA,CAAA,EA5BzC,cA4ByC;UAW3C,eAAA;eACV,CAAA,GAAA,EA5BS,kBA4BT,CAAA,EAAA,IAAA;WAcH,CAAK,SAAA,EAAA,MAAA,EAAA,OAAA,EAAA,MAAA,GAnCmC,MAmCnC,EAAA,KAAA,EAnCkD,OAmClD,EAAA,CAAA,EAAA,IAAA;QACL,CAAA,SAAK,EAAA,MAAA,EAAA,OAAA,EAAA,MAAA,GA3BgC,MA2BhC,EAAA,SAAA,EA3BmD,WA2BnD,EAAA,CAAA,EAAA,IAAA;iBAET,CAAA,SAAA,EAAA,MAAA,EAAA,OAAA,EAAA,MAAA,GAlBiB,MAkBjB,EAAA,MAAA,EAjBO,cAiBP,EAAA,EAAA,OAAA,CAAA,EAAA,MAAA,CAAA,EAAA,IAAA;eAoFiB,CAAA,GAAA,EAvFb,MAAA,CAAK,eAuFQ,EAAA,GAAA,EAtFb,MAAA,CAAK,cAsFQ,EAAA,QAAA,EAAA,MAAA,CAAA,EApFjB,OAoFiB,CAAA,OAAA,CAAA;QAIE,CAAA,CAAA,EAAA;IAMP,MAAA,EAAA,MAAA;
|
|
1
|
+
{"version":3,"file":"a2a-mock.d.cts","names":[],"sources":["../src/a2a-mock.ts"],"sourcesContent":[],"mappings":";;;;;;;cAuBa,OAAA,YAAmB;;EAAnB,QAAA,KAAQ;EAAA,QAAA,MAAA;UAUG,OAAA;UAYH,QAAA;UAO4B,OAAA;UAAe,OAAA;UASlB,UAAA;aAAmB,CAAA,OAAA,CAAA,EA5BzC,cA4ByC;UAW3C,eAAA;eACV,CAAA,GAAA,EA5BS,kBA4BT,CAAA,EAAA,IAAA;WAcH,CAAK,SAAA,EAAA,MAAA,EAAA,OAAA,EAAA,MAAA,GAnCmC,MAmCnC,EAAA,KAAA,EAnCkD,OAmClD,EAAA,CAAA,EAAA,IAAA;QACL,CAAA,SAAK,EAAA,MAAA,EAAA,OAAA,EAAA,MAAA,GA3BgC,MA2BhC,EAAA,SAAA,EA3BmD,WA2BnD,EAAA,CAAA,EAAA,IAAA;iBAET,CAAA,SAAA,EAAA,MAAA,EAAA,OAAA,EAAA,MAAA,GAlBiB,MAkBjB,EAAA,MAAA,EAjBO,cAiBP,EAAA,EAAA,OAAA,CAAA,EAAA,MAAA,CAAA,EAAA,IAAA;eAoFiB,CAAA,GAAA,EAvFb,MAAA,CAAK,eAuFQ,EAAA,GAAA,EAtFb,MAAA,CAAK,cAsFQ,EAAA,QAAA,EAAA,MAAA,CAAA,EApFjB,OAoFiB,CAAA,OAAA,CAAA;QAIE,CAAA,CAAA,EAAA;IAMP,MAAA,EAAA,MAAA;IAmCD,MAAA,EAAA,MAAA;IApMgB,KAAA,EAAA,MAAA;EAAS,CAAA;sBAuJnB;wBAIE;WAMP;UAmCD"}
|
package/dist/a2a-mock.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"a2a-mock.d.ts","names":[],"sources":["../src/a2a-mock.ts"],"sourcesContent":[],"mappings":";;;;;;;cAuBa,OAAA,YAAmB;;EAAnB,QAAA,KAAQ;EAAA,QAAA,MAAA;UAUG,OAAA;UAYH,QAAA;UAO4B,OAAA;UAAe,OAAA;UASlB,UAAA;aAAmB,CAAA,OAAA,CAAA,EA5BzC,cA4ByC;UAW3C,eAAA;eACV,CAAA,GAAA,EA5BS,kBA4BT,CAAA,EAAA,IAAA;WAcH,CAAK,SAAA,EAAA,MAAA,EAAA,OAAA,EAAA,MAAA,GAnCmC,MAmCnC,EAAA,KAAA,EAnCkD,OAmClD,EAAA,CAAA,EAAA,IAAA;QACL,CAAA,SAAK,EAAA,MAAA,EAAA,OAAA,EAAA,MAAA,GA3BgC,MA2BhC,EAAA,SAAA,EA3BmD,WA2BnD,EAAA,CAAA,EAAA,IAAA;iBAET,CAAA,SAAA,EAAA,MAAA,EAAA,OAAA,EAAA,MAAA,GAlBiB,MAkBjB,EAAA,MAAA,EAjBO,cAiBP,EAAA,EAAA,OAAA,CAAA,EAAA,MAAA,CAAA,EAAA,IAAA;eAoFiB,CAAA,GAAA,EAvFb,MAAA,CAAK,eAuFQ,EAAA,GAAA,EAtFb,MAAA,CAAK,cAsFQ,EAAA,QAAA,EAAA,MAAA,CAAA,EApFjB,OAoFiB,CAAA,OAAA,CAAA;QAIE,CAAA,CAAA,EAAA;IAMP,MAAA,EAAA,MAAA;
|
|
1
|
+
{"version":3,"file":"a2a-mock.d.ts","names":[],"sources":["../src/a2a-mock.ts"],"sourcesContent":[],"mappings":";;;;;;;cAuBa,OAAA,YAAmB;;EAAnB,QAAA,KAAQ;EAAA,QAAA,MAAA;UAUG,OAAA;UAYH,QAAA;UAO4B,OAAA;UAAe,OAAA;UASlB,UAAA;aAAmB,CAAA,OAAA,CAAA,EA5BzC,cA4ByC;UAW3C,eAAA;eACV,CAAA,GAAA,EA5BS,kBA4BT,CAAA,EAAA,IAAA;WAcH,CAAK,SAAA,EAAA,MAAA,EAAA,OAAA,EAAA,MAAA,GAnCmC,MAmCnC,EAAA,KAAA,EAnCkD,OAmClD,EAAA,CAAA,EAAA,IAAA;QACL,CAAA,SAAK,EAAA,MAAA,EAAA,OAAA,EAAA,MAAA,GA3BgC,MA2BhC,EAAA,SAAA,EA3BmD,WA2BnD,EAAA,CAAA,EAAA,IAAA;iBAET,CAAA,SAAA,EAAA,MAAA,EAAA,OAAA,EAAA,MAAA,GAlBiB,MAkBjB,EAAA,MAAA,EAjBO,cAiBP,EAAA,EAAA,OAAA,CAAA,EAAA,MAAA,CAAA,EAAA,IAAA;eAoFiB,CAAA,GAAA,EAvFb,MAAA,CAAK,eAuFQ,EAAA,GAAA,EAtFb,MAAA,CAAK,cAsFQ,EAAA,QAAA,EAAA,MAAA,CAAA,EApFjB,OAoFiB,CAAA,OAAA,CAAA;QAIE,CAAA,CAAA,EAAA;IAMP,MAAA,EAAA,MAAA;IAmCD,MAAA,EAAA,MAAA;IApMgB,KAAA,EAAA,MAAA;EAAS,CAAA;sBAuJnB;wBAIE;WAMP;UAmCD"}
|
package/dist/a2a-mock.js
CHANGED
package/dist/a2a-mock.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"a2a-mock.js","names":["http"],"sources":["../src/a2a-mock.ts"],"sourcesContent":["import * as http from \"node:http\";\nimport type { Mountable } from \"./types.js\";\nimport type { Journal } from \"./journal.js\";\nimport type { MetricsRegistry } from \"./metrics.js\";\nimport type {\n A2AAgentDefinition,\n A2AArtifact,\n A2AMockOptions,\n A2APart,\n A2AStreamEvent,\n A2ATask,\n} from \"./a2a-types.js\";\nimport type { PatternEntry } from \"./a2a-handler.js\";\nimport {\n buildAgentCard,\n createA2AMethods,\n extractText,\n findStreamingMatch,\n TERMINAL_STATES,\n} from \"./a2a-handler.js\";\nimport { createJsonRpcDispatcher } from \"./jsonrpc.js\";\nimport { generateId, flattenHeaders, readBody } from \"./helpers.js\";\n\nexport class A2AMock implements Mountable {\n private agents: Map<string, { def: A2AAgentDefinition; patterns: PatternEntry[] }> = new Map();\n private tasks: Map<string, A2ATask> = new Map();\n private server: http.Server | null = null;\n private journal: Journal | null = null;\n private registry: MetricsRegistry | null = null;\n private options: A2AMockOptions;\n private baseUrl = \"\";\n private dispatcher: ReturnType<typeof createJsonRpcDispatcher>;\n\n constructor(options?: A2AMockOptions) {\n this.options = options ?? {};\n this.dispatcher = this.buildDispatcher();\n }\n\n private buildDispatcher() {\n const methods = createA2AMethods(this.agents, this.tasks);\n return createJsonRpcDispatcher({ methods });\n }\n\n // ---- Agent registration ----\n\n registerAgent(def: A2AAgentDefinition): this {\n this.agents.set(def.name, { def, patterns: [] });\n return this;\n }\n\n // ---- Pattern registration ----\n\n onMessage(agentName: string, pattern: string | RegExp, parts: A2APart[]): this {\n const agent = this.agents.get(agentName);\n if (!agent) {\n throw new Error(`Agent \"${agentName}\" not registered`);\n }\n agent.patterns.push({ kind: \"message\", pattern, agentName, parts });\n return this;\n }\n\n onTask(agentName: string, pattern: string | RegExp, artifacts: A2AArtifact[]): this {\n const agent = this.agents.get(agentName);\n if (!agent) {\n throw new Error(`Agent \"${agentName}\" not registered`);\n }\n agent.patterns.push({ kind: \"task\", pattern, agentName, artifacts });\n return this;\n }\n\n onStreamingTask(\n agentName: string,\n pattern: string | RegExp,\n events: A2AStreamEvent[],\n delayMs?: number,\n ): this {\n const agent = this.agents.get(agentName);\n if (!agent) {\n throw new Error(`Agent \"${agentName}\" not registered`);\n }\n agent.patterns.push({ kind: \"streamingTask\", pattern, agentName, events, delayMs });\n return this;\n }\n\n // ---- Mountable interface ----\n\n async handleRequest(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n pathname: string,\n ): Promise<boolean> {\n // Agent card endpoint\n if (req.method === \"GET\" && pathname === \"/.well-known/agent-card.json\") {\n if (this.registry) {\n this.registry.incrementCounter(\"aimock_a2a_requests_total\", { method: \"GetAgentCard\" });\n }\n const card = buildAgentCard(this.agents, this.baseUrl);\n res.writeHead(200, {\n \"Content-Type\": \"application/json\",\n \"A2A-Version\": \"1.0\",\n });\n res.end(JSON.stringify(card));\n return true;\n }\n\n // JSON-RPC endpoint\n if (req.method === \"POST\" && (pathname === \"/\" || pathname === \"\")) {\n const body = await readBody(req);\n\n // Check for SendStreamingMessage before dispatching\n let parsed: unknown;\n try {\n parsed = JSON.parse(body);\n } catch {\n res.writeHead(200, {\n \"Content-Type\": \"application/json\",\n \"A2A-Version\": \"1.0\",\n });\n res.end(\n JSON.stringify({\n jsonrpc: \"2.0\",\n id: null,\n error: { code: -32700, message: \"Parse error\" },\n }),\n );\n return true;\n }\n\n // Record A2A method metric\n if (this.registry) {\n const rpcMethod =\n typeof parsed === \"object\" && parsed !== null && \"method\" in parsed\n ? String((parsed as Record<string, unknown>).method)\n : \"unknown\";\n this.registry.incrementCounter(\"aimock_a2a_requests_total\", { method: rpcMethod });\n }\n\n if (isStreamingRequest(parsed)) {\n await this.handleStreamingMessage(parsed as Record<string, unknown>, req, res);\n return true;\n }\n\n // Regular JSON-RPC dispatch\n // Add A2A-Version header before dispatching\n res.setHeader(\"A2A-Version\", \"1.0\");\n\n await this.dispatcher(req, res, body);\n\n // Journal the request after the handler completes\n if (this.journal) {\n this.journal.add({\n method: req.method ?? \"POST\",\n path: pathname,\n headers: flattenHeaders(req.headers),\n body: null,\n service: \"a2a\",\n response: { status: res.statusCode, fixture: null },\n });\n }\n\n return true;\n }\n\n return false;\n }\n\n health(): { status: string; agents: number; tasks: number } {\n return {\n status: \"ok\",\n agents: this.agents.size,\n tasks: this.tasks.size,\n };\n }\n\n setJournal(journal: Journal): void {\n this.journal = journal;\n }\n\n setRegistry(registry: MetricsRegistry): void {\n this.registry = registry;\n }\n\n // ---- Standalone mode ----\n\n async start(): Promise<string> {\n if (this.server) {\n throw new Error(\"A2AMock server already started\");\n }\n\n const host = this.options.host ?? \"127.0.0.1\";\n const port = this.options.port ?? 0;\n\n return new Promise<string>((resolve, reject) => {\n const srv = http.createServer(async (req, res) => {\n const url = new URL(req.url ?? \"/\", `http://${req.headers.host ?? \"localhost\"}`);\n await this.handleRequest(req, res, url.pathname).catch((err) => {\n console.error(\"A2AMock request error:\", err);\n if (!res.headersSent) {\n res.writeHead(500);\n res.end(\"Internal server error\");\n }\n });\n });\n\n srv.on(\"error\", reject);\n\n srv.listen(port, host, () => {\n const addr = srv.address();\n if (typeof addr === \"object\" && addr !== null) {\n this.baseUrl = `http://${host}:${addr.port}`;\n }\n this.server = srv;\n resolve(this.baseUrl);\n });\n });\n }\n\n async stop(): Promise<void> {\n if (!this.server) {\n throw new Error(\"A2AMock server not started\");\n }\n const srv = this.server;\n await new Promise<void>((resolve, reject) => {\n srv.close((err: Error | undefined) => (err ? reject(err) : resolve()));\n });\n this.server = null;\n }\n\n get url(): string {\n if (!this.server) {\n throw new Error(\"A2AMock server not started\");\n }\n return this.baseUrl;\n }\n\n // ---- Reset ----\n\n reset(): this {\n this.agents.clear();\n this.tasks.clear();\n return this;\n }\n\n // ---- Internal: set base URL when mounted ----\n\n setBaseUrl(url: string): void {\n this.baseUrl = url;\n }\n\n // ---- Private: streaming handler ----\n\n private async handleStreamingMessage(\n parsed: Record<string, unknown>,\n req: http.IncomingMessage,\n res: http.ServerResponse,\n ): Promise<void> {\n const params = parsed.params as Record<string, unknown> | undefined;\n const id = parsed.id as string | number;\n const text = extractText(params);\n const entry = findStreamingMatch(text, this.agents);\n\n if (!entry) {\n res.writeHead(200, {\n \"Content-Type\": \"application/json\",\n \"A2A-Version\": \"1.0\",\n });\n res.end(\n JSON.stringify({\n jsonrpc: \"2.0\",\n id,\n error: { code: -32000, message: \"No matching pattern for message\" },\n }),\n );\n return;\n }\n\n // Create task for the streaming response\n const taskId = generateId(\"task\");\n const contextId = generateId(\"ctx\");\n const userParts: A2APart[] = params?.message\n ? (((params.message as Record<string, unknown>).parts as A2APart[]) ?? [{ text }])\n : [{ text }];\n\n const task: A2ATask = {\n id: taskId,\n contextId,\n status: { state: \"TASK_STATE_WORKING\", timestamp: new Date().toISOString() },\n artifacts: [],\n history: [\n {\n messageId: generateId(\"msg\"),\n role: \"ROLE_USER\",\n parts: userParts,\n },\n ],\n };\n this.tasks.set(taskId, task);\n\n // Write SSE response\n res.writeHead(200, {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n \"A2A-Version\": \"1.0\",\n });\n\n const delayMs = entry.delayMs ?? 0;\n\n for (const event of entry.events) {\n if (delayMs > 0) {\n await delay(delayMs);\n }\n\n let resultPayload: Record<string, unknown>;\n\n if (event.type === \"status\") {\n task.status = { state: event.state, timestamp: new Date().toISOString() };\n resultPayload = {\n task: {\n id: task.id,\n contextId: task.contextId,\n status: task.status,\n },\n };\n } else {\n // artifact event\n const artifact = {\n parts: event.parts,\n name: event.name,\n append: event.append,\n lastChunk: event.lastChunk,\n };\n task.artifacts.push({ parts: event.parts, name: event.name });\n resultPayload = {\n task: {\n id: task.id,\n contextId: task.contextId,\n status: task.status,\n },\n artifact,\n };\n }\n\n const envelope = JSON.stringify({\n jsonrpc: \"2.0\",\n id,\n result: resultPayload,\n });\n\n res.write(`data: ${envelope}\\n\\n`);\n }\n\n // Final completion — only set COMPLETED if the task is not already in a terminal state\n if (!TERMINAL_STATES.has(task.status.state)) {\n task.status = { state: \"TASK_STATE_COMPLETED\", timestamp: new Date().toISOString() };\n }\n\n res.end();\n\n // Journal\n if (this.journal) {\n this.journal.add({\n method: \"POST\",\n path: \"/\",\n headers: flattenHeaders(req.headers),\n body: null,\n service: \"a2a\",\n response: { status: res.statusCode, fixture: null },\n });\n }\n }\n}\n\n// ---- Helpers ----\n\nfunction isStreamingRequest(parsed: unknown): boolean {\n if (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) return false;\n const obj = parsed as Record<string, unknown>;\n return obj.method === \"SendStreamingMessage\";\n}\n\nfunction delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"a2a-mock.js","names":["http"],"sources":["../src/a2a-mock.ts"],"sourcesContent":["import * as http from \"node:http\";\nimport type { Mountable } from \"./types.js\";\nimport type { Journal } from \"./journal.js\";\nimport type { MetricsRegistry } from \"./metrics.js\";\nimport type {\n A2AAgentDefinition,\n A2AArtifact,\n A2AMockOptions,\n A2APart,\n A2AStreamEvent,\n A2ATask,\n} from \"./a2a-types.js\";\nimport type { PatternEntry } from \"./a2a-handler.js\";\nimport {\n buildAgentCard,\n createA2AMethods,\n extractText,\n findStreamingMatch,\n TERMINAL_STATES,\n} from \"./a2a-handler.js\";\nimport { createJsonRpcDispatcher } from \"./jsonrpc.js\";\nimport { generateId, flattenHeaders, readBody } from \"./helpers.js\";\n\nexport class A2AMock implements Mountable {\n private agents: Map<string, { def: A2AAgentDefinition; patterns: PatternEntry[] }> = new Map();\n private tasks: Map<string, A2ATask> = new Map();\n private server: http.Server | null = null;\n private journal: Journal | null = null;\n private registry: MetricsRegistry | null = null;\n private options: A2AMockOptions;\n private baseUrl = \"\";\n private dispatcher: ReturnType<typeof createJsonRpcDispatcher>;\n\n constructor(options?: A2AMockOptions) {\n this.options = options ?? {};\n this.dispatcher = this.buildDispatcher();\n }\n\n private buildDispatcher() {\n const methods = createA2AMethods(this.agents, this.tasks);\n return createJsonRpcDispatcher({ methods });\n }\n\n // ---- Agent registration ----\n\n registerAgent(def: A2AAgentDefinition): this {\n this.agents.set(def.name, { def, patterns: [] });\n return this;\n }\n\n // ---- Pattern registration ----\n\n onMessage(agentName: string, pattern: string | RegExp, parts: A2APart[]): this {\n const agent = this.agents.get(agentName);\n if (!agent) {\n throw new Error(`Agent \"${agentName}\" not registered`);\n }\n agent.patterns.push({ kind: \"message\", pattern, agentName, parts });\n return this;\n }\n\n onTask(agentName: string, pattern: string | RegExp, artifacts: A2AArtifact[]): this {\n const agent = this.agents.get(agentName);\n if (!agent) {\n throw new Error(`Agent \"${agentName}\" not registered`);\n }\n agent.patterns.push({ kind: \"task\", pattern, agentName, artifacts });\n return this;\n }\n\n onStreamingTask(\n agentName: string,\n pattern: string | RegExp,\n events: A2AStreamEvent[],\n delayMs?: number,\n ): this {\n const agent = this.agents.get(agentName);\n if (!agent) {\n throw new Error(`Agent \"${agentName}\" not registered`);\n }\n agent.patterns.push({ kind: \"streamingTask\", pattern, agentName, events, delayMs });\n return this;\n }\n\n // ---- Mountable interface ----\n\n async handleRequest(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n pathname: string,\n ): Promise<boolean> {\n // Agent card endpoint\n if (req.method === \"GET\" && pathname === \"/.well-known/agent-card.json\") {\n if (this.registry) {\n this.registry.incrementCounter(\"aimock_a2a_requests_total\", { method: \"GetAgentCard\" });\n }\n const card = buildAgentCard(this.agents, this.baseUrl);\n res.writeHead(200, {\n \"Content-Type\": \"application/json\",\n \"A2A-Version\": \"1.0\",\n });\n res.end(JSON.stringify(card));\n return true;\n }\n\n // JSON-RPC endpoint\n if (req.method === \"POST\" && (pathname === \"/\" || pathname === \"\")) {\n const body = await readBody(req);\n\n // Check for SendStreamingMessage before dispatching\n let parsed: unknown;\n try {\n parsed = JSON.parse(body);\n } catch {\n res.writeHead(200, {\n \"Content-Type\": \"application/json\",\n \"A2A-Version\": \"1.0\",\n });\n res.end(\n JSON.stringify({\n jsonrpc: \"2.0\",\n id: null,\n error: { code: -32700, message: \"Parse error\" },\n }),\n );\n return true;\n }\n\n // Record A2A method metric\n if (this.registry) {\n const rpcMethod =\n typeof parsed === \"object\" && parsed !== null && \"method\" in parsed\n ? String((parsed as Record<string, unknown>).method)\n : \"unknown\";\n this.registry.incrementCounter(\"aimock_a2a_requests_total\", { method: rpcMethod });\n }\n\n if (isStreamingRequest(parsed)) {\n await this.handleStreamingMessage(parsed as Record<string, unknown>, req, res);\n return true;\n }\n\n // Regular JSON-RPC dispatch\n // Add A2A-Version header before dispatching\n res.setHeader(\"A2A-Version\", \"1.0\");\n\n await this.dispatcher(req, res, body);\n\n // Journal the request after the handler completes\n if (this.journal) {\n this.journal.add({\n method: req.method ?? \"POST\",\n path: pathname,\n headers: flattenHeaders(req.headers),\n body: null,\n service: \"a2a\",\n response: { status: res.statusCode, fixture: null },\n });\n }\n\n return true;\n }\n\n return false;\n }\n\n health(): { status: string; agents: number; tasks: number } {\n return {\n status: \"ok\",\n agents: this.agents.size,\n tasks: this.tasks.size,\n };\n }\n\n setJournal(journal: Journal): void {\n this.journal = journal;\n }\n\n setRegistry(registry: MetricsRegistry): void {\n this.registry = registry;\n }\n\n // ---- Standalone mode ----\n\n async start(): Promise<string> {\n if (this.server) {\n throw new Error(\"A2AMock server already started\");\n }\n\n const host = this.options.host ?? \"127.0.0.1\";\n const port = this.options.port ?? 0;\n\n return new Promise<string>((resolve, reject) => {\n const srv = http.createServer(async (req, res) => {\n const url = new URL(req.url ?? \"/\", `http://${req.headers.host ?? \"localhost\"}`);\n await this.handleRequest(req, res, url.pathname).catch((err) => {\n console.error(\"A2AMock request error:\", err);\n if (!res.headersSent) {\n res.writeHead(500);\n res.end(\"Internal server error\");\n } else if (!res.writableEnded) {\n res.end();\n }\n });\n });\n\n srv.on(\"error\", reject);\n\n srv.listen(port, host, () => {\n const addr = srv.address();\n if (typeof addr === \"object\" && addr !== null) {\n this.baseUrl = `http://${host}:${addr.port}`;\n }\n this.server = srv;\n resolve(this.baseUrl);\n });\n });\n }\n\n async stop(): Promise<void> {\n if (!this.server) {\n throw new Error(\"A2AMock server not started\");\n }\n const srv = this.server;\n await new Promise<void>((resolve, reject) => {\n srv.close((err: Error | undefined) => (err ? reject(err) : resolve()));\n });\n this.server = null;\n }\n\n get url(): string {\n if (!this.server) {\n throw new Error(\"A2AMock server not started\");\n }\n return this.baseUrl;\n }\n\n // ---- Reset ----\n\n reset(): this {\n this.agents.clear();\n this.tasks.clear();\n return this;\n }\n\n // ---- Internal: set base URL when mounted ----\n\n setBaseUrl(url: string): void {\n this.baseUrl = url;\n }\n\n // ---- Private: streaming handler ----\n\n private async handleStreamingMessage(\n parsed: Record<string, unknown>,\n req: http.IncomingMessage,\n res: http.ServerResponse,\n ): Promise<void> {\n const params = parsed.params as Record<string, unknown> | undefined;\n const id = parsed.id as string | number;\n const text = extractText(params);\n const entry = findStreamingMatch(text, this.agents);\n\n if (!entry) {\n res.writeHead(200, {\n \"Content-Type\": \"application/json\",\n \"A2A-Version\": \"1.0\",\n });\n res.end(\n JSON.stringify({\n jsonrpc: \"2.0\",\n id,\n error: { code: -32000, message: \"No matching pattern for message\" },\n }),\n );\n return;\n }\n\n // Create task for the streaming response\n const taskId = generateId(\"task\");\n const contextId = generateId(\"ctx\");\n const userParts: A2APart[] = params?.message\n ? (((params.message as Record<string, unknown>).parts as A2APart[]) ?? [{ text }])\n : [{ text }];\n\n const task: A2ATask = {\n id: taskId,\n contextId,\n status: { state: \"TASK_STATE_WORKING\", timestamp: new Date().toISOString() },\n artifacts: [],\n history: [\n {\n messageId: generateId(\"msg\"),\n role: \"ROLE_USER\",\n parts: userParts,\n },\n ],\n };\n this.tasks.set(taskId, task);\n\n // Write SSE response\n res.writeHead(200, {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n \"A2A-Version\": \"1.0\",\n });\n\n const delayMs = entry.delayMs ?? 0;\n\n for (const event of entry.events) {\n if (delayMs > 0) {\n await delay(delayMs);\n }\n\n let resultPayload: Record<string, unknown>;\n\n if (event.type === \"status\") {\n task.status = { state: event.state, timestamp: new Date().toISOString() };\n resultPayload = {\n task: {\n id: task.id,\n contextId: task.contextId,\n status: task.status,\n },\n };\n } else {\n // artifact event\n const artifact = {\n parts: event.parts,\n name: event.name,\n append: event.append,\n lastChunk: event.lastChunk,\n };\n task.artifacts.push({ parts: event.parts, name: event.name });\n resultPayload = {\n task: {\n id: task.id,\n contextId: task.contextId,\n status: task.status,\n },\n artifact,\n };\n }\n\n const envelope = JSON.stringify({\n jsonrpc: \"2.0\",\n id,\n result: resultPayload,\n });\n\n res.write(`data: ${envelope}\\n\\n`);\n }\n\n // Final completion — only set COMPLETED if the task is not already in a terminal state\n if (!TERMINAL_STATES.has(task.status.state)) {\n task.status = { state: \"TASK_STATE_COMPLETED\", timestamp: new Date().toISOString() };\n }\n\n res.end();\n\n // Journal\n if (this.journal) {\n this.journal.add({\n method: \"POST\",\n path: \"/\",\n headers: flattenHeaders(req.headers),\n body: null,\n service: \"a2a\",\n response: { status: res.statusCode, fixture: null },\n });\n }\n }\n}\n\n// ---- Helpers ----\n\nfunction isStreamingRequest(parsed: unknown): boolean {\n if (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) return false;\n const obj = parsed as Record<string, unknown>;\n return obj.method === \"SendStreamingMessage\";\n}\n\nfunction delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n"],"mappings":"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"}
|
package/dist/agui-recorder.cjs
CHANGED
|
@@ -28,8 +28,9 @@ async function proxyAndRecordAGUI(req, res, input, fixtures, config, logger) {
|
|
|
28
28
|
let target;
|
|
29
29
|
try {
|
|
30
30
|
target = new URL(config.upstream);
|
|
31
|
-
} catch {
|
|
32
|
-
|
|
31
|
+
} catch (err) {
|
|
32
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
33
|
+
logger.error(`Invalid upstream AG-UI URL: ${config.upstream} — ${detail}`);
|
|
33
34
|
res.writeHead(502, { "Content-Type": "application/json" });
|
|
34
35
|
res.end(JSON.stringify({ error: "Invalid upstream AG-UI URL" }));
|
|
35
36
|
return 502;
|
|
@@ -53,7 +54,7 @@ async function proxyAndRecordAGUI(req, res, input, fixtures, config, logger) {
|
|
|
53
54
|
if (!res.headersSent) {
|
|
54
55
|
res.writeHead(502, { "Content-Type": "application/json" });
|
|
55
56
|
res.end(JSON.stringify({ error: "Upstream AG-UI agent unreachable" }));
|
|
56
|
-
}
|
|
57
|
+
} else if (!res.writableEnded) res.end();
|
|
57
58
|
status = 502;
|
|
58
59
|
}
|
|
59
60
|
return status;
|
|
@@ -95,14 +96,22 @@ function teeUpstreamStream(target, headers, body, clientRes, input, fixtures, co
|
|
|
95
96
|
chunks.push(chunk);
|
|
96
97
|
});
|
|
97
98
|
upstreamRes.on("error", (err) => {
|
|
98
|
-
|
|
99
|
-
clientRes.
|
|
100
|
-
|
|
101
|
-
|
|
99
|
+
try {
|
|
100
|
+
if (!clientRes.headersSent) {
|
|
101
|
+
clientRes.writeHead(502, { "Content-Type": "application/json" });
|
|
102
|
+
clientRes.end(JSON.stringify({ error: "Upstream AG-UI agent unreachable" }));
|
|
103
|
+
} else if (!clientRes.writableEnded) clientRes.end();
|
|
104
|
+
} catch (writeErr) {
|
|
105
|
+
logger.warn("Failed to write error response to client:", writeErr instanceof Error ? writeErr.message : String(writeErr));
|
|
106
|
+
}
|
|
102
107
|
reject(err);
|
|
103
108
|
});
|
|
104
109
|
upstreamRes.on("end", () => {
|
|
105
|
-
|
|
110
|
+
try {
|
|
111
|
+
if (!clientRes.writableEnded) clientRes.end();
|
|
112
|
+
} catch (writeErr) {
|
|
113
|
+
logger.warn("Failed to end client response:", writeErr instanceof Error ? writeErr.message : String(writeErr));
|
|
114
|
+
}
|
|
106
115
|
const events = parseSSEEvents(Buffer.concat(chunks).toString(), logger);
|
|
107
116
|
const message = require_agui_handler.extractLastUserMessage(input);
|
|
108
117
|
const fixture = {
|
|
@@ -136,10 +145,14 @@ function teeUpstreamStream(target, headers, body, clientRes, input, fixtures, co
|
|
|
136
145
|
upstreamReq.destroy(/* @__PURE__ */ new Error(`Upstream AG-UI request timed out after ${UPSTREAM_TIMEOUT_MS / 1e3}s`));
|
|
137
146
|
});
|
|
138
147
|
upstreamReq.on("error", (err) => {
|
|
139
|
-
|
|
140
|
-
clientRes.
|
|
141
|
-
|
|
142
|
-
|
|
148
|
+
try {
|
|
149
|
+
if (!clientRes.headersSent) {
|
|
150
|
+
clientRes.writeHead(502, { "Content-Type": "application/json" });
|
|
151
|
+
clientRes.end(JSON.stringify({ error: "Upstream AG-UI agent unreachable" }));
|
|
152
|
+
} else if (!clientRes.writableEnded) clientRes.end();
|
|
153
|
+
} catch (writeErr) {
|
|
154
|
+
logger.warn("Failed to write error response to client:", writeErr instanceof Error ? writeErr.message : String(writeErr));
|
|
155
|
+
}
|
|
143
156
|
reject(err);
|
|
144
157
|
});
|
|
145
158
|
upstreamReq.write(body);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agui-recorder.cjs","names":["https","http","extractLastUserMessage","crypto","path"],"sources":["../src/agui-recorder.ts"],"sourcesContent":["import * as http from \"node:http\";\nimport * as https from \"node:https\";\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport * as crypto from \"node:crypto\";\nimport type { AGUIFixture, AGUIRecordConfig, AGUIEvent, AGUIRunAgentInput } from \"./agui-types.js\";\nimport { extractLastUserMessage } from \"./agui-handler.js\";\nimport type { Logger } from \"./logger.js\";\n\n/**\n * Proxy an unmatched AG-UI request to a real upstream agent, record the\n * SSE event stream as a fixture on disk and in memory, and relay the\n * response back to the original client in real time.\n *\n * Returns the HTTP status code written to the client if the request was proxied,\n * or `false` if no upstream is configured.\n */\nexport async function proxyAndRecordAGUI(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n input: AGUIRunAgentInput,\n fixtures: AGUIFixture[],\n config: AGUIRecordConfig,\n logger: Logger,\n): Promise<number | false> {\n if (!config.upstream) {\n logger.warn(\"No upstream URL configured for AG-UI recording — cannot proxy\");\n return false;\n }\n\n let target: URL;\n try {\n target = new URL(config.upstream);\n } catch {\n logger.error(`Invalid upstream AG-UI URL: ${config.upstream}`);\n res.writeHead(502, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Invalid upstream AG-UI URL\" }));\n return 502;\n }\n\n logger.warn(`NO AG-UI FIXTURE MATCH — proxying to ${config.upstream}`);\n\n // Build upstream request headers\n const forwardHeaders: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n Accept: \"text/event-stream\",\n };\n // Forward auth headers if present\n const authorization = req.headers[\"authorization\"];\n if (authorization) {\n forwardHeaders[\"Authorization\"] = Array.isArray(authorization)\n ? authorization.join(\", \")\n : authorization;\n }\n const apiKey = req.headers[\"x-api-key\"];\n if (apiKey) {\n forwardHeaders[\"x-api-key\"] = Array.isArray(apiKey) ? apiKey.join(\", \") : apiKey;\n }\n\n const requestBody = JSON.stringify(input);\n\n let status: number;\n try {\n status = await teeUpstreamStream(\n target,\n forwardHeaders,\n requestBody,\n res,\n input,\n fixtures,\n config,\n logger,\n );\n } catch (err) {\n const msg = err instanceof Error ? err.message : \"Unknown proxy error\";\n logger.error(`AG-UI proxy request failed: ${msg}`);\n if (!res.headersSent) {\n res.writeHead(502, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Upstream AG-UI agent unreachable\" }));\n }\n status = 502;\n }\n\n return status;\n}\n\n// ---------------------------------------------------------------------------\n// Internal: tee the upstream SSE stream to the client and buffer for recording\n// ---------------------------------------------------------------------------\n\nfunction teeUpstreamStream(\n target: URL,\n headers: Record<string, string>,\n body: string,\n clientRes: http.ServerResponse,\n input: AGUIRunAgentInput,\n fixtures: AGUIFixture[],\n config: AGUIRecordConfig,\n logger: Logger,\n): Promise<number> {\n return new Promise((resolve, reject) => {\n const transport = target.protocol === \"https:\" ? https : http;\n const UPSTREAM_TIMEOUT_MS = 30_000;\n\n const upstreamReq = transport.request(\n target,\n {\n method: \"POST\",\n timeout: UPSTREAM_TIMEOUT_MS,\n headers: {\n ...headers,\n \"Content-Length\": Buffer.byteLength(body).toString(),\n },\n },\n (upstreamRes) => {\n const upstreamStatus = upstreamRes.statusCode ?? 200;\n\n // Normalize status codes: aimock acts as a gateway, so upstream\n // provider details (429, 503, etc.) should not leak.\n // Successes → 200, errors → 502 (Bad Gateway).\n const clientStatus = upstreamStatus >= 200 && upstreamStatus < 300 ? 200 : 502;\n\n // Set appropriate headers on the client response.\n if (!clientRes.headersSent) {\n if (clientStatus === 200) {\n clientRes.writeHead(200, {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n });\n } else {\n const ct = upstreamRes.headers[\"content-type\"] || \"application/json\";\n clientRes.writeHead(502, { \"Content-Type\": ct });\n }\n }\n\n const chunks: Buffer[] = [];\n let clientWriteFailed = false;\n\n upstreamRes.on(\"data\", (chunk: Buffer) => {\n // Relay to client in real time\n try {\n clientRes.write(chunk);\n } catch (err) {\n if (!clientWriteFailed) {\n clientWriteFailed = true;\n logger?.warn(\n \"Client write failed during proxy relay:\",\n err instanceof Error ? err.message : String(err),\n );\n }\n }\n // Buffer for fixture construction\n chunks.push(chunk);\n });\n\n upstreamRes.on(\"error\", (err) => {\n if (!clientRes.headersSent) {\n clientRes.writeHead(502, { \"Content-Type\": \"application/json\" });\n clientRes.end(JSON.stringify({ error: \"Upstream AG-UI agent unreachable\" }));\n } else if (!clientRes.writableEnded) {\n clientRes.end();\n }\n reject(err);\n });\n\n upstreamRes.on(\"end\", () => {\n if (!clientRes.writableEnded) clientRes.end();\n\n // Parse buffered SSE events\n const buffered = Buffer.concat(chunks).toString();\n const events = parseSSEEvents(buffered, logger);\n\n // Build fixture\n const message = extractLastUserMessage(input);\n const fixture: AGUIFixture = {\n match: message\n ? { message }\n : {\n predicate: (inp: AGUIRunAgentInput) =>\n !inp.messages?.length || !inp.messages.some((m) => m.role === \"user\"),\n },\n events,\n };\n if (!message) {\n logger.warn(\n \"Recorded AG-UI fixture has no user message — will use __NO_USER_MESSAGE__ sentinel on disk\",\n );\n }\n\n if (!config.proxyOnly) {\n // Register in memory first (always available even if disk write fails)\n fixtures.push(fixture);\n\n // Write to disk — predicate functions are not serializable,\n // so replace with a sentinel string that won't match real user messages.\n const serializableFixture = {\n match: fixture.match.predicate ? { message: \"__NO_USER_MESSAGE__\" } : fixture.match,\n events: fixture.events,\n ...(fixture.delayMs !== undefined ? { delayMs: fixture.delayMs } : {}),\n };\n\n const fixturePath = config.fixturePath ?? \"./fixtures/agui-recorded\";\n const timestamp = new Date().toISOString().replace(/[:.]/g, \"-\");\n const filename = `agui-${timestamp}-${crypto.randomUUID().slice(0, 8)}.json`;\n const filepath = path.join(fixturePath, filename);\n\n try {\n fs.mkdirSync(fixturePath, { recursive: true });\n fs.writeFileSync(\n filepath,\n JSON.stringify({ fixtures: [serializableFixture] }, null, 2),\n \"utf-8\",\n );\n logger.warn(`AG-UI response recorded → ${filepath}`);\n } catch (err) {\n const msg = err instanceof Error ? err.message : \"Unknown filesystem error\";\n logger.error(\n `Failed to save AG-UI fixture to disk: ${msg} (fixture retained in memory)`,\n );\n }\n } else {\n logger.info(\"Proxied AG-UI request (proxy-only mode)\");\n }\n\n resolve(clientStatus);\n });\n },\n );\n\n upstreamReq.on(\"timeout\", () => {\n upstreamReq.destroy(\n new Error(`Upstream AG-UI request timed out after ${UPSTREAM_TIMEOUT_MS / 1000}s`),\n );\n });\n\n upstreamReq.on(\"error\", (err) => {\n if (!clientRes.headersSent) {\n clientRes.writeHead(502, { \"Content-Type\": \"application/json\" });\n clientRes.end(JSON.stringify({ error: \"Upstream AG-UI agent unreachable\" }));\n } else if (!clientRes.writableEnded) {\n clientRes.end();\n }\n reject(err);\n });\n\n upstreamReq.write(body);\n upstreamReq.end();\n });\n}\n\n/**\n * Parse SSE data lines from buffered stream text.\n */\nfunction parseSSEEvents(text: string, logger?: Logger): AGUIEvent[] {\n const events: AGUIEvent[] = [];\n const blocks = text.split(\"\\n\\n\");\n for (const block of blocks) {\n const lines = block.split(\"\\n\");\n for (const line of lines) {\n if (line.startsWith(\"data:\")) {\n const payload = line.startsWith(\"data: \") ? line.slice(6) : line.slice(5);\n try {\n const parsed = JSON.parse(payload) as AGUIEvent;\n events.push(parsed);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n if (logger) logger.warn(`Skipping unparseable SSE data line: ${payload.slice(0, 200)}`);\n else console.warn(`Skipping unparseable SSE data line: ${msg}`);\n }\n }\n }\n }\n return events;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"agui-recorder.cjs","names":["https","http","extractLastUserMessage","crypto","path"],"sources":["../src/agui-recorder.ts"],"sourcesContent":["import * as http from \"node:http\";\nimport * as https from \"node:https\";\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport * as crypto from \"node:crypto\";\nimport type { AGUIFixture, AGUIRecordConfig, AGUIEvent, AGUIRunAgentInput } from \"./agui-types.js\";\nimport { extractLastUserMessage } from \"./agui-handler.js\";\nimport type { Logger } from \"./logger.js\";\n\n/**\n * Proxy an unmatched AG-UI request to a real upstream agent, record the\n * SSE event stream as a fixture on disk and in memory, and relay the\n * response back to the original client in real time.\n *\n * Returns the HTTP status code written to the client if the request was proxied,\n * or `false` if no upstream is configured.\n */\nexport async function proxyAndRecordAGUI(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n input: AGUIRunAgentInput,\n fixtures: AGUIFixture[],\n config: AGUIRecordConfig,\n logger: Logger,\n): Promise<number | false> {\n if (!config.upstream) {\n logger.warn(\"No upstream URL configured for AG-UI recording — cannot proxy\");\n return false;\n }\n\n let target: URL;\n try {\n target = new URL(config.upstream);\n } catch (err) {\n const detail = err instanceof Error ? err.message : String(err);\n logger.error(`Invalid upstream AG-UI URL: ${config.upstream} — ${detail}`);\n res.writeHead(502, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Invalid upstream AG-UI URL\" }));\n return 502;\n }\n\n logger.warn(`NO AG-UI FIXTURE MATCH — proxying to ${config.upstream}`);\n\n // Build upstream request headers\n const forwardHeaders: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n Accept: \"text/event-stream\",\n };\n // Forward auth headers if present\n const authorization = req.headers[\"authorization\"];\n if (authorization) {\n forwardHeaders[\"Authorization\"] = Array.isArray(authorization)\n ? authorization.join(\", \")\n : authorization;\n }\n const apiKey = req.headers[\"x-api-key\"];\n if (apiKey) {\n forwardHeaders[\"x-api-key\"] = Array.isArray(apiKey) ? apiKey.join(\", \") : apiKey;\n }\n\n const requestBody = JSON.stringify(input);\n\n let status: number;\n try {\n status = await teeUpstreamStream(\n target,\n forwardHeaders,\n requestBody,\n res,\n input,\n fixtures,\n config,\n logger,\n );\n } catch (err) {\n const msg = err instanceof Error ? err.message : \"Unknown proxy error\";\n logger.error(`AG-UI proxy request failed: ${msg}`);\n if (!res.headersSent) {\n res.writeHead(502, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Upstream AG-UI agent unreachable\" }));\n } else if (!res.writableEnded) {\n res.end();\n }\n status = 502;\n }\n\n return status;\n}\n\n// ---------------------------------------------------------------------------\n// Internal: tee the upstream SSE stream to the client and buffer for recording\n// ---------------------------------------------------------------------------\n\nfunction teeUpstreamStream(\n target: URL,\n headers: Record<string, string>,\n body: string,\n clientRes: http.ServerResponse,\n input: AGUIRunAgentInput,\n fixtures: AGUIFixture[],\n config: AGUIRecordConfig,\n logger: Logger,\n): Promise<number> {\n return new Promise((resolve, reject) => {\n const transport = target.protocol === \"https:\" ? https : http;\n const UPSTREAM_TIMEOUT_MS = 30_000;\n\n const upstreamReq = transport.request(\n target,\n {\n method: \"POST\",\n timeout: UPSTREAM_TIMEOUT_MS,\n headers: {\n ...headers,\n \"Content-Length\": Buffer.byteLength(body).toString(),\n },\n },\n (upstreamRes) => {\n const upstreamStatus = upstreamRes.statusCode ?? 200;\n\n // Normalize status codes: aimock acts as a gateway, so upstream\n // provider details (429, 503, etc.) should not leak.\n // Successes → 200, errors → 502 (Bad Gateway).\n const clientStatus = upstreamStatus >= 200 && upstreamStatus < 300 ? 200 : 502;\n\n // Set appropriate headers on the client response.\n if (!clientRes.headersSent) {\n if (clientStatus === 200) {\n clientRes.writeHead(200, {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n });\n } else {\n const ct = upstreamRes.headers[\"content-type\"] || \"application/json\";\n clientRes.writeHead(502, { \"Content-Type\": ct });\n }\n }\n\n const chunks: Buffer[] = [];\n let clientWriteFailed = false;\n\n upstreamRes.on(\"data\", (chunk: Buffer) => {\n // Relay to client in real time\n try {\n clientRes.write(chunk);\n } catch (err) {\n if (!clientWriteFailed) {\n clientWriteFailed = true;\n logger?.warn(\n \"Client write failed during proxy relay:\",\n err instanceof Error ? err.message : String(err),\n );\n }\n }\n // Buffer for fixture construction\n chunks.push(chunk);\n });\n\n upstreamRes.on(\"error\", (err) => {\n try {\n if (!clientRes.headersSent) {\n clientRes.writeHead(502, { \"Content-Type\": \"application/json\" });\n clientRes.end(JSON.stringify({ error: \"Upstream AG-UI agent unreachable\" }));\n } else if (!clientRes.writableEnded) {\n clientRes.end();\n }\n } catch (writeErr) {\n logger.warn(\n \"Failed to write error response to client:\",\n writeErr instanceof Error ? writeErr.message : String(writeErr),\n );\n }\n reject(err);\n });\n\n upstreamRes.on(\"end\", () => {\n try {\n if (!clientRes.writableEnded) clientRes.end();\n } catch (writeErr) {\n logger.warn(\n \"Failed to end client response:\",\n writeErr instanceof Error ? writeErr.message : String(writeErr),\n );\n }\n\n // Parse buffered SSE events\n const buffered = Buffer.concat(chunks).toString();\n const events = parseSSEEvents(buffered, logger);\n\n // Build fixture\n const message = extractLastUserMessage(input);\n const fixture: AGUIFixture = {\n match: message\n ? { message }\n : {\n predicate: (inp: AGUIRunAgentInput) =>\n !inp.messages?.length || !inp.messages.some((m) => m.role === \"user\"),\n },\n events,\n };\n if (!message) {\n logger.warn(\n \"Recorded AG-UI fixture has no user message — will use __NO_USER_MESSAGE__ sentinel on disk\",\n );\n }\n\n if (!config.proxyOnly) {\n // Register in memory first (always available even if disk write fails)\n fixtures.push(fixture);\n\n // Write to disk — predicate functions are not serializable,\n // so replace with a sentinel string that won't match real user messages.\n const serializableFixture = {\n match: fixture.match.predicate ? { message: \"__NO_USER_MESSAGE__\" } : fixture.match,\n events: fixture.events,\n ...(fixture.delayMs !== undefined ? { delayMs: fixture.delayMs } : {}),\n };\n\n const fixturePath = config.fixturePath ?? \"./fixtures/agui-recorded\";\n const timestamp = new Date().toISOString().replace(/[:.]/g, \"-\");\n const filename = `agui-${timestamp}-${crypto.randomUUID().slice(0, 8)}.json`;\n const filepath = path.join(fixturePath, filename);\n\n try {\n fs.mkdirSync(fixturePath, { recursive: true });\n fs.writeFileSync(\n filepath,\n JSON.stringify({ fixtures: [serializableFixture] }, null, 2),\n \"utf-8\",\n );\n logger.warn(`AG-UI response recorded → ${filepath}`);\n } catch (err) {\n const msg = err instanceof Error ? err.message : \"Unknown filesystem error\";\n logger.error(\n `Failed to save AG-UI fixture to disk: ${msg} (fixture retained in memory)`,\n );\n }\n } else {\n logger.info(\"Proxied AG-UI request (proxy-only mode)\");\n }\n\n resolve(clientStatus);\n });\n },\n );\n\n upstreamReq.on(\"timeout\", () => {\n upstreamReq.destroy(\n new Error(`Upstream AG-UI request timed out after ${UPSTREAM_TIMEOUT_MS / 1000}s`),\n );\n });\n\n upstreamReq.on(\"error\", (err) => {\n try {\n if (!clientRes.headersSent) {\n clientRes.writeHead(502, { \"Content-Type\": \"application/json\" });\n clientRes.end(JSON.stringify({ error: \"Upstream AG-UI agent unreachable\" }));\n } else if (!clientRes.writableEnded) {\n clientRes.end();\n }\n } catch (writeErr) {\n logger.warn(\n \"Failed to write error response to client:\",\n writeErr instanceof Error ? writeErr.message : String(writeErr),\n );\n }\n reject(err);\n });\n\n upstreamReq.write(body);\n upstreamReq.end();\n });\n}\n\n/**\n * Parse SSE data lines from buffered stream text.\n */\nfunction parseSSEEvents(text: string, logger?: Logger): AGUIEvent[] {\n const events: AGUIEvent[] = [];\n const blocks = text.split(\"\\n\\n\");\n for (const block of blocks) {\n const lines = block.split(\"\\n\");\n for (const line of lines) {\n if (line.startsWith(\"data:\")) {\n const payload = line.startsWith(\"data: \") ? line.slice(6) : line.slice(5);\n try {\n const parsed = JSON.parse(payload) as AGUIEvent;\n events.push(parsed);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n if (logger) logger.warn(`Skipping unparseable SSE data line: ${payload.slice(0, 200)}`);\n else console.warn(`Skipping unparseable SSE data line: ${msg}`);\n }\n }\n }\n }\n return events;\n}\n"],"mappings":"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"}
|
package/dist/agui-recorder.js
CHANGED
|
@@ -22,8 +22,9 @@ async function proxyAndRecordAGUI(req, res, input, fixtures, config, logger) {
|
|
|
22
22
|
let target;
|
|
23
23
|
try {
|
|
24
24
|
target = new URL(config.upstream);
|
|
25
|
-
} catch {
|
|
26
|
-
|
|
25
|
+
} catch (err) {
|
|
26
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
27
|
+
logger.error(`Invalid upstream AG-UI URL: ${config.upstream} — ${detail}`);
|
|
27
28
|
res.writeHead(502, { "Content-Type": "application/json" });
|
|
28
29
|
res.end(JSON.stringify({ error: "Invalid upstream AG-UI URL" }));
|
|
29
30
|
return 502;
|
|
@@ -47,7 +48,7 @@ async function proxyAndRecordAGUI(req, res, input, fixtures, config, logger) {
|
|
|
47
48
|
if (!res.headersSent) {
|
|
48
49
|
res.writeHead(502, { "Content-Type": "application/json" });
|
|
49
50
|
res.end(JSON.stringify({ error: "Upstream AG-UI agent unreachable" }));
|
|
50
|
-
}
|
|
51
|
+
} else if (!res.writableEnded) res.end();
|
|
51
52
|
status = 502;
|
|
52
53
|
}
|
|
53
54
|
return status;
|
|
@@ -89,14 +90,22 @@ function teeUpstreamStream(target, headers, body, clientRes, input, fixtures, co
|
|
|
89
90
|
chunks.push(chunk);
|
|
90
91
|
});
|
|
91
92
|
upstreamRes.on("error", (err) => {
|
|
92
|
-
|
|
93
|
-
clientRes.
|
|
94
|
-
|
|
95
|
-
|
|
93
|
+
try {
|
|
94
|
+
if (!clientRes.headersSent) {
|
|
95
|
+
clientRes.writeHead(502, { "Content-Type": "application/json" });
|
|
96
|
+
clientRes.end(JSON.stringify({ error: "Upstream AG-UI agent unreachable" }));
|
|
97
|
+
} else if (!clientRes.writableEnded) clientRes.end();
|
|
98
|
+
} catch (writeErr) {
|
|
99
|
+
logger.warn("Failed to write error response to client:", writeErr instanceof Error ? writeErr.message : String(writeErr));
|
|
100
|
+
}
|
|
96
101
|
reject(err);
|
|
97
102
|
});
|
|
98
103
|
upstreamRes.on("end", () => {
|
|
99
|
-
|
|
104
|
+
try {
|
|
105
|
+
if (!clientRes.writableEnded) clientRes.end();
|
|
106
|
+
} catch (writeErr) {
|
|
107
|
+
logger.warn("Failed to end client response:", writeErr instanceof Error ? writeErr.message : String(writeErr));
|
|
108
|
+
}
|
|
100
109
|
const events = parseSSEEvents(Buffer.concat(chunks).toString(), logger);
|
|
101
110
|
const message = extractLastUserMessage(input);
|
|
102
111
|
const fixture = {
|
|
@@ -130,10 +139,14 @@ function teeUpstreamStream(target, headers, body, clientRes, input, fixtures, co
|
|
|
130
139
|
upstreamReq.destroy(/* @__PURE__ */ new Error(`Upstream AG-UI request timed out after ${UPSTREAM_TIMEOUT_MS / 1e3}s`));
|
|
131
140
|
});
|
|
132
141
|
upstreamReq.on("error", (err) => {
|
|
133
|
-
|
|
134
|
-
clientRes.
|
|
135
|
-
|
|
136
|
-
|
|
142
|
+
try {
|
|
143
|
+
if (!clientRes.headersSent) {
|
|
144
|
+
clientRes.writeHead(502, { "Content-Type": "application/json" });
|
|
145
|
+
clientRes.end(JSON.stringify({ error: "Upstream AG-UI agent unreachable" }));
|
|
146
|
+
} else if (!clientRes.writableEnded) clientRes.end();
|
|
147
|
+
} catch (writeErr) {
|
|
148
|
+
logger.warn("Failed to write error response to client:", writeErr instanceof Error ? writeErr.message : String(writeErr));
|
|
149
|
+
}
|
|
137
150
|
reject(err);
|
|
138
151
|
});
|
|
139
152
|
upstreamReq.write(body);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agui-recorder.js","names":["http","crypto"],"sources":["../src/agui-recorder.ts"],"sourcesContent":["import * as http from \"node:http\";\nimport * as https from \"node:https\";\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport * as crypto from \"node:crypto\";\nimport type { AGUIFixture, AGUIRecordConfig, AGUIEvent, AGUIRunAgentInput } from \"./agui-types.js\";\nimport { extractLastUserMessage } from \"./agui-handler.js\";\nimport type { Logger } from \"./logger.js\";\n\n/**\n * Proxy an unmatched AG-UI request to a real upstream agent, record the\n * SSE event stream as a fixture on disk and in memory, and relay the\n * response back to the original client in real time.\n *\n * Returns the HTTP status code written to the client if the request was proxied,\n * or `false` if no upstream is configured.\n */\nexport async function proxyAndRecordAGUI(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n input: AGUIRunAgentInput,\n fixtures: AGUIFixture[],\n config: AGUIRecordConfig,\n logger: Logger,\n): Promise<number | false> {\n if (!config.upstream) {\n logger.warn(\"No upstream URL configured for AG-UI recording — cannot proxy\");\n return false;\n }\n\n let target: URL;\n try {\n target = new URL(config.upstream);\n } catch {\n logger.error(`Invalid upstream AG-UI URL: ${config.upstream}`);\n res.writeHead(502, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Invalid upstream AG-UI URL\" }));\n return 502;\n }\n\n logger.warn(`NO AG-UI FIXTURE MATCH — proxying to ${config.upstream}`);\n\n // Build upstream request headers\n const forwardHeaders: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n Accept: \"text/event-stream\",\n };\n // Forward auth headers if present\n const authorization = req.headers[\"authorization\"];\n if (authorization) {\n forwardHeaders[\"Authorization\"] = Array.isArray(authorization)\n ? authorization.join(\", \")\n : authorization;\n }\n const apiKey = req.headers[\"x-api-key\"];\n if (apiKey) {\n forwardHeaders[\"x-api-key\"] = Array.isArray(apiKey) ? apiKey.join(\", \") : apiKey;\n }\n\n const requestBody = JSON.stringify(input);\n\n let status: number;\n try {\n status = await teeUpstreamStream(\n target,\n forwardHeaders,\n requestBody,\n res,\n input,\n fixtures,\n config,\n logger,\n );\n } catch (err) {\n const msg = err instanceof Error ? err.message : \"Unknown proxy error\";\n logger.error(`AG-UI proxy request failed: ${msg}`);\n if (!res.headersSent) {\n res.writeHead(502, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Upstream AG-UI agent unreachable\" }));\n }\n status = 502;\n }\n\n return status;\n}\n\n// ---------------------------------------------------------------------------\n// Internal: tee the upstream SSE stream to the client and buffer for recording\n// ---------------------------------------------------------------------------\n\nfunction teeUpstreamStream(\n target: URL,\n headers: Record<string, string>,\n body: string,\n clientRes: http.ServerResponse,\n input: AGUIRunAgentInput,\n fixtures: AGUIFixture[],\n config: AGUIRecordConfig,\n logger: Logger,\n): Promise<number> {\n return new Promise((resolve, reject) => {\n const transport = target.protocol === \"https:\" ? https : http;\n const UPSTREAM_TIMEOUT_MS = 30_000;\n\n const upstreamReq = transport.request(\n target,\n {\n method: \"POST\",\n timeout: UPSTREAM_TIMEOUT_MS,\n headers: {\n ...headers,\n \"Content-Length\": Buffer.byteLength(body).toString(),\n },\n },\n (upstreamRes) => {\n const upstreamStatus = upstreamRes.statusCode ?? 200;\n\n // Normalize status codes: aimock acts as a gateway, so upstream\n // provider details (429, 503, etc.) should not leak.\n // Successes → 200, errors → 502 (Bad Gateway).\n const clientStatus = upstreamStatus >= 200 && upstreamStatus < 300 ? 200 : 502;\n\n // Set appropriate headers on the client response.\n if (!clientRes.headersSent) {\n if (clientStatus === 200) {\n clientRes.writeHead(200, {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n });\n } else {\n const ct = upstreamRes.headers[\"content-type\"] || \"application/json\";\n clientRes.writeHead(502, { \"Content-Type\": ct });\n }\n }\n\n const chunks: Buffer[] = [];\n let clientWriteFailed = false;\n\n upstreamRes.on(\"data\", (chunk: Buffer) => {\n // Relay to client in real time\n try {\n clientRes.write(chunk);\n } catch (err) {\n if (!clientWriteFailed) {\n clientWriteFailed = true;\n logger?.warn(\n \"Client write failed during proxy relay:\",\n err instanceof Error ? err.message : String(err),\n );\n }\n }\n // Buffer for fixture construction\n chunks.push(chunk);\n });\n\n upstreamRes.on(\"error\", (err) => {\n if (!clientRes.headersSent) {\n clientRes.writeHead(502, { \"Content-Type\": \"application/json\" });\n clientRes.end(JSON.stringify({ error: \"Upstream AG-UI agent unreachable\" }));\n } else if (!clientRes.writableEnded) {\n clientRes.end();\n }\n reject(err);\n });\n\n upstreamRes.on(\"end\", () => {\n if (!clientRes.writableEnded) clientRes.end();\n\n // Parse buffered SSE events\n const buffered = Buffer.concat(chunks).toString();\n const events = parseSSEEvents(buffered, logger);\n\n // Build fixture\n const message = extractLastUserMessage(input);\n const fixture: AGUIFixture = {\n match: message\n ? { message }\n : {\n predicate: (inp: AGUIRunAgentInput) =>\n !inp.messages?.length || !inp.messages.some((m) => m.role === \"user\"),\n },\n events,\n };\n if (!message) {\n logger.warn(\n \"Recorded AG-UI fixture has no user message — will use __NO_USER_MESSAGE__ sentinel on disk\",\n );\n }\n\n if (!config.proxyOnly) {\n // Register in memory first (always available even if disk write fails)\n fixtures.push(fixture);\n\n // Write to disk — predicate functions are not serializable,\n // so replace with a sentinel string that won't match real user messages.\n const serializableFixture = {\n match: fixture.match.predicate ? { message: \"__NO_USER_MESSAGE__\" } : fixture.match,\n events: fixture.events,\n ...(fixture.delayMs !== undefined ? { delayMs: fixture.delayMs } : {}),\n };\n\n const fixturePath = config.fixturePath ?? \"./fixtures/agui-recorded\";\n const timestamp = new Date().toISOString().replace(/[:.]/g, \"-\");\n const filename = `agui-${timestamp}-${crypto.randomUUID().slice(0, 8)}.json`;\n const filepath = path.join(fixturePath, filename);\n\n try {\n fs.mkdirSync(fixturePath, { recursive: true });\n fs.writeFileSync(\n filepath,\n JSON.stringify({ fixtures: [serializableFixture] }, null, 2),\n \"utf-8\",\n );\n logger.warn(`AG-UI response recorded → ${filepath}`);\n } catch (err) {\n const msg = err instanceof Error ? err.message : \"Unknown filesystem error\";\n logger.error(\n `Failed to save AG-UI fixture to disk: ${msg} (fixture retained in memory)`,\n );\n }\n } else {\n logger.info(\"Proxied AG-UI request (proxy-only mode)\");\n }\n\n resolve(clientStatus);\n });\n },\n );\n\n upstreamReq.on(\"timeout\", () => {\n upstreamReq.destroy(\n new Error(`Upstream AG-UI request timed out after ${UPSTREAM_TIMEOUT_MS / 1000}s`),\n );\n });\n\n upstreamReq.on(\"error\", (err) => {\n if (!clientRes.headersSent) {\n clientRes.writeHead(502, { \"Content-Type\": \"application/json\" });\n clientRes.end(JSON.stringify({ error: \"Upstream AG-UI agent unreachable\" }));\n } else if (!clientRes.writableEnded) {\n clientRes.end();\n }\n reject(err);\n });\n\n upstreamReq.write(body);\n upstreamReq.end();\n });\n}\n\n/**\n * Parse SSE data lines from buffered stream text.\n */\nfunction parseSSEEvents(text: string, logger?: Logger): AGUIEvent[] {\n const events: AGUIEvent[] = [];\n const blocks = text.split(\"\\n\\n\");\n for (const block of blocks) {\n const lines = block.split(\"\\n\");\n for (const line of lines) {\n if (line.startsWith(\"data:\")) {\n const payload = line.startsWith(\"data: \") ? line.slice(6) : line.slice(5);\n try {\n const parsed = JSON.parse(payload) as AGUIEvent;\n events.push(parsed);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n if (logger) logger.warn(`Skipping unparseable SSE data line: ${payload.slice(0, 200)}`);\n else console.warn(`Skipping unparseable SSE data line: ${msg}`);\n }\n }\n }\n }\n return events;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"agui-recorder.js","names":["http","crypto"],"sources":["../src/agui-recorder.ts"],"sourcesContent":["import * as http from \"node:http\";\nimport * as https from \"node:https\";\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport * as crypto from \"node:crypto\";\nimport type { AGUIFixture, AGUIRecordConfig, AGUIEvent, AGUIRunAgentInput } from \"./agui-types.js\";\nimport { extractLastUserMessage } from \"./agui-handler.js\";\nimport type { Logger } from \"./logger.js\";\n\n/**\n * Proxy an unmatched AG-UI request to a real upstream agent, record the\n * SSE event stream as a fixture on disk and in memory, and relay the\n * response back to the original client in real time.\n *\n * Returns the HTTP status code written to the client if the request was proxied,\n * or `false` if no upstream is configured.\n */\nexport async function proxyAndRecordAGUI(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n input: AGUIRunAgentInput,\n fixtures: AGUIFixture[],\n config: AGUIRecordConfig,\n logger: Logger,\n): Promise<number | false> {\n if (!config.upstream) {\n logger.warn(\"No upstream URL configured for AG-UI recording — cannot proxy\");\n return false;\n }\n\n let target: URL;\n try {\n target = new URL(config.upstream);\n } catch (err) {\n const detail = err instanceof Error ? err.message : String(err);\n logger.error(`Invalid upstream AG-UI URL: ${config.upstream} — ${detail}`);\n res.writeHead(502, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Invalid upstream AG-UI URL\" }));\n return 502;\n }\n\n logger.warn(`NO AG-UI FIXTURE MATCH — proxying to ${config.upstream}`);\n\n // Build upstream request headers\n const forwardHeaders: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n Accept: \"text/event-stream\",\n };\n // Forward auth headers if present\n const authorization = req.headers[\"authorization\"];\n if (authorization) {\n forwardHeaders[\"Authorization\"] = Array.isArray(authorization)\n ? authorization.join(\", \")\n : authorization;\n }\n const apiKey = req.headers[\"x-api-key\"];\n if (apiKey) {\n forwardHeaders[\"x-api-key\"] = Array.isArray(apiKey) ? apiKey.join(\", \") : apiKey;\n }\n\n const requestBody = JSON.stringify(input);\n\n let status: number;\n try {\n status = await teeUpstreamStream(\n target,\n forwardHeaders,\n requestBody,\n res,\n input,\n fixtures,\n config,\n logger,\n );\n } catch (err) {\n const msg = err instanceof Error ? err.message : \"Unknown proxy error\";\n logger.error(`AG-UI proxy request failed: ${msg}`);\n if (!res.headersSent) {\n res.writeHead(502, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Upstream AG-UI agent unreachable\" }));\n } else if (!res.writableEnded) {\n res.end();\n }\n status = 502;\n }\n\n return status;\n}\n\n// ---------------------------------------------------------------------------\n// Internal: tee the upstream SSE stream to the client and buffer for recording\n// ---------------------------------------------------------------------------\n\nfunction teeUpstreamStream(\n target: URL,\n headers: Record<string, string>,\n body: string,\n clientRes: http.ServerResponse,\n input: AGUIRunAgentInput,\n fixtures: AGUIFixture[],\n config: AGUIRecordConfig,\n logger: Logger,\n): Promise<number> {\n return new Promise((resolve, reject) => {\n const transport = target.protocol === \"https:\" ? https : http;\n const UPSTREAM_TIMEOUT_MS = 30_000;\n\n const upstreamReq = transport.request(\n target,\n {\n method: \"POST\",\n timeout: UPSTREAM_TIMEOUT_MS,\n headers: {\n ...headers,\n \"Content-Length\": Buffer.byteLength(body).toString(),\n },\n },\n (upstreamRes) => {\n const upstreamStatus = upstreamRes.statusCode ?? 200;\n\n // Normalize status codes: aimock acts as a gateway, so upstream\n // provider details (429, 503, etc.) should not leak.\n // Successes → 200, errors → 502 (Bad Gateway).\n const clientStatus = upstreamStatus >= 200 && upstreamStatus < 300 ? 200 : 502;\n\n // Set appropriate headers on the client response.\n if (!clientRes.headersSent) {\n if (clientStatus === 200) {\n clientRes.writeHead(200, {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n });\n } else {\n const ct = upstreamRes.headers[\"content-type\"] || \"application/json\";\n clientRes.writeHead(502, { \"Content-Type\": ct });\n }\n }\n\n const chunks: Buffer[] = [];\n let clientWriteFailed = false;\n\n upstreamRes.on(\"data\", (chunk: Buffer) => {\n // Relay to client in real time\n try {\n clientRes.write(chunk);\n } catch (err) {\n if (!clientWriteFailed) {\n clientWriteFailed = true;\n logger?.warn(\n \"Client write failed during proxy relay:\",\n err instanceof Error ? err.message : String(err),\n );\n }\n }\n // Buffer for fixture construction\n chunks.push(chunk);\n });\n\n upstreamRes.on(\"error\", (err) => {\n try {\n if (!clientRes.headersSent) {\n clientRes.writeHead(502, { \"Content-Type\": \"application/json\" });\n clientRes.end(JSON.stringify({ error: \"Upstream AG-UI agent unreachable\" }));\n } else if (!clientRes.writableEnded) {\n clientRes.end();\n }\n } catch (writeErr) {\n logger.warn(\n \"Failed to write error response to client:\",\n writeErr instanceof Error ? writeErr.message : String(writeErr),\n );\n }\n reject(err);\n });\n\n upstreamRes.on(\"end\", () => {\n try {\n if (!clientRes.writableEnded) clientRes.end();\n } catch (writeErr) {\n logger.warn(\n \"Failed to end client response:\",\n writeErr instanceof Error ? writeErr.message : String(writeErr),\n );\n }\n\n // Parse buffered SSE events\n const buffered = Buffer.concat(chunks).toString();\n const events = parseSSEEvents(buffered, logger);\n\n // Build fixture\n const message = extractLastUserMessage(input);\n const fixture: AGUIFixture = {\n match: message\n ? { message }\n : {\n predicate: (inp: AGUIRunAgentInput) =>\n !inp.messages?.length || !inp.messages.some((m) => m.role === \"user\"),\n },\n events,\n };\n if (!message) {\n logger.warn(\n \"Recorded AG-UI fixture has no user message — will use __NO_USER_MESSAGE__ sentinel on disk\",\n );\n }\n\n if (!config.proxyOnly) {\n // Register in memory first (always available even if disk write fails)\n fixtures.push(fixture);\n\n // Write to disk — predicate functions are not serializable,\n // so replace with a sentinel string that won't match real user messages.\n const serializableFixture = {\n match: fixture.match.predicate ? { message: \"__NO_USER_MESSAGE__\" } : fixture.match,\n events: fixture.events,\n ...(fixture.delayMs !== undefined ? { delayMs: fixture.delayMs } : {}),\n };\n\n const fixturePath = config.fixturePath ?? \"./fixtures/agui-recorded\";\n const timestamp = new Date().toISOString().replace(/[:.]/g, \"-\");\n const filename = `agui-${timestamp}-${crypto.randomUUID().slice(0, 8)}.json`;\n const filepath = path.join(fixturePath, filename);\n\n try {\n fs.mkdirSync(fixturePath, { recursive: true });\n fs.writeFileSync(\n filepath,\n JSON.stringify({ fixtures: [serializableFixture] }, null, 2),\n \"utf-8\",\n );\n logger.warn(`AG-UI response recorded → ${filepath}`);\n } catch (err) {\n const msg = err instanceof Error ? err.message : \"Unknown filesystem error\";\n logger.error(\n `Failed to save AG-UI fixture to disk: ${msg} (fixture retained in memory)`,\n );\n }\n } else {\n logger.info(\"Proxied AG-UI request (proxy-only mode)\");\n }\n\n resolve(clientStatus);\n });\n },\n );\n\n upstreamReq.on(\"timeout\", () => {\n upstreamReq.destroy(\n new Error(`Upstream AG-UI request timed out after ${UPSTREAM_TIMEOUT_MS / 1000}s`),\n );\n });\n\n upstreamReq.on(\"error\", (err) => {\n try {\n if (!clientRes.headersSent) {\n clientRes.writeHead(502, { \"Content-Type\": \"application/json\" });\n clientRes.end(JSON.stringify({ error: \"Upstream AG-UI agent unreachable\" }));\n } else if (!clientRes.writableEnded) {\n clientRes.end();\n }\n } catch (writeErr) {\n logger.warn(\n \"Failed to write error response to client:\",\n writeErr instanceof Error ? writeErr.message : String(writeErr),\n );\n }\n reject(err);\n });\n\n upstreamReq.write(body);\n upstreamReq.end();\n });\n}\n\n/**\n * Parse SSE data lines from buffered stream text.\n */\nfunction parseSSEEvents(text: string, logger?: Logger): AGUIEvent[] {\n const events: AGUIEvent[] = [];\n const blocks = text.split(\"\\n\\n\");\n for (const block of blocks) {\n const lines = block.split(\"\\n\");\n for (const line of lines) {\n if (line.startsWith(\"data:\")) {\n const payload = line.startsWith(\"data: \") ? line.slice(6) : line.slice(5);\n try {\n const parsed = JSON.parse(payload) as AGUIEvent;\n events.push(parsed);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n if (logger) logger.warn(`Skipping unparseable SSE data line: ${payload.slice(0, 200)}`);\n else console.warn(`Skipping unparseable SSE data line: ${msg}`);\n }\n }\n }\n }\n return events;\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agui-types.d.cts","names":[],"sources":["../src/agui-types.ts"],"sourcesContent":[],"mappings":";KAOY,aAAA;AAAA,UA6CK,aAAA,CA7CQ;EA6CR,IAAA,EACT,aADsB;EAUb,SAAA,CAAA,EAAA,MAAA;EAAoB,QAAA,CAAA,EAAA,OAAA;;AAAQ,UAA5B,mBAAA,SAA4B,aAAA,CAAA;EAAa,IAAA,EAAA,aAAA;EAQzC,QAAA,EAAA,MAAA;EAAqB,KAAA,EAAA,MAAA;aAK1B,CAAA,EAAA,MAAA;OALkC,CAAA,EAHpC,iBAGoC;;AAQ7B,UARA,oBAAA,SAA6B,aAQU,CAAA;EAMvC,IAAA,EAAA,cAAA;EAKA,QAAA,EAAA,MAAA;EAOL,KAAA,EAAA,MAAA;EAEA,MAAA,CAAA,EAAA,OAAA;EASK,OAAA,CAAA,EAhCL,sBAgC+B;;AAGnC,UAhCS,iBAAA,SAA0B,aAgCnC,CAAA;MAH2C,EAAA,WAAA;EAAa,OAAA,EAAA,MAAA;EAO/C,IAAA,CAAA,EAAA,MAAA;AAMjB;AAKiB,UAzCA,oBAAA,SAA6B,aAyCH,CAAA;EAAA,IAAA,EAAA,cAAA;UAGlC,EAAA,MAAA;;AAHuD,UApC/C,qBAAA,SAA8B,aAoCiB,CAAA;EAU/C,IAAA,EAAA,eAAA;EAOA,QAAA,EAAA,MAAA;AAMjB;AAKiB,KAzDL,mBAAA,GAyD4B,WAAQ,GAAA,QAAa,GAAA,WAAA,GAAA,MAAA;AAQ5C,KA/DL,eAAA,GA+D6B,
|
|
1
|
+
{"version":3,"file":"agui-types.d.cts","names":[],"sources":["../src/agui-types.ts"],"sourcesContent":[],"mappings":"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"}
|