@copilotkit/aimock 1.10.0 → 1.11.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/dist/agui-handler.cjs +340 -0
- package/dist/agui-handler.cjs.map +1 -0
- package/dist/agui-handler.d.cts +96 -0
- package/dist/agui-handler.d.cts.map +1 -0
- package/dist/agui-handler.d.ts +96 -0
- package/dist/agui-handler.d.ts.map +1 -0
- package/dist/agui-handler.js +326 -0
- package/dist/agui-handler.js.map +1 -0
- package/dist/agui-mock.cjs +190 -0
- package/dist/agui-mock.cjs.map +1 -0
- package/dist/agui-mock.d.cts +50 -0
- package/dist/agui-mock.d.cts.map +1 -0
- package/dist/agui-mock.d.ts +50 -0
- package/dist/agui-mock.d.ts.map +1 -0
- package/dist/agui-mock.js +188 -0
- package/dist/agui-mock.js.map +1 -0
- package/dist/agui-recorder.cjs +153 -0
- package/dist/agui-recorder.cjs.map +1 -0
- package/dist/agui-recorder.d.cts +19 -0
- package/dist/agui-recorder.d.cts.map +1 -0
- package/dist/agui-recorder.d.ts +19 -0
- package/dist/agui-recorder.d.ts.map +1 -0
- package/dist/agui-recorder.js +147 -0
- package/dist/agui-recorder.js.map +1 -0
- package/dist/agui-types.d.cts +231 -0
- package/dist/agui-types.d.cts.map +1 -0
- package/dist/agui-types.d.ts +231 -0
- package/dist/agui-types.d.ts.map +1 -0
- package/dist/cli.cjs +32 -1
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +32 -1
- package/dist/cli.js.map +1 -1
- package/dist/config-loader.cjs +19 -0
- package/dist/config-loader.cjs.map +1 -1
- package/dist/config-loader.d.cts +16 -0
- package/dist/config-loader.d.cts.map +1 -1
- package/dist/config-loader.d.ts +16 -0
- package/dist/config-loader.d.ts.map +1 -1
- package/dist/config-loader.js +19 -0
- package/dist/config-loader.js.map +1 -1
- package/dist/index.cjs +19 -0
- package/dist/index.d.cts +5 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +4 -1
- package/dist/suite.cjs +8 -0
- package/dist/suite.cjs.map +1 -1
- package/dist/suite.d.cts +4 -0
- package/dist/suite.d.cts.map +1 -1
- package/dist/suite.d.ts +4 -0
- package/dist/suite.d.ts.map +1 -1
- package/dist/suite.js +8 -0
- package/dist/suite.js.map +1 -1
- package/dist/vector-types.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { flattenHeaders, readBody } from "./helpers.js";
|
|
2
|
+
import { Logger } from "./logger.js";
|
|
3
|
+
import { buildReasoningResponse, buildStateUpdate, buildTextResponse, buildToolCallResponse, findFixture, writeAGUIEventStream } from "./agui-handler.js";
|
|
4
|
+
import { proxyAndRecordAGUI } from "./agui-recorder.js";
|
|
5
|
+
import * as http from "node:http";
|
|
6
|
+
|
|
7
|
+
//#region src/agui-mock.ts
|
|
8
|
+
var AGUIMock = class {
|
|
9
|
+
fixtures = [];
|
|
10
|
+
server = null;
|
|
11
|
+
journal = null;
|
|
12
|
+
registry = null;
|
|
13
|
+
options;
|
|
14
|
+
baseUrl = "";
|
|
15
|
+
recordConfig;
|
|
16
|
+
logger;
|
|
17
|
+
constructor(options) {
|
|
18
|
+
this.options = options ?? {};
|
|
19
|
+
this.logger = new Logger("silent");
|
|
20
|
+
}
|
|
21
|
+
addFixture(fixture) {
|
|
22
|
+
this.fixtures.push(fixture);
|
|
23
|
+
return this;
|
|
24
|
+
}
|
|
25
|
+
onMessage(pattern, text, opts) {
|
|
26
|
+
const events = buildTextResponse(text);
|
|
27
|
+
this.fixtures.push({
|
|
28
|
+
match: { message: pattern },
|
|
29
|
+
events,
|
|
30
|
+
delayMs: opts?.delayMs
|
|
31
|
+
});
|
|
32
|
+
return this;
|
|
33
|
+
}
|
|
34
|
+
onRun(pattern, events, delayMs) {
|
|
35
|
+
this.fixtures.push({
|
|
36
|
+
match: { message: pattern },
|
|
37
|
+
events,
|
|
38
|
+
delayMs
|
|
39
|
+
});
|
|
40
|
+
return this;
|
|
41
|
+
}
|
|
42
|
+
onToolCall(pattern, toolName, args, opts) {
|
|
43
|
+
const events = buildToolCallResponse(toolName, args, { result: opts?.result });
|
|
44
|
+
this.fixtures.push({
|
|
45
|
+
match: { message: pattern },
|
|
46
|
+
events,
|
|
47
|
+
delayMs: opts?.delayMs
|
|
48
|
+
});
|
|
49
|
+
return this;
|
|
50
|
+
}
|
|
51
|
+
onStateKey(key, snapshot, delayMs) {
|
|
52
|
+
const events = buildStateUpdate(snapshot);
|
|
53
|
+
this.fixtures.push({
|
|
54
|
+
match: { stateKey: key },
|
|
55
|
+
events,
|
|
56
|
+
delayMs
|
|
57
|
+
});
|
|
58
|
+
return this;
|
|
59
|
+
}
|
|
60
|
+
onReasoning(pattern, text, opts) {
|
|
61
|
+
const events = buildReasoningResponse(text);
|
|
62
|
+
this.fixtures.push({
|
|
63
|
+
match: { message: pattern },
|
|
64
|
+
events,
|
|
65
|
+
delayMs: opts?.delayMs
|
|
66
|
+
});
|
|
67
|
+
return this;
|
|
68
|
+
}
|
|
69
|
+
onPredicate(predicate, events, delayMs) {
|
|
70
|
+
this.fixtures.push({
|
|
71
|
+
match: { predicate },
|
|
72
|
+
events,
|
|
73
|
+
delayMs
|
|
74
|
+
});
|
|
75
|
+
return this;
|
|
76
|
+
}
|
|
77
|
+
enableRecording(config) {
|
|
78
|
+
this.recordConfig = config;
|
|
79
|
+
return this;
|
|
80
|
+
}
|
|
81
|
+
reset() {
|
|
82
|
+
this.fixtures = [];
|
|
83
|
+
this.recordConfig = void 0;
|
|
84
|
+
return this;
|
|
85
|
+
}
|
|
86
|
+
async handleRequest(req, res, pathname) {
|
|
87
|
+
if (req.method !== "POST" || pathname !== "/" && pathname !== "") return false;
|
|
88
|
+
if (this.registry) this.registry.incrementCounter("aimock_agui_requests_total", { method: "POST" });
|
|
89
|
+
const body = await readBody(req);
|
|
90
|
+
let input;
|
|
91
|
+
try {
|
|
92
|
+
input = JSON.parse(body);
|
|
93
|
+
} catch {
|
|
94
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
95
|
+
res.end(JSON.stringify({ error: "Invalid JSON body" }));
|
|
96
|
+
this.journalRequest(req, pathname, 400);
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
const fixture = findFixture(input, this.fixtures);
|
|
100
|
+
if (fixture) {
|
|
101
|
+
await writeAGUIEventStream(res, fixture.events, { delayMs: fixture.delayMs });
|
|
102
|
+
this.journalRequest(req, pathname, 200);
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
if (this.recordConfig) {
|
|
106
|
+
if (await proxyAndRecordAGUI(req, res, input, this.fixtures, this.recordConfig, this.logger)) {
|
|
107
|
+
this.journalRequest(req, pathname, 200);
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
112
|
+
res.end(JSON.stringify({ error: "No matching AG-UI fixture" }));
|
|
113
|
+
this.journalRequest(req, pathname, 404);
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
health() {
|
|
117
|
+
return {
|
|
118
|
+
status: "ok",
|
|
119
|
+
fixtures: this.fixtures.length
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
setJournal(journal) {
|
|
123
|
+
this.journal = journal;
|
|
124
|
+
}
|
|
125
|
+
setBaseUrl(url) {
|
|
126
|
+
this.baseUrl = url;
|
|
127
|
+
}
|
|
128
|
+
setRegistry(registry) {
|
|
129
|
+
this.registry = registry;
|
|
130
|
+
}
|
|
131
|
+
async start() {
|
|
132
|
+
if (this.server) throw new Error("AGUIMock server already started");
|
|
133
|
+
const host = this.options.host ?? "127.0.0.1";
|
|
134
|
+
const port = this.options.port ?? 0;
|
|
135
|
+
return new Promise((resolve, reject) => {
|
|
136
|
+
const srv = http.createServer(async (req, res) => {
|
|
137
|
+
const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
|
|
138
|
+
if (!await this.handleRequest(req, res, url.pathname).catch((err) => {
|
|
139
|
+
this.logger.error(`AGUIMock request error: ${err instanceof Error ? err.message : err}`);
|
|
140
|
+
if (!res.headersSent) {
|
|
141
|
+
res.writeHead(500);
|
|
142
|
+
res.end("Internal server error");
|
|
143
|
+
} else if (!res.writableEnded) res.end();
|
|
144
|
+
return true;
|
|
145
|
+
}) && !res.headersSent) {
|
|
146
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
147
|
+
res.end(JSON.stringify({ error: "Not found" }));
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
srv.on("error", reject);
|
|
151
|
+
srv.listen(port, host, () => {
|
|
152
|
+
const addr = srv.address();
|
|
153
|
+
if (typeof addr === "object" && addr !== null) this.baseUrl = `http://${host}:${addr.port}`;
|
|
154
|
+
this.server = srv;
|
|
155
|
+
resolve(this.baseUrl);
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
async stop() {
|
|
160
|
+
if (!this.server) throw new Error("AGUIMock server not started");
|
|
161
|
+
const srv = this.server;
|
|
162
|
+
await new Promise((resolve, reject) => {
|
|
163
|
+
srv.close((err) => err ? reject(err) : resolve());
|
|
164
|
+
});
|
|
165
|
+
this.server = null;
|
|
166
|
+
}
|
|
167
|
+
get url() {
|
|
168
|
+
if (!this.server) throw new Error("AGUIMock server not started");
|
|
169
|
+
return this.baseUrl;
|
|
170
|
+
}
|
|
171
|
+
journalRequest(req, pathname, status) {
|
|
172
|
+
if (this.journal) this.journal.add({
|
|
173
|
+
method: req.method ?? "POST",
|
|
174
|
+
path: req.url ?? pathname,
|
|
175
|
+
headers: flattenHeaders(req.headers),
|
|
176
|
+
body: null,
|
|
177
|
+
service: "agui",
|
|
178
|
+
response: {
|
|
179
|
+
status,
|
|
180
|
+
fixture: null
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
//#endregion
|
|
187
|
+
export { AGUIMock };
|
|
188
|
+
//# sourceMappingURL=agui-mock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agui-mock.js","names":[],"sources":["../src/agui-mock.ts"],"sourcesContent":["import * as http from \"node:http\";\nimport type { Mountable } from \"./types.js\";\nimport type { Journal } from \"./journal.js\";\nimport type { MetricsRegistry } from \"./metrics.js\";\nimport type {\n AGUIFixture,\n AGUIMockOptions,\n AGUIRecordConfig,\n AGUIEvent,\n AGUIRunAgentInput,\n} from \"./agui-types.js\";\nimport {\n findFixture,\n buildTextResponse,\n buildToolCallResponse,\n buildStateUpdate,\n buildReasoningResponse,\n writeAGUIEventStream,\n} from \"./agui-handler.js\";\nimport { flattenHeaders, readBody } from \"./helpers.js\";\nimport { proxyAndRecordAGUI } from \"./agui-recorder.js\";\nimport { Logger } from \"./logger.js\";\n\nexport class AGUIMock implements Mountable {\n private fixtures: AGUIFixture[] = [];\n private server: http.Server | null = null;\n private journal: Journal | null = null;\n private registry: MetricsRegistry | null = null;\n private options: AGUIMockOptions;\n private baseUrl = \"\";\n private recordConfig: AGUIRecordConfig | undefined;\n private logger: Logger;\n\n constructor(options?: AGUIMockOptions) {\n this.options = options ?? {};\n this.logger = new Logger(\"silent\");\n }\n\n // ---- Fluent registration API ----\n\n addFixture(fixture: AGUIFixture): this {\n this.fixtures.push(fixture);\n return this;\n }\n\n onMessage(pattern: string | RegExp, text: string, opts?: { delayMs?: number }): this {\n const events = buildTextResponse(text);\n this.fixtures.push({\n match: { message: pattern },\n events,\n delayMs: opts?.delayMs,\n });\n return this;\n }\n\n onRun(pattern: string | RegExp, events: AGUIEvent[], delayMs?: number): this {\n this.fixtures.push({\n match: { message: pattern },\n events,\n delayMs,\n });\n return this;\n }\n\n onToolCall(\n pattern: string | RegExp,\n toolName: string,\n args: string,\n opts?: { result?: string; delayMs?: number },\n ): this {\n const events = buildToolCallResponse(toolName, args, {\n result: opts?.result,\n });\n this.fixtures.push({\n match: { message: pattern },\n events,\n delayMs: opts?.delayMs,\n });\n return this;\n }\n\n onStateKey(key: string, snapshot: Record<string, unknown>, delayMs?: number): this {\n const events = buildStateUpdate(snapshot);\n this.fixtures.push({\n match: { stateKey: key },\n events,\n delayMs,\n });\n return this;\n }\n\n onReasoning(pattern: string | RegExp, text: string, opts?: { delayMs?: number }): this {\n const events = buildReasoningResponse(text);\n this.fixtures.push({\n match: { message: pattern },\n events,\n delayMs: opts?.delayMs,\n });\n return this;\n }\n\n onPredicate(\n predicate: (input: AGUIRunAgentInput) => boolean,\n events: AGUIEvent[],\n delayMs?: number,\n ): this {\n this.fixtures.push({\n match: { predicate },\n events,\n delayMs,\n });\n return this;\n }\n\n enableRecording(config: AGUIRecordConfig): this {\n this.recordConfig = config;\n return this;\n }\n\n reset(): this {\n this.fixtures = [];\n this.recordConfig = undefined;\n return this;\n }\n\n // ---- Mountable interface ----\n\n async handleRequest(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n pathname: string,\n ): Promise<boolean> {\n if (req.method !== \"POST\" || (pathname !== \"/\" && pathname !== \"\")) {\n return false;\n }\n\n if (this.registry) {\n this.registry.incrementCounter(\"aimock_agui_requests_total\", { method: \"POST\" });\n }\n\n const body = await readBody(req);\n\n let input: AGUIRunAgentInput;\n try {\n input = JSON.parse(body) as AGUIRunAgentInput;\n } catch {\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Invalid JSON body\" }));\n this.journalRequest(req, pathname, 400);\n return true;\n }\n\n const fixture = findFixture(input, this.fixtures);\n\n if (fixture) {\n await writeAGUIEventStream(res, fixture.events, { delayMs: fixture.delayMs });\n this.journalRequest(req, pathname, 200);\n return true;\n }\n\n // No match — if recording is enabled, proxy to upstream\n if (this.recordConfig) {\n const proxied = await proxyAndRecordAGUI(\n req,\n res,\n input,\n this.fixtures,\n this.recordConfig,\n this.logger,\n );\n if (proxied) {\n this.journalRequest(req, pathname, 200);\n return true;\n }\n }\n\n // No match, no recorder — 404\n res.writeHead(404, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"No matching AG-UI fixture\" }));\n this.journalRequest(req, pathname, 404);\n return true;\n }\n\n health(): { status: string; fixtures: number } {\n return {\n status: \"ok\",\n fixtures: this.fixtures.length,\n };\n }\n\n setJournal(journal: Journal): void {\n this.journal = journal;\n }\n\n setBaseUrl(url: string): void {\n this.baseUrl = url;\n }\n\n setRegistry(registry: MetricsRegistry): void {\n this.registry = registry;\n }\n\n // ---- Standalone mode ----\n\n async start(): Promise<string> {\n if (this.server) {\n throw new Error(\"AGUIMock server already started\");\n }\n\n const host = this.options.host ?? \"127.0.0.1\";\n const port = this.options.port ?? 0;\n\n return new Promise<string>((resolve, reject) => {\n const srv = http.createServer(async (req, res) => {\n const url = new URL(req.url ?? \"/\", `http://${req.headers.host ?? \"localhost\"}`);\n const handled = await this.handleRequest(req, res, url.pathname).catch((err) => {\n this.logger.error(`AGUIMock request error: ${err instanceof Error ? err.message : err}`);\n if (!res.headersSent) {\n res.writeHead(500);\n res.end(\"Internal server error\");\n } else if (!res.writableEnded) {\n res.end();\n }\n return true;\n });\n if (!handled && !res.headersSent) {\n res.writeHead(404, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Not found\" }));\n }\n });\n\n srv.on(\"error\", reject);\n\n srv.listen(port, host, () => {\n const addr = srv.address();\n if (typeof addr === \"object\" && addr !== null) {\n this.baseUrl = `http://${host}:${addr.port}`;\n }\n this.server = srv;\n resolve(this.baseUrl);\n });\n });\n }\n\n async stop(): Promise<void> {\n if (!this.server) {\n throw new Error(\"AGUIMock server not started\");\n }\n const srv = this.server;\n await new Promise<void>((resolve, reject) => {\n srv.close((err: Error | undefined) => (err ? reject(err) : resolve()));\n });\n this.server = null;\n }\n\n get url(): string {\n if (!this.server) {\n throw new Error(\"AGUIMock server not started\");\n }\n return this.baseUrl;\n }\n\n // ---- Private helpers ----\n\n private journalRequest(req: http.IncomingMessage, pathname: string, status: number): void {\n if (this.journal) {\n this.journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? pathname,\n headers: flattenHeaders(req.headers),\n body: null,\n service: \"agui\",\n response: { status, fixture: null },\n });\n }\n }\n}\n"],"mappings":";;;;;;;AAuBA,IAAa,WAAb,MAA2C;CACzC,AAAQ,WAA0B,EAAE;CACpC,AAAQ,SAA6B;CACrC,AAAQ,UAA0B;CAClC,AAAQ,WAAmC;CAC3C,AAAQ;CACR,AAAQ,UAAU;CAClB,AAAQ;CACR,AAAQ;CAER,YAAY,SAA2B;AACrC,OAAK,UAAU,WAAW,EAAE;AAC5B,OAAK,SAAS,IAAI,OAAO,SAAS;;CAKpC,WAAW,SAA4B;AACrC,OAAK,SAAS,KAAK,QAAQ;AAC3B,SAAO;;CAGT,UAAU,SAA0B,MAAc,MAAmC;EACnF,MAAM,SAAS,kBAAkB,KAAK;AACtC,OAAK,SAAS,KAAK;GACjB,OAAO,EAAE,SAAS,SAAS;GAC3B;GACA,SAAS,MAAM;GAChB,CAAC;AACF,SAAO;;CAGT,MAAM,SAA0B,QAAqB,SAAwB;AAC3E,OAAK,SAAS,KAAK;GACjB,OAAO,EAAE,SAAS,SAAS;GAC3B;GACA;GACD,CAAC;AACF,SAAO;;CAGT,WACE,SACA,UACA,MACA,MACM;EACN,MAAM,SAAS,sBAAsB,UAAU,MAAM,EACnD,QAAQ,MAAM,QACf,CAAC;AACF,OAAK,SAAS,KAAK;GACjB,OAAO,EAAE,SAAS,SAAS;GAC3B;GACA,SAAS,MAAM;GAChB,CAAC;AACF,SAAO;;CAGT,WAAW,KAAa,UAAmC,SAAwB;EACjF,MAAM,SAAS,iBAAiB,SAAS;AACzC,OAAK,SAAS,KAAK;GACjB,OAAO,EAAE,UAAU,KAAK;GACxB;GACA;GACD,CAAC;AACF,SAAO;;CAGT,YAAY,SAA0B,MAAc,MAAmC;EACrF,MAAM,SAAS,uBAAuB,KAAK;AAC3C,OAAK,SAAS,KAAK;GACjB,OAAO,EAAE,SAAS,SAAS;GAC3B;GACA,SAAS,MAAM;GAChB,CAAC;AACF,SAAO;;CAGT,YACE,WACA,QACA,SACM;AACN,OAAK,SAAS,KAAK;GACjB,OAAO,EAAE,WAAW;GACpB;GACA;GACD,CAAC;AACF,SAAO;;CAGT,gBAAgB,QAAgC;AAC9C,OAAK,eAAe;AACpB,SAAO;;CAGT,QAAc;AACZ,OAAK,WAAW,EAAE;AAClB,OAAK,eAAe;AACpB,SAAO;;CAKT,MAAM,cACJ,KACA,KACA,UACkB;AAClB,MAAI,IAAI,WAAW,UAAW,aAAa,OAAO,aAAa,GAC7D,QAAO;AAGT,MAAI,KAAK,SACP,MAAK,SAAS,iBAAiB,8BAA8B,EAAE,QAAQ,QAAQ,CAAC;EAGlF,MAAM,OAAO,MAAM,SAAS,IAAI;EAEhC,IAAI;AACJ,MAAI;AACF,WAAQ,KAAK,MAAM,KAAK;UAClB;AACN,OAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,OAAI,IAAI,KAAK,UAAU,EAAE,OAAO,qBAAqB,CAAC,CAAC;AACvD,QAAK,eAAe,KAAK,UAAU,IAAI;AACvC,UAAO;;EAGT,MAAM,UAAU,YAAY,OAAO,KAAK,SAAS;AAEjD,MAAI,SAAS;AACX,SAAM,qBAAqB,KAAK,QAAQ,QAAQ,EAAE,SAAS,QAAQ,SAAS,CAAC;AAC7E,QAAK,eAAe,KAAK,UAAU,IAAI;AACvC,UAAO;;AAIT,MAAI,KAAK,cASP;OARgB,MAAM,mBACpB,KACA,KACA,OACA,KAAK,UACL,KAAK,cACL,KAAK,OACN,EACY;AACX,SAAK,eAAe,KAAK,UAAU,IAAI;AACvC,WAAO;;;AAKX,MAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,MAAI,IAAI,KAAK,UAAU,EAAE,OAAO,6BAA6B,CAAC,CAAC;AAC/D,OAAK,eAAe,KAAK,UAAU,IAAI;AACvC,SAAO;;CAGT,SAA+C;AAC7C,SAAO;GACL,QAAQ;GACR,UAAU,KAAK,SAAS;GACzB;;CAGH,WAAW,SAAwB;AACjC,OAAK,UAAU;;CAGjB,WAAW,KAAmB;AAC5B,OAAK,UAAU;;CAGjB,YAAY,UAAiC;AAC3C,OAAK,WAAW;;CAKlB,MAAM,QAAyB;AAC7B,MAAI,KAAK,OACP,OAAM,IAAI,MAAM,kCAAkC;EAGpD,MAAM,OAAO,KAAK,QAAQ,QAAQ;EAClC,MAAM,OAAO,KAAK,QAAQ,QAAQ;AAElC,SAAO,IAAI,SAAiB,SAAS,WAAW;GAC9C,MAAM,MAAM,KAAK,aAAa,OAAO,KAAK,QAAQ;IAChD,MAAM,MAAM,IAAI,IAAI,IAAI,OAAO,KAAK,UAAU,IAAI,QAAQ,QAAQ,cAAc;AAWhF,QAAI,CAVY,MAAM,KAAK,cAAc,KAAK,KAAK,IAAI,SAAS,CAAC,OAAO,QAAQ;AAC9E,UAAK,OAAO,MAAM,2BAA2B,eAAe,QAAQ,IAAI,UAAU,MAAM;AACxF,SAAI,CAAC,IAAI,aAAa;AACpB,UAAI,UAAU,IAAI;AAClB,UAAI,IAAI,wBAAwB;gBACvB,CAAC,IAAI,cACd,KAAI,KAAK;AAEX,YAAO;MACP,IACc,CAAC,IAAI,aAAa;AAChC,SAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,SAAI,IAAI,KAAK,UAAU,EAAE,OAAO,aAAa,CAAC,CAAC;;KAEjD;AAEF,OAAI,GAAG,SAAS,OAAO;AAEvB,OAAI,OAAO,MAAM,YAAY;IAC3B,MAAM,OAAO,IAAI,SAAS;AAC1B,QAAI,OAAO,SAAS,YAAY,SAAS,KACvC,MAAK,UAAU,UAAU,KAAK,GAAG,KAAK;AAExC,SAAK,SAAS;AACd,YAAQ,KAAK,QAAQ;KACrB;IACF;;CAGJ,MAAM,OAAsB;AAC1B,MAAI,CAAC,KAAK,OACR,OAAM,IAAI,MAAM,8BAA8B;EAEhD,MAAM,MAAM,KAAK;AACjB,QAAM,IAAI,SAAe,SAAS,WAAW;AAC3C,OAAI,OAAO,QAA4B,MAAM,OAAO,IAAI,GAAG,SAAS,CAAE;IACtE;AACF,OAAK,SAAS;;CAGhB,IAAI,MAAc;AAChB,MAAI,CAAC,KAAK,OACR,OAAM,IAAI,MAAM,8BAA8B;AAEhD,SAAO,KAAK;;CAKd,AAAQ,eAAe,KAA2B,UAAkB,QAAsB;AACxF,MAAI,KAAK,QACP,MAAK,QAAQ,IAAI;GACf,QAAQ,IAAI,UAAU;GACtB,MAAM,IAAI,OAAO;GACjB,SAAS,eAAe,IAAI,QAAQ;GACpC,MAAM;GACN,SAAS;GACT,UAAU;IAAE;IAAQ,SAAS;IAAM;GACpC,CAAC"}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
const require_runtime = require('./_virtual/_rolldown/runtime.cjs');
|
|
2
|
+
const require_agui_handler = require('./agui-handler.cjs');
|
|
3
|
+
let node_http = require("node:http");
|
|
4
|
+
node_http = require_runtime.__toESM(node_http);
|
|
5
|
+
let node_crypto = require("node:crypto");
|
|
6
|
+
node_crypto = require_runtime.__toESM(node_crypto);
|
|
7
|
+
let node_path = require("node:path");
|
|
8
|
+
node_path = require_runtime.__toESM(node_path);
|
|
9
|
+
let node_fs = require("node:fs");
|
|
10
|
+
node_fs = require_runtime.__toESM(node_fs);
|
|
11
|
+
let node_https = require("node:https");
|
|
12
|
+
node_https = require_runtime.__toESM(node_https);
|
|
13
|
+
|
|
14
|
+
//#region src/agui-recorder.ts
|
|
15
|
+
/**
|
|
16
|
+
* Proxy an unmatched AG-UI request to a real upstream agent, record the
|
|
17
|
+
* SSE event stream as a fixture on disk and in memory, and relay the
|
|
18
|
+
* response back to the original client in real time.
|
|
19
|
+
*
|
|
20
|
+
* Returns `true` if the request was proxied, `false` if no upstream is configured.
|
|
21
|
+
*/
|
|
22
|
+
async function proxyAndRecordAGUI(req, res, input, fixtures, config, logger) {
|
|
23
|
+
if (!config.upstream) {
|
|
24
|
+
logger.warn("No upstream URL configured for AG-UI recording — cannot proxy");
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
let target;
|
|
28
|
+
try {
|
|
29
|
+
target = new URL(config.upstream);
|
|
30
|
+
} catch {
|
|
31
|
+
logger.error(`Invalid upstream AG-UI URL: ${config.upstream}`);
|
|
32
|
+
res.writeHead(502, { "Content-Type": "application/json" });
|
|
33
|
+
res.end(JSON.stringify({ error: "Invalid upstream AG-UI URL" }));
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
logger.warn(`NO AG-UI FIXTURE MATCH — proxying to ${config.upstream}`);
|
|
37
|
+
const forwardHeaders = {
|
|
38
|
+
"Content-Type": "application/json",
|
|
39
|
+
Accept: "text/event-stream"
|
|
40
|
+
};
|
|
41
|
+
const authorization = req.headers["authorization"];
|
|
42
|
+
if (authorization) forwardHeaders["Authorization"] = Array.isArray(authorization) ? authorization.join(", ") : authorization;
|
|
43
|
+
const apiKey = req.headers["x-api-key"];
|
|
44
|
+
if (apiKey) forwardHeaders["x-api-key"] = Array.isArray(apiKey) ? apiKey.join(", ") : apiKey;
|
|
45
|
+
const requestBody = JSON.stringify(input);
|
|
46
|
+
try {
|
|
47
|
+
await teeUpstreamStream(target, forwardHeaders, requestBody, res, input, fixtures, config, logger);
|
|
48
|
+
} catch (err) {
|
|
49
|
+
const msg = err instanceof Error ? err.message : "Unknown proxy error";
|
|
50
|
+
logger.error(`AG-UI proxy request failed: ${msg}`);
|
|
51
|
+
if (!res.headersSent) {
|
|
52
|
+
res.writeHead(502, { "Content-Type": "application/json" });
|
|
53
|
+
res.end(JSON.stringify({ error: "Upstream AG-UI agent unreachable" }));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
function teeUpstreamStream(target, headers, body, clientRes, input, fixtures, config, logger) {
|
|
59
|
+
return new Promise((resolve, reject) => {
|
|
60
|
+
const transport = target.protocol === "https:" ? node_https : node_http;
|
|
61
|
+
const UPSTREAM_TIMEOUT_MS = 3e4;
|
|
62
|
+
const upstreamReq = transport.request(target, {
|
|
63
|
+
method: "POST",
|
|
64
|
+
timeout: UPSTREAM_TIMEOUT_MS,
|
|
65
|
+
headers: {
|
|
66
|
+
...headers,
|
|
67
|
+
"Content-Length": Buffer.byteLength(body).toString()
|
|
68
|
+
}
|
|
69
|
+
}, (upstreamRes) => {
|
|
70
|
+
if (!clientRes.headersSent) clientRes.writeHead(upstreamRes.statusCode ?? 200, {
|
|
71
|
+
"Content-Type": "text/event-stream",
|
|
72
|
+
"Cache-Control": "no-cache",
|
|
73
|
+
Connection: "keep-alive"
|
|
74
|
+
});
|
|
75
|
+
const chunks = [];
|
|
76
|
+
upstreamRes.on("data", (chunk) => {
|
|
77
|
+
try {
|
|
78
|
+
clientRes.write(chunk);
|
|
79
|
+
} catch {}
|
|
80
|
+
chunks.push(chunk);
|
|
81
|
+
});
|
|
82
|
+
upstreamRes.on("error", (err) => {
|
|
83
|
+
if (!clientRes.headersSent) {
|
|
84
|
+
clientRes.writeHead(502, { "Content-Type": "application/json" });
|
|
85
|
+
clientRes.end(JSON.stringify({ error: "Upstream AG-UI agent unreachable" }));
|
|
86
|
+
} else if (!clientRes.writableEnded) clientRes.end();
|
|
87
|
+
reject(err);
|
|
88
|
+
});
|
|
89
|
+
upstreamRes.on("end", () => {
|
|
90
|
+
if (!clientRes.writableEnded) clientRes.end();
|
|
91
|
+
const events = parseSSEEvents(Buffer.concat(chunks).toString(), logger);
|
|
92
|
+
const message = require_agui_handler.extractLastUserMessage(input);
|
|
93
|
+
if (!message) logger.warn("Recorded AG-UI fixture has no message match — it will match ALL requests");
|
|
94
|
+
const fixture = {
|
|
95
|
+
match: { message: message || void 0 },
|
|
96
|
+
events
|
|
97
|
+
};
|
|
98
|
+
if (!config.proxyOnly) {
|
|
99
|
+
fixtures.push(fixture);
|
|
100
|
+
const fixturePath = config.fixturePath ?? "./fixtures/agui-recorded";
|
|
101
|
+
const filename = `agui-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}-${node_crypto.randomUUID().slice(0, 8)}.json`;
|
|
102
|
+
const filepath = node_path.join(fixturePath, filename);
|
|
103
|
+
try {
|
|
104
|
+
node_fs.mkdirSync(fixturePath, { recursive: true });
|
|
105
|
+
node_fs.writeFileSync(filepath, JSON.stringify({ fixtures: [{
|
|
106
|
+
match: fixture.match,
|
|
107
|
+
events: fixture.events
|
|
108
|
+
}] }, null, 2), "utf-8");
|
|
109
|
+
logger.warn(`AG-UI response recorded → ${filepath}`);
|
|
110
|
+
} catch (err) {
|
|
111
|
+
const msg = err instanceof Error ? err.message : "Unknown filesystem error";
|
|
112
|
+
logger.error(`Failed to save AG-UI fixture to disk: ${msg} (fixture retained in memory)`);
|
|
113
|
+
}
|
|
114
|
+
} else logger.info("Proxied AG-UI request (proxy-only mode)");
|
|
115
|
+
resolve();
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
upstreamReq.on("timeout", () => {
|
|
119
|
+
if (!clientRes.writableEnded) clientRes.end();
|
|
120
|
+
upstreamReq.destroy(/* @__PURE__ */ new Error(`Upstream AG-UI request timed out after ${UPSTREAM_TIMEOUT_MS / 1e3}s`));
|
|
121
|
+
});
|
|
122
|
+
upstreamReq.on("error", (err) => {
|
|
123
|
+
if (!clientRes.headersSent) {
|
|
124
|
+
clientRes.writeHead(502, { "Content-Type": "application/json" });
|
|
125
|
+
clientRes.end(JSON.stringify({ error: "Upstream AG-UI agent unreachable" }));
|
|
126
|
+
} else if (!clientRes.writableEnded) clientRes.end();
|
|
127
|
+
reject(err);
|
|
128
|
+
});
|
|
129
|
+
upstreamReq.write(body);
|
|
130
|
+
upstreamReq.end();
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Parse SSE data lines from buffered stream text.
|
|
135
|
+
*/
|
|
136
|
+
function parseSSEEvents(text, logger) {
|
|
137
|
+
const events = [];
|
|
138
|
+
const blocks = text.split("\n\n");
|
|
139
|
+
for (const block of blocks) {
|
|
140
|
+
const lines = block.split("\n");
|
|
141
|
+
for (const line of lines) if (line.startsWith("data: ")) try {
|
|
142
|
+
const parsed = JSON.parse(line.slice(6));
|
|
143
|
+
events.push(parsed);
|
|
144
|
+
} catch {
|
|
145
|
+
logger?.warn(`Skipping unparseable SSE data line: ${line.slice(0, 200)}`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return events;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
//#endregion
|
|
152
|
+
exports.proxyAndRecordAGUI = proxyAndRecordAGUI;
|
|
153
|
+
//# sourceMappingURL=agui-recorder.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agui-recorder.cjs","names":["https","http","extractLastUserMessage","crypto","path"],"sources":["../src/agui-recorder.ts"],"sourcesContent":["import * as http from \"node:http\";\nimport * as https from \"node:https\";\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport * as crypto from \"node:crypto\";\nimport type { AGUIFixture, AGUIRecordConfig, AGUIEvent, AGUIRunAgentInput } from \"./agui-types.js\";\nimport { extractLastUserMessage } from \"./agui-handler.js\";\nimport type { Logger } from \"./logger.js\";\n\n/**\n * Proxy an unmatched AG-UI request to a real upstream agent, record the\n * SSE event stream as a fixture on disk and in memory, and relay the\n * response back to the original client in real time.\n *\n * Returns `true` if the request was proxied, `false` if no upstream is configured.\n */\nexport async function proxyAndRecordAGUI(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n input: AGUIRunAgentInput,\n fixtures: AGUIFixture[],\n config: AGUIRecordConfig,\n logger: Logger,\n): Promise<boolean> {\n if (!config.upstream) {\n logger.warn(\"No upstream URL configured for AG-UI recording — cannot proxy\");\n return false;\n }\n\n let target: URL;\n try {\n target = new URL(config.upstream);\n } catch {\n logger.error(`Invalid upstream AG-UI URL: ${config.upstream}`);\n res.writeHead(502, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Invalid upstream AG-UI URL\" }));\n return true;\n }\n\n logger.warn(`NO AG-UI FIXTURE MATCH — proxying to ${config.upstream}`);\n\n // Build upstream request headers\n const forwardHeaders: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n Accept: \"text/event-stream\",\n };\n // Forward auth headers if present\n const authorization = req.headers[\"authorization\"];\n if (authorization) {\n forwardHeaders[\"Authorization\"] = Array.isArray(authorization)\n ? authorization.join(\", \")\n : authorization;\n }\n const apiKey = req.headers[\"x-api-key\"];\n if (apiKey) {\n forwardHeaders[\"x-api-key\"] = Array.isArray(apiKey) ? apiKey.join(\", \") : apiKey;\n }\n\n const requestBody = JSON.stringify(input);\n\n try {\n await teeUpstreamStream(\n target,\n forwardHeaders,\n requestBody,\n res,\n input,\n fixtures,\n config,\n logger,\n );\n } catch (err) {\n const msg = err instanceof Error ? err.message : \"Unknown proxy error\";\n logger.error(`AG-UI proxy request failed: ${msg}`);\n if (!res.headersSent) {\n res.writeHead(502, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Upstream AG-UI agent unreachable\" }));\n }\n }\n\n return true;\n}\n\n// ---------------------------------------------------------------------------\n// Internal: tee the upstream SSE stream to the client and buffer for recording\n// ---------------------------------------------------------------------------\n\nfunction teeUpstreamStream(\n target: URL,\n headers: Record<string, string>,\n body: string,\n clientRes: http.ServerResponse,\n input: AGUIRunAgentInput,\n fixtures: AGUIFixture[],\n config: AGUIRecordConfig,\n logger: Logger,\n): Promise<void> {\n return new Promise((resolve, reject) => {\n const transport = target.protocol === \"https:\" ? https : http;\n const UPSTREAM_TIMEOUT_MS = 30_000;\n\n const upstreamReq = transport.request(\n target,\n {\n method: \"POST\",\n timeout: UPSTREAM_TIMEOUT_MS,\n headers: {\n ...headers,\n \"Content-Length\": Buffer.byteLength(body).toString(),\n },\n },\n (upstreamRes) => {\n // Set SSE headers on the client response\n if (!clientRes.headersSent) {\n clientRes.writeHead(upstreamRes.statusCode ?? 200, {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n });\n }\n\n const chunks: Buffer[] = [];\n\n upstreamRes.on(\"data\", (chunk: Buffer) => {\n // Relay to client in real time\n try {\n clientRes.write(chunk);\n } catch {\n // Client connection may have closed — continue buffering for recording\n }\n // Buffer for fixture construction\n chunks.push(chunk);\n });\n\n upstreamRes.on(\"error\", (err) => {\n if (!clientRes.headersSent) {\n clientRes.writeHead(502, { \"Content-Type\": \"application/json\" });\n clientRes.end(JSON.stringify({ error: \"Upstream AG-UI agent unreachable\" }));\n } else if (!clientRes.writableEnded) {\n clientRes.end();\n }\n reject(err);\n });\n\n upstreamRes.on(\"end\", () => {\n if (!clientRes.writableEnded) clientRes.end();\n\n // Parse buffered SSE events\n const buffered = Buffer.concat(chunks).toString();\n const events = parseSSEEvents(buffered, logger);\n\n // Build fixture\n const message = extractLastUserMessage(input);\n if (!message) {\n logger.warn(\"Recorded AG-UI fixture has no message match — it will match ALL requests\");\n }\n const fixture: AGUIFixture = {\n match: { message: message || undefined },\n events,\n };\n\n if (!config.proxyOnly) {\n // Register in memory first (always available even if disk write fails)\n fixtures.push(fixture);\n\n // Write to disk\n const fixturePath = config.fixturePath ?? \"./fixtures/agui-recorded\";\n const timestamp = new Date().toISOString().replace(/[:.]/g, \"-\");\n const filename = `agui-${timestamp}-${crypto.randomUUID().slice(0, 8)}.json`;\n const filepath = path.join(fixturePath, filename);\n\n try {\n fs.mkdirSync(fixturePath, { recursive: true });\n fs.writeFileSync(\n filepath,\n JSON.stringify(\n { fixtures: [{ match: fixture.match, events: fixture.events }] },\n null,\n 2,\n ),\n \"utf-8\",\n );\n logger.warn(`AG-UI response recorded → ${filepath}`);\n } catch (err) {\n const msg = err instanceof Error ? err.message : \"Unknown filesystem error\";\n logger.error(\n `Failed to save AG-UI fixture to disk: ${msg} (fixture retained in memory)`,\n );\n }\n } else {\n logger.info(\"Proxied AG-UI request (proxy-only mode)\");\n }\n\n resolve();\n });\n },\n );\n\n upstreamReq.on(\"timeout\", () => {\n if (!clientRes.writableEnded) clientRes.end();\n upstreamReq.destroy(\n new Error(`Upstream AG-UI request timed out after ${UPSTREAM_TIMEOUT_MS / 1000}s`),\n );\n });\n\n upstreamReq.on(\"error\", (err) => {\n if (!clientRes.headersSent) {\n clientRes.writeHead(502, { \"Content-Type\": \"application/json\" });\n clientRes.end(JSON.stringify({ error: \"Upstream AG-UI agent unreachable\" }));\n } else if (!clientRes.writableEnded) {\n clientRes.end();\n }\n reject(err);\n });\n\n upstreamReq.write(body);\n upstreamReq.end();\n });\n}\n\n/**\n * Parse SSE data lines from buffered stream text.\n */\nfunction parseSSEEvents(text: string, logger?: Logger): AGUIEvent[] {\n const events: AGUIEvent[] = [];\n const blocks = text.split(\"\\n\\n\");\n for (const block of blocks) {\n const lines = block.split(\"\\n\");\n for (const line of lines) {\n if (line.startsWith(\"data: \")) {\n try {\n const parsed = JSON.parse(line.slice(6)) as AGUIEvent;\n events.push(parsed);\n } catch {\n logger?.warn(`Skipping unparseable SSE data line: ${line.slice(0, 200)}`);\n }\n }\n }\n }\n return events;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAgBA,eAAsB,mBACpB,KACA,KACA,OACA,UACA,QACA,QACkB;AAClB,KAAI,CAAC,OAAO,UAAU;AACpB,SAAO,KAAK,gEAAgE;AAC5E,SAAO;;CAGT,IAAI;AACJ,KAAI;AACF,WAAS,IAAI,IAAI,OAAO,SAAS;SAC3B;AACN,SAAO,MAAM,+BAA+B,OAAO,WAAW;AAC9D,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;AAEzC,KAAI;AACF,QAAM,kBACJ,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;;;AAI1E,QAAO;;AAOT,SAAS,kBACP,QACA,SACA,MACA,WACA,OACA,UACA,QACA,QACe;AACf,QAAO,IAAI,SAAS,SAAS,WAAW;EACtC,MAAM,YAAY,OAAO,aAAa,WAAWA,aAAQC;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;AAEf,OAAI,CAAC,UAAU,YACb,WAAU,UAAU,YAAY,cAAc,KAAK;IACjD,gBAAgB;IAChB,iBAAiB;IACjB,YAAY;IACb,CAAC;GAGJ,MAAM,SAAmB,EAAE;AAE3B,eAAY,GAAG,SAAS,UAAkB;AAExC,QAAI;AACF,eAAU,MAAM,MAAM;YAChB;AAIR,WAAO,KAAK,MAAM;KAClB;AAEF,eAAY,GAAG,UAAU,QAAQ;AAC/B,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;AAEjB,WAAO,IAAI;KACX;AAEF,eAAY,GAAG,aAAa;AAC1B,QAAI,CAAC,UAAU,cAAe,WAAU,KAAK;IAI7C,MAAM,SAAS,eADE,OAAO,OAAO,OAAO,CAAC,UAAU,EACT,OAAO;IAG/C,MAAM,UAAUC,4CAAuB,MAAM;AAC7C,QAAI,CAAC,QACH,QAAO,KAAK,2EAA2E;IAEzF,MAAM,UAAuB;KAC3B,OAAO,EAAE,SAAS,WAAW,QAAW;KACxC;KACD;AAED,QAAI,CAAC,OAAO,WAAW;AAErB,cAAS,KAAK,QAAQ;KAGtB,MAAM,cAAc,OAAO,eAAe;KAE1C,MAAM,WAAW,yBADC,IAAI,MAAM,EAAC,aAAa,CAAC,QAAQ,SAAS,IAAI,CAC7B,GAAGC,YAAO,YAAY,CAAC,MAAM,GAAG,EAAE,CAAC;KACtE,MAAM,WAAWC,UAAK,KAAK,aAAa,SAAS;AAEjD,SAAI;AACF,cAAG,UAAU,aAAa,EAAE,WAAW,MAAM,CAAC;AAC9C,cAAG,cACD,UACA,KAAK,UACH,EAAE,UAAU,CAAC;OAAE,OAAO,QAAQ;OAAO,QAAQ,QAAQ;OAAQ,CAAC,EAAE,EAChE,MACA,EACD,EACD,QACD;AACD,aAAO,KAAK,6BAA6B,WAAW;cAC7C,KAAK;MACZ,MAAM,MAAM,eAAe,QAAQ,IAAI,UAAU;AACjD,aAAO,MACL,yCAAyC,IAAI,+BAC9C;;UAGH,QAAO,KAAK,0CAA0C;AAGxD,aAAS;KACT;IAEL;AAED,cAAY,GAAG,iBAAiB;AAC9B,OAAI,CAAC,UAAU,cAAe,WAAU,KAAK;AAC7C,eAAY,wBACV,IAAI,MAAM,0CAA0C,sBAAsB,IAAK,GAAG,CACnF;IACD;AAEF,cAAY,GAAG,UAAU,QAAQ;AAC/B,OAAI,CAAC,UAAU,aAAa;AAC1B,cAAU,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAChE,cAAU,IAAI,KAAK,UAAU,EAAE,OAAO,oCAAoC,CAAC,CAAC;cACnE,CAAC,UAAU,cACpB,WAAU,KAAK;AAEjB,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,SAAS,CAC3B,KAAI;GACF,MAAM,SAAS,KAAK,MAAM,KAAK,MAAM,EAAE,CAAC;AACxC,UAAO,KAAK,OAAO;UACb;AACN,WAAQ,KAAK,uCAAuC,KAAK,MAAM,GAAG,IAAI,GAAG;;;AAKjF,QAAO"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Logger } from "./logger.cjs";
|
|
2
|
+
import { AGUIFixture, AGUIRecordConfig, AGUIRunAgentInput } from "./agui-types.cjs";
|
|
3
|
+
import * as http from "node:http";
|
|
4
|
+
|
|
5
|
+
//#region src/agui-recorder.d.ts
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Proxy an unmatched AG-UI request to a real upstream agent, record the
|
|
9
|
+
* SSE event stream as a fixture on disk and in memory, and relay the
|
|
10
|
+
* response back to the original client in real time.
|
|
11
|
+
*
|
|
12
|
+
* Returns `true` if the request was proxied, `false` if no upstream is configured.
|
|
13
|
+
*/
|
|
14
|
+
declare function proxyAndRecordAGUI(req: http.IncomingMessage, res: http.ServerResponse, input: AGUIRunAgentInput, fixtures: AGUIFixture[], config: AGUIRecordConfig, logger: Logger): Promise<boolean>;
|
|
15
|
+
//# sourceMappingURL=agui-recorder.d.ts.map
|
|
16
|
+
|
|
17
|
+
//#endregion
|
|
18
|
+
export { proxyAndRecordAGUI };
|
|
19
|
+
//# sourceMappingURL=agui-recorder.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agui-recorder.d.cts","names":[],"sources":["../src/agui-recorder.ts"],"sourcesContent":[],"mappings":";;;;;;;;AAgBA;;;;;AAIY,iBAJU,kBAAA,CAIV,GAAA,EAHL,IAAA,CAAK,eAGA,EAAA,GAAA,EAFL,IAAA,CAAK,cAEA,EAAA,KAAA,EADH,iBACG,EAAA,QAAA,EAAA,WAAA,EAAA,EAAA,MAAA,EACF,gBADE,EAAA,MAAA,EAEF,MAFE,CAAA,EAGT,OAHS,CAAA,OAAA,CAAA"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Logger } from "./logger.js";
|
|
2
|
+
import { AGUIFixture, AGUIRecordConfig, AGUIRunAgentInput } from "./agui-types.js";
|
|
3
|
+
import * as http from "node:http";
|
|
4
|
+
|
|
5
|
+
//#region src/agui-recorder.d.ts
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Proxy an unmatched AG-UI request to a real upstream agent, record the
|
|
9
|
+
* SSE event stream as a fixture on disk and in memory, and relay the
|
|
10
|
+
* response back to the original client in real time.
|
|
11
|
+
*
|
|
12
|
+
* Returns `true` if the request was proxied, `false` if no upstream is configured.
|
|
13
|
+
*/
|
|
14
|
+
declare function proxyAndRecordAGUI(req: http.IncomingMessage, res: http.ServerResponse, input: AGUIRunAgentInput, fixtures: AGUIFixture[], config: AGUIRecordConfig, logger: Logger): Promise<boolean>;
|
|
15
|
+
//# sourceMappingURL=agui-recorder.d.ts.map
|
|
16
|
+
|
|
17
|
+
//#endregion
|
|
18
|
+
export { proxyAndRecordAGUI };
|
|
19
|
+
//# sourceMappingURL=agui-recorder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agui-recorder.d.ts","names":[],"sources":["../src/agui-recorder.ts"],"sourcesContent":[],"mappings":";;;;;;;;AAgBA;;;;;AAIY,iBAJU,kBAAA,CAIV,GAAA,EAHL,IAAA,CAAK,eAGA,EAAA,GAAA,EAFL,IAAA,CAAK,cAEA,EAAA,KAAA,EADH,iBACG,EAAA,QAAA,EAAA,WAAA,EAAA,EAAA,MAAA,EACF,gBADE,EAAA,MAAA,EAEF,MAFE,CAAA,EAGT,OAHS,CAAA,OAAA,CAAA"}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { extractLastUserMessage } from "./agui-handler.js";
|
|
2
|
+
import * as http from "node:http";
|
|
3
|
+
import * as crypto from "node:crypto";
|
|
4
|
+
import * as path from "node:path";
|
|
5
|
+
import * as fs from "node:fs";
|
|
6
|
+
import * as https from "node:https";
|
|
7
|
+
|
|
8
|
+
//#region src/agui-recorder.ts
|
|
9
|
+
/**
|
|
10
|
+
* Proxy an unmatched AG-UI request to a real upstream agent, record the
|
|
11
|
+
* SSE event stream as a fixture on disk and in memory, and relay the
|
|
12
|
+
* response back to the original client in real time.
|
|
13
|
+
*
|
|
14
|
+
* Returns `true` if the request was proxied, `false` if no upstream is configured.
|
|
15
|
+
*/
|
|
16
|
+
async function proxyAndRecordAGUI(req, res, input, fixtures, config, logger) {
|
|
17
|
+
if (!config.upstream) {
|
|
18
|
+
logger.warn("No upstream URL configured for AG-UI recording — cannot proxy");
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
let target;
|
|
22
|
+
try {
|
|
23
|
+
target = new URL(config.upstream);
|
|
24
|
+
} catch {
|
|
25
|
+
logger.error(`Invalid upstream AG-UI URL: ${config.upstream}`);
|
|
26
|
+
res.writeHead(502, { "Content-Type": "application/json" });
|
|
27
|
+
res.end(JSON.stringify({ error: "Invalid upstream AG-UI URL" }));
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
logger.warn(`NO AG-UI FIXTURE MATCH — proxying to ${config.upstream}`);
|
|
31
|
+
const forwardHeaders = {
|
|
32
|
+
"Content-Type": "application/json",
|
|
33
|
+
Accept: "text/event-stream"
|
|
34
|
+
};
|
|
35
|
+
const authorization = req.headers["authorization"];
|
|
36
|
+
if (authorization) forwardHeaders["Authorization"] = Array.isArray(authorization) ? authorization.join(", ") : authorization;
|
|
37
|
+
const apiKey = req.headers["x-api-key"];
|
|
38
|
+
if (apiKey) forwardHeaders["x-api-key"] = Array.isArray(apiKey) ? apiKey.join(", ") : apiKey;
|
|
39
|
+
const requestBody = JSON.stringify(input);
|
|
40
|
+
try {
|
|
41
|
+
await teeUpstreamStream(target, forwardHeaders, requestBody, res, input, fixtures, config, logger);
|
|
42
|
+
} catch (err) {
|
|
43
|
+
const msg = err instanceof Error ? err.message : "Unknown proxy error";
|
|
44
|
+
logger.error(`AG-UI proxy request failed: ${msg}`);
|
|
45
|
+
if (!res.headersSent) {
|
|
46
|
+
res.writeHead(502, { "Content-Type": "application/json" });
|
|
47
|
+
res.end(JSON.stringify({ error: "Upstream AG-UI agent unreachable" }));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
function teeUpstreamStream(target, headers, body, clientRes, input, fixtures, config, logger) {
|
|
53
|
+
return new Promise((resolve, reject) => {
|
|
54
|
+
const transport = target.protocol === "https:" ? https : http;
|
|
55
|
+
const UPSTREAM_TIMEOUT_MS = 3e4;
|
|
56
|
+
const upstreamReq = transport.request(target, {
|
|
57
|
+
method: "POST",
|
|
58
|
+
timeout: UPSTREAM_TIMEOUT_MS,
|
|
59
|
+
headers: {
|
|
60
|
+
...headers,
|
|
61
|
+
"Content-Length": Buffer.byteLength(body).toString()
|
|
62
|
+
}
|
|
63
|
+
}, (upstreamRes) => {
|
|
64
|
+
if (!clientRes.headersSent) clientRes.writeHead(upstreamRes.statusCode ?? 200, {
|
|
65
|
+
"Content-Type": "text/event-stream",
|
|
66
|
+
"Cache-Control": "no-cache",
|
|
67
|
+
Connection: "keep-alive"
|
|
68
|
+
});
|
|
69
|
+
const chunks = [];
|
|
70
|
+
upstreamRes.on("data", (chunk) => {
|
|
71
|
+
try {
|
|
72
|
+
clientRes.write(chunk);
|
|
73
|
+
} catch {}
|
|
74
|
+
chunks.push(chunk);
|
|
75
|
+
});
|
|
76
|
+
upstreamRes.on("error", (err) => {
|
|
77
|
+
if (!clientRes.headersSent) {
|
|
78
|
+
clientRes.writeHead(502, { "Content-Type": "application/json" });
|
|
79
|
+
clientRes.end(JSON.stringify({ error: "Upstream AG-UI agent unreachable" }));
|
|
80
|
+
} else if (!clientRes.writableEnded) clientRes.end();
|
|
81
|
+
reject(err);
|
|
82
|
+
});
|
|
83
|
+
upstreamRes.on("end", () => {
|
|
84
|
+
if (!clientRes.writableEnded) clientRes.end();
|
|
85
|
+
const events = parseSSEEvents(Buffer.concat(chunks).toString(), logger);
|
|
86
|
+
const message = extractLastUserMessage(input);
|
|
87
|
+
if (!message) logger.warn("Recorded AG-UI fixture has no message match — it will match ALL requests");
|
|
88
|
+
const fixture = {
|
|
89
|
+
match: { message: message || void 0 },
|
|
90
|
+
events
|
|
91
|
+
};
|
|
92
|
+
if (!config.proxyOnly) {
|
|
93
|
+
fixtures.push(fixture);
|
|
94
|
+
const fixturePath = config.fixturePath ?? "./fixtures/agui-recorded";
|
|
95
|
+
const filename = `agui-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}-${crypto.randomUUID().slice(0, 8)}.json`;
|
|
96
|
+
const filepath = path.join(fixturePath, filename);
|
|
97
|
+
try {
|
|
98
|
+
fs.mkdirSync(fixturePath, { recursive: true });
|
|
99
|
+
fs.writeFileSync(filepath, JSON.stringify({ fixtures: [{
|
|
100
|
+
match: fixture.match,
|
|
101
|
+
events: fixture.events
|
|
102
|
+
}] }, null, 2), "utf-8");
|
|
103
|
+
logger.warn(`AG-UI response recorded → ${filepath}`);
|
|
104
|
+
} catch (err) {
|
|
105
|
+
const msg = err instanceof Error ? err.message : "Unknown filesystem error";
|
|
106
|
+
logger.error(`Failed to save AG-UI fixture to disk: ${msg} (fixture retained in memory)`);
|
|
107
|
+
}
|
|
108
|
+
} else logger.info("Proxied AG-UI request (proxy-only mode)");
|
|
109
|
+
resolve();
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
upstreamReq.on("timeout", () => {
|
|
113
|
+
if (!clientRes.writableEnded) clientRes.end();
|
|
114
|
+
upstreamReq.destroy(/* @__PURE__ */ new Error(`Upstream AG-UI request timed out after ${UPSTREAM_TIMEOUT_MS / 1e3}s`));
|
|
115
|
+
});
|
|
116
|
+
upstreamReq.on("error", (err) => {
|
|
117
|
+
if (!clientRes.headersSent) {
|
|
118
|
+
clientRes.writeHead(502, { "Content-Type": "application/json" });
|
|
119
|
+
clientRes.end(JSON.stringify({ error: "Upstream AG-UI agent unreachable" }));
|
|
120
|
+
} else if (!clientRes.writableEnded) clientRes.end();
|
|
121
|
+
reject(err);
|
|
122
|
+
});
|
|
123
|
+
upstreamReq.write(body);
|
|
124
|
+
upstreamReq.end();
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Parse SSE data lines from buffered stream text.
|
|
129
|
+
*/
|
|
130
|
+
function parseSSEEvents(text, logger) {
|
|
131
|
+
const events = [];
|
|
132
|
+
const blocks = text.split("\n\n");
|
|
133
|
+
for (const block of blocks) {
|
|
134
|
+
const lines = block.split("\n");
|
|
135
|
+
for (const line of lines) if (line.startsWith("data: ")) try {
|
|
136
|
+
const parsed = JSON.parse(line.slice(6));
|
|
137
|
+
events.push(parsed);
|
|
138
|
+
} catch {
|
|
139
|
+
logger?.warn(`Skipping unparseable SSE data line: ${line.slice(0, 200)}`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return events;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
//#endregion
|
|
146
|
+
export { proxyAndRecordAGUI };
|
|
147
|
+
//# sourceMappingURL=agui-recorder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agui-recorder.js","names":[],"sources":["../src/agui-recorder.ts"],"sourcesContent":["import * as http from \"node:http\";\nimport * as https from \"node:https\";\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport * as crypto from \"node:crypto\";\nimport type { AGUIFixture, AGUIRecordConfig, AGUIEvent, AGUIRunAgentInput } from \"./agui-types.js\";\nimport { extractLastUserMessage } from \"./agui-handler.js\";\nimport type { Logger } from \"./logger.js\";\n\n/**\n * Proxy an unmatched AG-UI request to a real upstream agent, record the\n * SSE event stream as a fixture on disk and in memory, and relay the\n * response back to the original client in real time.\n *\n * Returns `true` if the request was proxied, `false` if no upstream is configured.\n */\nexport async function proxyAndRecordAGUI(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n input: AGUIRunAgentInput,\n fixtures: AGUIFixture[],\n config: AGUIRecordConfig,\n logger: Logger,\n): Promise<boolean> {\n if (!config.upstream) {\n logger.warn(\"No upstream URL configured for AG-UI recording — cannot proxy\");\n return false;\n }\n\n let target: URL;\n try {\n target = new URL(config.upstream);\n } catch {\n logger.error(`Invalid upstream AG-UI URL: ${config.upstream}`);\n res.writeHead(502, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Invalid upstream AG-UI URL\" }));\n return true;\n }\n\n logger.warn(`NO AG-UI FIXTURE MATCH — proxying to ${config.upstream}`);\n\n // Build upstream request headers\n const forwardHeaders: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n Accept: \"text/event-stream\",\n };\n // Forward auth headers if present\n const authorization = req.headers[\"authorization\"];\n if (authorization) {\n forwardHeaders[\"Authorization\"] = Array.isArray(authorization)\n ? authorization.join(\", \")\n : authorization;\n }\n const apiKey = req.headers[\"x-api-key\"];\n if (apiKey) {\n forwardHeaders[\"x-api-key\"] = Array.isArray(apiKey) ? apiKey.join(\", \") : apiKey;\n }\n\n const requestBody = JSON.stringify(input);\n\n try {\n await teeUpstreamStream(\n target,\n forwardHeaders,\n requestBody,\n res,\n input,\n fixtures,\n config,\n logger,\n );\n } catch (err) {\n const msg = err instanceof Error ? err.message : \"Unknown proxy error\";\n logger.error(`AG-UI proxy request failed: ${msg}`);\n if (!res.headersSent) {\n res.writeHead(502, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Upstream AG-UI agent unreachable\" }));\n }\n }\n\n return true;\n}\n\n// ---------------------------------------------------------------------------\n// Internal: tee the upstream SSE stream to the client and buffer for recording\n// ---------------------------------------------------------------------------\n\nfunction teeUpstreamStream(\n target: URL,\n headers: Record<string, string>,\n body: string,\n clientRes: http.ServerResponse,\n input: AGUIRunAgentInput,\n fixtures: AGUIFixture[],\n config: AGUIRecordConfig,\n logger: Logger,\n): Promise<void> {\n return new Promise((resolve, reject) => {\n const transport = target.protocol === \"https:\" ? https : http;\n const UPSTREAM_TIMEOUT_MS = 30_000;\n\n const upstreamReq = transport.request(\n target,\n {\n method: \"POST\",\n timeout: UPSTREAM_TIMEOUT_MS,\n headers: {\n ...headers,\n \"Content-Length\": Buffer.byteLength(body).toString(),\n },\n },\n (upstreamRes) => {\n // Set SSE headers on the client response\n if (!clientRes.headersSent) {\n clientRes.writeHead(upstreamRes.statusCode ?? 200, {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n });\n }\n\n const chunks: Buffer[] = [];\n\n upstreamRes.on(\"data\", (chunk: Buffer) => {\n // Relay to client in real time\n try {\n clientRes.write(chunk);\n } catch {\n // Client connection may have closed — continue buffering for recording\n }\n // Buffer for fixture construction\n chunks.push(chunk);\n });\n\n upstreamRes.on(\"error\", (err) => {\n if (!clientRes.headersSent) {\n clientRes.writeHead(502, { \"Content-Type\": \"application/json\" });\n clientRes.end(JSON.stringify({ error: \"Upstream AG-UI agent unreachable\" }));\n } else if (!clientRes.writableEnded) {\n clientRes.end();\n }\n reject(err);\n });\n\n upstreamRes.on(\"end\", () => {\n if (!clientRes.writableEnded) clientRes.end();\n\n // Parse buffered SSE events\n const buffered = Buffer.concat(chunks).toString();\n const events = parseSSEEvents(buffered, logger);\n\n // Build fixture\n const message = extractLastUserMessage(input);\n if (!message) {\n logger.warn(\"Recorded AG-UI fixture has no message match — it will match ALL requests\");\n }\n const fixture: AGUIFixture = {\n match: { message: message || undefined },\n events,\n };\n\n if (!config.proxyOnly) {\n // Register in memory first (always available even if disk write fails)\n fixtures.push(fixture);\n\n // Write to disk\n const fixturePath = config.fixturePath ?? \"./fixtures/agui-recorded\";\n const timestamp = new Date().toISOString().replace(/[:.]/g, \"-\");\n const filename = `agui-${timestamp}-${crypto.randomUUID().slice(0, 8)}.json`;\n const filepath = path.join(fixturePath, filename);\n\n try {\n fs.mkdirSync(fixturePath, { recursive: true });\n fs.writeFileSync(\n filepath,\n JSON.stringify(\n { fixtures: [{ match: fixture.match, events: fixture.events }] },\n null,\n 2,\n ),\n \"utf-8\",\n );\n logger.warn(`AG-UI response recorded → ${filepath}`);\n } catch (err) {\n const msg = err instanceof Error ? err.message : \"Unknown filesystem error\";\n logger.error(\n `Failed to save AG-UI fixture to disk: ${msg} (fixture retained in memory)`,\n );\n }\n } else {\n logger.info(\"Proxied AG-UI request (proxy-only mode)\");\n }\n\n resolve();\n });\n },\n );\n\n upstreamReq.on(\"timeout\", () => {\n if (!clientRes.writableEnded) clientRes.end();\n upstreamReq.destroy(\n new Error(`Upstream AG-UI request timed out after ${UPSTREAM_TIMEOUT_MS / 1000}s`),\n );\n });\n\n upstreamReq.on(\"error\", (err) => {\n if (!clientRes.headersSent) {\n clientRes.writeHead(502, { \"Content-Type\": \"application/json\" });\n clientRes.end(JSON.stringify({ error: \"Upstream AG-UI agent unreachable\" }));\n } else if (!clientRes.writableEnded) {\n clientRes.end();\n }\n reject(err);\n });\n\n upstreamReq.write(body);\n upstreamReq.end();\n });\n}\n\n/**\n * Parse SSE data lines from buffered stream text.\n */\nfunction parseSSEEvents(text: string, logger?: Logger): AGUIEvent[] {\n const events: AGUIEvent[] = [];\n const blocks = text.split(\"\\n\\n\");\n for (const block of blocks) {\n const lines = block.split(\"\\n\");\n for (const line of lines) {\n if (line.startsWith(\"data: \")) {\n try {\n const parsed = JSON.parse(line.slice(6)) as AGUIEvent;\n events.push(parsed);\n } catch {\n logger?.warn(`Skipping unparseable SSE data line: ${line.slice(0, 200)}`);\n }\n }\n }\n }\n return events;\n}\n"],"mappings":"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"}
|