@copilotkit/aimock 1.16.3 → 1.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +30 -0
- package/README.md +10 -10
- package/dist/a2a-mock.d.cts +2 -2
- package/dist/a2a-mock.d.cts.map +1 -1
- package/dist/a2a-mock.d.ts +2 -2
- package/dist/a2a-mock.d.ts.map +1 -1
- package/dist/a2a-mock.js +2 -2
- package/dist/a2a-mock.js.map +1 -1
- package/dist/agui-handler.cjs +120 -5
- package/dist/agui-handler.cjs.map +1 -1
- package/dist/agui-handler.d.cts +41 -5
- package/dist/agui-handler.d.cts.map +1 -1
- package/dist/agui-handler.d.ts +41 -5
- package/dist/agui-handler.d.ts.map +1 -1
- package/dist/agui-handler.js +114 -6
- package/dist/agui-handler.js.map +1 -1
- package/dist/agui-mock.cjs +18 -7
- package/dist/agui-mock.cjs.map +1 -1
- package/dist/agui-mock.d.cts +2 -2
- package/dist/agui-mock.d.cts.map +1 -1
- package/dist/agui-mock.d.ts +2 -2
- package/dist/agui-mock.d.ts.map +1 -1
- package/dist/agui-mock.js +20 -9
- package/dist/agui-mock.js.map +1 -1
- package/dist/agui-recorder.cjs +43 -22
- package/dist/agui-recorder.cjs.map +1 -1
- package/dist/agui-recorder.d.cts +4 -3
- package/dist/agui-recorder.d.cts.map +1 -1
- package/dist/agui-recorder.d.ts +4 -3
- package/dist/agui-recorder.d.ts.map +1 -1
- package/dist/agui-recorder.js +45 -24
- package/dist/agui-recorder.js.map +1 -1
- package/dist/agui-stub.cjs +28 -0
- package/dist/agui-stub.d.cts +5 -0
- package/dist/agui-stub.d.ts +5 -0
- package/dist/agui-stub.js +5 -0
- package/dist/agui-types.d.cts +33 -6
- package/dist/agui-types.d.cts.map +1 -1
- package/dist/agui-types.d.ts +33 -6
- package/dist/agui-types.d.ts.map +1 -1
- package/dist/aimock-cli.cjs +1 -1
- package/dist/aimock-cli.js +1 -1
- package/dist/aws-event-stream.d.cts +2 -2
- package/dist/aws-event-stream.d.cts.map +1 -1
- package/dist/aws-event-stream.d.ts +2 -2
- package/dist/aws-event-stream.d.ts.map +1 -1
- package/dist/bedrock-converse.d.cts +3 -3
- package/dist/bedrock-converse.d.cts.map +1 -1
- package/dist/bedrock-converse.d.ts +3 -3
- package/dist/bedrock-converse.d.ts.map +1 -1
- package/dist/bedrock.d.cts +3 -3
- package/dist/bedrock.d.cts.map +1 -1
- package/dist/bedrock.d.ts +3 -3
- package/dist/bedrock.d.ts.map +1 -1
- package/dist/chaos.d.cts +3 -3
- package/dist/chaos.d.cts.map +1 -1
- package/dist/chaos.d.ts +3 -3
- package/dist/chaos.d.ts.map +1 -1
- package/dist/cli.cjs +6 -5
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +6 -5
- package/dist/cli.js.map +1 -1
- package/dist/cohere.d.cts +2 -2
- package/dist/cohere.d.cts.map +1 -1
- package/dist/cohere.d.ts +2 -2
- package/dist/cohere.d.ts.map +1 -1
- package/dist/config-loader.cjs +3 -3
- package/dist/config-loader.d.cts +1 -1
- package/dist/config-loader.d.cts.map +1 -1
- package/dist/config-loader.d.ts +1 -1
- package/dist/config-loader.js +2 -2
- package/dist/convert-vidaimock.cjs +1 -1
- package/dist/convert-vidaimock.js +1 -1
- package/dist/convert.cjs +1 -1
- package/dist/convert.js +1 -1
- package/dist/elevenlabs-audio.cjs +209 -0
- package/dist/elevenlabs-audio.cjs.map +1 -0
- package/dist/elevenlabs-audio.d.cts +11 -0
- package/dist/elevenlabs-audio.d.cts.map +1 -0
- package/dist/elevenlabs-audio.d.ts +11 -0
- package/dist/elevenlabs-audio.d.ts.map +1 -0
- package/dist/elevenlabs-audio.js +209 -0
- package/dist/elevenlabs-audio.js.map +1 -0
- package/dist/embeddings.d.cts +2 -2
- package/dist/embeddings.d.cts.map +1 -1
- package/dist/embeddings.d.ts +2 -2
- package/dist/embeddings.d.ts.map +1 -1
- package/dist/fal-audio.cjs +477 -0
- package/dist/fal-audio.cjs.map +1 -0
- package/dist/fal-audio.d.cts +10 -0
- package/dist/fal-audio.d.cts.map +1 -0
- package/dist/fal-audio.d.ts +10 -0
- package/dist/fal-audio.d.ts.map +1 -0
- package/dist/fal-audio.js +474 -0
- package/dist/fal-audio.js.map +1 -0
- package/dist/fixture-loader.cjs +14 -1
- package/dist/fixture-loader.cjs.map +1 -1
- package/dist/fixture-loader.js +14 -1
- package/dist/fixture-loader.js.map +1 -1
- package/dist/fixtures-remote.cjs +1 -1
- package/dist/fixtures-remote.js +1 -1
- package/dist/gemini-interactions.cjs +617 -0
- package/dist/gemini-interactions.cjs.map +1 -0
- package/dist/gemini-interactions.d.cts +46 -0
- package/dist/gemini-interactions.d.cts.map +1 -0
- package/dist/gemini-interactions.d.ts +46 -0
- package/dist/gemini-interactions.d.ts.map +1 -0
- package/dist/gemini-interactions.js +616 -0
- package/dist/gemini-interactions.js.map +1 -0
- package/dist/gemini.cjs +76 -0
- package/dist/gemini.cjs.map +1 -1
- package/dist/gemini.d.cts +2 -2
- package/dist/gemini.d.cts.map +1 -1
- package/dist/gemini.d.ts +2 -2
- package/dist/gemini.d.ts.map +1 -1
- package/dist/gemini.js +77 -1
- package/dist/gemini.js.map +1 -1
- package/dist/helpers.cjs +24 -1
- package/dist/helpers.cjs.map +1 -1
- package/dist/helpers.d.cts +13 -3
- package/dist/helpers.d.cts.map +1 -1
- package/dist/helpers.d.ts +13 -3
- package/dist/helpers.d.ts.map +1 -1
- package/dist/helpers.js +23 -2
- package/dist/helpers.js.map +1 -1
- package/dist/images.d.cts +2 -2
- package/dist/images.d.cts.map +1 -1
- package/dist/images.d.ts +2 -2
- package/dist/images.d.ts.map +1 -1
- package/dist/index.cjs +21 -4
- package/dist/index.d.cts +10 -7
- package/dist/index.d.ts +10 -7
- package/dist/index.js +10 -7
- package/dist/jest.cjs +1 -1
- package/dist/jest.js +1 -1
- package/dist/jsonrpc.d.cts +3 -3
- package/dist/jsonrpc.d.cts.map +1 -1
- package/dist/jsonrpc.d.ts +3 -3
- package/dist/jsonrpc.d.ts.map +1 -1
- package/dist/llmock.cjs +38 -2
- package/dist/llmock.cjs.map +1 -1
- package/dist/llmock.d.cts +4 -0
- package/dist/llmock.d.cts.map +1 -1
- package/dist/llmock.d.ts +4 -0
- package/dist/llmock.d.ts.map +1 -1
- package/dist/llmock.js +38 -2
- package/dist/llmock.js.map +1 -1
- package/dist/logger.cjs +5 -4
- package/dist/logger.cjs.map +1 -1
- package/dist/logger.d.cts +1 -1
- package/dist/logger.d.cts.map +1 -1
- package/dist/logger.d.ts +1 -1
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +5 -4
- package/dist/logger.js.map +1 -1
- package/dist/mcp-mock.d.cts +2 -2
- package/dist/mcp-mock.d.cts.map +1 -1
- package/dist/mcp-mock.d.ts +2 -2
- package/dist/mcp-mock.d.ts.map +1 -1
- package/dist/mcp-mock.js +2 -2
- package/dist/mcp-mock.js.map +1 -1
- package/dist/messages.d.cts +2 -2
- package/dist/messages.d.cts.map +1 -1
- package/dist/messages.d.ts +2 -2
- package/dist/messages.d.ts.map +1 -1
- package/dist/moderation.d.cts +3 -3
- package/dist/moderation.d.cts.map +1 -1
- package/dist/moderation.d.ts +3 -3
- package/dist/moderation.d.ts.map +1 -1
- package/dist/ndjson-writer.d.cts +2 -2
- package/dist/ndjson-writer.d.cts.map +1 -1
- package/dist/ndjson-writer.d.ts +2 -2
- package/dist/ndjson-writer.d.ts.map +1 -1
- package/dist/ollama.d.cts +3 -3
- package/dist/ollama.d.cts.map +1 -1
- package/dist/ollama.d.ts +3 -3
- package/dist/ollama.d.ts.map +1 -1
- package/dist/recorder.cjs +64 -21
- package/dist/recorder.cjs.map +1 -1
- package/dist/recorder.d.cts +2 -2
- package/dist/recorder.d.cts.map +1 -1
- package/dist/recorder.d.ts +2 -2
- package/dist/recorder.d.ts.map +1 -1
- package/dist/recorder.js +66 -23
- package/dist/recorder.js.map +1 -1
- package/dist/rerank.d.cts +3 -3
- package/dist/rerank.d.cts.map +1 -1
- package/dist/rerank.d.ts +3 -3
- package/dist/rerank.d.ts.map +1 -1
- package/dist/responses.d.cts +2 -2
- package/dist/responses.d.cts.map +1 -1
- package/dist/responses.d.ts +2 -2
- package/dist/responses.d.ts.map +1 -1
- package/dist/router.cjs +3 -3
- package/dist/router.cjs.map +1 -1
- package/dist/router.js +3 -3
- package/dist/router.js.map +1 -1
- package/dist/search.d.cts +3 -3
- package/dist/search.d.cts.map +1 -1
- package/dist/search.d.ts +3 -3
- package/dist/search.d.ts.map +1 -1
- package/dist/server.cjs +170 -1
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +2 -2
- package/dist/server.d.cts.map +1 -1
- package/dist/server.d.ts +2 -2
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +173 -4
- package/dist/server.js.map +1 -1
- package/dist/speech.cjs +18 -9
- package/dist/speech.cjs.map +1 -1
- package/dist/speech.d.cts +2 -2
- package/dist/speech.d.cts.map +1 -1
- package/dist/speech.d.ts +2 -2
- package/dist/speech.d.ts.map +1 -1
- package/dist/speech.js +18 -9
- package/dist/speech.js.map +1 -1
- package/dist/sse-writer.d.cts +3 -3
- package/dist/sse-writer.d.cts.map +1 -1
- package/dist/sse-writer.d.ts +3 -3
- package/dist/sse-writer.d.ts.map +1 -1
- package/dist/stream-collapse.cjs +80 -9
- package/dist/stream-collapse.cjs.map +1 -1
- package/dist/stream-collapse.d.cts +11 -1
- package/dist/stream-collapse.d.cts.map +1 -1
- package/dist/stream-collapse.d.ts +11 -1
- package/dist/stream-collapse.d.ts.map +1 -1
- package/dist/stream-collapse.js +80 -10
- package/dist/stream-collapse.js.map +1 -1
- package/dist/suite.cjs +1 -1
- package/dist/suite.d.cts +2 -2
- package/dist/suite.d.ts +2 -2
- package/dist/suite.js +1 -1
- package/dist/transcription.d.cts +2 -2
- package/dist/transcription.d.cts.map +1 -1
- package/dist/transcription.d.ts +2 -2
- package/dist/transcription.d.ts.map +1 -1
- package/dist/types.d.cts +10 -7
- package/dist/types.d.cts.map +1 -1
- package/dist/types.d.ts +10 -7
- package/dist/types.d.ts.map +1 -1
- package/dist/vector-mock.d.cts +2 -2
- package/dist/vector-mock.d.cts.map +1 -1
- package/dist/vector-mock.d.ts +2 -2
- package/dist/vector-mock.d.ts.map +1 -1
- package/dist/vector-mock.js +2 -2
- package/dist/vector-mock.js.map +1 -1
- package/dist/vector-types.d.ts.map +1 -1
- package/dist/video.d.cts +3 -3
- package/dist/video.d.cts.map +1 -1
- package/dist/video.d.ts +3 -3
- package/dist/video.d.ts.map +1 -1
- package/dist/vitest.cjs +1 -1
- package/dist/vitest.js +1 -1
- package/dist/ws-framing.d.cts +2 -2
- package/dist/ws-framing.d.cts.map +1 -1
- package/dist/ws-framing.d.ts +2 -2
- package/dist/ws-framing.d.ts.map +1 -1
- package/dist/ws-gemini-live.cjs +145 -2
- package/dist/ws-gemini-live.cjs.map +1 -1
- package/dist/ws-gemini-live.d.cts.map +1 -1
- package/dist/ws-gemini-live.d.ts.map +1 -1
- package/dist/ws-gemini-live.js +146 -3
- package/dist/ws-gemini-live.js.map +1 -1
- package/package.json +16 -2
- package/skills/write-fixtures/SKILL.md +10 -10
package/dist/agui-mock.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agui-mock.js","names":[],"sources":["../src/agui-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 AGUIFixture,\n AGUIMockOptions,\n AGUIRecordConfig,\n AGUIEvent,\n AGUIRunAgentInput,\n} from \"./agui-types.js\";\nimport {\n findFixture,\n buildTextResponse,\n buildToolCallResponse,\n buildStateUpdate,\n buildReasoningResponse,\n writeAGUIEventStream,\n} from \"./agui-handler.js\";\nimport { flattenHeaders, readBody } from \"./helpers.js\";\nimport { proxyAndRecordAGUI } from \"./agui-recorder.js\";\nimport { Logger } from \"./logger.js\";\n\nexport class AGUIMock implements Mountable {\n private fixtures: AGUIFixture[] = [];\n private server: http.Server | null = null;\n private journal: Journal | null = null;\n private registry: MetricsRegistry | null = null;\n private options: AGUIMockOptions;\n private baseUrl = \"\";\n private recordConfig: AGUIRecordConfig | undefined;\n private logger: Logger;\n\n constructor(options?: AGUIMockOptions) {\n this.options = options ?? {};\n this.logger = new Logger(\"silent\");\n }\n\n // ---- Fluent registration API ----\n\n addFixture(fixture: AGUIFixture): this {\n this.fixtures.push(fixture);\n return this;\n }\n\n onMessage(pattern: string | RegExp, text: string, opts?: { delayMs?: number }): this {\n const events = buildTextResponse(text);\n this.fixtures.push({\n match: { message: pattern },\n events,\n delayMs: opts?.delayMs,\n });\n return this;\n }\n\n onRun(pattern: string | RegExp, events: AGUIEvent[], delayMs?: number): this {\n this.fixtures.push({\n match: { message: pattern },\n events,\n delayMs,\n });\n return this;\n }\n\n onToolCall(\n pattern: string | RegExp,\n toolName: string,\n args: string,\n opts?: { result?: string; delayMs?: number },\n ): this {\n const events = buildToolCallResponse(toolName, args, {\n result: opts?.result,\n });\n this.fixtures.push({\n match: { message: pattern },\n events,\n delayMs: opts?.delayMs,\n });\n return this;\n }\n\n onStateKey(key: string, snapshot: Record<string, unknown>, delayMs?: number): this {\n const events = buildStateUpdate(snapshot);\n this.fixtures.push({\n match: { stateKey: key },\n events,\n delayMs,\n });\n return this;\n }\n\n onReasoning(pattern: string | RegExp, text: string, opts?: { delayMs?: number }): this {\n const events = buildReasoningResponse(text);\n this.fixtures.push({\n match: { message: pattern },\n events,\n delayMs: opts?.delayMs,\n });\n return this;\n }\n\n onPredicate(\n predicate: (input: AGUIRunAgentInput) => boolean,\n events: AGUIEvent[],\n delayMs?: number,\n ): this {\n this.fixtures.push({\n match: { predicate },\n events,\n delayMs,\n });\n return this;\n }\n\n enableRecording(config: AGUIRecordConfig): this {\n this.recordConfig = config;\n return this;\n }\n\n reset(): this {\n this.fixtures = [];\n this.recordConfig = undefined;\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 if (req.method !== \"POST\" || (pathname !== \"/\" && pathname !== \"\")) {\n return false;\n }\n\n if (this.registry) {\n this.registry.incrementCounter(\"aimock_agui_requests_total\", { method: \"POST\" });\n }\n\n const body = await readBody(req);\n\n let input: AGUIRunAgentInput;\n try {\n input = JSON.parse(body) as AGUIRunAgentInput;\n } catch {\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Invalid JSON body\" }));\n this.journalRequest(req, pathname, 400);\n return true;\n }\n\n const fixture = findFixture(input, this.fixtures);\n\n if (fixture) {\n await writeAGUIEventStream(res, fixture.events, { delayMs: fixture.delayMs });\n this.journalRequest(req, pathname, 200);\n return true;\n }\n\n // No match — if recording is enabled, proxy to upstream\n if (this.recordConfig) {\n const proxied = await proxyAndRecordAGUI(\n req,\n res,\n input,\n this.fixtures,\n this.recordConfig,\n this.logger,\n );\n if (proxied) {\n this.journalRequest(req, pathname, 200);\n return true;\n }\n }\n\n // No match, no recorder — 404\n res.writeHead(404, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"No matching AG-UI fixture\" }));\n this.journalRequest(req, pathname, 404);\n return true;\n }\n\n health(): { status: string; fixtures: number } {\n return {\n status: \"ok\",\n fixtures: this.fixtures.length,\n };\n }\n\n setJournal(journal: Journal): void {\n this.journal = journal;\n }\n\n setBaseUrl(url: string): void {\n this.baseUrl = url;\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(\"AGUIMock 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 const handled = await this.handleRequest(req, res, url.pathname).catch((err) => {\n this.logger.error(`AGUIMock request error: ${err instanceof Error ? err.message : 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 return true;\n });\n if (!handled && !res.headersSent) {\n res.writeHead(404, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Not found\" }));\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(\"AGUIMock 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(\"AGUIMock server not started\");\n }\n return this.baseUrl;\n }\n\n // ---- Private helpers ----\n\n private journalRequest(req: http.IncomingMessage, pathname: string, status: number): void {\n if (this.journal) {\n this.journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? pathname,\n headers: flattenHeaders(req.headers),\n body: null,\n service: \"agui\",\n response: { status, fixture: null },\n });\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"agui-mock.js","names":["http"],"sources":["../src/agui-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 AGUIFixture,\n AGUIMockOptions,\n AGUIRecordConfig,\n AGUIEvent,\n AGUIRunAgentInput,\n} from \"./agui-types.js\";\nimport {\n findFixture,\n buildTextResponse,\n buildToolCallResponse,\n buildStateUpdate,\n buildReasoningResponse,\n writeAGUIEventStream,\n} from \"./agui-handler.js\";\nimport { flattenHeaders, readBody } from \"./helpers.js\";\nimport { proxyAndRecordAGUI } from \"./agui-recorder.js\";\nimport { Logger, type LogLevel } from \"./logger.js\";\n\nexport class AGUIMock implements Mountable {\n private fixtures: AGUIFixture[] = [];\n private server: http.Server | null = null;\n private journal: Journal | null = null;\n private registry: MetricsRegistry | null = null;\n private options: AGUIMockOptions;\n private baseUrl = \"\";\n private recordConfig: AGUIRecordConfig | undefined;\n private logger: Logger;\n\n constructor(options?: AGUIMockOptions) {\n this.options = options ?? {};\n this.logger = new Logger((options?.logLevel as LogLevel) ?? \"warn\");\n }\n\n // ---- Fluent registration API ----\n\n addFixture(fixture: AGUIFixture): this {\n this.fixtures.push(fixture);\n return this;\n }\n\n onMessage(pattern: string | RegExp, text: string, opts?: { delayMs?: number }): this {\n const events = buildTextResponse(text);\n this.fixtures.push({\n match: { message: pattern },\n events,\n delayMs: opts?.delayMs,\n });\n return this;\n }\n\n onRun(pattern: string | RegExp, events: AGUIEvent[], delayMs?: number): this {\n this.fixtures.push({\n match: { message: pattern },\n events,\n delayMs,\n });\n return this;\n }\n\n onToolCall(\n pattern: string | RegExp,\n toolName: string,\n args: string,\n opts?: { result?: string; delayMs?: number },\n ): this {\n const events = buildToolCallResponse(toolName, args, {\n result: opts?.result,\n });\n this.fixtures.push({\n match: { message: pattern },\n events,\n delayMs: opts?.delayMs,\n });\n return this;\n }\n\n onStateKey(key: string, snapshot: Record<string, unknown>, delayMs?: number): this {\n const events = buildStateUpdate(snapshot);\n this.fixtures.push({\n match: { stateKey: key },\n events,\n delayMs,\n });\n return this;\n }\n\n onReasoning(pattern: string | RegExp, text: string, opts?: { delayMs?: number }): this {\n const events = buildReasoningResponse(text);\n this.fixtures.push({\n match: { message: pattern },\n events,\n delayMs: opts?.delayMs,\n });\n return this;\n }\n\n onPredicate(\n predicate: (input: AGUIRunAgentInput) => boolean,\n events: AGUIEvent[],\n delayMs?: number,\n ): this {\n this.fixtures.push({\n match: { predicate },\n events,\n delayMs,\n });\n return this;\n }\n\n enableRecording(config: AGUIRecordConfig): this {\n this.recordConfig = config;\n return this;\n }\n\n reset(): this {\n this.fixtures = [];\n this.recordConfig = undefined;\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 if (req.method !== \"POST\" || (pathname !== \"/\" && pathname !== \"\")) {\n return false;\n }\n\n if (this.registry) {\n this.registry.incrementCounter(\"aimock_agui_requests_total\", { method: \"POST\" });\n }\n\n let body: string;\n try {\n body = await readBody(req);\n } catch (err) {\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n const detail = err instanceof Error ? err.message : \"body read failed\";\n res.end(JSON.stringify({ error: `Failed to read request body: ${detail}` }));\n this.journalRequest(req, pathname, 400);\n return true;\n }\n\n let input: AGUIRunAgentInput;\n try {\n input = JSON.parse(body) as AGUIRunAgentInput;\n } catch (err) {\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n const detail = err instanceof Error ? err.message : \"unknown parse error\";\n res.end(JSON.stringify({ error: `Invalid JSON body: ${detail}` }));\n this.journalRequest(req, pathname, 400);\n return true;\n }\n\n const fixture = findFixture(input, this.fixtures);\n\n if (fixture) {\n await writeAGUIEventStream(res, fixture.events, { delayMs: fixture.delayMs });\n this.journalRequest(req, pathname, 200);\n return true;\n }\n\n // No match — if recording is enabled, proxy to upstream\n if (this.recordConfig) {\n const result = await proxyAndRecordAGUI(\n req,\n res,\n input,\n this.fixtures,\n this.recordConfig,\n this.logger,\n );\n if (result !== false) {\n this.journalRequest(req, pathname, result);\n return true;\n }\n }\n\n // No match, no recorder — 404\n res.writeHead(404, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"No matching AG-UI fixture\" }));\n this.journalRequest(req, pathname, 404);\n return true;\n }\n\n health(): { status: string; fixtures: number } {\n return {\n status: \"ok\",\n fixtures: this.fixtures.length,\n };\n }\n\n setJournal(journal: Journal): void {\n this.journal = journal;\n }\n\n setBaseUrl(url: string): void {\n this.baseUrl = url;\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(\"AGUIMock 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 const handled = await this.handleRequest(req, res, url.pathname).catch((err) => {\n this.logger.error(`AGUIMock request error: ${err instanceof Error ? err.message : 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 return true;\n });\n if (!handled && !res.headersSent) {\n res.writeHead(404, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Not found\" }));\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(\"AGUIMock 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(\"AGUIMock server not started\");\n }\n return this.baseUrl;\n }\n\n // ---- Private helpers ----\n\n private journalRequest(req: http.IncomingMessage, pathname: string, status: number): void {\n if (this.journal) {\n this.journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? pathname,\n headers: flattenHeaders(req.headers),\n body: null,\n service: \"agui\",\n response: { status, fixture: null },\n });\n }\n }\n}\n"],"mappings":"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"}
|
package/dist/agui-recorder.cjs
CHANGED
|
@@ -4,12 +4,12 @@ let node_http = require("node:http");
|
|
|
4
4
|
node_http = require_runtime.__toESM(node_http);
|
|
5
5
|
let node_crypto = require("node:crypto");
|
|
6
6
|
node_crypto = require_runtime.__toESM(node_crypto);
|
|
7
|
-
let node_path = require("node:path");
|
|
8
|
-
node_path = require_runtime.__toESM(node_path);
|
|
9
|
-
let node_fs = require("node:fs");
|
|
10
|
-
node_fs = require_runtime.__toESM(node_fs);
|
|
11
7
|
let node_https = require("node:https");
|
|
12
8
|
node_https = require_runtime.__toESM(node_https);
|
|
9
|
+
let node_fs = require("node:fs");
|
|
10
|
+
node_fs = require_runtime.__toESM(node_fs);
|
|
11
|
+
let node_path = require("node:path");
|
|
12
|
+
node_path = require_runtime.__toESM(node_path);
|
|
13
13
|
|
|
14
14
|
//#region src/agui-recorder.ts
|
|
15
15
|
/**
|
|
@@ -17,7 +17,8 @@ node_https = require_runtime.__toESM(node_https);
|
|
|
17
17
|
* SSE event stream as a fixture on disk and in memory, and relay the
|
|
18
18
|
* response back to the original client in real time.
|
|
19
19
|
*
|
|
20
|
-
* Returns
|
|
20
|
+
* Returns the HTTP status code written to the client if the request was proxied,
|
|
21
|
+
* or `false` if no upstream is configured.
|
|
21
22
|
*/
|
|
22
23
|
async function proxyAndRecordAGUI(req, res, input, fixtures, config, logger) {
|
|
23
24
|
if (!config.upstream) {
|
|
@@ -31,7 +32,7 @@ async function proxyAndRecordAGUI(req, res, input, fixtures, config, logger) {
|
|
|
31
32
|
logger.error(`Invalid upstream AG-UI URL: ${config.upstream}`);
|
|
32
33
|
res.writeHead(502, { "Content-Type": "application/json" });
|
|
33
34
|
res.end(JSON.stringify({ error: "Invalid upstream AG-UI URL" }));
|
|
34
|
-
return
|
|
35
|
+
return 502;
|
|
35
36
|
}
|
|
36
37
|
logger.warn(`NO AG-UI FIXTURE MATCH — proxying to ${config.upstream}`);
|
|
37
38
|
const forwardHeaders = {
|
|
@@ -43,8 +44,9 @@ async function proxyAndRecordAGUI(req, res, input, fixtures, config, logger) {
|
|
|
43
44
|
const apiKey = req.headers["x-api-key"];
|
|
44
45
|
if (apiKey) forwardHeaders["x-api-key"] = Array.isArray(apiKey) ? apiKey.join(", ") : apiKey;
|
|
45
46
|
const requestBody = JSON.stringify(input);
|
|
47
|
+
let status;
|
|
46
48
|
try {
|
|
47
|
-
await teeUpstreamStream(target, forwardHeaders, requestBody, res, input, fixtures, config, logger);
|
|
49
|
+
status = await teeUpstreamStream(target, forwardHeaders, requestBody, res, input, fixtures, config, logger);
|
|
48
50
|
} catch (err) {
|
|
49
51
|
const msg = err instanceof Error ? err.message : "Unknown proxy error";
|
|
50
52
|
logger.error(`AG-UI proxy request failed: ${msg}`);
|
|
@@ -52,8 +54,9 @@ async function proxyAndRecordAGUI(req, res, input, fixtures, config, logger) {
|
|
|
52
54
|
res.writeHead(502, { "Content-Type": "application/json" });
|
|
53
55
|
res.end(JSON.stringify({ error: "Upstream AG-UI agent unreachable" }));
|
|
54
56
|
}
|
|
57
|
+
status = 502;
|
|
55
58
|
}
|
|
56
|
-
return
|
|
59
|
+
return status;
|
|
57
60
|
}
|
|
58
61
|
function teeUpstreamStream(target, headers, body, clientRes, input, fixtures, config, logger) {
|
|
59
62
|
return new Promise((resolve, reject) => {
|
|
@@ -67,16 +70,27 @@ function teeUpstreamStream(target, headers, body, clientRes, input, fixtures, co
|
|
|
67
70
|
"Content-Length": Buffer.byteLength(body).toString()
|
|
68
71
|
}
|
|
69
72
|
}, (upstreamRes) => {
|
|
70
|
-
|
|
73
|
+
const upstreamStatus = upstreamRes.statusCode ?? 200;
|
|
74
|
+
if (!clientRes.headersSent) if (upstreamStatus >= 200 && upstreamStatus < 300) clientRes.writeHead(upstreamStatus, {
|
|
71
75
|
"Content-Type": "text/event-stream",
|
|
72
76
|
"Cache-Control": "no-cache",
|
|
73
77
|
Connection: "keep-alive"
|
|
74
78
|
});
|
|
79
|
+
else {
|
|
80
|
+
const ct = upstreamRes.headers["content-type"] || "application/json";
|
|
81
|
+
clientRes.writeHead(upstreamStatus, { "Content-Type": ct });
|
|
82
|
+
}
|
|
75
83
|
const chunks = [];
|
|
84
|
+
let clientWriteFailed = false;
|
|
76
85
|
upstreamRes.on("data", (chunk) => {
|
|
77
86
|
try {
|
|
78
87
|
clientRes.write(chunk);
|
|
79
|
-
} catch {
|
|
88
|
+
} catch (err) {
|
|
89
|
+
if (!clientWriteFailed) {
|
|
90
|
+
clientWriteFailed = true;
|
|
91
|
+
logger?.warn("Client write failed during proxy relay:", err instanceof Error ? err.message : String(err));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
80
94
|
chunks.push(chunk);
|
|
81
95
|
});
|
|
82
96
|
upstreamRes.on("error", (err) => {
|
|
@@ -90,29 +104,31 @@ function teeUpstreamStream(target, headers, body, clientRes, input, fixtures, co
|
|
|
90
104
|
if (!clientRes.writableEnded) clientRes.end();
|
|
91
105
|
const events = parseSSEEvents(Buffer.concat(chunks).toString(), logger);
|
|
92
106
|
const message = require_agui_handler.extractLastUserMessage(input);
|
|
93
|
-
if (!message) logger.warn("Recorded AG-UI fixture has no message match — it will match ALL requests");
|
|
94
107
|
const fixture = {
|
|
95
|
-
match: { message:
|
|
108
|
+
match: message ? { message } : { predicate: (inp) => !inp.messages?.length || !inp.messages.some((m) => m.role === "user") },
|
|
96
109
|
events
|
|
97
110
|
};
|
|
111
|
+
if (!message) logger.warn("Recorded AG-UI fixture has no user message — will use __NO_USER_MESSAGE__ sentinel on disk");
|
|
98
112
|
if (!config.proxyOnly) {
|
|
99
113
|
fixtures.push(fixture);
|
|
114
|
+
const serializableFixture = {
|
|
115
|
+
match: fixture.match.predicate ? { message: "__NO_USER_MESSAGE__" } : fixture.match,
|
|
116
|
+
events: fixture.events,
|
|
117
|
+
...fixture.delayMs !== void 0 ? { delayMs: fixture.delayMs } : {}
|
|
118
|
+
};
|
|
100
119
|
const fixturePath = config.fixturePath ?? "./fixtures/agui-recorded";
|
|
101
120
|
const filename = `agui-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}-${node_crypto.randomUUID().slice(0, 8)}.json`;
|
|
102
121
|
const filepath = node_path.join(fixturePath, filename);
|
|
103
122
|
try {
|
|
104
123
|
node_fs.mkdirSync(fixturePath, { recursive: true });
|
|
105
|
-
node_fs.writeFileSync(filepath, JSON.stringify({ fixtures: [
|
|
106
|
-
match: fixture.match,
|
|
107
|
-
events: fixture.events
|
|
108
|
-
}] }, null, 2), "utf-8");
|
|
124
|
+
node_fs.writeFileSync(filepath, JSON.stringify({ fixtures: [serializableFixture] }, null, 2), "utf-8");
|
|
109
125
|
logger.warn(`AG-UI response recorded → ${filepath}`);
|
|
110
126
|
} catch (err) {
|
|
111
127
|
const msg = err instanceof Error ? err.message : "Unknown filesystem error";
|
|
112
128
|
logger.error(`Failed to save AG-UI fixture to disk: ${msg} (fixture retained in memory)`);
|
|
113
129
|
}
|
|
114
130
|
} else logger.info("Proxied AG-UI request (proxy-only mode)");
|
|
115
|
-
resolve();
|
|
131
|
+
resolve(upstreamStatus);
|
|
116
132
|
});
|
|
117
133
|
});
|
|
118
134
|
upstreamReq.on("timeout", () => {
|
|
@@ -138,11 +154,16 @@ function parseSSEEvents(text, logger) {
|
|
|
138
154
|
const blocks = text.split("\n\n");
|
|
139
155
|
for (const block of blocks) {
|
|
140
156
|
const lines = block.split("\n");
|
|
141
|
-
for (const line of lines) if (line.startsWith("data:
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
157
|
+
for (const line of lines) if (line.startsWith("data:")) {
|
|
158
|
+
const payload = line.startsWith("data: ") ? line.slice(6) : line.slice(5);
|
|
159
|
+
try {
|
|
160
|
+
const parsed = JSON.parse(payload);
|
|
161
|
+
events.push(parsed);
|
|
162
|
+
} catch (err) {
|
|
163
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
164
|
+
if (logger) logger.warn(`Skipping unparseable SSE data line: ${payload.slice(0, 200)}`);
|
|
165
|
+
else console.warn(`Skipping unparseable SSE data line: ${msg}`);
|
|
166
|
+
}
|
|
146
167
|
}
|
|
147
168
|
}
|
|
148
169
|
return events;
|
|
@@ -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 `true` if the request was proxied, `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<boolean> {\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 true;\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 try {\n 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 }\n\n return true;\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<void> {\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 // Set SSE headers on the client response\n if (!clientRes.headersSent) {\n clientRes.writeHead(upstreamRes.statusCode ?? 200, {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n });\n }\n\n const chunks: Buffer[] = [];\n\n upstreamRes.on(\"data\", (chunk: Buffer) => {\n // Relay to client in real time\n try {\n clientRes.write(chunk);\n } catch {\n // Client connection may have closed — continue buffering for recording\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 if (!message) {\n logger.warn(\"Recorded AG-UI fixture has no message match — it will match ALL requests\");\n }\n const fixture: AGUIFixture = {\n match: { message: message || undefined },\n events,\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\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(\n { fixtures: [{ match: fixture.match, events: fixture.events }] },\n null,\n 2,\n ),\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();\n });\n },\n );\n\n upstreamReq.on(\"timeout\", () => {\n if (!clientRes.writableEnded) clientRes.end();\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 try {\n const parsed = JSON.parse(line.slice(6)) as AGUIEvent;\n events.push(parsed);\n } catch {\n logger?.warn(`Skipping unparseable SSE data line: ${line.slice(0, 200)}`);\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 {\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 // Set appropriate headers on the client response\n if (!clientRes.headersSent) {\n if (upstreamStatus >= 200 && upstreamStatus < 300) {\n clientRes.writeHead(upstreamStatus, {\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(upstreamStatus, { \"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(upstreamStatus);\n });\n },\n );\n\n upstreamReq.on(\"timeout\", () => {\n if (!clientRes.writableEnded) clientRes.end();\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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"}
|
package/dist/agui-recorder.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Logger } from "./logger.cjs";
|
|
2
2
|
import { AGUIFixture, AGUIRecordConfig, AGUIRunAgentInput } from "./agui-types.cjs";
|
|
3
|
-
import * as http from "node:http";
|
|
3
|
+
import * as http$1 from "node:http";
|
|
4
4
|
|
|
5
5
|
//#region src/agui-recorder.d.ts
|
|
6
6
|
|
|
@@ -9,9 +9,10 @@ import * as http from "node:http";
|
|
|
9
9
|
* SSE event stream as a fixture on disk and in memory, and relay the
|
|
10
10
|
* response back to the original client in real time.
|
|
11
11
|
*
|
|
12
|
-
* Returns
|
|
12
|
+
* Returns the HTTP status code written to the client if the request was proxied,
|
|
13
|
+
* or `false` if no upstream is configured.
|
|
13
14
|
*/
|
|
14
|
-
declare function proxyAndRecordAGUI(req: http.IncomingMessage, res: http.ServerResponse, input: AGUIRunAgentInput, fixtures: AGUIFixture[], config: AGUIRecordConfig, logger: Logger): Promise<
|
|
15
|
+
declare function proxyAndRecordAGUI(req: http$1.IncomingMessage, res: http$1.ServerResponse, input: AGUIRunAgentInput, fixtures: AGUIFixture[], config: AGUIRecordConfig, logger: Logger): Promise<number | false>;
|
|
15
16
|
//# sourceMappingURL=agui-recorder.d.ts.map
|
|
16
17
|
|
|
17
18
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agui-recorder.d.cts","names":[],"sources":["../src/agui-recorder.ts"],"sourcesContent":[],"mappings":";;;;;;;;
|
|
1
|
+
{"version":3,"file":"agui-recorder.d.cts","names":[],"sources":["../src/agui-recorder.ts"],"sourcesContent":[],"mappings":";;;;;;;;AAiBA;;;;;;AAKU,iBALY,kBAAA,CAKZ,GAAA,EAJH,MAAA,CAAK,eAIF,EAAA,GAAA,EAHH,MAAA,CAAK,cAGF,EAAA,KAAA,EAFD,iBAEC,EAAA,QAAA,EADE,WACF,EAAA,EAAA,MAAA,EAAA,gBAAA,EAAA,MAAA,EACA,MADA,CAAA,EAEP,OAFO,CAAA,MAAA,GAAA,KAAA,CAAA"}
|
package/dist/agui-recorder.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Logger } from "./logger.js";
|
|
2
2
|
import { AGUIFixture, AGUIRecordConfig, AGUIRunAgentInput } from "./agui-types.js";
|
|
3
|
-
import * as http from "node:http";
|
|
3
|
+
import * as http$1 from "node:http";
|
|
4
4
|
|
|
5
5
|
//#region src/agui-recorder.d.ts
|
|
6
6
|
|
|
@@ -9,9 +9,10 @@ import * as http from "node:http";
|
|
|
9
9
|
* SSE event stream as a fixture on disk and in memory, and relay the
|
|
10
10
|
* response back to the original client in real time.
|
|
11
11
|
*
|
|
12
|
-
* Returns
|
|
12
|
+
* Returns the HTTP status code written to the client if the request was proxied,
|
|
13
|
+
* or `false` if no upstream is configured.
|
|
13
14
|
*/
|
|
14
|
-
declare function proxyAndRecordAGUI(req: http.IncomingMessage, res: http.ServerResponse, input: AGUIRunAgentInput, fixtures: AGUIFixture[], config: AGUIRecordConfig, logger: Logger): Promise<
|
|
15
|
+
declare function proxyAndRecordAGUI(req: http$1.IncomingMessage, res: http$1.ServerResponse, input: AGUIRunAgentInput, fixtures: AGUIFixture[], config: AGUIRecordConfig, logger: Logger): Promise<number | false>;
|
|
15
16
|
//# sourceMappingURL=agui-recorder.d.ts.map
|
|
16
17
|
|
|
17
18
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agui-recorder.d.ts","names":[],"sources":["../src/agui-recorder.ts"],"sourcesContent":[],"mappings":";;;;;;;;
|
|
1
|
+
{"version":3,"file":"agui-recorder.d.ts","names":[],"sources":["../src/agui-recorder.ts"],"sourcesContent":[],"mappings":";;;;;;;;AAiBA;;;;;;AAKU,iBALY,kBAAA,CAKZ,GAAA,EAJH,MAAA,CAAK,eAIF,EAAA,GAAA,EAHH,MAAA,CAAK,cAGF,EAAA,KAAA,EAFD,iBAEC,EAAA,QAAA,EADE,WACF,EAAA,EAAA,MAAA,EAAA,gBAAA,EAAA,MAAA,EACA,MADA,CAAA,EAEP,OAFO,CAAA,MAAA,GAAA,KAAA,CAAA"}
|
package/dist/agui-recorder.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { extractLastUserMessage } from "./agui-handler.js";
|
|
2
|
-
import * as http from "node:http";
|
|
3
|
-
import * as crypto from "node:crypto";
|
|
4
|
-
import * as path from "node:path";
|
|
5
|
-
import * as fs from "node:fs";
|
|
2
|
+
import * as http$1 from "node:http";
|
|
3
|
+
import * as crypto$1 from "node:crypto";
|
|
6
4
|
import * as https from "node:https";
|
|
5
|
+
import * as fs from "node:fs";
|
|
6
|
+
import * as path from "node:path";
|
|
7
7
|
|
|
8
8
|
//#region src/agui-recorder.ts
|
|
9
9
|
/**
|
|
@@ -11,7 +11,8 @@ import * as https from "node:https";
|
|
|
11
11
|
* SSE event stream as a fixture on disk and in memory, and relay the
|
|
12
12
|
* response back to the original client in real time.
|
|
13
13
|
*
|
|
14
|
-
* Returns
|
|
14
|
+
* Returns the HTTP status code written to the client if the request was proxied,
|
|
15
|
+
* or `false` if no upstream is configured.
|
|
15
16
|
*/
|
|
16
17
|
async function proxyAndRecordAGUI(req, res, input, fixtures, config, logger) {
|
|
17
18
|
if (!config.upstream) {
|
|
@@ -25,7 +26,7 @@ async function proxyAndRecordAGUI(req, res, input, fixtures, config, logger) {
|
|
|
25
26
|
logger.error(`Invalid upstream AG-UI URL: ${config.upstream}`);
|
|
26
27
|
res.writeHead(502, { "Content-Type": "application/json" });
|
|
27
28
|
res.end(JSON.stringify({ error: "Invalid upstream AG-UI URL" }));
|
|
28
|
-
return
|
|
29
|
+
return 502;
|
|
29
30
|
}
|
|
30
31
|
logger.warn(`NO AG-UI FIXTURE MATCH — proxying to ${config.upstream}`);
|
|
31
32
|
const forwardHeaders = {
|
|
@@ -37,8 +38,9 @@ async function proxyAndRecordAGUI(req, res, input, fixtures, config, logger) {
|
|
|
37
38
|
const apiKey = req.headers["x-api-key"];
|
|
38
39
|
if (apiKey) forwardHeaders["x-api-key"] = Array.isArray(apiKey) ? apiKey.join(", ") : apiKey;
|
|
39
40
|
const requestBody = JSON.stringify(input);
|
|
41
|
+
let status;
|
|
40
42
|
try {
|
|
41
|
-
await teeUpstreamStream(target, forwardHeaders, requestBody, res, input, fixtures, config, logger);
|
|
43
|
+
status = await teeUpstreamStream(target, forwardHeaders, requestBody, res, input, fixtures, config, logger);
|
|
42
44
|
} catch (err) {
|
|
43
45
|
const msg = err instanceof Error ? err.message : "Unknown proxy error";
|
|
44
46
|
logger.error(`AG-UI proxy request failed: ${msg}`);
|
|
@@ -46,12 +48,13 @@ async function proxyAndRecordAGUI(req, res, input, fixtures, config, logger) {
|
|
|
46
48
|
res.writeHead(502, { "Content-Type": "application/json" });
|
|
47
49
|
res.end(JSON.stringify({ error: "Upstream AG-UI agent unreachable" }));
|
|
48
50
|
}
|
|
51
|
+
status = 502;
|
|
49
52
|
}
|
|
50
|
-
return
|
|
53
|
+
return status;
|
|
51
54
|
}
|
|
52
55
|
function teeUpstreamStream(target, headers, body, clientRes, input, fixtures, config, logger) {
|
|
53
56
|
return new Promise((resolve, reject) => {
|
|
54
|
-
const transport = target.protocol === "https:" ? https : http;
|
|
57
|
+
const transport = target.protocol === "https:" ? https : http$1;
|
|
55
58
|
const UPSTREAM_TIMEOUT_MS = 3e4;
|
|
56
59
|
const upstreamReq = transport.request(target, {
|
|
57
60
|
method: "POST",
|
|
@@ -61,16 +64,27 @@ function teeUpstreamStream(target, headers, body, clientRes, input, fixtures, co
|
|
|
61
64
|
"Content-Length": Buffer.byteLength(body).toString()
|
|
62
65
|
}
|
|
63
66
|
}, (upstreamRes) => {
|
|
64
|
-
|
|
67
|
+
const upstreamStatus = upstreamRes.statusCode ?? 200;
|
|
68
|
+
if (!clientRes.headersSent) if (upstreamStatus >= 200 && upstreamStatus < 300) clientRes.writeHead(upstreamStatus, {
|
|
65
69
|
"Content-Type": "text/event-stream",
|
|
66
70
|
"Cache-Control": "no-cache",
|
|
67
71
|
Connection: "keep-alive"
|
|
68
72
|
});
|
|
73
|
+
else {
|
|
74
|
+
const ct = upstreamRes.headers["content-type"] || "application/json";
|
|
75
|
+
clientRes.writeHead(upstreamStatus, { "Content-Type": ct });
|
|
76
|
+
}
|
|
69
77
|
const chunks = [];
|
|
78
|
+
let clientWriteFailed = false;
|
|
70
79
|
upstreamRes.on("data", (chunk) => {
|
|
71
80
|
try {
|
|
72
81
|
clientRes.write(chunk);
|
|
73
|
-
} catch {
|
|
82
|
+
} catch (err) {
|
|
83
|
+
if (!clientWriteFailed) {
|
|
84
|
+
clientWriteFailed = true;
|
|
85
|
+
logger?.warn("Client write failed during proxy relay:", err instanceof Error ? err.message : String(err));
|
|
86
|
+
}
|
|
87
|
+
}
|
|
74
88
|
chunks.push(chunk);
|
|
75
89
|
});
|
|
76
90
|
upstreamRes.on("error", (err) => {
|
|
@@ -84,29 +98,31 @@ function teeUpstreamStream(target, headers, body, clientRes, input, fixtures, co
|
|
|
84
98
|
if (!clientRes.writableEnded) clientRes.end();
|
|
85
99
|
const events = parseSSEEvents(Buffer.concat(chunks).toString(), logger);
|
|
86
100
|
const message = extractLastUserMessage(input);
|
|
87
|
-
if (!message) logger.warn("Recorded AG-UI fixture has no message match — it will match ALL requests");
|
|
88
101
|
const fixture = {
|
|
89
|
-
match: { message:
|
|
102
|
+
match: message ? { message } : { predicate: (inp) => !inp.messages?.length || !inp.messages.some((m) => m.role === "user") },
|
|
90
103
|
events
|
|
91
104
|
};
|
|
105
|
+
if (!message) logger.warn("Recorded AG-UI fixture has no user message — will use __NO_USER_MESSAGE__ sentinel on disk");
|
|
92
106
|
if (!config.proxyOnly) {
|
|
93
107
|
fixtures.push(fixture);
|
|
108
|
+
const serializableFixture = {
|
|
109
|
+
match: fixture.match.predicate ? { message: "__NO_USER_MESSAGE__" } : fixture.match,
|
|
110
|
+
events: fixture.events,
|
|
111
|
+
...fixture.delayMs !== void 0 ? { delayMs: fixture.delayMs } : {}
|
|
112
|
+
};
|
|
94
113
|
const fixturePath = config.fixturePath ?? "./fixtures/agui-recorded";
|
|
95
|
-
const filename = `agui-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}-${crypto.randomUUID().slice(0, 8)}.json`;
|
|
114
|
+
const filename = `agui-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}-${crypto$1.randomUUID().slice(0, 8)}.json`;
|
|
96
115
|
const filepath = path.join(fixturePath, filename);
|
|
97
116
|
try {
|
|
98
117
|
fs.mkdirSync(fixturePath, { recursive: true });
|
|
99
|
-
fs.writeFileSync(filepath, JSON.stringify({ fixtures: [
|
|
100
|
-
match: fixture.match,
|
|
101
|
-
events: fixture.events
|
|
102
|
-
}] }, null, 2), "utf-8");
|
|
118
|
+
fs.writeFileSync(filepath, JSON.stringify({ fixtures: [serializableFixture] }, null, 2), "utf-8");
|
|
103
119
|
logger.warn(`AG-UI response recorded → ${filepath}`);
|
|
104
120
|
} catch (err) {
|
|
105
121
|
const msg = err instanceof Error ? err.message : "Unknown filesystem error";
|
|
106
122
|
logger.error(`Failed to save AG-UI fixture to disk: ${msg} (fixture retained in memory)`);
|
|
107
123
|
}
|
|
108
124
|
} else logger.info("Proxied AG-UI request (proxy-only mode)");
|
|
109
|
-
resolve();
|
|
125
|
+
resolve(upstreamStatus);
|
|
110
126
|
});
|
|
111
127
|
});
|
|
112
128
|
upstreamReq.on("timeout", () => {
|
|
@@ -132,11 +148,16 @@ function parseSSEEvents(text, logger) {
|
|
|
132
148
|
const blocks = text.split("\n\n");
|
|
133
149
|
for (const block of blocks) {
|
|
134
150
|
const lines = block.split("\n");
|
|
135
|
-
for (const line of lines) if (line.startsWith("data:
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
151
|
+
for (const line of lines) if (line.startsWith("data:")) {
|
|
152
|
+
const payload = line.startsWith("data: ") ? line.slice(6) : line.slice(5);
|
|
153
|
+
try {
|
|
154
|
+
const parsed = JSON.parse(payload);
|
|
155
|
+
events.push(parsed);
|
|
156
|
+
} catch (err) {
|
|
157
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
158
|
+
if (logger) logger.warn(`Skipping unparseable SSE data line: ${payload.slice(0, 200)}`);
|
|
159
|
+
else console.warn(`Skipping unparseable SSE data line: ${msg}`);
|
|
160
|
+
}
|
|
140
161
|
}
|
|
141
162
|
}
|
|
142
163
|
return events;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agui-recorder.js","names":[],"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 `true` if the request was proxied, `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<boolean> {\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 true;\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 try {\n 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 }\n\n return true;\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<void> {\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 // Set SSE headers on the client response\n if (!clientRes.headersSent) {\n clientRes.writeHead(upstreamRes.statusCode ?? 200, {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n });\n }\n\n const chunks: Buffer[] = [];\n\n upstreamRes.on(\"data\", (chunk: Buffer) => {\n // Relay to client in real time\n try {\n clientRes.write(chunk);\n } catch {\n // Client connection may have closed — continue buffering for recording\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 if (!message) {\n logger.warn(\"Recorded AG-UI fixture has no message match — it will match ALL requests\");\n }\n const fixture: AGUIFixture = {\n match: { message: message || undefined },\n events,\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\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(\n { fixtures: [{ match: fixture.match, events: fixture.events }] },\n null,\n 2,\n ),\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();\n });\n },\n );\n\n upstreamReq.on(\"timeout\", () => {\n if (!clientRes.writableEnded) clientRes.end();\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 try {\n const parsed = JSON.parse(line.slice(6)) as AGUIEvent;\n events.push(parsed);\n } catch {\n logger?.warn(`Skipping unparseable SSE data line: ${line.slice(0, 200)}`);\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 {\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 // Set appropriate headers on the client response\n if (!clientRes.headersSent) {\n if (upstreamStatus >= 200 && upstreamStatus < 300) {\n clientRes.writeHead(upstreamStatus, {\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(upstreamStatus, { \"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(upstreamStatus);\n });\n },\n );\n\n upstreamReq.on(\"timeout\", () => {\n if (!clientRes.writableEnded) clientRes.end();\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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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
const require_agui_handler = require('./agui-handler.cjs');
|
|
3
|
+
const require_agui_recorder = require('./agui-recorder.cjs');
|
|
4
|
+
const require_agui_mock = require('./agui-mock.cjs');
|
|
5
|
+
|
|
6
|
+
exports.AGUIMock = require_agui_mock.AGUIMock;
|
|
7
|
+
exports.buildActivityDelta = require_agui_handler.buildActivityDelta;
|
|
8
|
+
exports.buildActivityResponse = require_agui_handler.buildActivityResponse;
|
|
9
|
+
exports.buildCompositeResponse = require_agui_handler.buildCompositeResponse;
|
|
10
|
+
exports.buildCustomEvent = require_agui_handler.buildCustomEvent;
|
|
11
|
+
exports.buildErrorResponse = require_agui_handler.buildErrorResponse;
|
|
12
|
+
exports.buildMessagesSnapshot = require_agui_handler.buildMessagesSnapshot;
|
|
13
|
+
exports.buildRawEvent = require_agui_handler.buildRawEvent;
|
|
14
|
+
exports.buildReasoningChunk = require_agui_handler.buildReasoningChunk;
|
|
15
|
+
exports.buildReasoningEncryptedValue = require_agui_handler.buildReasoningEncryptedValue;
|
|
16
|
+
exports.buildReasoningResponse = require_agui_handler.buildReasoningResponse;
|
|
17
|
+
exports.buildStateDelta = require_agui_handler.buildStateDelta;
|
|
18
|
+
exports.buildStateUpdate = require_agui_handler.buildStateUpdate;
|
|
19
|
+
exports.buildStepWithText = require_agui_handler.buildStepWithText;
|
|
20
|
+
exports.buildTextChunkResponse = require_agui_handler.buildTextChunkResponse;
|
|
21
|
+
exports.buildTextResponse = require_agui_handler.buildTextResponse;
|
|
22
|
+
exports.buildToolCallChunk = require_agui_handler.buildToolCallChunk;
|
|
23
|
+
exports.buildToolCallResponse = require_agui_handler.buildToolCallResponse;
|
|
24
|
+
exports.extractLastUserMessage = require_agui_handler.extractLastUserMessage;
|
|
25
|
+
exports.findFixture = require_agui_handler.findFixture;
|
|
26
|
+
exports.matchesFixture = require_agui_handler.matchesFixture;
|
|
27
|
+
exports.proxyAndRecordAGUI = require_agui_recorder.proxyAndRecordAGUI;
|
|
28
|
+
exports.writeAGUIEventStream = require_agui_handler.writeAGUIEventStream;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { AGUIActivityDeltaEvent, AGUIActivitySnapshotEvent, AGUIBaseEvent, AGUICustomEvent, AGUIEvent, AGUIEventType, AGUIFixture, AGUIFixtureMatch, AGUIInterrupt, AGUIMessage, AGUIMessageRole, AGUIMessagesSnapshotEvent, AGUIMockOptions, AGUIRawEvent, AGUIReasoningEncryptedValueEvent, AGUIReasoningEncryptedValueSubtype, AGUIReasoningEndEvent, AGUIReasoningMessageChunkEvent, AGUIReasoningMessageContentEvent, AGUIReasoningMessageEndEvent, AGUIReasoningMessageStartEvent, AGUIReasoningStartEvent, AGUIRecordConfig, AGUIResumeEntry, AGUIRunAgentInput, AGUIRunErrorEvent, AGUIRunFinishedEvent, AGUIRunFinishedOutcome, AGUIRunStartedEvent, AGUIStateDeltaEvent, AGUIStateSnapshotEvent, AGUIStepFinishedEvent, AGUIStepStartedEvent, AGUITextMessageChunkEvent, AGUITextMessageContentEvent, AGUITextMessageEndEvent, AGUITextMessageRole, AGUITextMessageStartEvent, AGUIThinkingEndEvent, AGUIThinkingStartEvent, AGUIThinkingTextMessageContentEvent, AGUIThinkingTextMessageEndEvent, AGUIThinkingTextMessageStartEvent, AGUIToolCall, AGUIToolCallArgsEvent, AGUIToolCallChunkEvent, AGUIToolCallEndEvent, AGUIToolCallResultEvent, AGUIToolCallStartEvent, AGUIToolDefinition } from "./agui-types.cjs";
|
|
2
|
+
import { AGUIMock } from "./agui-mock.cjs";
|
|
3
|
+
import { AGUIBuildOpts, buildActivityDelta, buildActivityResponse, buildCompositeResponse, buildCustomEvent, buildErrorResponse, buildMessagesSnapshot, buildRawEvent, buildReasoningChunk, buildReasoningEncryptedValue, buildReasoningResponse, buildStateDelta, buildStateUpdate, buildStepWithText, buildTextChunkResponse, buildTextResponse, buildToolCallChunk, buildToolCallResponse, extractLastUserMessage, findFixture, matchesFixture, writeAGUIEventStream } from "./agui-handler.cjs";
|
|
4
|
+
import { proxyAndRecordAGUI } from "./agui-recorder.cjs";
|
|
5
|
+
export { type AGUIActivityDeltaEvent, type AGUIActivitySnapshotEvent, type AGUIBaseEvent, type AGUIBuildOpts, type AGUICustomEvent, type AGUIEvent, type AGUIEventType, type AGUIFixture, type AGUIFixtureMatch, type AGUIInterrupt, type AGUIMessage, type AGUIMessageRole, type AGUIMessagesSnapshotEvent, AGUIMock, type AGUIMockOptions, type AGUIRawEvent, type AGUIReasoningEncryptedValueEvent, type AGUIReasoningEncryptedValueSubtype, type AGUIReasoningEndEvent, type AGUIReasoningMessageChunkEvent, type AGUIReasoningMessageContentEvent, type AGUIReasoningMessageEndEvent, type AGUIReasoningMessageStartEvent, type AGUIReasoningStartEvent, type AGUIRecordConfig, type AGUIResumeEntry, type AGUIRunAgentInput, type AGUIRunErrorEvent, type AGUIRunFinishedEvent, type AGUIRunFinishedOutcome, type AGUIRunStartedEvent, type AGUIStateDeltaEvent, type AGUIStateSnapshotEvent, type AGUIStepFinishedEvent, type AGUIStepStartedEvent, type AGUITextMessageChunkEvent, type AGUITextMessageContentEvent, type AGUITextMessageEndEvent, type AGUITextMessageRole, type AGUITextMessageStartEvent, type AGUIThinkingEndEvent, type AGUIThinkingStartEvent, type AGUIThinkingTextMessageContentEvent, type AGUIThinkingTextMessageEndEvent, type AGUIThinkingTextMessageStartEvent, type AGUIToolCall, type AGUIToolCallArgsEvent, type AGUIToolCallChunkEvent, type AGUIToolCallEndEvent, type AGUIToolCallResultEvent, type AGUIToolCallStartEvent, type AGUIToolDefinition, buildActivityDelta, buildActivityResponse, buildCompositeResponse, buildCustomEvent, buildErrorResponse, buildMessagesSnapshot, buildRawEvent, buildReasoningChunk, buildReasoningEncryptedValue, buildReasoningResponse, buildStateDelta, buildStateUpdate, buildStepWithText, buildTextChunkResponse, buildTextResponse, buildToolCallChunk, buildToolCallResponse, extractLastUserMessage, findFixture, matchesFixture, proxyAndRecordAGUI, writeAGUIEventStream };
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { AGUIActivityDeltaEvent, AGUIActivitySnapshotEvent, AGUIBaseEvent, AGUICustomEvent, AGUIEvent, AGUIEventType, AGUIFixture, AGUIFixtureMatch, AGUIInterrupt, AGUIMessage, AGUIMessageRole, AGUIMessagesSnapshotEvent, AGUIMockOptions, AGUIRawEvent, AGUIReasoningEncryptedValueEvent, AGUIReasoningEncryptedValueSubtype, AGUIReasoningEndEvent, AGUIReasoningMessageChunkEvent, AGUIReasoningMessageContentEvent, AGUIReasoningMessageEndEvent, AGUIReasoningMessageStartEvent, AGUIReasoningStartEvent, AGUIRecordConfig, AGUIResumeEntry, AGUIRunAgentInput, AGUIRunErrorEvent, AGUIRunFinishedEvent, AGUIRunFinishedOutcome, AGUIRunStartedEvent, AGUIStateDeltaEvent, AGUIStateSnapshotEvent, AGUIStepFinishedEvent, AGUIStepStartedEvent, AGUITextMessageChunkEvent, AGUITextMessageContentEvent, AGUITextMessageEndEvent, AGUITextMessageRole, AGUITextMessageStartEvent, AGUIThinkingEndEvent, AGUIThinkingStartEvent, AGUIThinkingTextMessageContentEvent, AGUIThinkingTextMessageEndEvent, AGUIThinkingTextMessageStartEvent, AGUIToolCall, AGUIToolCallArgsEvent, AGUIToolCallChunkEvent, AGUIToolCallEndEvent, AGUIToolCallResultEvent, AGUIToolCallStartEvent, AGUIToolDefinition } from "./agui-types.js";
|
|
2
|
+
import { AGUIMock } from "./agui-mock.js";
|
|
3
|
+
import { AGUIBuildOpts, buildActivityDelta, buildActivityResponse, buildCompositeResponse, buildCustomEvent, buildErrorResponse, buildMessagesSnapshot, buildRawEvent, buildReasoningChunk, buildReasoningEncryptedValue, buildReasoningResponse, buildStateDelta, buildStateUpdate, buildStepWithText, buildTextChunkResponse, buildTextResponse, buildToolCallChunk, buildToolCallResponse, extractLastUserMessage, findFixture, matchesFixture, writeAGUIEventStream } from "./agui-handler.js";
|
|
4
|
+
import { proxyAndRecordAGUI } from "./agui-recorder.js";
|
|
5
|
+
export { type AGUIActivityDeltaEvent, type AGUIActivitySnapshotEvent, type AGUIBaseEvent, type AGUIBuildOpts, type AGUICustomEvent, type AGUIEvent, type AGUIEventType, type AGUIFixture, type AGUIFixtureMatch, type AGUIInterrupt, type AGUIMessage, type AGUIMessageRole, type AGUIMessagesSnapshotEvent, AGUIMock, type AGUIMockOptions, type AGUIRawEvent, type AGUIReasoningEncryptedValueEvent, type AGUIReasoningEncryptedValueSubtype, type AGUIReasoningEndEvent, type AGUIReasoningMessageChunkEvent, type AGUIReasoningMessageContentEvent, type AGUIReasoningMessageEndEvent, type AGUIReasoningMessageStartEvent, type AGUIReasoningStartEvent, type AGUIRecordConfig, type AGUIResumeEntry, type AGUIRunAgentInput, type AGUIRunErrorEvent, type AGUIRunFinishedEvent, type AGUIRunFinishedOutcome, type AGUIRunStartedEvent, type AGUIStateDeltaEvent, type AGUIStateSnapshotEvent, type AGUIStepFinishedEvent, type AGUIStepStartedEvent, type AGUITextMessageChunkEvent, type AGUITextMessageContentEvent, type AGUITextMessageEndEvent, type AGUITextMessageRole, type AGUITextMessageStartEvent, type AGUIThinkingEndEvent, type AGUIThinkingStartEvent, type AGUIThinkingTextMessageContentEvent, type AGUIThinkingTextMessageEndEvent, type AGUIThinkingTextMessageStartEvent, type AGUIToolCall, type AGUIToolCallArgsEvent, type AGUIToolCallChunkEvent, type AGUIToolCallEndEvent, type AGUIToolCallResultEvent, type AGUIToolCallStartEvent, type AGUIToolDefinition, buildActivityDelta, buildActivityResponse, buildCompositeResponse, buildCustomEvent, buildErrorResponse, buildMessagesSnapshot, buildRawEvent, buildReasoningChunk, buildReasoningEncryptedValue, buildReasoningResponse, buildStateDelta, buildStateUpdate, buildStepWithText, buildTextChunkResponse, buildTextResponse, buildToolCallChunk, buildToolCallResponse, extractLastUserMessage, findFixture, matchesFixture, proxyAndRecordAGUI, writeAGUIEventStream };
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { buildActivityDelta, buildActivityResponse, buildCompositeResponse, buildCustomEvent, buildErrorResponse, buildMessagesSnapshot, buildRawEvent, buildReasoningChunk, buildReasoningEncryptedValue, buildReasoningResponse, buildStateDelta, buildStateUpdate, buildStepWithText, buildTextChunkResponse, buildTextResponse, buildToolCallChunk, buildToolCallResponse, extractLastUserMessage, findFixture, matchesFixture, writeAGUIEventStream } from "./agui-handler.js";
|
|
2
|
+
import { proxyAndRecordAGUI } from "./agui-recorder.js";
|
|
3
|
+
import { AGUIMock } from "./agui-mock.js";
|
|
4
|
+
|
|
5
|
+
export { AGUIMock, buildActivityDelta, buildActivityResponse, buildCompositeResponse, buildCustomEvent, buildErrorResponse, buildMessagesSnapshot, buildRawEvent, buildReasoningChunk, buildReasoningEncryptedValue, buildReasoningResponse, buildStateDelta, buildStateUpdate, buildStepWithText, buildTextChunkResponse, buildTextResponse, buildToolCallChunk, buildToolCallResponse, extractLastUserMessage, findFixture, matchesFixture, proxyAndRecordAGUI, writeAGUIEventStream };
|