@copilotkit/aimock 1.37.4 → 1.39.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 +48 -0
- package/README.md +50 -11
- package/dist/agui-handler.cjs +20 -18
- package/dist/agui-handler.cjs.map +1 -1
- package/dist/agui-handler.d.cts +8 -1
- package/dist/agui-handler.d.cts.map +1 -1
- package/dist/agui-handler.d.ts +8 -1
- package/dist/agui-handler.d.ts.map +1 -1
- package/dist/agui-handler.js +20 -18
- package/dist/agui-handler.js.map +1 -1
- package/dist/agui-recorder.cjs +6 -0
- package/dist/agui-recorder.cjs.map +1 -1
- package/dist/agui-recorder.d.cts.map +1 -1
- package/dist/agui-recorder.d.ts.map +1 -1
- package/dist/agui-recorder.js +6 -0
- package/dist/agui-recorder.js.map +1 -1
- package/dist/agui-stub.d.cts +2 -2
- package/dist/agui-stub.d.ts +2 -2
- package/dist/agui-types.d.cts +19 -1
- package/dist/agui-types.d.cts.map +1 -1
- package/dist/agui-types.d.ts +19 -1
- package/dist/agui-types.d.ts.map +1 -1
- package/dist/aimock-cli.cjs +1 -1
- package/dist/aimock-cli.cjs.map +1 -1
- package/dist/aimock-cli.js +1 -1
- package/dist/aimock-cli.js.map +1 -1
- package/dist/api-key-auth.cjs +166 -0
- package/dist/api-key-auth.cjs.map +1 -0
- package/dist/api-key-auth.d.cts +17 -0
- package/dist/api-key-auth.d.cts.map +1 -0
- package/dist/api-key-auth.d.ts +17 -0
- package/dist/api-key-auth.d.ts.map +1 -0
- package/dist/api-key-auth.js +158 -0
- package/dist/api-key-auth.js.map +1 -0
- package/dist/cli.cjs +14 -11
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +14 -11
- package/dist/cli.js.map +1 -1
- package/dist/config-loader.cjs +4 -2
- package/dist/config-loader.cjs.map +1 -1
- package/dist/config-loader.d.cts +2 -1
- package/dist/config-loader.d.cts.map +1 -1
- package/dist/config-loader.d.ts +2 -1
- package/dist/config-loader.d.ts.map +1 -1
- package/dist/config-loader.js +5 -3
- package/dist/config-loader.js.map +1 -1
- package/dist/fal-audio.cjs +2 -0
- 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 +2 -0
- package/dist/fal-audio.js.map +1 -1
- package/dist/fal.cjs +4 -1
- 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 +4 -1
- package/dist/fal.js.map +1 -1
- package/dist/fixture-loader.cjs +0 -0
- package/dist/fixture-loader.cjs.map +1 -1
- package/dist/fixture-loader.d.cts +3 -1
- package/dist/fixture-loader.d.cts.map +1 -1
- package/dist/fixture-loader.d.ts +3 -1
- package/dist/fixture-loader.d.ts.map +1 -1
- package/dist/fixture-loader.js +0 -0
- package/dist/fixture-loader.js.map +1 -1
- package/dist/grok-video.cjs +9 -2
- package/dist/grok-video.cjs.map +1 -1
- package/dist/grok-video.js +10 -3
- package/dist/grok-video.js.map +1 -1
- package/dist/helpers.cjs +16 -22
- package/dist/helpers.cjs.map +1 -1
- package/dist/helpers.d.cts.map +1 -1
- package/dist/helpers.d.ts.map +1 -1
- package/dist/helpers.js +16 -21
- package/dist/helpers.js.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/llmock.cjs +37 -12
- package/dist/llmock.cjs.map +1 -1
- package/dist/llmock.d.cts +26 -2
- package/dist/llmock.d.cts.map +1 -1
- package/dist/llmock.d.ts +26 -2
- package/dist/llmock.d.ts.map +1 -1
- package/dist/llmock.js +39 -15
- package/dist/llmock.js.map +1 -1
- package/dist/openrouter-chat.cjs +333 -0
- package/dist/openrouter-chat.cjs.map +1 -0
- package/dist/openrouter-chat.js +325 -0
- package/dist/openrouter-chat.js.map +1 -0
- package/dist/openrouter-video.cjs +24 -7
- package/dist/openrouter-video.cjs.map +1 -1
- package/dist/openrouter-video.d.cts.map +1 -1
- package/dist/openrouter-video.d.ts.map +1 -1
- package/dist/openrouter-video.js +25 -8
- package/dist/openrouter-video.js.map +1 -1
- package/dist/provider-auth.cjs +20 -0
- package/dist/provider-auth.cjs.map +1 -1
- package/dist/provider-auth.js +20 -1
- package/dist/provider-auth.js.map +1 -1
- package/dist/recorder.cjs +50 -8
- package/dist/recorder.cjs.map +1 -1
- package/dist/recorder.d.cts.map +1 -1
- package/dist/recorder.d.ts.map +1 -1
- package/dist/recorder.js +50 -10
- package/dist/recorder.js.map +1 -1
- package/dist/responses.cjs +200 -89
- package/dist/responses.cjs.map +1 -1
- package/dist/responses.d.cts +18 -3
- package/dist/responses.d.cts.map +1 -1
- package/dist/responses.d.ts +18 -3
- package/dist/responses.d.ts.map +1 -1
- package/dist/responses.js +199 -90
- package/dist/responses.js.map +1 -1
- package/dist/server.cjs +190 -83
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +7 -2
- package/dist/server.d.cts.map +1 -1
- package/dist/server.d.ts +7 -2
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +190 -85
- package/dist/server.js.map +1 -1
- package/dist/sse-writer.cjs +3 -0
- package/dist/sse-writer.cjs.map +1 -1
- package/dist/sse-writer.d.cts +8 -0
- package/dist/sse-writer.d.cts.map +1 -1
- package/dist/sse-writer.d.ts +8 -0
- package/dist/sse-writer.d.ts.map +1 -1
- package/dist/sse-writer.js +3 -0
- package/dist/sse-writer.js.map +1 -1
- package/dist/stream-collapse.cjs +103 -0
- package/dist/stream-collapse.cjs.map +1 -1
- package/dist/stream-collapse.d.cts +7 -0
- package/dist/stream-collapse.d.cts.map +1 -1
- package/dist/stream-collapse.d.ts +7 -0
- package/dist/stream-collapse.d.ts.map +1 -1
- package/dist/stream-collapse.js +103 -0
- package/dist/stream-collapse.js.map +1 -1
- package/dist/transcription.cjs +49 -2
- 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 +50 -3
- package/dist/transcription.js.map +1 -1
- package/dist/types.d.cts +187 -5
- package/dist/types.d.cts.map +1 -1
- package/dist/types.d.ts +187 -5
- package/dist/types.d.ts.map +1 -1
- package/dist/vector-types.d.ts.map +1 -1
- package/dist/veo-video.cjs +9 -2
- package/dist/veo-video.cjs.map +1 -1
- package/dist/veo-video.js +10 -3
- package/dist/veo-video.js.map +1 -1
- package/dist/ws-gemini-live.cjs +41 -6
- package/dist/ws-gemini-live.cjs.map +1 -1
- package/dist/ws-gemini-live.js +41 -6
- package/dist/ws-gemini-live.js.map +1 -1
- package/dist/ws-realtime.cjs +306 -57
- package/dist/ws-realtime.cjs.map +1 -1
- package/dist/ws-realtime.d.cts +1 -0
- package/dist/ws-realtime.d.cts.map +1 -1
- package/dist/ws-realtime.d.ts +1 -0
- package/dist/ws-realtime.d.ts.map +1 -1
- package/dist/ws-realtime.js +307 -58
- package/dist/ws-realtime.js.map +1 -1
- package/dist/ws-responses.cjs +11 -6
- package/dist/ws-responses.cjs.map +1 -1
- package/dist/ws-responses.d.cts.map +1 -1
- package/dist/ws-responses.d.ts.map +1 -1
- package/dist/ws-responses.js +12 -7
- package/dist/ws-responses.js.map +1 -1
- package/package.json +3 -2
- package/skills/write-fixtures/SKILL.md +53 -19
package/dist/agui-handler.js
CHANGED
|
@@ -81,7 +81,8 @@ function makeRunFinished(started, finishOpts) {
|
|
|
81
81
|
threadId: started.threadId,
|
|
82
82
|
runId: started.runId,
|
|
83
83
|
...finishOpts?.result !== void 0 ? { result: finishOpts.result } : {},
|
|
84
|
-
...finishOpts?.outcome !== void 0 ? { outcome: finishOpts.outcome } : {}
|
|
84
|
+
...finishOpts?.outcome !== void 0 ? { outcome: finishOpts.outcome } : {},
|
|
85
|
+
...finishOpts?.usage !== void 0 ? { usage: finishOpts.usage } : {}
|
|
85
86
|
};
|
|
86
87
|
}
|
|
87
88
|
/**
|
|
@@ -107,7 +108,7 @@ function buildTextResponse(text, opts) {
|
|
|
107
108
|
type: "TEXT_MESSAGE_END",
|
|
108
109
|
messageId
|
|
109
110
|
},
|
|
110
|
-
makeRunFinished(started)
|
|
111
|
+
makeRunFinished(started, { usage: opts?.usage })
|
|
111
112
|
];
|
|
112
113
|
}
|
|
113
114
|
/**
|
|
@@ -124,7 +125,7 @@ function buildTextChunkResponse(text, opts) {
|
|
|
124
125
|
role: "assistant",
|
|
125
126
|
delta: text
|
|
126
127
|
},
|
|
127
|
-
makeRunFinished(started)
|
|
128
|
+
makeRunFinished(started, { usage: opts?.usage })
|
|
128
129
|
];
|
|
129
130
|
}
|
|
130
131
|
/**
|
|
@@ -158,7 +159,7 @@ function buildToolCallResponse(toolName, args, opts) {
|
|
|
158
159
|
content: opts.result,
|
|
159
160
|
role: "tool"
|
|
160
161
|
});
|
|
161
|
-
events.push(makeRunFinished(started));
|
|
162
|
+
events.push(makeRunFinished(started, { usage: opts?.usage }));
|
|
162
163
|
return events;
|
|
163
164
|
}
|
|
164
165
|
/**
|
|
@@ -173,7 +174,7 @@ function buildStateUpdate(snapshot, opts) {
|
|
|
173
174
|
type: "STATE_SNAPSHOT",
|
|
174
175
|
snapshot
|
|
175
176
|
},
|
|
176
|
-
makeRunFinished(started)
|
|
177
|
+
makeRunFinished(started, { usage: opts?.usage })
|
|
177
178
|
];
|
|
178
179
|
}
|
|
179
180
|
/**
|
|
@@ -188,7 +189,7 @@ function buildStateDelta(patches, opts) {
|
|
|
188
189
|
type: "STATE_DELTA",
|
|
189
190
|
delta: patches
|
|
190
191
|
},
|
|
191
|
-
makeRunFinished(started)
|
|
192
|
+
makeRunFinished(started, { usage: opts?.usage })
|
|
192
193
|
];
|
|
193
194
|
}
|
|
194
195
|
/**
|
|
@@ -203,7 +204,7 @@ function buildMessagesSnapshot(messages, opts) {
|
|
|
203
204
|
type: "MESSAGES_SNAPSHOT",
|
|
204
205
|
messages
|
|
205
206
|
},
|
|
206
|
-
makeRunFinished(started)
|
|
207
|
+
makeRunFinished(started, { usage: opts?.usage })
|
|
207
208
|
];
|
|
208
209
|
}
|
|
209
210
|
/**
|
|
@@ -238,7 +239,7 @@ function buildReasoningResponse(text, opts) {
|
|
|
238
239
|
type: "REASONING_END",
|
|
239
240
|
messageId
|
|
240
241
|
},
|
|
241
|
-
makeRunFinished(started)
|
|
242
|
+
makeRunFinished(started, { usage: opts?.usage })
|
|
242
243
|
];
|
|
243
244
|
}
|
|
244
245
|
/**
|
|
@@ -256,7 +257,7 @@ function buildActivityResponse(messageId, activityType, content, opts) {
|
|
|
256
257
|
content,
|
|
257
258
|
replace: true
|
|
258
259
|
},
|
|
259
|
-
makeRunFinished(started)
|
|
260
|
+
makeRunFinished(started, { usage: opts?.usage })
|
|
260
261
|
];
|
|
261
262
|
}
|
|
262
263
|
/**
|
|
@@ -267,7 +268,8 @@ function buildErrorResponse(message, code, opts) {
|
|
|
267
268
|
return [makeRunStarted(opts), {
|
|
268
269
|
type: "RUN_ERROR",
|
|
269
270
|
message,
|
|
270
|
-
...code !== void 0 ? { code } : {}
|
|
271
|
+
...code !== void 0 ? { code } : {},
|
|
272
|
+
...opts?.usage !== void 0 ? { usage: opts.usage } : {}
|
|
271
273
|
}];
|
|
272
274
|
}
|
|
273
275
|
/**
|
|
@@ -302,7 +304,7 @@ function buildStepWithText(stepName, text, opts) {
|
|
|
302
304
|
type: "STEP_FINISHED",
|
|
303
305
|
stepName
|
|
304
306
|
},
|
|
305
|
-
makeRunFinished(started)
|
|
307
|
+
makeRunFinished(started, { usage: opts?.usage })
|
|
306
308
|
];
|
|
307
309
|
}
|
|
308
310
|
/**
|
|
@@ -318,7 +320,7 @@ function buildCompositeResponse(builderOutputs, opts) {
|
|
|
318
320
|
return [
|
|
319
321
|
started,
|
|
320
322
|
...inner,
|
|
321
|
-
...hasError ? [] : [makeRunFinished(started)]
|
|
323
|
+
...hasError ? [] : [makeRunFinished(started, { usage: opts?.usage })]
|
|
322
324
|
];
|
|
323
325
|
}
|
|
324
326
|
/**
|
|
@@ -335,7 +337,7 @@ function buildActivityDelta(messageId, activityType, patch, opts) {
|
|
|
335
337
|
activityType,
|
|
336
338
|
patch
|
|
337
339
|
},
|
|
338
|
-
makeRunFinished(started)
|
|
340
|
+
makeRunFinished(started, { usage: opts?.usage })
|
|
339
341
|
];
|
|
340
342
|
}
|
|
341
343
|
/**
|
|
@@ -353,7 +355,7 @@ function buildToolCallChunk(delta, opts) {
|
|
|
353
355
|
...opts?.parentMessageId !== void 0 ? { parentMessageId: opts.parentMessageId } : {},
|
|
354
356
|
delta
|
|
355
357
|
},
|
|
356
|
-
makeRunFinished(started)
|
|
358
|
+
makeRunFinished(started, { usage: opts?.usage })
|
|
357
359
|
];
|
|
358
360
|
}
|
|
359
361
|
/**
|
|
@@ -369,7 +371,7 @@ function buildRawEvent(event, source, opts) {
|
|
|
369
371
|
event,
|
|
370
372
|
...source !== void 0 ? { source } : {}
|
|
371
373
|
},
|
|
372
|
-
makeRunFinished(started)
|
|
374
|
+
makeRunFinished(started, { usage: opts?.usage })
|
|
373
375
|
];
|
|
374
376
|
}
|
|
375
377
|
/**
|
|
@@ -385,7 +387,7 @@ function buildCustomEvent(name, value, opts) {
|
|
|
385
387
|
name,
|
|
386
388
|
value
|
|
387
389
|
},
|
|
388
|
-
makeRunFinished(started)
|
|
390
|
+
makeRunFinished(started, { usage: opts?.usage })
|
|
389
391
|
];
|
|
390
392
|
}
|
|
391
393
|
/**
|
|
@@ -401,7 +403,7 @@ function buildReasoningChunk(delta, opts) {
|
|
|
401
403
|
...opts?.messageId !== void 0 ? { messageId: opts.messageId } : {},
|
|
402
404
|
delta
|
|
403
405
|
},
|
|
404
|
-
makeRunFinished(started)
|
|
406
|
+
makeRunFinished(started, { usage: opts?.usage })
|
|
405
407
|
];
|
|
406
408
|
}
|
|
407
409
|
/**
|
|
@@ -418,7 +420,7 @@ function buildReasoningEncryptedValue(subtype, entityId, encryptedValue, opts) {
|
|
|
418
420
|
entityId,
|
|
419
421
|
encryptedValue
|
|
420
422
|
},
|
|
421
|
-
makeRunFinished(started)
|
|
423
|
+
makeRunFinished(started, { usage: opts?.usage })
|
|
422
424
|
];
|
|
423
425
|
}
|
|
424
426
|
/**
|
package/dist/agui-handler.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agui-handler.js","names":[],"sources":["../src/agui-handler.ts"],"sourcesContent":["// ─── AG-UI Handler ───────────────────────────────────────────────────────────\n//\n// Matching functions, event builders, and SSE writer for AG-UI protocol.\n\nimport * as http from \"node:http\";\nimport { randomUUID } from \"node:crypto\";\n\nimport type { Logger } from \"./logger.js\";\nimport type {\n AGUIRunAgentInput,\n AGUIFixtureMatch,\n AGUIFixture,\n AGUIEvent,\n AGUIMessage,\n AGUIRunStartedEvent,\n AGUIRunFinishedEvent,\n AGUIRunFinishedOutcome,\n AGUIRunErrorEvent,\n AGUITextMessageStartEvent,\n AGUITextMessageContentEvent,\n AGUITextMessageEndEvent,\n AGUITextMessageChunkEvent,\n AGUIToolCallStartEvent,\n AGUIToolCallArgsEvent,\n AGUIToolCallEndEvent,\n AGUIToolCallChunkEvent,\n AGUIToolCallResultEvent,\n AGUIStateSnapshotEvent,\n AGUIStateDeltaEvent,\n AGUIMessagesSnapshotEvent,\n AGUIStepStartedEvent,\n AGUIStepFinishedEvent,\n AGUIReasoningStartEvent,\n AGUIReasoningMessageStartEvent,\n AGUIReasoningMessageContentEvent,\n AGUIReasoningMessageEndEvent,\n AGUIReasoningMessageChunkEvent,\n AGUIReasoningEndEvent,\n AGUIReasoningEncryptedValueEvent,\n AGUIReasoningEncryptedValueSubtype,\n AGUIActivitySnapshotEvent,\n AGUIActivityDeltaEvent,\n AGUIRawEvent,\n AGUICustomEvent,\n} from \"./agui-types.js\";\n\n// ─── Matching functions ──────────────────────────────────────────────────────\n\n/**\n * Extract the content of the last message with role \"user\" from the input.\n * Walks structured content arrays (e.g. `[{type:\"text\", text:\"...\"}, {type:\"document\", ...}]`)\n * and joins their text parts. Returns `\"\"` when no user message is present or has no text.\n */\nexport function extractLastUserMessage(input: AGUIRunAgentInput): string {\n if (!input.messages || input.messages.length === 0) return \"\";\n for (let i = input.messages.length - 1; i >= 0; i--) {\n const msg = input.messages[i];\n if (msg.role !== \"user\") continue;\n const text = extractTextFromContent(msg.content);\n if (text) return text;\n }\n return \"\";\n}\n\nfunction extractTextFromContent(content: AGUIMessage[\"content\"]): string {\n if (typeof content === \"string\") return content;\n if (!Array.isArray(content)) return \"\";\n const parts: string[] = [];\n for (const part of content) {\n if (\n part &&\n typeof part === \"object\" &&\n part.type === \"text\" &&\n typeof part.text === \"string\" &&\n part.text\n ) {\n parts.push(part.text);\n }\n }\n return parts.join(\" \").trim();\n}\n\n/**\n * Return the absolute last message if it has role \"tool\", otherwise null.\n */\nexport function getLastMessageIfToolResult(input: AGUIRunAgentInput): AGUIMessage | null {\n if (!input.messages || input.messages.length === 0) return null;\n const last = input.messages[input.messages.length - 1];\n return last.role === \"tool\" ? last : null;\n}\n\n/**\n * Check whether an input matches a fixture's match criteria.\n * All specified criteria must pass (AND logic).\n */\nexport function matchesFixture(input: AGUIRunAgentInput, match: AGUIFixtureMatch): boolean {\n if (match.message !== undefined) {\n const text = extractLastUserMessage(input);\n if (typeof match.message === \"string\") {\n if (!text.includes(match.message)) return false;\n } else {\n match.message.lastIndex = 0;\n if (!match.message.test(text)) return false;\n }\n }\n\n if (match.toolCallId !== undefined) {\n const lastMsg = input.messages?.[input.messages.length - 1];\n if (!lastMsg || lastMsg.role !== \"tool\" || lastMsg.toolCallId !== match.toolCallId) {\n return false;\n }\n }\n\n if (match.toolName !== undefined) {\n const tools = input.tools ?? [];\n if (!tools.some((t) => t.name === match.toolName)) return false;\n }\n\n if (match.stateKey !== undefined) {\n if (\n input.state === null ||\n input.state === undefined ||\n typeof input.state !== \"object\" ||\n !(match.stateKey in (input.state as Record<string, unknown>))\n ) {\n return false;\n }\n }\n\n if (match.predicate !== undefined) {\n if (!match.predicate(input)) return false;\n }\n\n return true;\n}\n\n/**\n * Find the first fixture whose match criteria pass for the given input.\n */\nexport function findFixture(input: AGUIRunAgentInput, fixtures: AGUIFixture[]): AGUIFixture | null {\n for (const fixture of fixtures) {\n if (matchesFixture(input, fixture.match)) {\n return fixture;\n }\n }\n return null;\n}\n\n// ─── Builder options ─────────────────────────────────────────────────────────\n\nexport interface AGUIBuildOpts {\n threadId?: string;\n runId?: string;\n parentRunId?: string;\n /** For tool call builder: include a result event */\n result?: string;\n}\n\n// ─── Event builders ──────────────────────────────────────────────────────────\n\nfunction makeRunStarted(opts?: AGUIBuildOpts): AGUIRunStartedEvent {\n return {\n type: \"RUN_STARTED\",\n threadId: opts?.threadId ?? randomUUID(),\n runId: opts?.runId ?? randomUUID(),\n ...(opts?.parentRunId ? { parentRunId: opts.parentRunId } : {}),\n };\n}\n\nfunction makeRunFinished(\n started: AGUIRunStartedEvent,\n finishOpts?: { outcome?: AGUIRunFinishedOutcome; result?: unknown },\n): AGUIRunFinishedEvent {\n return {\n type: \"RUN_FINISHED\",\n threadId: started.threadId,\n runId: started.runId,\n ...(finishOpts?.result !== undefined ? { result: finishOpts.result } : {}),\n ...(finishOpts?.outcome !== undefined ? { outcome: finishOpts.outcome } : {}),\n };\n}\n\n/**\n * Build a complete text message response sequence.\n * [RUN_STARTED, TEXT_MESSAGE_START, TEXT_MESSAGE_CONTENT, TEXT_MESSAGE_END, RUN_FINISHED]\n */\nexport function buildTextResponse(text: string, opts?: AGUIBuildOpts): AGUIEvent[] {\n const started = makeRunStarted(opts);\n const messageId = randomUUID();\n return [\n started,\n {\n type: \"TEXT_MESSAGE_START\",\n messageId,\n role: \"assistant\",\n } as AGUITextMessageStartEvent,\n {\n type: \"TEXT_MESSAGE_CONTENT\",\n messageId,\n delta: text,\n } as AGUITextMessageContentEvent,\n {\n type: \"TEXT_MESSAGE_END\",\n messageId,\n } as AGUITextMessageEndEvent,\n makeRunFinished(started),\n ];\n}\n\n/**\n * Build a text chunk response (single chunk, no start/end envelope).\n * [RUN_STARTED, TEXT_MESSAGE_CHUNK, RUN_FINISHED]\n */\nexport function buildTextChunkResponse(text: string, opts?: AGUIBuildOpts): AGUIEvent[] {\n const started = makeRunStarted(opts);\n return [\n started,\n {\n type: \"TEXT_MESSAGE_CHUNK\",\n messageId: randomUUID(),\n role: \"assistant\",\n delta: text,\n } as AGUITextMessageChunkEvent,\n makeRunFinished(started),\n ];\n}\n\n/**\n * Build a tool call response sequence.\n * [RUN_STARTED, TOOL_CALL_START, TOOL_CALL_ARGS, TOOL_CALL_END, (TOOL_CALL_RESULT)?, RUN_FINISHED]\n */\nexport function buildToolCallResponse(\n toolName: string,\n args: string,\n opts?: AGUIBuildOpts,\n): AGUIEvent[] {\n const started = makeRunStarted(opts);\n const toolCallId = randomUUID();\n const events: AGUIEvent[] = [\n started,\n {\n type: \"TOOL_CALL_START\",\n toolCallId,\n toolCallName: toolName,\n } as AGUIToolCallStartEvent,\n {\n type: \"TOOL_CALL_ARGS\",\n toolCallId,\n delta: args,\n } as AGUIToolCallArgsEvent,\n {\n type: \"TOOL_CALL_END\",\n toolCallId,\n } as AGUIToolCallEndEvent,\n ];\n\n if (opts?.result !== undefined) {\n events.push({\n type: \"TOOL_CALL_RESULT\",\n messageId: randomUUID(),\n toolCallId,\n content: opts.result,\n role: \"tool\",\n } as AGUIToolCallResultEvent);\n }\n\n events.push(makeRunFinished(started));\n return events;\n}\n\n/**\n * Build a state snapshot response.\n * [RUN_STARTED, STATE_SNAPSHOT, RUN_FINISHED]\n */\nexport function buildStateUpdate(snapshot: unknown, opts?: AGUIBuildOpts): AGUIEvent[] {\n const started = makeRunStarted(opts);\n return [\n started,\n {\n type: \"STATE_SNAPSHOT\",\n snapshot,\n } as AGUIStateSnapshotEvent,\n makeRunFinished(started),\n ];\n}\n\n/**\n * Build a state delta response (JSON Patch).\n * [RUN_STARTED, STATE_DELTA, RUN_FINISHED]\n */\nexport function buildStateDelta(patches: unknown[], opts?: AGUIBuildOpts): AGUIEvent[] {\n const started = makeRunStarted(opts);\n return [\n started,\n {\n type: \"STATE_DELTA\",\n delta: patches,\n } as AGUIStateDeltaEvent,\n makeRunFinished(started),\n ];\n}\n\n/**\n * Build a messages snapshot response.\n * [RUN_STARTED, MESSAGES_SNAPSHOT, RUN_FINISHED]\n */\nexport function buildMessagesSnapshot(messages: AGUIMessage[], opts?: AGUIBuildOpts): AGUIEvent[] {\n const started = makeRunStarted(opts);\n return [\n started,\n {\n type: \"MESSAGES_SNAPSHOT\",\n messages,\n } as AGUIMessagesSnapshotEvent,\n makeRunFinished(started),\n ];\n}\n\n/**\n * Build a reasoning response sequence.\n * [RUN_STARTED, REASONING_START, REASONING_MESSAGE_START, REASONING_MESSAGE_CONTENT,\n * REASONING_MESSAGE_END, REASONING_END, RUN_FINISHED]\n */\nexport function buildReasoningResponse(text: string, opts?: AGUIBuildOpts): AGUIEvent[] {\n const started = makeRunStarted(opts);\n const messageId = randomUUID();\n return [\n started,\n {\n type: \"REASONING_START\",\n messageId,\n } as AGUIReasoningStartEvent,\n {\n type: \"REASONING_MESSAGE_START\",\n messageId,\n role: \"reasoning\",\n } as AGUIReasoningMessageStartEvent,\n {\n type: \"REASONING_MESSAGE_CONTENT\",\n messageId,\n delta: text,\n } as AGUIReasoningMessageContentEvent,\n {\n type: \"REASONING_MESSAGE_END\",\n messageId,\n } as AGUIReasoningMessageEndEvent,\n {\n type: \"REASONING_END\",\n messageId,\n } as AGUIReasoningEndEvent,\n makeRunFinished(started),\n ];\n}\n\n/**\n * Build an activity snapshot response.\n * [RUN_STARTED, ACTIVITY_SNAPSHOT, RUN_FINISHED]\n */\nexport function buildActivityResponse(\n messageId: string,\n activityType: string,\n content: Record<string, unknown>,\n opts?: AGUIBuildOpts,\n): AGUIEvent[] {\n const started = makeRunStarted(opts);\n return [\n started,\n {\n type: \"ACTIVITY_SNAPSHOT\",\n messageId,\n activityType,\n content,\n replace: true,\n } as AGUIActivitySnapshotEvent,\n makeRunFinished(started),\n ];\n}\n\n/**\n * Build an error response.\n * [RUN_STARTED, RUN_ERROR] (no RUN_FINISHED — the run errored)\n */\nexport function buildErrorResponse(\n message: string,\n code?: string,\n opts?: AGUIBuildOpts,\n): AGUIEvent[] {\n const started = makeRunStarted(opts);\n return [\n started,\n {\n type: \"RUN_ERROR\",\n message,\n ...(code !== undefined ? { code } : {}),\n } as AGUIRunErrorEvent,\n ];\n}\n\n/**\n * Build a step-wrapped text response.\n * [RUN_STARTED, STEP_STARTED, TEXT_MESSAGE_START, TEXT_MESSAGE_CONTENT,\n * TEXT_MESSAGE_END, STEP_FINISHED, RUN_FINISHED]\n */\nexport function buildStepWithText(\n stepName: string,\n text: string,\n opts?: AGUIBuildOpts,\n): AGUIEvent[] {\n const started = makeRunStarted(opts);\n const messageId = randomUUID();\n return [\n started,\n {\n type: \"STEP_STARTED\",\n stepName,\n } as AGUIStepStartedEvent,\n {\n type: \"TEXT_MESSAGE_START\",\n messageId,\n role: \"assistant\",\n } as AGUITextMessageStartEvent,\n {\n type: \"TEXT_MESSAGE_CONTENT\",\n messageId,\n delta: text,\n } as AGUITextMessageContentEvent,\n {\n type: \"TEXT_MESSAGE_END\",\n messageId,\n } as AGUITextMessageEndEvent,\n {\n type: \"STEP_FINISHED\",\n stepName,\n } as AGUIStepFinishedEvent,\n makeRunFinished(started),\n ];\n}\n\n/**\n * Combine multiple builder outputs into a single run.\n * Strips RUN_STARTED/RUN_FINISHED from each input, wraps all inner events\n * in one RUN_STARTED...RUN_FINISHED pair.\n */\nexport function buildCompositeResponse(\n builderOutputs: AGUIEvent[][],\n opts?: AGUIBuildOpts,\n): AGUIEvent[] {\n const started = makeRunStarted(opts);\n const inner: AGUIEvent[] = [];\n\n for (const events of builderOutputs) {\n for (const event of events) {\n if (event.type !== \"RUN_STARTED\" && event.type !== \"RUN_FINISHED\") {\n inner.push(event);\n }\n }\n }\n\n const hasError = inner.some((e) => e.type === \"RUN_ERROR\");\n return [started, ...inner, ...(hasError ? [] : [makeRunFinished(started)])];\n}\n\n// ─── Convenience event builders ─────────────────────────────────────────────\n\n/**\n * Build an activity delta response (JSON Patch on an activity).\n * [RUN_STARTED, ACTIVITY_DELTA, RUN_FINISHED]\n */\nexport function buildActivityDelta(\n messageId: string,\n activityType: string,\n patch: unknown[],\n opts?: AGUIBuildOpts,\n): AGUIEvent[] {\n const started = makeRunStarted(opts);\n return [\n started,\n {\n type: \"ACTIVITY_DELTA\",\n messageId,\n activityType,\n patch,\n } as AGUIActivityDeltaEvent,\n makeRunFinished(started),\n ];\n}\n\n/**\n * Build a tool call chunk response (single chunk, no start/end envelope).\n * [RUN_STARTED, TOOL_CALL_CHUNK, RUN_FINISHED]\n */\nexport function buildToolCallChunk(\n delta: string,\n opts?: AGUIBuildOpts & {\n toolCallId?: string;\n toolCallName?: string;\n parentMessageId?: string;\n },\n): AGUIEvent[] {\n const started = makeRunStarted(opts);\n return [\n started,\n {\n type: \"TOOL_CALL_CHUNK\",\n ...(opts?.toolCallId !== undefined ? { toolCallId: opts.toolCallId } : {}),\n ...(opts?.toolCallName !== undefined ? { toolCallName: opts.toolCallName } : {}),\n ...(opts?.parentMessageId !== undefined ? { parentMessageId: opts.parentMessageId } : {}),\n delta,\n } as AGUIToolCallChunkEvent,\n makeRunFinished(started),\n ];\n}\n\n/**\n * Build a raw event response.\n * [RUN_STARTED, RAW, RUN_FINISHED]\n */\nexport function buildRawEvent(event: unknown, source?: string, opts?: AGUIBuildOpts): AGUIEvent[] {\n const started = makeRunStarted(opts);\n return [\n started,\n {\n type: \"RAW\",\n event,\n ...(source !== undefined ? { source } : {}),\n } as AGUIRawEvent,\n makeRunFinished(started),\n ];\n}\n\n/**\n * Build a custom event response.\n * [RUN_STARTED, CUSTOM, RUN_FINISHED]\n */\nexport function buildCustomEvent(name: string, value: unknown, opts?: AGUIBuildOpts): AGUIEvent[] {\n const started = makeRunStarted(opts);\n return [\n started,\n {\n type: \"CUSTOM\",\n name,\n value,\n } as AGUICustomEvent,\n makeRunFinished(started),\n ];\n}\n\n/**\n * Build a reasoning message chunk response (single chunk, no start/end envelope).\n * [RUN_STARTED, REASONING_MESSAGE_CHUNK, RUN_FINISHED]\n */\nexport function buildReasoningChunk(\n delta: string,\n opts?: AGUIBuildOpts & { messageId?: string },\n): AGUIEvent[] {\n const started = makeRunStarted(opts);\n return [\n started,\n {\n type: \"REASONING_MESSAGE_CHUNK\",\n ...(opts?.messageId !== undefined ? { messageId: opts.messageId } : {}),\n delta,\n } as AGUIReasoningMessageChunkEvent,\n makeRunFinished(started),\n ];\n}\n\n/**\n * Build a reasoning encrypted value event response.\n * [RUN_STARTED, REASONING_ENCRYPTED_VALUE, RUN_FINISHED]\n */\nexport function buildReasoningEncryptedValue(\n subtype: AGUIReasoningEncryptedValueSubtype,\n entityId: string,\n encryptedValue: string,\n opts?: AGUIBuildOpts,\n): AGUIEvent[] {\n const started = makeRunStarted(opts);\n return [\n started,\n {\n type: \"REASONING_ENCRYPTED_VALUE\",\n subtype,\n entityId,\n encryptedValue,\n } as AGUIReasoningEncryptedValueEvent,\n makeRunFinished(started),\n ];\n}\n\n// ─── SSE writer ──────────────────────────────────────────────────────────────\n\n/**\n * Write AG-UI events as an SSE stream to an HTTP response.\n * Sets appropriate headers, serializes each event as `data: {...}\\n\\n`,\n * and optionally delays between events.\n */\nexport async function writeAGUIEventStream(\n res: http.ServerResponse,\n events: AGUIEvent[],\n opts?: { delayMs?: number; signal?: AbortSignal; logger?: Logger },\n): Promise<void> {\n const delayMs = opts?.delayMs ?? 0;\n\n res.writeHead(200, {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n });\n\n for (const event of events) {\n if (opts?.signal?.aborted) break;\n if (res.socket?.destroyed) break;\n\n const stamped = { ...event, timestamp: event.timestamp ?? Date.now() };\n try {\n res.write(`data: ${JSON.stringify(stamped)}\\n\\n`);\n } catch (err) {\n if (err instanceof TypeError || err instanceof RangeError) {\n const msg = (err as Error).message;\n if (opts?.logger) {\n opts.logger.warn(\"AG-UI SSE write failed (serialization):\", msg);\n } else {\n console.warn(\"AG-UI SSE write failed (serialization):\", msg);\n }\n } else if (err instanceof Error) {\n if (opts?.logger) {\n opts.logger.warn(\"AG-UI SSE write failed:\", err.message);\n } else {\n console.warn(\"AG-UI SSE write failed:\", err.message);\n }\n } else {\n const msg = String(err);\n if (opts?.logger) {\n opts.logger.warn(\"AG-UI SSE write failed:\", msg);\n } else {\n console.warn(\"AG-UI SSE write failed:\", msg);\n }\n }\n break;\n }\n\n if (delayMs > 0) {\n await new Promise<void>((resolve) => setTimeout(resolve, delayMs));\n }\n }\n\n if (!res.writableEnded) res.end();\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"agui-handler.js","names":[],"sources":["../src/agui-handler.ts"],"sourcesContent":["// ─── AG-UI Handler ───────────────────────────────────────────────────────────\n//\n// Matching functions, event builders, and SSE writer for AG-UI protocol.\n\nimport * as http from \"node:http\";\nimport { randomUUID } from \"node:crypto\";\n\nimport type { Logger } from \"./logger.js\";\nimport type {\n AGUIRunAgentInput,\n AGUIFixtureMatch,\n AGUIFixture,\n AGUIEvent,\n AGUIMessage,\n AGUIRunStartedEvent,\n AGUIRunFinishedEvent,\n AGUIRunFinishedOutcome,\n AGUITokenUsage,\n AGUIRunErrorEvent,\n AGUITextMessageStartEvent,\n AGUITextMessageContentEvent,\n AGUITextMessageEndEvent,\n AGUITextMessageChunkEvent,\n AGUIToolCallStartEvent,\n AGUIToolCallArgsEvent,\n AGUIToolCallEndEvent,\n AGUIToolCallChunkEvent,\n AGUIToolCallResultEvent,\n AGUIStateSnapshotEvent,\n AGUIStateDeltaEvent,\n AGUIMessagesSnapshotEvent,\n AGUIStepStartedEvent,\n AGUIStepFinishedEvent,\n AGUIReasoningStartEvent,\n AGUIReasoningMessageStartEvent,\n AGUIReasoningMessageContentEvent,\n AGUIReasoningMessageEndEvent,\n AGUIReasoningMessageChunkEvent,\n AGUIReasoningEndEvent,\n AGUIReasoningEncryptedValueEvent,\n AGUIReasoningEncryptedValueSubtype,\n AGUIActivitySnapshotEvent,\n AGUIActivityDeltaEvent,\n AGUIRawEvent,\n AGUICustomEvent,\n} from \"./agui-types.js\";\n\n// ─── Matching functions ──────────────────────────────────────────────────────\n\n/**\n * Extract the content of the last message with role \"user\" from the input.\n * Walks structured content arrays (e.g. `[{type:\"text\", text:\"...\"}, {type:\"document\", ...}]`)\n * and joins their text parts. Returns `\"\"` when no user message is present or has no text.\n */\nexport function extractLastUserMessage(input: AGUIRunAgentInput): string {\n if (!input.messages || input.messages.length === 0) return \"\";\n for (let i = input.messages.length - 1; i >= 0; i--) {\n const msg = input.messages[i];\n if (msg.role !== \"user\") continue;\n const text = extractTextFromContent(msg.content);\n if (text) return text;\n }\n return \"\";\n}\n\nfunction extractTextFromContent(content: AGUIMessage[\"content\"]): string {\n if (typeof content === \"string\") return content;\n if (!Array.isArray(content)) return \"\";\n const parts: string[] = [];\n for (const part of content) {\n if (\n part &&\n typeof part === \"object\" &&\n part.type === \"text\" &&\n typeof part.text === \"string\" &&\n part.text\n ) {\n parts.push(part.text);\n }\n }\n return parts.join(\" \").trim();\n}\n\n/**\n * Return the absolute last message if it has role \"tool\", otherwise null.\n */\nexport function getLastMessageIfToolResult(input: AGUIRunAgentInput): AGUIMessage | null {\n if (!input.messages || input.messages.length === 0) return null;\n const last = input.messages[input.messages.length - 1];\n return last.role === \"tool\" ? last : null;\n}\n\n/**\n * Check whether an input matches a fixture's match criteria.\n * All specified criteria must pass (AND logic).\n */\nexport function matchesFixture(input: AGUIRunAgentInput, match: AGUIFixtureMatch): boolean {\n if (match.message !== undefined) {\n const text = extractLastUserMessage(input);\n if (typeof match.message === \"string\") {\n if (!text.includes(match.message)) return false;\n } else {\n match.message.lastIndex = 0;\n if (!match.message.test(text)) return false;\n }\n }\n\n if (match.toolCallId !== undefined) {\n const lastMsg = input.messages?.[input.messages.length - 1];\n if (!lastMsg || lastMsg.role !== \"tool\" || lastMsg.toolCallId !== match.toolCallId) {\n return false;\n }\n }\n\n if (match.toolName !== undefined) {\n const tools = input.tools ?? [];\n if (!tools.some((t) => t.name === match.toolName)) return false;\n }\n\n if (match.stateKey !== undefined) {\n if (\n input.state === null ||\n input.state === undefined ||\n typeof input.state !== \"object\" ||\n !(match.stateKey in (input.state as Record<string, unknown>))\n ) {\n return false;\n }\n }\n\n if (match.predicate !== undefined) {\n if (!match.predicate(input)) return false;\n }\n\n return true;\n}\n\n/**\n * Find the first fixture whose match criteria pass for the given input.\n */\nexport function findFixture(input: AGUIRunAgentInput, fixtures: AGUIFixture[]): AGUIFixture | null {\n for (const fixture of fixtures) {\n if (matchesFixture(input, fixture.match)) {\n return fixture;\n }\n }\n return null;\n}\n\n// ─── Builder options ─────────────────────────────────────────────────────────\n\nexport interface AGUIBuildOpts {\n threadId?: string;\n runId?: string;\n parentRunId?: string;\n /** For tool call builder: include a result event */\n result?: string;\n /**\n * Token usage to attach to the terminal RUN_FINISHED / RUN_ERROR event.\n * Optional and never synthesized: aimock does not estimate token counts\n * from fixture content, so `usage` is emitted only when a caller supplies\n * it explicitly (same policy as the Bedrock/Cohere/Gemini usage overrides).\n */\n usage?: AGUITokenUsage[];\n}\n\n// ─── Event builders ──────────────────────────────────────────────────────────\n\nfunction makeRunStarted(opts?: AGUIBuildOpts): AGUIRunStartedEvent {\n return {\n type: \"RUN_STARTED\",\n threadId: opts?.threadId ?? randomUUID(),\n runId: opts?.runId ?? randomUUID(),\n ...(opts?.parentRunId ? { parentRunId: opts.parentRunId } : {}),\n };\n}\n\nfunction makeRunFinished(\n started: AGUIRunStartedEvent,\n finishOpts?: { outcome?: AGUIRunFinishedOutcome; result?: unknown; usage?: AGUITokenUsage[] },\n): AGUIRunFinishedEvent {\n return {\n type: \"RUN_FINISHED\",\n threadId: started.threadId,\n runId: started.runId,\n ...(finishOpts?.result !== undefined ? { result: finishOpts.result } : {}),\n ...(finishOpts?.outcome !== undefined ? { outcome: finishOpts.outcome } : {}),\n ...(finishOpts?.usage !== undefined ? { usage: finishOpts.usage } : {}),\n };\n}\n\n/**\n * Build a complete text message response sequence.\n * [RUN_STARTED, TEXT_MESSAGE_START, TEXT_MESSAGE_CONTENT, TEXT_MESSAGE_END, RUN_FINISHED]\n */\nexport function buildTextResponse(text: string, opts?: AGUIBuildOpts): AGUIEvent[] {\n const started = makeRunStarted(opts);\n const messageId = randomUUID();\n return [\n started,\n {\n type: \"TEXT_MESSAGE_START\",\n messageId,\n role: \"assistant\",\n } as AGUITextMessageStartEvent,\n {\n type: \"TEXT_MESSAGE_CONTENT\",\n messageId,\n delta: text,\n } as AGUITextMessageContentEvent,\n {\n type: \"TEXT_MESSAGE_END\",\n messageId,\n } as AGUITextMessageEndEvent,\n makeRunFinished(started, { usage: opts?.usage }),\n ];\n}\n\n/**\n * Build a text chunk response (single chunk, no start/end envelope).\n * [RUN_STARTED, TEXT_MESSAGE_CHUNK, RUN_FINISHED]\n */\nexport function buildTextChunkResponse(text: string, opts?: AGUIBuildOpts): AGUIEvent[] {\n const started = makeRunStarted(opts);\n return [\n started,\n {\n type: \"TEXT_MESSAGE_CHUNK\",\n messageId: randomUUID(),\n role: \"assistant\",\n delta: text,\n } as AGUITextMessageChunkEvent,\n makeRunFinished(started, { usage: opts?.usage }),\n ];\n}\n\n/**\n * Build a tool call response sequence.\n * [RUN_STARTED, TOOL_CALL_START, TOOL_CALL_ARGS, TOOL_CALL_END, (TOOL_CALL_RESULT)?, RUN_FINISHED]\n */\nexport function buildToolCallResponse(\n toolName: string,\n args: string,\n opts?: AGUIBuildOpts,\n): AGUIEvent[] {\n const started = makeRunStarted(opts);\n const toolCallId = randomUUID();\n const events: AGUIEvent[] = [\n started,\n {\n type: \"TOOL_CALL_START\",\n toolCallId,\n toolCallName: toolName,\n } as AGUIToolCallStartEvent,\n {\n type: \"TOOL_CALL_ARGS\",\n toolCallId,\n delta: args,\n } as AGUIToolCallArgsEvent,\n {\n type: \"TOOL_CALL_END\",\n toolCallId,\n } as AGUIToolCallEndEvent,\n ];\n\n if (opts?.result !== undefined) {\n events.push({\n type: \"TOOL_CALL_RESULT\",\n messageId: randomUUID(),\n toolCallId,\n content: opts.result,\n role: \"tool\",\n } as AGUIToolCallResultEvent);\n }\n\n events.push(makeRunFinished(started, { usage: opts?.usage }));\n return events;\n}\n\n/**\n * Build a state snapshot response.\n * [RUN_STARTED, STATE_SNAPSHOT, RUN_FINISHED]\n */\nexport function buildStateUpdate(snapshot: unknown, opts?: AGUIBuildOpts): AGUIEvent[] {\n const started = makeRunStarted(opts);\n return [\n started,\n {\n type: \"STATE_SNAPSHOT\",\n snapshot,\n } as AGUIStateSnapshotEvent,\n makeRunFinished(started, { usage: opts?.usage }),\n ];\n}\n\n/**\n * Build a state delta response (JSON Patch).\n * [RUN_STARTED, STATE_DELTA, RUN_FINISHED]\n */\nexport function buildStateDelta(patches: unknown[], opts?: AGUIBuildOpts): AGUIEvent[] {\n const started = makeRunStarted(opts);\n return [\n started,\n {\n type: \"STATE_DELTA\",\n delta: patches,\n } as AGUIStateDeltaEvent,\n makeRunFinished(started, { usage: opts?.usage }),\n ];\n}\n\n/**\n * Build a messages snapshot response.\n * [RUN_STARTED, MESSAGES_SNAPSHOT, RUN_FINISHED]\n */\nexport function buildMessagesSnapshot(messages: AGUIMessage[], opts?: AGUIBuildOpts): AGUIEvent[] {\n const started = makeRunStarted(opts);\n return [\n started,\n {\n type: \"MESSAGES_SNAPSHOT\",\n messages,\n } as AGUIMessagesSnapshotEvent,\n makeRunFinished(started, { usage: opts?.usage }),\n ];\n}\n\n/**\n * Build a reasoning response sequence.\n * [RUN_STARTED, REASONING_START, REASONING_MESSAGE_START, REASONING_MESSAGE_CONTENT,\n * REASONING_MESSAGE_END, REASONING_END, RUN_FINISHED]\n */\nexport function buildReasoningResponse(text: string, opts?: AGUIBuildOpts): AGUIEvent[] {\n const started = makeRunStarted(opts);\n const messageId = randomUUID();\n return [\n started,\n {\n type: \"REASONING_START\",\n messageId,\n } as AGUIReasoningStartEvent,\n {\n type: \"REASONING_MESSAGE_START\",\n messageId,\n role: \"reasoning\",\n } as AGUIReasoningMessageStartEvent,\n {\n type: \"REASONING_MESSAGE_CONTENT\",\n messageId,\n delta: text,\n } as AGUIReasoningMessageContentEvent,\n {\n type: \"REASONING_MESSAGE_END\",\n messageId,\n } as AGUIReasoningMessageEndEvent,\n {\n type: \"REASONING_END\",\n messageId,\n } as AGUIReasoningEndEvent,\n makeRunFinished(started, { usage: opts?.usage }),\n ];\n}\n\n/**\n * Build an activity snapshot response.\n * [RUN_STARTED, ACTIVITY_SNAPSHOT, RUN_FINISHED]\n */\nexport function buildActivityResponse(\n messageId: string,\n activityType: string,\n content: Record<string, unknown>,\n opts?: AGUIBuildOpts,\n): AGUIEvent[] {\n const started = makeRunStarted(opts);\n return [\n started,\n {\n type: \"ACTIVITY_SNAPSHOT\",\n messageId,\n activityType,\n content,\n replace: true,\n } as AGUIActivitySnapshotEvent,\n makeRunFinished(started, { usage: opts?.usage }),\n ];\n}\n\n/**\n * Build an error response.\n * [RUN_STARTED, RUN_ERROR] (no RUN_FINISHED — the run errored)\n */\nexport function buildErrorResponse(\n message: string,\n code?: string,\n opts?: AGUIBuildOpts,\n): AGUIEvent[] {\n const started = makeRunStarted(opts);\n return [\n started,\n {\n type: \"RUN_ERROR\",\n message,\n ...(code !== undefined ? { code } : {}),\n ...(opts?.usage !== undefined ? { usage: opts.usage } : {}),\n } as AGUIRunErrorEvent,\n ];\n}\n\n/**\n * Build a step-wrapped text response.\n * [RUN_STARTED, STEP_STARTED, TEXT_MESSAGE_START, TEXT_MESSAGE_CONTENT,\n * TEXT_MESSAGE_END, STEP_FINISHED, RUN_FINISHED]\n */\nexport function buildStepWithText(\n stepName: string,\n text: string,\n opts?: AGUIBuildOpts,\n): AGUIEvent[] {\n const started = makeRunStarted(opts);\n const messageId = randomUUID();\n return [\n started,\n {\n type: \"STEP_STARTED\",\n stepName,\n } as AGUIStepStartedEvent,\n {\n type: \"TEXT_MESSAGE_START\",\n messageId,\n role: \"assistant\",\n } as AGUITextMessageStartEvent,\n {\n type: \"TEXT_MESSAGE_CONTENT\",\n messageId,\n delta: text,\n } as AGUITextMessageContentEvent,\n {\n type: \"TEXT_MESSAGE_END\",\n messageId,\n } as AGUITextMessageEndEvent,\n {\n type: \"STEP_FINISHED\",\n stepName,\n } as AGUIStepFinishedEvent,\n makeRunFinished(started, { usage: opts?.usage }),\n ];\n}\n\n/**\n * Combine multiple builder outputs into a single run.\n * Strips RUN_STARTED/RUN_FINISHED from each input, wraps all inner events\n * in one RUN_STARTED...RUN_FINISHED pair.\n */\nexport function buildCompositeResponse(\n builderOutputs: AGUIEvent[][],\n opts?: AGUIBuildOpts,\n): AGUIEvent[] {\n const started = makeRunStarted(opts);\n const inner: AGUIEvent[] = [];\n\n for (const events of builderOutputs) {\n for (const event of events) {\n if (event.type !== \"RUN_STARTED\" && event.type !== \"RUN_FINISHED\") {\n inner.push(event);\n }\n }\n }\n\n const hasError = inner.some((e) => e.type === \"RUN_ERROR\");\n return [\n started,\n ...inner,\n ...(hasError ? [] : [makeRunFinished(started, { usage: opts?.usage })]),\n ];\n}\n\n// ─── Convenience event builders ─────────────────────────────────────────────\n\n/**\n * Build an activity delta response (JSON Patch on an activity).\n * [RUN_STARTED, ACTIVITY_DELTA, RUN_FINISHED]\n */\nexport function buildActivityDelta(\n messageId: string,\n activityType: string,\n patch: unknown[],\n opts?: AGUIBuildOpts,\n): AGUIEvent[] {\n const started = makeRunStarted(opts);\n return [\n started,\n {\n type: \"ACTIVITY_DELTA\",\n messageId,\n activityType,\n patch,\n } as AGUIActivityDeltaEvent,\n makeRunFinished(started, { usage: opts?.usage }),\n ];\n}\n\n/**\n * Build a tool call chunk response (single chunk, no start/end envelope).\n * [RUN_STARTED, TOOL_CALL_CHUNK, RUN_FINISHED]\n */\nexport function buildToolCallChunk(\n delta: string,\n opts?: AGUIBuildOpts & {\n toolCallId?: string;\n toolCallName?: string;\n parentMessageId?: string;\n },\n): AGUIEvent[] {\n const started = makeRunStarted(opts);\n return [\n started,\n {\n type: \"TOOL_CALL_CHUNK\",\n ...(opts?.toolCallId !== undefined ? { toolCallId: opts.toolCallId } : {}),\n ...(opts?.toolCallName !== undefined ? { toolCallName: opts.toolCallName } : {}),\n ...(opts?.parentMessageId !== undefined ? { parentMessageId: opts.parentMessageId } : {}),\n delta,\n } as AGUIToolCallChunkEvent,\n makeRunFinished(started, { usage: opts?.usage }),\n ];\n}\n\n/**\n * Build a raw event response.\n * [RUN_STARTED, RAW, RUN_FINISHED]\n */\nexport function buildRawEvent(event: unknown, source?: string, opts?: AGUIBuildOpts): AGUIEvent[] {\n const started = makeRunStarted(opts);\n return [\n started,\n {\n type: \"RAW\",\n event,\n ...(source !== undefined ? { source } : {}),\n } as AGUIRawEvent,\n makeRunFinished(started, { usage: opts?.usage }),\n ];\n}\n\n/**\n * Build a custom event response.\n * [RUN_STARTED, CUSTOM, RUN_FINISHED]\n */\nexport function buildCustomEvent(name: string, value: unknown, opts?: AGUIBuildOpts): AGUIEvent[] {\n const started = makeRunStarted(opts);\n return [\n started,\n {\n type: \"CUSTOM\",\n name,\n value,\n } as AGUICustomEvent,\n makeRunFinished(started, { usage: opts?.usage }),\n ];\n}\n\n/**\n * Build a reasoning message chunk response (single chunk, no start/end envelope).\n * [RUN_STARTED, REASONING_MESSAGE_CHUNK, RUN_FINISHED]\n */\nexport function buildReasoningChunk(\n delta: string,\n opts?: AGUIBuildOpts & { messageId?: string },\n): AGUIEvent[] {\n const started = makeRunStarted(opts);\n return [\n started,\n {\n type: \"REASONING_MESSAGE_CHUNK\",\n ...(opts?.messageId !== undefined ? { messageId: opts.messageId } : {}),\n delta,\n } as AGUIReasoningMessageChunkEvent,\n makeRunFinished(started, { usage: opts?.usage }),\n ];\n}\n\n/**\n * Build a reasoning encrypted value event response.\n * [RUN_STARTED, REASONING_ENCRYPTED_VALUE, RUN_FINISHED]\n */\nexport function buildReasoningEncryptedValue(\n subtype: AGUIReasoningEncryptedValueSubtype,\n entityId: string,\n encryptedValue: string,\n opts?: AGUIBuildOpts,\n): AGUIEvent[] {\n const started = makeRunStarted(opts);\n return [\n started,\n {\n type: \"REASONING_ENCRYPTED_VALUE\",\n subtype,\n entityId,\n encryptedValue,\n } as AGUIReasoningEncryptedValueEvent,\n makeRunFinished(started, { usage: opts?.usage }),\n ];\n}\n\n// ─── SSE writer ──────────────────────────────────────────────────────────────\n\n/**\n * Write AG-UI events as an SSE stream to an HTTP response.\n * Sets appropriate headers, serializes each event as `data: {...}\\n\\n`,\n * and optionally delays between events.\n */\nexport async function writeAGUIEventStream(\n res: http.ServerResponse,\n events: AGUIEvent[],\n opts?: { delayMs?: number; signal?: AbortSignal; logger?: Logger },\n): Promise<void> {\n const delayMs = opts?.delayMs ?? 0;\n\n res.writeHead(200, {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n });\n\n for (const event of events) {\n if (opts?.signal?.aborted) break;\n if (res.socket?.destroyed) break;\n\n const stamped = { ...event, timestamp: event.timestamp ?? Date.now() };\n try {\n res.write(`data: ${JSON.stringify(stamped)}\\n\\n`);\n } catch (err) {\n if (err instanceof TypeError || err instanceof RangeError) {\n const msg = (err as Error).message;\n if (opts?.logger) {\n opts.logger.warn(\"AG-UI SSE write failed (serialization):\", msg);\n } else {\n console.warn(\"AG-UI SSE write failed (serialization):\", msg);\n }\n } else if (err instanceof Error) {\n if (opts?.logger) {\n opts.logger.warn(\"AG-UI SSE write failed:\", err.message);\n } else {\n console.warn(\"AG-UI SSE write failed:\", err.message);\n }\n } else {\n const msg = String(err);\n if (opts?.logger) {\n opts.logger.warn(\"AG-UI SSE write failed:\", msg);\n } else {\n console.warn(\"AG-UI SSE write failed:\", msg);\n }\n }\n break;\n }\n\n if (delayMs > 0) {\n await new Promise<void>((resolve) => setTimeout(resolve, delayMs));\n }\n }\n\n if (!res.writableEnded) res.end();\n}\n"],"mappings":"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"}
|
package/dist/agui-recorder.cjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const require_runtime = require('./_virtual/_rolldown/runtime.cjs');
|
|
2
|
+
const require_api_key_auth = require('./api-key-auth.cjs');
|
|
2
3
|
const require_agui_handler = require('./agui-handler.cjs');
|
|
3
4
|
let node_http = require("node:http");
|
|
4
5
|
node_http = require_runtime.__toESM(node_http);
|
|
@@ -63,6 +64,11 @@ function resolveAGUIRecordBufferCap(configured) {
|
|
|
63
64
|
* or `false` if no upstream is configured.
|
|
64
65
|
*/
|
|
65
66
|
async function proxyAndRecordAGUI(req, res, input, fixtures, config, logger) {
|
|
67
|
+
if (require_api_key_auth.isAuthenticatedRequest(req)) {
|
|
68
|
+
res.writeHead(502, { "Content-Type": "application/json" });
|
|
69
|
+
res.end(JSON.stringify({ error: "No configured provider credential" }));
|
|
70
|
+
return 502;
|
|
71
|
+
}
|
|
66
72
|
if (!config.upstream) {
|
|
67
73
|
logger.warn("No upstream URL configured for AG-UI recording — cannot proxy");
|
|
68
74
|
return false;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agui-recorder.cjs","names":["https","http","getLastMessageIfToolResult","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 {\n AGUIFixture,\n AGUIFixtureMatch,\n AGUIRecordConfig,\n AGUIEvent,\n AGUIRunAgentInput,\n} from \"./agui-types.js\";\nimport { extractLastUserMessage, getLastMessageIfToolResult } from \"./agui-handler.js\";\nimport type { Logger } from \"./logger.js\";\n\n/**\n * Sentinel `match.message` value written to disk when the request had no\n * extractable user text. Keeps the on-disk fixture serializable (predicate\n * matchers aren't) but won't match any real user input on replay.\n */\nexport const NO_USER_MESSAGE_SENTINEL = \"__NO_USER_MESSAGE__\";\n\n/**\n * Default ceiling (bytes) for the in-memory AG-UI record buffer. The recorder\n * tees every upstream SSE chunk straight to the client AND buffers a copy so it\n * can `Buffer.concat(chunks).toString()` + parse the SSE events into a fixture\n * once the stream ends. With no cap, a large upstream response (amplified since\n * the real-key fixture-miss passthrough landed) builds a string past V8's\n * ~512 MiB max string length and throws `RangeError: Invalid string length`.\n * 64 MiB mirrors the generic proxy path's `DEFAULT_MAX_PROXY_BUFFER_BYTES` and\n * is generous for any real agent turn. Overridable via\n * `AGUIRecordConfig.maxRecordBufferBytes`.\n */\nexport const DEFAULT_AGUI_RECORD_BUFFER_BYTES = 64 * 1024 * 1024; // 64 MiB\n\n/**\n * Absolute hard ceiling (bytes) for the AG-UI record buffer, independent of the\n * configurable `maxRecordBufferBytes`, mirroring the generic proxy path's\n * `PROXY_BUFFER_HARD_CEILING`. 256 MiB of bytes can never decode to more than\n * 256 Mi UTF-16 code units, comfortably below V8's 2^29 - 1 string-length limit,\n * so the eventual `Buffer.concat(chunks).toString()` can never throw.\n */\nexport const AGUI_RECORD_BUFFER_HARD_CEILING = 256 * 1024 * 1024; // 256 MiB\n\n/**\n * Test-only override of the effective AG-UI record-buffer ceiling. Lets the cap\n * suite exercise the over-cap truncation path with a small body instead of\n * streaming hundreds of MB. `undefined` (the default) uses the configured /\n * default cap. NEVER set from production code.\n */\nlet aguiRecordBufferCeilingOverride: number | undefined;\n\n/** @internal test-only — see {@link aguiRecordBufferCeilingOverride}. */\nexport function setAGUIRecordBufferCeilingForTests(value: number | undefined): void {\n aguiRecordBufferCeilingOverride = value;\n}\n\n/**\n * Resolve the effective AG-UI record-buffer byte cap: honor a test-only\n * override, else the configured `maxRecordBufferBytes` (clamped to the hard\n * ceiling), else the default. Non-finite / non-positive configured values fall\n * back to the default.\n */\nfunction resolveAGUIRecordBufferCap(configured: number | undefined): number {\n if (aguiRecordBufferCeilingOverride !== undefined) return aguiRecordBufferCeilingOverride;\n if (configured == null || !Number.isFinite(configured) || configured <= 0) {\n return DEFAULT_AGUI_RECORD_BUFFER_BYTES;\n }\n return Math.min(configured, AGUI_RECORD_BUFFER_HARD_CEILING);\n}\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 // Bound the in-memory record buffer so a single huge upstream response\n // cannot build a string past V8's ~512 MiB limit\n // (RangeError: Invalid string length) when we `Buffer.concat`+stringify\n // it below. The client relay above is INDEPENDENT of this buffer, so\n // capping it never truncates what the client receives — the cap means\n // \"don't journal\", not \"don't answer\" (mirrors the generic proxy path).\n const recordBufferCap = resolveAGUIRecordBufferCap(config.maxRecordBufferBytes);\n let bufferedBytes = 0;\n let recordTruncated = 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, bounded by the record-buffer cap.\n // Once the cap is crossed we stop accumulating and free the buffer so\n // heap growth is bounded and the end-handler skips concat/stringify.\n if (recordTruncated) return;\n if (bufferedBytes + chunk.length > recordBufferCap) {\n recordTruncated = true;\n chunks.length = 0;\n bufferedBytes = 0;\n logger?.warn(\n `AG-UI upstream response exceeded the ${recordBufferCap}-byte record buffer cap — relaying full body to client, but skipping fixture recording to bound memory`,\n );\n return;\n }\n chunks.push(chunk);\n bufferedBytes += chunk.length;\n });\n\n let settled = false;\n\n upstreamRes.on(\"error\", (err) => {\n if (settled) return;\n settled = true;\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 if (settled) return;\n settled = true;\n\n // Don't record fixtures for non-2xx upstream responses\n if (clientStatus !== 200) {\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 resolve(clientStatus);\n return;\n }\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 // Record buffer cap tripped: the buffered copy is partial (and was\n // freed), so we cannot faithfully build a fixture. The client already\n // received every byte via the live tee above, so just skip recording\n // — never stringify the (now-empty) buffer, never persist a truncated\n // fixture. Mirrors the generic proxy path's over-cap \"skip recording,\n // still answer the client\" behavior.\n if (recordTruncated) {\n logger.warn(\n \"AG-UI record buffer cap exceeded — response relayed to client, recording skipped\",\n );\n resolve(clientStatus);\n return;\n }\n\n // Parse buffered SSE events\n const buffered = Buffer.concat(chunks).toString();\n const events = parseSSEEvents(buffered, logger);\n\n // Build fixture — three-way match priority:\n // 1. Tool-result continuation (HITL): match by toolCallId\n // 2. User message: match by last user message content\n // 3. Fallback predicate: no user message present\n let match: AGUIFixtureMatch;\n const lastToolResult = getLastMessageIfToolResult(input);\n if (lastToolResult?.toolCallId) {\n match = { toolCallId: lastToolResult.toolCallId };\n logger.info(`Recorded AG-UI fixture keyed on toolCallId=${lastToolResult.toolCallId}`);\n } else {\n const message = extractLastUserMessage(input);\n if (message) {\n match = { message };\n } else {\n match = {\n predicate: (inp: AGUIRunAgentInput) =>\n !inp.messages?.length || !inp.messages.some((m) => m.role === \"user\"),\n };\n logger.warn(\n \"Recorded AG-UI fixture has no user message — available in-memory only (predicate fixtures cannot be persisted to disk)\",\n );\n }\n }\n const fixture: AGUIFixture = { match, events };\n\n if (!config.proxyOnly) {\n // Register in memory first (always available even if disk write fails)\n fixtures.push(fixture);\n\n // Predicate fixtures (no user message, no toolCallId) cannot be\n // meaningfully serialized — the sentinel becomes a literal string\n // match that never matches real requests. Keep in-memory only.\n if (fixture.match.predicate) {\n logger.warn(\n \"Skipping disk write for predicate fixture — in-memory only (cannot be persisted)\",\n );\n } else {\n const serializableFixture = {\n match: 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 }\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 const warning = `Skipping unparseable SSE data line (${msg}): ${payload.slice(0, 200)}`;\n if (logger) logger.warn(warning);\n else console.warn(warning);\n }\n }\n }\n }\n return events;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"agui-recorder.cjs","names":["isAuthenticatedRequest","https","http","getLastMessageIfToolResult","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 {\n AGUIFixture,\n AGUIFixtureMatch,\n AGUIRecordConfig,\n AGUIEvent,\n AGUIRunAgentInput,\n} from \"./agui-types.js\";\nimport { extractLastUserMessage, getLastMessageIfToolResult } from \"./agui-handler.js\";\nimport type { Logger } from \"./logger.js\";\nimport { isAuthenticatedRequest } from \"./api-key-auth.js\";\n\n/**\n * Sentinel `match.message` value written to disk when the request had no\n * extractable user text. Keeps the on-disk fixture serializable (predicate\n * matchers aren't) but won't match any real user input on replay.\n */\nexport const NO_USER_MESSAGE_SENTINEL = \"__NO_USER_MESSAGE__\";\n\n/**\n * Default ceiling (bytes) for the in-memory AG-UI record buffer. The recorder\n * tees every upstream SSE chunk straight to the client AND buffers a copy so it\n * can `Buffer.concat(chunks).toString()` + parse the SSE events into a fixture\n * once the stream ends. With no cap, a large upstream response (amplified since\n * the real-key fixture-miss passthrough landed) builds a string past V8's\n * ~512 MiB max string length and throws `RangeError: Invalid string length`.\n * 64 MiB mirrors the generic proxy path's `DEFAULT_MAX_PROXY_BUFFER_BYTES` and\n * is generous for any real agent turn. Overridable via\n * `AGUIRecordConfig.maxRecordBufferBytes`.\n */\nexport const DEFAULT_AGUI_RECORD_BUFFER_BYTES = 64 * 1024 * 1024; // 64 MiB\n\n/**\n * Absolute hard ceiling (bytes) for the AG-UI record buffer, independent of the\n * configurable `maxRecordBufferBytes`, mirroring the generic proxy path's\n * `PROXY_BUFFER_HARD_CEILING`. 256 MiB of bytes can never decode to more than\n * 256 Mi UTF-16 code units, comfortably below V8's 2^29 - 1 string-length limit,\n * so the eventual `Buffer.concat(chunks).toString()` can never throw.\n */\nexport const AGUI_RECORD_BUFFER_HARD_CEILING = 256 * 1024 * 1024; // 256 MiB\n\n/**\n * Test-only override of the effective AG-UI record-buffer ceiling. Lets the cap\n * suite exercise the over-cap truncation path with a small body instead of\n * streaming hundreds of MB. `undefined` (the default) uses the configured /\n * default cap. NEVER set from production code.\n */\nlet aguiRecordBufferCeilingOverride: number | undefined;\n\n/** @internal test-only — see {@link aguiRecordBufferCeilingOverride}. */\nexport function setAGUIRecordBufferCeilingForTests(value: number | undefined): void {\n aguiRecordBufferCeilingOverride = value;\n}\n\n/**\n * Resolve the effective AG-UI record-buffer byte cap: honor a test-only\n * override, else the configured `maxRecordBufferBytes` (clamped to the hard\n * ceiling), else the default. Non-finite / non-positive configured values fall\n * back to the default.\n */\nfunction resolveAGUIRecordBufferCap(configured: number | undefined): number {\n if (aguiRecordBufferCeilingOverride !== undefined) return aguiRecordBufferCeilingOverride;\n if (configured == null || !Number.isFinite(configured) || configured <= 0) {\n return DEFAULT_AGUI_RECORD_BUFFER_BYTES;\n }\n return Math.min(configured, AGUI_RECORD_BUFFER_HARD_CEILING);\n}\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 // AG-UI has no provider-specific static credential contract. An inbound\n // test key must never become an implicit upstream credential, so fail before\n // opening an upstream connection.\n if (isAuthenticatedRequest(req)) {\n res.writeHead(502, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"No configured provider credential\" }));\n return 502;\n }\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 // Bound the in-memory record buffer so a single huge upstream response\n // cannot build a string past V8's ~512 MiB limit\n // (RangeError: Invalid string length) when we `Buffer.concat`+stringify\n // it below. The client relay above is INDEPENDENT of this buffer, so\n // capping it never truncates what the client receives — the cap means\n // \"don't journal\", not \"don't answer\" (mirrors the generic proxy path).\n const recordBufferCap = resolveAGUIRecordBufferCap(config.maxRecordBufferBytes);\n let bufferedBytes = 0;\n let recordTruncated = 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, bounded by the record-buffer cap.\n // Once the cap is crossed we stop accumulating and free the buffer so\n // heap growth is bounded and the end-handler skips concat/stringify.\n if (recordTruncated) return;\n if (bufferedBytes + chunk.length > recordBufferCap) {\n recordTruncated = true;\n chunks.length = 0;\n bufferedBytes = 0;\n logger?.warn(\n `AG-UI upstream response exceeded the ${recordBufferCap}-byte record buffer cap — relaying full body to client, but skipping fixture recording to bound memory`,\n );\n return;\n }\n chunks.push(chunk);\n bufferedBytes += chunk.length;\n });\n\n let settled = false;\n\n upstreamRes.on(\"error\", (err) => {\n if (settled) return;\n settled = true;\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 if (settled) return;\n settled = true;\n\n // Don't record fixtures for non-2xx upstream responses\n if (clientStatus !== 200) {\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 resolve(clientStatus);\n return;\n }\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 // Record buffer cap tripped: the buffered copy is partial (and was\n // freed), so we cannot faithfully build a fixture. The client already\n // received every byte via the live tee above, so just skip recording\n // — never stringify the (now-empty) buffer, never persist a truncated\n // fixture. Mirrors the generic proxy path's over-cap \"skip recording,\n // still answer the client\" behavior.\n if (recordTruncated) {\n logger.warn(\n \"AG-UI record buffer cap exceeded — response relayed to client, recording skipped\",\n );\n resolve(clientStatus);\n return;\n }\n\n // Parse buffered SSE events\n const buffered = Buffer.concat(chunks).toString();\n const events = parseSSEEvents(buffered, logger);\n\n // Build fixture — three-way match priority:\n // 1. Tool-result continuation (HITL): match by toolCallId\n // 2. User message: match by last user message content\n // 3. Fallback predicate: no user message present\n let match: AGUIFixtureMatch;\n const lastToolResult = getLastMessageIfToolResult(input);\n if (lastToolResult?.toolCallId) {\n match = { toolCallId: lastToolResult.toolCallId };\n logger.info(`Recorded AG-UI fixture keyed on toolCallId=${lastToolResult.toolCallId}`);\n } else {\n const message = extractLastUserMessage(input);\n if (message) {\n match = { message };\n } else {\n match = {\n predicate: (inp: AGUIRunAgentInput) =>\n !inp.messages?.length || !inp.messages.some((m) => m.role === \"user\"),\n };\n logger.warn(\n \"Recorded AG-UI fixture has no user message — available in-memory only (predicate fixtures cannot be persisted to disk)\",\n );\n }\n }\n const fixture: AGUIFixture = { match, events };\n\n if (!config.proxyOnly) {\n // Register in memory first (always available even if disk write fails)\n fixtures.push(fixture);\n\n // Predicate fixtures (no user message, no toolCallId) cannot be\n // meaningfully serialized — the sentinel becomes a literal string\n // match that never matches real requests. Keep in-memory only.\n if (fixture.match.predicate) {\n logger.warn(\n \"Skipping disk write for predicate fixture — in-memory only (cannot be persisted)\",\n );\n } else {\n const serializableFixture = {\n match: 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 }\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 const warning = `Skipping unparseable SSE data line (${msg}): ${payload.slice(0, 200)}`;\n if (logger) logger.warn(warning);\n else console.warn(warning);\n }\n }\n }\n }\n return events;\n}\n"],"mappings":"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"}
|
|
@@ -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":";;;;;;;;;;;;;;;;;cAkCa;;;;;;;;cASA;;iBAWG,kCAAA;;;;;;;;;iBA0BM,kBAAA,MACf,MAAA,CAAK,sBACL,MAAA,CAAK,uBACH,6BACG,uBACF,0BACA,SACP"}
|
|
@@ -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":";;;;;;;;;;;;;;;;;cAkCa;;;;;;;;cASA;;iBAWG,kCAAA;;;;;;;;;iBA0BM,kBAAA,MACf,MAAA,CAAK,sBACL,MAAA,CAAK,uBACH,6BACG,uBACF,0BACA,SACP"}
|
package/dist/agui-recorder.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isAuthenticatedRequest } from "./api-key-auth.js";
|
|
1
2
|
import { extractLastUserMessage, getLastMessageIfToolResult } from "./agui-handler.js";
|
|
2
3
|
import * as http$1 from "node:http";
|
|
3
4
|
import * as crypto$1 from "node:crypto";
|
|
@@ -57,6 +58,11 @@ function resolveAGUIRecordBufferCap(configured) {
|
|
|
57
58
|
* or `false` if no upstream is configured.
|
|
58
59
|
*/
|
|
59
60
|
async function proxyAndRecordAGUI(req, res, input, fixtures, config, logger) {
|
|
61
|
+
if (isAuthenticatedRequest(req)) {
|
|
62
|
+
res.writeHead(502, { "Content-Type": "application/json" });
|
|
63
|
+
res.end(JSON.stringify({ error: "No configured provider credential" }));
|
|
64
|
+
return 502;
|
|
65
|
+
}
|
|
60
66
|
if (!config.upstream) {
|
|
61
67
|
logger.warn("No upstream URL configured for AG-UI recording — cannot proxy");
|
|
62
68
|
return false;
|
|
@@ -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 {\n AGUIFixture,\n AGUIFixtureMatch,\n AGUIRecordConfig,\n AGUIEvent,\n AGUIRunAgentInput,\n} from \"./agui-types.js\";\nimport { extractLastUserMessage, getLastMessageIfToolResult } from \"./agui-handler.js\";\nimport type { Logger } from \"./logger.js\";\n\n/**\n * Sentinel `match.message` value written to disk when the request had no\n * extractable user text. Keeps the on-disk fixture serializable (predicate\n * matchers aren't) but won't match any real user input on replay.\n */\nexport const NO_USER_MESSAGE_SENTINEL = \"__NO_USER_MESSAGE__\";\n\n/**\n * Default ceiling (bytes) for the in-memory AG-UI record buffer. The recorder\n * tees every upstream SSE chunk straight to the client AND buffers a copy so it\n * can `Buffer.concat(chunks).toString()` + parse the SSE events into a fixture\n * once the stream ends. With no cap, a large upstream response (amplified since\n * the real-key fixture-miss passthrough landed) builds a string past V8's\n * ~512 MiB max string length and throws `RangeError: Invalid string length`.\n * 64 MiB mirrors the generic proxy path's `DEFAULT_MAX_PROXY_BUFFER_BYTES` and\n * is generous for any real agent turn. Overridable via\n * `AGUIRecordConfig.maxRecordBufferBytes`.\n */\nexport const DEFAULT_AGUI_RECORD_BUFFER_BYTES = 64 * 1024 * 1024; // 64 MiB\n\n/**\n * Absolute hard ceiling (bytes) for the AG-UI record buffer, independent of the\n * configurable `maxRecordBufferBytes`, mirroring the generic proxy path's\n * `PROXY_BUFFER_HARD_CEILING`. 256 MiB of bytes can never decode to more than\n * 256 Mi UTF-16 code units, comfortably below V8's 2^29 - 1 string-length limit,\n * so the eventual `Buffer.concat(chunks).toString()` can never throw.\n */\nexport const AGUI_RECORD_BUFFER_HARD_CEILING = 256 * 1024 * 1024; // 256 MiB\n\n/**\n * Test-only override of the effective AG-UI record-buffer ceiling. Lets the cap\n * suite exercise the over-cap truncation path with a small body instead of\n * streaming hundreds of MB. `undefined` (the default) uses the configured /\n * default cap. NEVER set from production code.\n */\nlet aguiRecordBufferCeilingOverride: number | undefined;\n\n/** @internal test-only — see {@link aguiRecordBufferCeilingOverride}. */\nexport function setAGUIRecordBufferCeilingForTests(value: number | undefined): void {\n aguiRecordBufferCeilingOverride = value;\n}\n\n/**\n * Resolve the effective AG-UI record-buffer byte cap: honor a test-only\n * override, else the configured `maxRecordBufferBytes` (clamped to the hard\n * ceiling), else the default. Non-finite / non-positive configured values fall\n * back to the default.\n */\nfunction resolveAGUIRecordBufferCap(configured: number | undefined): number {\n if (aguiRecordBufferCeilingOverride !== undefined) return aguiRecordBufferCeilingOverride;\n if (configured == null || !Number.isFinite(configured) || configured <= 0) {\n return DEFAULT_AGUI_RECORD_BUFFER_BYTES;\n }\n return Math.min(configured, AGUI_RECORD_BUFFER_HARD_CEILING);\n}\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 // Bound the in-memory record buffer so a single huge upstream response\n // cannot build a string past V8's ~512 MiB limit\n // (RangeError: Invalid string length) when we `Buffer.concat`+stringify\n // it below. The client relay above is INDEPENDENT of this buffer, so\n // capping it never truncates what the client receives — the cap means\n // \"don't journal\", not \"don't answer\" (mirrors the generic proxy path).\n const recordBufferCap = resolveAGUIRecordBufferCap(config.maxRecordBufferBytes);\n let bufferedBytes = 0;\n let recordTruncated = 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, bounded by the record-buffer cap.\n // Once the cap is crossed we stop accumulating and free the buffer so\n // heap growth is bounded and the end-handler skips concat/stringify.\n if (recordTruncated) return;\n if (bufferedBytes + chunk.length > recordBufferCap) {\n recordTruncated = true;\n chunks.length = 0;\n bufferedBytes = 0;\n logger?.warn(\n `AG-UI upstream response exceeded the ${recordBufferCap}-byte record buffer cap — relaying full body to client, but skipping fixture recording to bound memory`,\n );\n return;\n }\n chunks.push(chunk);\n bufferedBytes += chunk.length;\n });\n\n let settled = false;\n\n upstreamRes.on(\"error\", (err) => {\n if (settled) return;\n settled = true;\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 if (settled) return;\n settled = true;\n\n // Don't record fixtures for non-2xx upstream responses\n if (clientStatus !== 200) {\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 resolve(clientStatus);\n return;\n }\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 // Record buffer cap tripped: the buffered copy is partial (and was\n // freed), so we cannot faithfully build a fixture. The client already\n // received every byte via the live tee above, so just skip recording\n // — never stringify the (now-empty) buffer, never persist a truncated\n // fixture. Mirrors the generic proxy path's over-cap \"skip recording,\n // still answer the client\" behavior.\n if (recordTruncated) {\n logger.warn(\n \"AG-UI record buffer cap exceeded — response relayed to client, recording skipped\",\n );\n resolve(clientStatus);\n return;\n }\n\n // Parse buffered SSE events\n const buffered = Buffer.concat(chunks).toString();\n const events = parseSSEEvents(buffered, logger);\n\n // Build fixture — three-way match priority:\n // 1. Tool-result continuation (HITL): match by toolCallId\n // 2. User message: match by last user message content\n // 3. Fallback predicate: no user message present\n let match: AGUIFixtureMatch;\n const lastToolResult = getLastMessageIfToolResult(input);\n if (lastToolResult?.toolCallId) {\n match = { toolCallId: lastToolResult.toolCallId };\n logger.info(`Recorded AG-UI fixture keyed on toolCallId=${lastToolResult.toolCallId}`);\n } else {\n const message = extractLastUserMessage(input);\n if (message) {\n match = { message };\n } else {\n match = {\n predicate: (inp: AGUIRunAgentInput) =>\n !inp.messages?.length || !inp.messages.some((m) => m.role === \"user\"),\n };\n logger.warn(\n \"Recorded AG-UI fixture has no user message — available in-memory only (predicate fixtures cannot be persisted to disk)\",\n );\n }\n }\n const fixture: AGUIFixture = { match, events };\n\n if (!config.proxyOnly) {\n // Register in memory first (always available even if disk write fails)\n fixtures.push(fixture);\n\n // Predicate fixtures (no user message, no toolCallId) cannot be\n // meaningfully serialized — the sentinel becomes a literal string\n // match that never matches real requests. Keep in-memory only.\n if (fixture.match.predicate) {\n logger.warn(\n \"Skipping disk write for predicate fixture — in-memory only (cannot be persisted)\",\n );\n } else {\n const serializableFixture = {\n match: 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 }\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 const warning = `Skipping unparseable SSE data line (${msg}): ${payload.slice(0, 200)}`;\n if (logger) logger.warn(warning);\n else console.warn(warning);\n }\n }\n }\n }\n return events;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAiCA,MAAa,mCAAmC,KAAK,OAAO;;;;;;;;AAS5D,MAAa,kCAAkC,MAAM,OAAO;;;;;;;AAQ5D,IAAI;;AAGJ,SAAgB,mCAAmC,OAAiC;AAClF,mCAAkC;;;;;;;;AASpC,SAAS,2BAA2B,YAAwC;AAC1E,KAAI,oCAAoC,OAAW,QAAO;AAC1D,KAAI,cAAc,QAAQ,CAAC,OAAO,SAAS,WAAW,IAAI,cAAc,EACtE,QAAO;AAET,QAAO,KAAK,IAAI,YAAY,gCAAgC;;;;;;;;;;AAW9D,eAAsB,mBACpB,KACA,KACA,OACA,UACA,QACA,QACyB;AACzB,KAAI,CAAC,OAAO,UAAU;AACpB,SAAO,KAAK,gEAAgE;AAC5E,SAAO;;CAGT,IAAI;AACJ,KAAI;AACF,WAAS,IAAI,IAAI,OAAO,SAAS;UAC1B,KAAK;EACZ,MAAM,SAAS,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAC/D,SAAO,MAAM,+BAA+B,OAAO,SAAS,KAAK,SAAS;AAC1E,MAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,MAAI,IAAI,KAAK,UAAU,EAAE,OAAO,8BAA8B,CAAC,CAAC;AAChE,SAAO;;AAGT,QAAO,KAAK,wCAAwC,OAAO,WAAW;CAGtE,MAAM,iBAAyC;EAC7C,gBAAgB;EAChB,QAAQ;EACT;CAED,MAAM,gBAAgB,IAAI,QAAQ;AAClC,KAAI,cACF,gBAAe,mBAAmB,MAAM,QAAQ,cAAc,GAC1D,cAAc,KAAK,KAAK,GACxB;CAEN,MAAM,SAAS,IAAI,QAAQ;AAC3B,KAAI,OACF,gBAAe,eAAe,MAAM,QAAQ,OAAO,GAAG,OAAO,KAAK,KAAK,GAAG;CAG5E,MAAM,cAAc,KAAK,UAAU,MAAM;CAEzC,IAAI;AACJ,KAAI;AACF,WAAS,MAAM,kBACb,QACA,gBACA,aACA,KACA,OACA,UACA,QACA,OACD;UACM,KAAK;EACZ,MAAM,MAAM,eAAe,QAAQ,IAAI,UAAU;AACjD,SAAO,MAAM,+BAA+B,MAAM;AAClD,MAAI,CAAC,IAAI,aAAa;AACpB,OAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,OAAI,IAAI,KAAK,UAAU,EAAE,OAAO,oCAAoC,CAAC,CAAC;aAC7D,CAAC,IAAI,cACd,KAAI,KAAK;AAEX,WAAS;;AAGX,QAAO;;AAOT,SAAS,kBACP,QACA,SACA,MACA,WACA,OACA,UACA,QACA,QACiB;AACjB,QAAO,IAAI,SAAS,SAAS,WAAW;EACtC,MAAM,YAAY,OAAO,aAAa,WAAW,QAAQA;EACzD,MAAM,sBAAsB;EAE5B,MAAM,cAAc,UAAU,QAC5B,QACA;GACE,QAAQ;GACR,SAAS;GACT,SAAS;IACP,GAAG;IACH,kBAAkB,OAAO,WAAW,KAAK,CAAC,UAAU;IACrD;GACF,GACA,gBAAgB;GACf,MAAM,iBAAiB,YAAY,cAAc;GAKjD,MAAM,eAAe,kBAAkB,OAAO,iBAAiB,MAAM,MAAM;AAG3E,OAAI,CAAC,UAAU,YACb,KAAI,iBAAiB,IACnB,WAAU,UAAU,KAAK;IACvB,gBAAgB;IAChB,iBAAiB;IACjB,YAAY;IACb,CAAC;QACG;IACL,MAAM,KAAK,YAAY,QAAQ,mBAAmB;AAClD,cAAU,UAAU,KAAK,EAAE,gBAAgB,IAAI,CAAC;;GAIpD,MAAM,SAAmB,EAAE;GAC3B,IAAI,oBAAoB;GAOxB,MAAM,kBAAkB,2BAA2B,OAAO,qBAAqB;GAC/E,IAAI,gBAAgB;GACpB,IAAI,kBAAkB;AAEtB,eAAY,GAAG,SAAS,UAAkB;AAExC,QAAI;AACF,eAAU,MAAM,MAAM;aACf,KAAK;AACZ,SAAI,CAAC,mBAAmB;AACtB,0BAAoB;AACpB,cAAQ,KACN,2CACA,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,CACjD;;;AAML,QAAI,gBAAiB;AACrB,QAAI,gBAAgB,MAAM,SAAS,iBAAiB;AAClD,uBAAkB;AAClB,YAAO,SAAS;AAChB,qBAAgB;AAChB,aAAQ,KACN,wCAAwC,gBAAgB,wGACzD;AACD;;AAEF,WAAO,KAAK,MAAM;AAClB,qBAAiB,MAAM;KACvB;GAEF,IAAI,UAAU;AAEd,eAAY,GAAG,UAAU,QAAQ;AAC/B,QAAI,QAAS;AACb,cAAU;AACV,QAAI;AACF,SAAI,CAAC,UAAU,aAAa;AAC1B,gBAAU,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAChE,gBAAU,IAAI,KAAK,UAAU,EAAE,OAAO,oCAAoC,CAAC,CAAC;gBACnE,CAAC,UAAU,cACpB,WAAU,KAAK;aAEV,UAAU;AACjB,YAAO,KACL,6CACA,oBAAoB,QAAQ,SAAS,UAAU,OAAO,SAAS,CAChE;;AAEH,WAAO,IAAI;KACX;AAEF,eAAY,GAAG,aAAa;AAC1B,QAAI,QAAS;AACb,cAAU;AAGV,QAAI,iBAAiB,KAAK;AACxB,SAAI;AACF,UAAI,CAAC,UAAU,cAAe,WAAU,KAAK;cACtC,UAAU;AACjB,aAAO,KACL,kCACA,oBAAoB,QAAQ,SAAS,UAAU,OAAO,SAAS,CAChE;;AAEH,aAAQ,aAAa;AACrB;;AAEF,QAAI;AACF,SAAI,CAAC,UAAU,cAAe,WAAU,KAAK;aACtC,UAAU;AACjB,YAAO,KACL,kCACA,oBAAoB,QAAQ,SAAS,UAAU,OAAO,SAAS,CAChE;;AASH,QAAI,iBAAiB;AACnB,YAAO,KACL,mFACD;AACD,aAAQ,aAAa;AACrB;;IAKF,MAAM,SAAS,eADE,OAAO,OAAO,OAAO,CAAC,UAAU,EACT,OAAO;IAM/C,IAAI;IACJ,MAAM,iBAAiB,2BAA2B,MAAM;AACxD,QAAI,gBAAgB,YAAY;AAC9B,aAAQ,EAAE,YAAY,eAAe,YAAY;AACjD,YAAO,KAAK,8CAA8C,eAAe,aAAa;WACjF;KACL,MAAM,UAAU,uBAAuB,MAAM;AAC7C,SAAI,QACF,SAAQ,EAAE,SAAS;UACd;AACL,cAAQ,EACN,YAAY,QACV,CAAC,IAAI,UAAU,UAAU,CAAC,IAAI,SAAS,MAAM,MAAM,EAAE,SAAS,OAAO,EACxE;AACD,aAAO,KACL,yHACD;;;IAGL,MAAM,UAAuB;KAAE;KAAO;KAAQ;AAE9C,QAAI,CAAC,OAAO,WAAW;AAErB,cAAS,KAAK,QAAQ;AAKtB,SAAI,QAAQ,MAAM,UAChB,QAAO,KACL,mFACD;UACI;MACL,MAAM,sBAAsB;OAC1B,OAAO,QAAQ;OACf,QAAQ,QAAQ;OAChB,GAAI,QAAQ,YAAY,SAAY,EAAE,SAAS,QAAQ,SAAS,GAAG,EAAE;OACtE;MAED,MAAM,cAAc,OAAO,eAAe;MAE1C,MAAM,WAAW,yBADC,IAAI,MAAM,EAAC,aAAa,CAAC,QAAQ,SAAS,IAAI,CAC7B,GAAGC,SAAO,YAAY,CAAC,MAAM,GAAG,EAAE,CAAC;MACtE,MAAM,WAAW,KAAK,KAAK,aAAa,SAAS;AAEjD,UAAI;AACF,UAAG,UAAU,aAAa,EAAE,WAAW,MAAM,CAAC;AAC9C,UAAG,cACD,UACA,KAAK,UAAU,EAAE,UAAU,CAAC,oBAAoB,EAAE,EAAE,MAAM,EAAE,EAC5D,QACD;AACD,cAAO,KAAK,6BAA6B,WAAW;eAC7C,KAAK;OACZ,MAAM,MAAM,eAAe,QAAQ,IAAI,UAAU;AACjD,cAAO,MACL,yCAAyC,IAAI,+BAC9C;;;UAIL,QAAO,KAAK,0CAA0C;AAGxD,YAAQ,aAAa;KACrB;IAEL;AAED,cAAY,GAAG,iBAAiB;AAC9B,eAAY,wBACV,IAAI,MAAM,0CAA0C,sBAAsB,IAAK,GAAG,CACnF;IACD;AAEF,cAAY,GAAG,UAAU,QAAQ;AAC/B,OAAI;AACF,QAAI,CAAC,UAAU,aAAa;AAC1B,eAAU,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAChE,eAAU,IAAI,KAAK,UAAU,EAAE,OAAO,oCAAoC,CAAC,CAAC;eACnE,CAAC,UAAU,cACpB,WAAU,KAAK;YAEV,UAAU;AACjB,WAAO,KACL,6CACA,oBAAoB,QAAQ,SAAS,UAAU,OAAO,SAAS,CAChE;;AAEH,UAAO,IAAI;IACX;AAEF,cAAY,MAAM,KAAK;AACvB,cAAY,KAAK;GACjB;;;;;AAMJ,SAAS,eAAe,MAAc,QAA8B;CAClE,MAAM,SAAsB,EAAE;CAC9B,MAAM,SAAS,KAAK,MAAM,OAAO;AACjC,MAAK,MAAM,SAAS,QAAQ;EAC1B,MAAM,QAAQ,MAAM,MAAM,KAAK;AAC/B,OAAK,MAAM,QAAQ,MACjB,KAAI,KAAK,WAAW,QAAQ,EAAE;GAC5B,MAAM,UAAU,KAAK,WAAW,SAAS,GAAG,KAAK,MAAM,EAAE,GAAG,KAAK,MAAM,EAAE;AACzE,OAAI;IACF,MAAM,SAAS,KAAK,MAAM,QAAQ;AAClC,WAAO,KAAK,OAAO;YACZ,KAAK;IAEZ,MAAM,UAAU,uCADJ,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,CACD,KAAK,QAAQ,MAAM,GAAG,IAAI;AACrF,QAAI,OAAQ,QAAO,KAAK,QAAQ;QAC3B,SAAQ,KAAK,QAAQ;;;;AAKlC,QAAO"}
|
|
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 {\n AGUIFixture,\n AGUIFixtureMatch,\n AGUIRecordConfig,\n AGUIEvent,\n AGUIRunAgentInput,\n} from \"./agui-types.js\";\nimport { extractLastUserMessage, getLastMessageIfToolResult } from \"./agui-handler.js\";\nimport type { Logger } from \"./logger.js\";\nimport { isAuthenticatedRequest } from \"./api-key-auth.js\";\n\n/**\n * Sentinel `match.message` value written to disk when the request had no\n * extractable user text. Keeps the on-disk fixture serializable (predicate\n * matchers aren't) but won't match any real user input on replay.\n */\nexport const NO_USER_MESSAGE_SENTINEL = \"__NO_USER_MESSAGE__\";\n\n/**\n * Default ceiling (bytes) for the in-memory AG-UI record buffer. The recorder\n * tees every upstream SSE chunk straight to the client AND buffers a copy so it\n * can `Buffer.concat(chunks).toString()` + parse the SSE events into a fixture\n * once the stream ends. With no cap, a large upstream response (amplified since\n * the real-key fixture-miss passthrough landed) builds a string past V8's\n * ~512 MiB max string length and throws `RangeError: Invalid string length`.\n * 64 MiB mirrors the generic proxy path's `DEFAULT_MAX_PROXY_BUFFER_BYTES` and\n * is generous for any real agent turn. Overridable via\n * `AGUIRecordConfig.maxRecordBufferBytes`.\n */\nexport const DEFAULT_AGUI_RECORD_BUFFER_BYTES = 64 * 1024 * 1024; // 64 MiB\n\n/**\n * Absolute hard ceiling (bytes) for the AG-UI record buffer, independent of the\n * configurable `maxRecordBufferBytes`, mirroring the generic proxy path's\n * `PROXY_BUFFER_HARD_CEILING`. 256 MiB of bytes can never decode to more than\n * 256 Mi UTF-16 code units, comfortably below V8's 2^29 - 1 string-length limit,\n * so the eventual `Buffer.concat(chunks).toString()` can never throw.\n */\nexport const AGUI_RECORD_BUFFER_HARD_CEILING = 256 * 1024 * 1024; // 256 MiB\n\n/**\n * Test-only override of the effective AG-UI record-buffer ceiling. Lets the cap\n * suite exercise the over-cap truncation path with a small body instead of\n * streaming hundreds of MB. `undefined` (the default) uses the configured /\n * default cap. NEVER set from production code.\n */\nlet aguiRecordBufferCeilingOverride: number | undefined;\n\n/** @internal test-only — see {@link aguiRecordBufferCeilingOverride}. */\nexport function setAGUIRecordBufferCeilingForTests(value: number | undefined): void {\n aguiRecordBufferCeilingOverride = value;\n}\n\n/**\n * Resolve the effective AG-UI record-buffer byte cap: honor a test-only\n * override, else the configured `maxRecordBufferBytes` (clamped to the hard\n * ceiling), else the default. Non-finite / non-positive configured values fall\n * back to the default.\n */\nfunction resolveAGUIRecordBufferCap(configured: number | undefined): number {\n if (aguiRecordBufferCeilingOverride !== undefined) return aguiRecordBufferCeilingOverride;\n if (configured == null || !Number.isFinite(configured) || configured <= 0) {\n return DEFAULT_AGUI_RECORD_BUFFER_BYTES;\n }\n return Math.min(configured, AGUI_RECORD_BUFFER_HARD_CEILING);\n}\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 // AG-UI has no provider-specific static credential contract. An inbound\n // test key must never become an implicit upstream credential, so fail before\n // opening an upstream connection.\n if (isAuthenticatedRequest(req)) {\n res.writeHead(502, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"No configured provider credential\" }));\n return 502;\n }\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 // Bound the in-memory record buffer so a single huge upstream response\n // cannot build a string past V8's ~512 MiB limit\n // (RangeError: Invalid string length) when we `Buffer.concat`+stringify\n // it below. The client relay above is INDEPENDENT of this buffer, so\n // capping it never truncates what the client receives — the cap means\n // \"don't journal\", not \"don't answer\" (mirrors the generic proxy path).\n const recordBufferCap = resolveAGUIRecordBufferCap(config.maxRecordBufferBytes);\n let bufferedBytes = 0;\n let recordTruncated = 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, bounded by the record-buffer cap.\n // Once the cap is crossed we stop accumulating and free the buffer so\n // heap growth is bounded and the end-handler skips concat/stringify.\n if (recordTruncated) return;\n if (bufferedBytes + chunk.length > recordBufferCap) {\n recordTruncated = true;\n chunks.length = 0;\n bufferedBytes = 0;\n logger?.warn(\n `AG-UI upstream response exceeded the ${recordBufferCap}-byte record buffer cap — relaying full body to client, but skipping fixture recording to bound memory`,\n );\n return;\n }\n chunks.push(chunk);\n bufferedBytes += chunk.length;\n });\n\n let settled = false;\n\n upstreamRes.on(\"error\", (err) => {\n if (settled) return;\n settled = true;\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 if (settled) return;\n settled = true;\n\n // Don't record fixtures for non-2xx upstream responses\n if (clientStatus !== 200) {\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 resolve(clientStatus);\n return;\n }\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 // Record buffer cap tripped: the buffered copy is partial (and was\n // freed), so we cannot faithfully build a fixture. The client already\n // received every byte via the live tee above, so just skip recording\n // — never stringify the (now-empty) buffer, never persist a truncated\n // fixture. Mirrors the generic proxy path's over-cap \"skip recording,\n // still answer the client\" behavior.\n if (recordTruncated) {\n logger.warn(\n \"AG-UI record buffer cap exceeded — response relayed to client, recording skipped\",\n );\n resolve(clientStatus);\n return;\n }\n\n // Parse buffered SSE events\n const buffered = Buffer.concat(chunks).toString();\n const events = parseSSEEvents(buffered, logger);\n\n // Build fixture — three-way match priority:\n // 1. Tool-result continuation (HITL): match by toolCallId\n // 2. User message: match by last user message content\n // 3. Fallback predicate: no user message present\n let match: AGUIFixtureMatch;\n const lastToolResult = getLastMessageIfToolResult(input);\n if (lastToolResult?.toolCallId) {\n match = { toolCallId: lastToolResult.toolCallId };\n logger.info(`Recorded AG-UI fixture keyed on toolCallId=${lastToolResult.toolCallId}`);\n } else {\n const message = extractLastUserMessage(input);\n if (message) {\n match = { message };\n } else {\n match = {\n predicate: (inp: AGUIRunAgentInput) =>\n !inp.messages?.length || !inp.messages.some((m) => m.role === \"user\"),\n };\n logger.warn(\n \"Recorded AG-UI fixture has no user message — available in-memory only (predicate fixtures cannot be persisted to disk)\",\n );\n }\n }\n const fixture: AGUIFixture = { match, events };\n\n if (!config.proxyOnly) {\n // Register in memory first (always available even if disk write fails)\n fixtures.push(fixture);\n\n // Predicate fixtures (no user message, no toolCallId) cannot be\n // meaningfully serialized — the sentinel becomes a literal string\n // match that never matches real requests. Keep in-memory only.\n if (fixture.match.predicate) {\n logger.warn(\n \"Skipping disk write for predicate fixture — in-memory only (cannot be persisted)\",\n );\n } else {\n const serializableFixture = {\n match: 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 }\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 const warning = `Skipping unparseable SSE data line (${msg}): ${payload.slice(0, 200)}`;\n if (logger) logger.warn(warning);\n else console.warn(warning);\n }\n }\n }\n }\n return events;\n}\n"],"mappings":"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"}
|
package/dist/agui-stub.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { AGUIActivityDeltaEvent, AGUIActivitySnapshotEvent, AGUIBaseEvent, AGUICustomEvent, AGUIEvent, AGUIEventType, AGUIFixture, AGUIFixtureMatch, AGUIInterrupt, AGUIMessage, AGUIMessageContentPart, 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";
|
|
1
|
+
import { AGUIActivityDeltaEvent, AGUIActivitySnapshotEvent, AGUIBaseEvent, AGUICustomEvent, AGUIEvent, AGUIEventType, AGUIFixture, AGUIFixtureMatch, AGUIInterrupt, AGUIMessage, AGUIMessageContentPart, 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, AGUITokenUsage, AGUIToolCall, AGUIToolCallArgsEvent, AGUIToolCallChunkEvent, AGUIToolCallEndEvent, AGUIToolCallResultEvent, AGUIToolCallStartEvent, AGUIToolDefinition } from "./agui-types.cjs";
|
|
2
2
|
import { AGUIMock } from "./agui-mock.cjs";
|
|
3
3
|
import { AGUIBuildOpts, buildActivityDelta, buildActivityResponse, buildCompositeResponse, buildCustomEvent, buildErrorResponse, buildMessagesSnapshot, buildRawEvent, buildReasoningChunk, buildReasoningEncryptedValue, buildReasoningResponse, buildStateDelta, buildStateUpdate, buildStepWithText, buildTextChunkResponse, buildTextResponse, buildToolCallChunk, buildToolCallResponse, extractLastUserMessage, findFixture, getLastMessageIfToolResult, matchesFixture, writeAGUIEventStream } from "./agui-handler.cjs";
|
|
4
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 AGUIMessageContentPart, 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, getLastMessageIfToolResult, matchesFixture, proxyAndRecordAGUI, writeAGUIEventStream };
|
|
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 AGUIMessageContentPart, 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 AGUITokenUsage, 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, getLastMessageIfToolResult, matchesFixture, proxyAndRecordAGUI, writeAGUIEventStream };
|
package/dist/agui-stub.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { AGUIActivityDeltaEvent, AGUIActivitySnapshotEvent, AGUIBaseEvent, AGUICustomEvent, AGUIEvent, AGUIEventType, AGUIFixture, AGUIFixtureMatch, AGUIInterrupt, AGUIMessage, AGUIMessageContentPart, 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";
|
|
1
|
+
import { AGUIActivityDeltaEvent, AGUIActivitySnapshotEvent, AGUIBaseEvent, AGUICustomEvent, AGUIEvent, AGUIEventType, AGUIFixture, AGUIFixtureMatch, AGUIInterrupt, AGUIMessage, AGUIMessageContentPart, 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, AGUITokenUsage, AGUIToolCall, AGUIToolCallArgsEvent, AGUIToolCallChunkEvent, AGUIToolCallEndEvent, AGUIToolCallResultEvent, AGUIToolCallStartEvent, AGUIToolDefinition } from "./agui-types.js";
|
|
2
2
|
import { AGUIMock } from "./agui-mock.js";
|
|
3
3
|
import { AGUIBuildOpts, buildActivityDelta, buildActivityResponse, buildCompositeResponse, buildCustomEvent, buildErrorResponse, buildMessagesSnapshot, buildRawEvent, buildReasoningChunk, buildReasoningEncryptedValue, buildReasoningResponse, buildStateDelta, buildStateUpdate, buildStepWithText, buildTextChunkResponse, buildTextResponse, buildToolCallChunk, buildToolCallResponse, extractLastUserMessage, findFixture, getLastMessageIfToolResult, matchesFixture, writeAGUIEventStream } from "./agui-handler.js";
|
|
4
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 AGUIMessageContentPart, 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, getLastMessageIfToolResult, matchesFixture, proxyAndRecordAGUI, writeAGUIEventStream };
|
|
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 AGUIMessageContentPart, 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 AGUITokenUsage, 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, getLastMessageIfToolResult, matchesFixture, proxyAndRecordAGUI, writeAGUIEventStream };
|