@agentium/transport 1.1.2 → 2.0.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/dist/index.cjs +205 -0
- package/dist/index.d.cts +131 -1
- package/dist/index.d.ts +131 -1
- package/dist/index.js +199 -0
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -20,12 +20,15 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
+
InMemoryEventLog: () => InMemoryEventLog,
|
|
23
24
|
MCPManager: () => MCPManager,
|
|
25
|
+
agentUIStream: () => agentUIStream,
|
|
24
26
|
buildMultiModalInput: () => buildMultiModalInput,
|
|
25
27
|
createA2AServer: () => createA2AServer,
|
|
26
28
|
createAdminRouter: () => createAdminRouter,
|
|
27
29
|
createAgentGateway: () => createAgentGateway,
|
|
28
30
|
createAgentRouter: () => createAgentRouter,
|
|
31
|
+
createAgentUIStreamResponse: () => createAgentUIStreamResponse,
|
|
29
32
|
createBrowserGateway: () => createBrowserGateway,
|
|
30
33
|
createFileUploadMiddleware: () => createFileUploadMiddleware,
|
|
31
34
|
createGatewayRouter: () => createGatewayRouter,
|
|
@@ -33,10 +36,13 @@ __export(index_exports, {
|
|
|
33
36
|
createRbacMiddleware: () => createRbacMiddleware,
|
|
34
37
|
createVisionGateway: () => createVisionGateway,
|
|
35
38
|
createVoiceGateway: () => createVoiceGateway,
|
|
39
|
+
defaultEventLog: () => defaultEventLog,
|
|
36
40
|
errorHandler: () => errorHandler,
|
|
41
|
+
formatSSEEvent: () => formatSSEEvent,
|
|
37
42
|
generateAgentCard: () => generateAgentCard,
|
|
38
43
|
generateMultiAgentCard: () => generateMultiAgentCard,
|
|
39
44
|
generateOpenAPISpec: () => generateOpenAPISpec,
|
|
45
|
+
pipeAgentUIStreamToResponse: () => pipeAgentUIStreamToResponse,
|
|
40
46
|
requestLogger: () => requestLogger
|
|
41
47
|
});
|
|
42
48
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -2035,6 +2041,143 @@ function createAgentRouter(opts) {
|
|
|
2035
2041
|
return router;
|
|
2036
2042
|
}
|
|
2037
2043
|
|
|
2044
|
+
// src/express/ui-stream.ts
|
|
2045
|
+
function ssePayload(obj) {
|
|
2046
|
+
return `data: ${JSON.stringify(obj)}
|
|
2047
|
+
|
|
2048
|
+
`;
|
|
2049
|
+
}
|
|
2050
|
+
function agentUIStream(agent, input, options = {}) {
|
|
2051
|
+
const encoder = new TextEncoder();
|
|
2052
|
+
const messageId = `msg-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
2053
|
+
let textPartId = 0;
|
|
2054
|
+
return new ReadableStream({
|
|
2055
|
+
async start(controller) {
|
|
2056
|
+
try {
|
|
2057
|
+
controller.enqueue(encoder.encode(ssePayload({ type: "start", messageId })));
|
|
2058
|
+
let currentTextId = null;
|
|
2059
|
+
for await (const chunk of agent.stream(input, {
|
|
2060
|
+
sessionId: options.sessionId,
|
|
2061
|
+
userId: options.userId,
|
|
2062
|
+
apiKey: options.apiKey,
|
|
2063
|
+
signal: options.signal
|
|
2064
|
+
})) {
|
|
2065
|
+
const t = chunk.type;
|
|
2066
|
+
if (t === "text" || t === "text-delta") {
|
|
2067
|
+
const delta = chunk.delta ?? chunk.text ?? "";
|
|
2068
|
+
if (!delta) continue;
|
|
2069
|
+
if (!currentTextId) {
|
|
2070
|
+
currentTextId = `text-${++textPartId}`;
|
|
2071
|
+
controller.enqueue(encoder.encode(ssePayload({ type: "text-start", id: currentTextId })));
|
|
2072
|
+
}
|
|
2073
|
+
controller.enqueue(encoder.encode(ssePayload({ type: "text-delta", id: currentTextId, textDelta: delta })));
|
|
2074
|
+
} else if (t === "reasoning" || t === "thinking") {
|
|
2075
|
+
const r = chunk.reasoning ?? chunk.thinking ?? chunk.delta ?? chunk.text ?? "";
|
|
2076
|
+
if (r) {
|
|
2077
|
+
controller.enqueue(
|
|
2078
|
+
encoder.encode(ssePayload({ type: "reasoning-delta", id: messageId, reasoningDelta: r }))
|
|
2079
|
+
);
|
|
2080
|
+
}
|
|
2081
|
+
} else if (t === "tool.call" || t === "tool-call" || t === "tool-input-start") {
|
|
2082
|
+
if (currentTextId) {
|
|
2083
|
+
controller.enqueue(encoder.encode(ssePayload({ type: "text-end", id: currentTextId })));
|
|
2084
|
+
currentTextId = null;
|
|
2085
|
+
}
|
|
2086
|
+
controller.enqueue(
|
|
2087
|
+
encoder.encode(
|
|
2088
|
+
ssePayload({
|
|
2089
|
+
type: "tool-input-start",
|
|
2090
|
+
toolCallId: chunk.toolCallId,
|
|
2091
|
+
toolName: chunk.toolName
|
|
2092
|
+
})
|
|
2093
|
+
)
|
|
2094
|
+
);
|
|
2095
|
+
const args = chunk.arguments ?? chunk.args ?? chunk.input;
|
|
2096
|
+
if (args !== void 0) {
|
|
2097
|
+
controller.enqueue(
|
|
2098
|
+
encoder.encode(
|
|
2099
|
+
ssePayload({
|
|
2100
|
+
type: "tool-input-available",
|
|
2101
|
+
toolCallId: chunk.toolCallId,
|
|
2102
|
+
toolName: chunk.toolName,
|
|
2103
|
+
input: args
|
|
2104
|
+
})
|
|
2105
|
+
)
|
|
2106
|
+
);
|
|
2107
|
+
}
|
|
2108
|
+
} else if (t === "tool.result" || t === "tool-result" || t === "tool-output-available") {
|
|
2109
|
+
controller.enqueue(
|
|
2110
|
+
encoder.encode(
|
|
2111
|
+
ssePayload({
|
|
2112
|
+
type: "tool-output-available",
|
|
2113
|
+
toolCallId: chunk.toolCallId,
|
|
2114
|
+
output: chunk.output ?? chunk.result
|
|
2115
|
+
})
|
|
2116
|
+
)
|
|
2117
|
+
);
|
|
2118
|
+
} else if (t === "error") {
|
|
2119
|
+
const errText = typeof chunk.error === "string" ? chunk.error : chunk.error?.message ?? "unknown error";
|
|
2120
|
+
controller.enqueue(encoder.encode(ssePayload({ type: "error", errorText: errText })));
|
|
2121
|
+
} else if (t === "finish" || t === "done" || t === "stream.end") {
|
|
2122
|
+
if (currentTextId) {
|
|
2123
|
+
controller.enqueue(encoder.encode(ssePayload({ type: "text-end", id: currentTextId })));
|
|
2124
|
+
currentTextId = null;
|
|
2125
|
+
}
|
|
2126
|
+
controller.enqueue(
|
|
2127
|
+
encoder.encode(
|
|
2128
|
+
ssePayload({
|
|
2129
|
+
type: "finish-step",
|
|
2130
|
+
finishReason: chunk.finishReason ?? "stop",
|
|
2131
|
+
usage: chunk.usage
|
|
2132
|
+
})
|
|
2133
|
+
)
|
|
2134
|
+
);
|
|
2135
|
+
}
|
|
2136
|
+
}
|
|
2137
|
+
if (currentTextId) {
|
|
2138
|
+
controller.enqueue(encoder.encode(ssePayload({ type: "text-end", id: currentTextId })));
|
|
2139
|
+
}
|
|
2140
|
+
controller.enqueue(encoder.encode(ssePayload({ type: "finish" })));
|
|
2141
|
+
controller.enqueue(encoder.encode("data: [DONE]\n\n"));
|
|
2142
|
+
controller.close();
|
|
2143
|
+
} catch (err) {
|
|
2144
|
+
controller.enqueue(encoder.encode(ssePayload({ type: "error", errorText: err?.message ?? String(err) })));
|
|
2145
|
+
controller.close();
|
|
2146
|
+
}
|
|
2147
|
+
}
|
|
2148
|
+
});
|
|
2149
|
+
}
|
|
2150
|
+
function createAgentUIStreamResponse(agent, input, options = {}) {
|
|
2151
|
+
const body = agentUIStream(agent, input, options);
|
|
2152
|
+
return new Response(body, {
|
|
2153
|
+
headers: {
|
|
2154
|
+
"Content-Type": "text/event-stream",
|
|
2155
|
+
"Cache-Control": "no-cache",
|
|
2156
|
+
Connection: "keep-alive",
|
|
2157
|
+
"x-vercel-ai-ui-message-stream": "v1"
|
|
2158
|
+
}
|
|
2159
|
+
});
|
|
2160
|
+
}
|
|
2161
|
+
async function pipeAgentUIStreamToResponse(agent, input, res, options = {}) {
|
|
2162
|
+
res.writeHead(200, {
|
|
2163
|
+
"Content-Type": "text/event-stream",
|
|
2164
|
+
"Cache-Control": "no-cache",
|
|
2165
|
+
Connection: "keep-alive",
|
|
2166
|
+
"x-vercel-ai-ui-message-stream": "v1"
|
|
2167
|
+
});
|
|
2168
|
+
const stream = agentUIStream(agent, input, options);
|
|
2169
|
+
const reader = stream.getReader();
|
|
2170
|
+
try {
|
|
2171
|
+
while (true) {
|
|
2172
|
+
const { done, value } = await reader.read();
|
|
2173
|
+
if (done) break;
|
|
2174
|
+
res.write(Buffer.from(value));
|
|
2175
|
+
}
|
|
2176
|
+
} finally {
|
|
2177
|
+
res.end();
|
|
2178
|
+
}
|
|
2179
|
+
}
|
|
2180
|
+
|
|
2038
2181
|
// src/socketio/browser-gateway.ts
|
|
2039
2182
|
function createBrowserGateway(opts) {
|
|
2040
2183
|
const ns = opts.io.of(opts.namespace ?? "/agentium-browser");
|
|
@@ -2567,14 +2710,73 @@ function createVoiceGateway(opts) {
|
|
|
2567
2710
|
});
|
|
2568
2711
|
});
|
|
2569
2712
|
}
|
|
2713
|
+
|
|
2714
|
+
// src/sse-event-log.ts
|
|
2715
|
+
var InMemoryEventLog = class {
|
|
2716
|
+
buffers = /* @__PURE__ */ new Map();
|
|
2717
|
+
nextIds = /* @__PURE__ */ new Map();
|
|
2718
|
+
finalizeTimers = /* @__PURE__ */ new Map();
|
|
2719
|
+
maxEvents;
|
|
2720
|
+
ttlMs;
|
|
2721
|
+
constructor(config = {}) {
|
|
2722
|
+
this.maxEvents = config.maxEventsPerRun ?? 1024;
|
|
2723
|
+
this.ttlMs = config.ttlMs ?? 5 * 6e4;
|
|
2724
|
+
}
|
|
2725
|
+
record(runId, event) {
|
|
2726
|
+
const buf = this.buffers.get(runId) ?? [];
|
|
2727
|
+
const nextId = (this.nextIds.get(runId) ?? 0) + 1;
|
|
2728
|
+
const full = { id: nextId, recordedAt: Date.now(), ...event };
|
|
2729
|
+
buf.push(full);
|
|
2730
|
+
if (buf.length > this.maxEvents) buf.splice(0, buf.length - this.maxEvents);
|
|
2731
|
+
this.buffers.set(runId, buf);
|
|
2732
|
+
this.nextIds.set(runId, nextId);
|
|
2733
|
+
return full;
|
|
2734
|
+
}
|
|
2735
|
+
since(runId, afterId) {
|
|
2736
|
+
const buf = this.buffers.get(runId) ?? [];
|
|
2737
|
+
return buf.filter((e) => e.id > afterId);
|
|
2738
|
+
}
|
|
2739
|
+
all(runId) {
|
|
2740
|
+
return this.buffers.get(runId) ?? [];
|
|
2741
|
+
}
|
|
2742
|
+
finalize(runId) {
|
|
2743
|
+
const existing = this.finalizeTimers.get(runId);
|
|
2744
|
+
if (existing) clearTimeout(existing);
|
|
2745
|
+
const t = setTimeout(() => this.drop(runId), this.ttlMs);
|
|
2746
|
+
t.unref?.();
|
|
2747
|
+
this.finalizeTimers.set(runId, t);
|
|
2748
|
+
}
|
|
2749
|
+
drop(runId) {
|
|
2750
|
+
this.buffers.delete(runId);
|
|
2751
|
+
this.nextIds.delete(runId);
|
|
2752
|
+
const t = this.finalizeTimers.get(runId);
|
|
2753
|
+
if (t) clearTimeout(t);
|
|
2754
|
+
this.finalizeTimers.delete(runId);
|
|
2755
|
+
}
|
|
2756
|
+
};
|
|
2757
|
+
function formatSSEEvent(ev) {
|
|
2758
|
+
let out = `id: ${ev.id}
|
|
2759
|
+
`;
|
|
2760
|
+
if (ev.event) out += `event: ${ev.event}
|
|
2761
|
+
`;
|
|
2762
|
+
const data = typeof ev.payload === "string" ? ev.payload : JSON.stringify(ev.payload);
|
|
2763
|
+
out += `data: ${data}
|
|
2764
|
+
|
|
2765
|
+
`;
|
|
2766
|
+
return out;
|
|
2767
|
+
}
|
|
2768
|
+
var defaultEventLog = new InMemoryEventLog();
|
|
2570
2769
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2571
2770
|
0 && (module.exports = {
|
|
2771
|
+
InMemoryEventLog,
|
|
2572
2772
|
MCPManager,
|
|
2773
|
+
agentUIStream,
|
|
2573
2774
|
buildMultiModalInput,
|
|
2574
2775
|
createA2AServer,
|
|
2575
2776
|
createAdminRouter,
|
|
2576
2777
|
createAgentGateway,
|
|
2577
2778
|
createAgentRouter,
|
|
2779
|
+
createAgentUIStreamResponse,
|
|
2578
2780
|
createBrowserGateway,
|
|
2579
2781
|
createFileUploadMiddleware,
|
|
2580
2782
|
createGatewayRouter,
|
|
@@ -2582,9 +2784,12 @@ function createVoiceGateway(opts) {
|
|
|
2582
2784
|
createRbacMiddleware,
|
|
2583
2785
|
createVisionGateway,
|
|
2584
2786
|
createVoiceGateway,
|
|
2787
|
+
defaultEventLog,
|
|
2585
2788
|
errorHandler,
|
|
2789
|
+
formatSSEEvent,
|
|
2586
2790
|
generateAgentCard,
|
|
2587
2791
|
generateMultiAgentCard,
|
|
2588
2792
|
generateOpenAPISpec,
|
|
2793
|
+
pipeAgentUIStreamToResponse,
|
|
2589
2794
|
requestLogger
|
|
2590
2795
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -280,6 +280,82 @@ interface OpenAPISpec {
|
|
|
280
280
|
}
|
|
281
281
|
declare function generateOpenAPISpec(routerOpts: RouterOptions, swaggerOpts?: SwaggerOptions): OpenAPISpec;
|
|
282
282
|
|
|
283
|
+
/**
|
|
284
|
+
* Vercel AI SDK UI Message Stream adapter.
|
|
285
|
+
*
|
|
286
|
+
* Converts the chunks emitted by `agent.stream(...)` into the line-delimited
|
|
287
|
+
* JSON protocol consumed by Vercel's `useChat` / `createAgentUIStreamResponse`.
|
|
288
|
+
*
|
|
289
|
+
* Spec: https://ai-sdk.dev/docs/ai-sdk-ui/stream-protocol
|
|
290
|
+
*/
|
|
291
|
+
interface AgentUIStreamOptions {
|
|
292
|
+
/** sessionId forwarded to `agent.run` / `agent.stream`. */
|
|
293
|
+
sessionId?: string;
|
|
294
|
+
/** userId forwarded to `agent.run` / `agent.stream`. */
|
|
295
|
+
userId?: string;
|
|
296
|
+
/** Optional per-request API key override. */
|
|
297
|
+
apiKey?: string;
|
|
298
|
+
/** Optional abort signal to cancel the run. */
|
|
299
|
+
signal?: AbortSignal;
|
|
300
|
+
}
|
|
301
|
+
interface AgentLikeChunk {
|
|
302
|
+
type?: string;
|
|
303
|
+
text?: string;
|
|
304
|
+
delta?: string;
|
|
305
|
+
toolCallId?: string;
|
|
306
|
+
toolName?: string;
|
|
307
|
+
arguments?: unknown;
|
|
308
|
+
args?: unknown;
|
|
309
|
+
input?: unknown;
|
|
310
|
+
output?: unknown;
|
|
311
|
+
result?: unknown;
|
|
312
|
+
finishReason?: string;
|
|
313
|
+
usage?: {
|
|
314
|
+
promptTokens?: number;
|
|
315
|
+
completionTokens?: number;
|
|
316
|
+
totalTokens?: number;
|
|
317
|
+
};
|
|
318
|
+
error?: string | {
|
|
319
|
+
message?: string;
|
|
320
|
+
};
|
|
321
|
+
reasoning?: string;
|
|
322
|
+
thinking?: string;
|
|
323
|
+
}
|
|
324
|
+
interface AgentLike {
|
|
325
|
+
stream(input: string, opts?: {
|
|
326
|
+
sessionId?: string;
|
|
327
|
+
userId?: string;
|
|
328
|
+
apiKey?: string;
|
|
329
|
+
signal?: AbortSignal;
|
|
330
|
+
}): AsyncIterable<AgentLikeChunk>;
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Render an `agent.stream()` source as a Vercel-compatible UI message stream.
|
|
334
|
+
*
|
|
335
|
+
* The returned `ReadableStream<Uint8Array>` can be wrapped in a `Response`
|
|
336
|
+
* (web / edge / fetch) or piped to a Node `ServerResponse`.
|
|
337
|
+
*/
|
|
338
|
+
declare function agentUIStream(agent: Agent | AgentLike, input: string, options?: AgentUIStreamOptions): ReadableStream<Uint8Array>;
|
|
339
|
+
/**
|
|
340
|
+
* Create a `Response` whose body streams Vercel UI messages. Use as the return
|
|
341
|
+
* value of a Web / Edge / Next.js Route Handler.
|
|
342
|
+
*
|
|
343
|
+
* @example
|
|
344
|
+
* ```ts
|
|
345
|
+
* import { createAgentUIStreamResponse } from "@agentium/transport";
|
|
346
|
+
*
|
|
347
|
+
* export async function POST(req: Request) {
|
|
348
|
+
* const { input, sessionId } = await req.json();
|
|
349
|
+
* return createAgentUIStreamResponse(agent, input, { sessionId });
|
|
350
|
+
* }
|
|
351
|
+
* ```
|
|
352
|
+
*/
|
|
353
|
+
declare function createAgentUIStreamResponse(agent: Agent | AgentLike, input: string, options?: AgentUIStreamOptions): Response;
|
|
354
|
+
/**
|
|
355
|
+
* Pipe a Vercel UI message stream into a Node `ServerResponse` (Express style).
|
|
356
|
+
*/
|
|
357
|
+
declare function pipeAgentUIStreamToResponse(agent: Agent | AgentLike, input: string, res: any, options?: AgentUIStreamOptions): Promise<void>;
|
|
358
|
+
|
|
283
359
|
/**
|
|
284
360
|
* Minimal interface for a BrowserAgent — avoids a hard dependency on @agentium/browser.
|
|
285
361
|
* Any object that matches this shape (e.g. a real BrowserAgent) works.
|
|
@@ -390,4 +466,58 @@ interface VoiceGatewayOptions {
|
|
|
390
466
|
}
|
|
391
467
|
declare function createVoiceGateway(opts: VoiceGatewayOptions): void;
|
|
392
468
|
|
|
393
|
-
|
|
469
|
+
/**
|
|
470
|
+
* Per-run in-memory ring buffer of SSE events. Each event gets a monotonically
|
|
471
|
+
* increasing numeric `id` so clients can pass `Last-Event-ID` on reconnect and
|
|
472
|
+
* replay missed events.
|
|
473
|
+
*
|
|
474
|
+
* Storage is intentionally in-process; for multi-instance deployments, plug a
|
|
475
|
+
* Redis-backed implementation behind the same interface.
|
|
476
|
+
*/
|
|
477
|
+
interface SSEEvent {
|
|
478
|
+
/** Monotonic numeric ID. */
|
|
479
|
+
id: number;
|
|
480
|
+
/** Optional `event:` line value. Default omitted. */
|
|
481
|
+
event?: string;
|
|
482
|
+
/** Payload (will be JSON-stringified into the `data:` field). */
|
|
483
|
+
payload: unknown;
|
|
484
|
+
/** Wall-clock time the event was recorded. */
|
|
485
|
+
recordedAt: number;
|
|
486
|
+
}
|
|
487
|
+
interface SSEEventLog {
|
|
488
|
+
/** Record a new event and return its assigned id. */
|
|
489
|
+
record(runId: string, event: Omit<SSEEvent, "id" | "recordedAt">): SSEEvent;
|
|
490
|
+
/** Return events for a run with id greater than `afterId`. */
|
|
491
|
+
since(runId: string, afterId: number): SSEEvent[];
|
|
492
|
+
/** All events for a run. */
|
|
493
|
+
all(runId: string): SSEEvent[];
|
|
494
|
+
/** Mark a run completed (frees buffers after `ttlMs`). */
|
|
495
|
+
finalize(runId: string): void;
|
|
496
|
+
/** Remove a run's buffer immediately. */
|
|
497
|
+
drop(runId: string): void;
|
|
498
|
+
}
|
|
499
|
+
interface InMemoryEventLogConfig {
|
|
500
|
+
/** Max events kept per run. Oldest evicted when exceeded. Default: 1024. */
|
|
501
|
+
maxEventsPerRun?: number;
|
|
502
|
+
/** Milliseconds after finalize before a run's buffer is dropped. Default: 300_000 (5min). */
|
|
503
|
+
ttlMs?: number;
|
|
504
|
+
}
|
|
505
|
+
declare class InMemoryEventLog implements SSEEventLog {
|
|
506
|
+
private buffers;
|
|
507
|
+
private nextIds;
|
|
508
|
+
private finalizeTimers;
|
|
509
|
+
private maxEvents;
|
|
510
|
+
private ttlMs;
|
|
511
|
+
constructor(config?: InMemoryEventLogConfig);
|
|
512
|
+
record(runId: string, event: Omit<SSEEvent, "id" | "recordedAt">): SSEEvent;
|
|
513
|
+
since(runId: string, afterId: number): SSEEvent[];
|
|
514
|
+
all(runId: string): SSEEvent[];
|
|
515
|
+
finalize(runId: string): void;
|
|
516
|
+
drop(runId: string): void;
|
|
517
|
+
}
|
|
518
|
+
/** Format an `SSEEvent` for the wire. */
|
|
519
|
+
declare function formatSSEEvent(ev: SSEEvent): string;
|
|
520
|
+
/** Process-wide default log so multiple endpoints can share one buffer. */
|
|
521
|
+
declare const defaultEventLog: InMemoryEventLog;
|
|
522
|
+
|
|
523
|
+
export { type A2AServerOptions, type AdminRouterOptions, type AgentUIStreamOptions, type BrowserGatewayOptions, type FileUploadOptions, type GatewayConfig, type GatewayOptions, InMemoryEventLog, type InMemoryEventLogConfig, type JwtConfig, MCPManager, type MCPServerEntry, type MCPServerSummary, type RbacConfig, type RemoteEndpoint, type RouterOptions, type SSEEvent, type SSEEventLog, type SwaggerOptions, type VisionGatewayOptions, type VoiceGatewayOptions, agentUIStream, buildMultiModalInput, createA2AServer, createAdminRouter, createAgentGateway, createAgentRouter, createAgentUIStreamResponse, createBrowserGateway, createFileUploadMiddleware, createGatewayRouter, createJwtMiddleware, createRbacMiddleware, createVisionGateway, createVoiceGateway, defaultEventLog, errorHandler, formatSSEEvent, generateAgentCard, generateMultiAgentCard, generateOpenAPISpec, pipeAgentUIStreamToResponse, requestLogger };
|
package/dist/index.d.ts
CHANGED
|
@@ -280,6 +280,82 @@ interface OpenAPISpec {
|
|
|
280
280
|
}
|
|
281
281
|
declare function generateOpenAPISpec(routerOpts: RouterOptions, swaggerOpts?: SwaggerOptions): OpenAPISpec;
|
|
282
282
|
|
|
283
|
+
/**
|
|
284
|
+
* Vercel AI SDK UI Message Stream adapter.
|
|
285
|
+
*
|
|
286
|
+
* Converts the chunks emitted by `agent.stream(...)` into the line-delimited
|
|
287
|
+
* JSON protocol consumed by Vercel's `useChat` / `createAgentUIStreamResponse`.
|
|
288
|
+
*
|
|
289
|
+
* Spec: https://ai-sdk.dev/docs/ai-sdk-ui/stream-protocol
|
|
290
|
+
*/
|
|
291
|
+
interface AgentUIStreamOptions {
|
|
292
|
+
/** sessionId forwarded to `agent.run` / `agent.stream`. */
|
|
293
|
+
sessionId?: string;
|
|
294
|
+
/** userId forwarded to `agent.run` / `agent.stream`. */
|
|
295
|
+
userId?: string;
|
|
296
|
+
/** Optional per-request API key override. */
|
|
297
|
+
apiKey?: string;
|
|
298
|
+
/** Optional abort signal to cancel the run. */
|
|
299
|
+
signal?: AbortSignal;
|
|
300
|
+
}
|
|
301
|
+
interface AgentLikeChunk {
|
|
302
|
+
type?: string;
|
|
303
|
+
text?: string;
|
|
304
|
+
delta?: string;
|
|
305
|
+
toolCallId?: string;
|
|
306
|
+
toolName?: string;
|
|
307
|
+
arguments?: unknown;
|
|
308
|
+
args?: unknown;
|
|
309
|
+
input?: unknown;
|
|
310
|
+
output?: unknown;
|
|
311
|
+
result?: unknown;
|
|
312
|
+
finishReason?: string;
|
|
313
|
+
usage?: {
|
|
314
|
+
promptTokens?: number;
|
|
315
|
+
completionTokens?: number;
|
|
316
|
+
totalTokens?: number;
|
|
317
|
+
};
|
|
318
|
+
error?: string | {
|
|
319
|
+
message?: string;
|
|
320
|
+
};
|
|
321
|
+
reasoning?: string;
|
|
322
|
+
thinking?: string;
|
|
323
|
+
}
|
|
324
|
+
interface AgentLike {
|
|
325
|
+
stream(input: string, opts?: {
|
|
326
|
+
sessionId?: string;
|
|
327
|
+
userId?: string;
|
|
328
|
+
apiKey?: string;
|
|
329
|
+
signal?: AbortSignal;
|
|
330
|
+
}): AsyncIterable<AgentLikeChunk>;
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Render an `agent.stream()` source as a Vercel-compatible UI message stream.
|
|
334
|
+
*
|
|
335
|
+
* The returned `ReadableStream<Uint8Array>` can be wrapped in a `Response`
|
|
336
|
+
* (web / edge / fetch) or piped to a Node `ServerResponse`.
|
|
337
|
+
*/
|
|
338
|
+
declare function agentUIStream(agent: Agent | AgentLike, input: string, options?: AgentUIStreamOptions): ReadableStream<Uint8Array>;
|
|
339
|
+
/**
|
|
340
|
+
* Create a `Response` whose body streams Vercel UI messages. Use as the return
|
|
341
|
+
* value of a Web / Edge / Next.js Route Handler.
|
|
342
|
+
*
|
|
343
|
+
* @example
|
|
344
|
+
* ```ts
|
|
345
|
+
* import { createAgentUIStreamResponse } from "@agentium/transport";
|
|
346
|
+
*
|
|
347
|
+
* export async function POST(req: Request) {
|
|
348
|
+
* const { input, sessionId } = await req.json();
|
|
349
|
+
* return createAgentUIStreamResponse(agent, input, { sessionId });
|
|
350
|
+
* }
|
|
351
|
+
* ```
|
|
352
|
+
*/
|
|
353
|
+
declare function createAgentUIStreamResponse(agent: Agent | AgentLike, input: string, options?: AgentUIStreamOptions): Response;
|
|
354
|
+
/**
|
|
355
|
+
* Pipe a Vercel UI message stream into a Node `ServerResponse` (Express style).
|
|
356
|
+
*/
|
|
357
|
+
declare function pipeAgentUIStreamToResponse(agent: Agent | AgentLike, input: string, res: any, options?: AgentUIStreamOptions): Promise<void>;
|
|
358
|
+
|
|
283
359
|
/**
|
|
284
360
|
* Minimal interface for a BrowserAgent — avoids a hard dependency on @agentium/browser.
|
|
285
361
|
* Any object that matches this shape (e.g. a real BrowserAgent) works.
|
|
@@ -390,4 +466,58 @@ interface VoiceGatewayOptions {
|
|
|
390
466
|
}
|
|
391
467
|
declare function createVoiceGateway(opts: VoiceGatewayOptions): void;
|
|
392
468
|
|
|
393
|
-
|
|
469
|
+
/**
|
|
470
|
+
* Per-run in-memory ring buffer of SSE events. Each event gets a monotonically
|
|
471
|
+
* increasing numeric `id` so clients can pass `Last-Event-ID` on reconnect and
|
|
472
|
+
* replay missed events.
|
|
473
|
+
*
|
|
474
|
+
* Storage is intentionally in-process; for multi-instance deployments, plug a
|
|
475
|
+
* Redis-backed implementation behind the same interface.
|
|
476
|
+
*/
|
|
477
|
+
interface SSEEvent {
|
|
478
|
+
/** Monotonic numeric ID. */
|
|
479
|
+
id: number;
|
|
480
|
+
/** Optional `event:` line value. Default omitted. */
|
|
481
|
+
event?: string;
|
|
482
|
+
/** Payload (will be JSON-stringified into the `data:` field). */
|
|
483
|
+
payload: unknown;
|
|
484
|
+
/** Wall-clock time the event was recorded. */
|
|
485
|
+
recordedAt: number;
|
|
486
|
+
}
|
|
487
|
+
interface SSEEventLog {
|
|
488
|
+
/** Record a new event and return its assigned id. */
|
|
489
|
+
record(runId: string, event: Omit<SSEEvent, "id" | "recordedAt">): SSEEvent;
|
|
490
|
+
/** Return events for a run with id greater than `afterId`. */
|
|
491
|
+
since(runId: string, afterId: number): SSEEvent[];
|
|
492
|
+
/** All events for a run. */
|
|
493
|
+
all(runId: string): SSEEvent[];
|
|
494
|
+
/** Mark a run completed (frees buffers after `ttlMs`). */
|
|
495
|
+
finalize(runId: string): void;
|
|
496
|
+
/** Remove a run's buffer immediately. */
|
|
497
|
+
drop(runId: string): void;
|
|
498
|
+
}
|
|
499
|
+
interface InMemoryEventLogConfig {
|
|
500
|
+
/** Max events kept per run. Oldest evicted when exceeded. Default: 1024. */
|
|
501
|
+
maxEventsPerRun?: number;
|
|
502
|
+
/** Milliseconds after finalize before a run's buffer is dropped. Default: 300_000 (5min). */
|
|
503
|
+
ttlMs?: number;
|
|
504
|
+
}
|
|
505
|
+
declare class InMemoryEventLog implements SSEEventLog {
|
|
506
|
+
private buffers;
|
|
507
|
+
private nextIds;
|
|
508
|
+
private finalizeTimers;
|
|
509
|
+
private maxEvents;
|
|
510
|
+
private ttlMs;
|
|
511
|
+
constructor(config?: InMemoryEventLogConfig);
|
|
512
|
+
record(runId: string, event: Omit<SSEEvent, "id" | "recordedAt">): SSEEvent;
|
|
513
|
+
since(runId: string, afterId: number): SSEEvent[];
|
|
514
|
+
all(runId: string): SSEEvent[];
|
|
515
|
+
finalize(runId: string): void;
|
|
516
|
+
drop(runId: string): void;
|
|
517
|
+
}
|
|
518
|
+
/** Format an `SSEEvent` for the wire. */
|
|
519
|
+
declare function formatSSEEvent(ev: SSEEvent): string;
|
|
520
|
+
/** Process-wide default log so multiple endpoints can share one buffer. */
|
|
521
|
+
declare const defaultEventLog: InMemoryEventLog;
|
|
522
|
+
|
|
523
|
+
export { type A2AServerOptions, type AdminRouterOptions, type AgentUIStreamOptions, type BrowserGatewayOptions, type FileUploadOptions, type GatewayConfig, type GatewayOptions, InMemoryEventLog, type InMemoryEventLogConfig, type JwtConfig, MCPManager, type MCPServerEntry, type MCPServerSummary, type RbacConfig, type RemoteEndpoint, type RouterOptions, type SSEEvent, type SSEEventLog, type SwaggerOptions, type VisionGatewayOptions, type VoiceGatewayOptions, agentUIStream, buildMultiModalInput, createA2AServer, createAdminRouter, createAgentGateway, createAgentRouter, createAgentUIStreamResponse, createBrowserGateway, createFileUploadMiddleware, createGatewayRouter, createJwtMiddleware, createRbacMiddleware, createVisionGateway, createVoiceGateway, defaultEventLog, errorHandler, formatSSEEvent, generateAgentCard, generateMultiAgentCard, generateOpenAPISpec, pipeAgentUIStreamToResponse, requestLogger };
|
package/dist/index.js
CHANGED
|
@@ -1993,6 +1993,143 @@ function createAgentRouter(opts) {
|
|
|
1993
1993
|
return router;
|
|
1994
1994
|
}
|
|
1995
1995
|
|
|
1996
|
+
// src/express/ui-stream.ts
|
|
1997
|
+
function ssePayload(obj) {
|
|
1998
|
+
return `data: ${JSON.stringify(obj)}
|
|
1999
|
+
|
|
2000
|
+
`;
|
|
2001
|
+
}
|
|
2002
|
+
function agentUIStream(agent, input, options = {}) {
|
|
2003
|
+
const encoder = new TextEncoder();
|
|
2004
|
+
const messageId = `msg-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
2005
|
+
let textPartId = 0;
|
|
2006
|
+
return new ReadableStream({
|
|
2007
|
+
async start(controller) {
|
|
2008
|
+
try {
|
|
2009
|
+
controller.enqueue(encoder.encode(ssePayload({ type: "start", messageId })));
|
|
2010
|
+
let currentTextId = null;
|
|
2011
|
+
for await (const chunk of agent.stream(input, {
|
|
2012
|
+
sessionId: options.sessionId,
|
|
2013
|
+
userId: options.userId,
|
|
2014
|
+
apiKey: options.apiKey,
|
|
2015
|
+
signal: options.signal
|
|
2016
|
+
})) {
|
|
2017
|
+
const t = chunk.type;
|
|
2018
|
+
if (t === "text" || t === "text-delta") {
|
|
2019
|
+
const delta = chunk.delta ?? chunk.text ?? "";
|
|
2020
|
+
if (!delta) continue;
|
|
2021
|
+
if (!currentTextId) {
|
|
2022
|
+
currentTextId = `text-${++textPartId}`;
|
|
2023
|
+
controller.enqueue(encoder.encode(ssePayload({ type: "text-start", id: currentTextId })));
|
|
2024
|
+
}
|
|
2025
|
+
controller.enqueue(encoder.encode(ssePayload({ type: "text-delta", id: currentTextId, textDelta: delta })));
|
|
2026
|
+
} else if (t === "reasoning" || t === "thinking") {
|
|
2027
|
+
const r = chunk.reasoning ?? chunk.thinking ?? chunk.delta ?? chunk.text ?? "";
|
|
2028
|
+
if (r) {
|
|
2029
|
+
controller.enqueue(
|
|
2030
|
+
encoder.encode(ssePayload({ type: "reasoning-delta", id: messageId, reasoningDelta: r }))
|
|
2031
|
+
);
|
|
2032
|
+
}
|
|
2033
|
+
} else if (t === "tool.call" || t === "tool-call" || t === "tool-input-start") {
|
|
2034
|
+
if (currentTextId) {
|
|
2035
|
+
controller.enqueue(encoder.encode(ssePayload({ type: "text-end", id: currentTextId })));
|
|
2036
|
+
currentTextId = null;
|
|
2037
|
+
}
|
|
2038
|
+
controller.enqueue(
|
|
2039
|
+
encoder.encode(
|
|
2040
|
+
ssePayload({
|
|
2041
|
+
type: "tool-input-start",
|
|
2042
|
+
toolCallId: chunk.toolCallId,
|
|
2043
|
+
toolName: chunk.toolName
|
|
2044
|
+
})
|
|
2045
|
+
)
|
|
2046
|
+
);
|
|
2047
|
+
const args = chunk.arguments ?? chunk.args ?? chunk.input;
|
|
2048
|
+
if (args !== void 0) {
|
|
2049
|
+
controller.enqueue(
|
|
2050
|
+
encoder.encode(
|
|
2051
|
+
ssePayload({
|
|
2052
|
+
type: "tool-input-available",
|
|
2053
|
+
toolCallId: chunk.toolCallId,
|
|
2054
|
+
toolName: chunk.toolName,
|
|
2055
|
+
input: args
|
|
2056
|
+
})
|
|
2057
|
+
)
|
|
2058
|
+
);
|
|
2059
|
+
}
|
|
2060
|
+
} else if (t === "tool.result" || t === "tool-result" || t === "tool-output-available") {
|
|
2061
|
+
controller.enqueue(
|
|
2062
|
+
encoder.encode(
|
|
2063
|
+
ssePayload({
|
|
2064
|
+
type: "tool-output-available",
|
|
2065
|
+
toolCallId: chunk.toolCallId,
|
|
2066
|
+
output: chunk.output ?? chunk.result
|
|
2067
|
+
})
|
|
2068
|
+
)
|
|
2069
|
+
);
|
|
2070
|
+
} else if (t === "error") {
|
|
2071
|
+
const errText = typeof chunk.error === "string" ? chunk.error : chunk.error?.message ?? "unknown error";
|
|
2072
|
+
controller.enqueue(encoder.encode(ssePayload({ type: "error", errorText: errText })));
|
|
2073
|
+
} else if (t === "finish" || t === "done" || t === "stream.end") {
|
|
2074
|
+
if (currentTextId) {
|
|
2075
|
+
controller.enqueue(encoder.encode(ssePayload({ type: "text-end", id: currentTextId })));
|
|
2076
|
+
currentTextId = null;
|
|
2077
|
+
}
|
|
2078
|
+
controller.enqueue(
|
|
2079
|
+
encoder.encode(
|
|
2080
|
+
ssePayload({
|
|
2081
|
+
type: "finish-step",
|
|
2082
|
+
finishReason: chunk.finishReason ?? "stop",
|
|
2083
|
+
usage: chunk.usage
|
|
2084
|
+
})
|
|
2085
|
+
)
|
|
2086
|
+
);
|
|
2087
|
+
}
|
|
2088
|
+
}
|
|
2089
|
+
if (currentTextId) {
|
|
2090
|
+
controller.enqueue(encoder.encode(ssePayload({ type: "text-end", id: currentTextId })));
|
|
2091
|
+
}
|
|
2092
|
+
controller.enqueue(encoder.encode(ssePayload({ type: "finish" })));
|
|
2093
|
+
controller.enqueue(encoder.encode("data: [DONE]\n\n"));
|
|
2094
|
+
controller.close();
|
|
2095
|
+
} catch (err) {
|
|
2096
|
+
controller.enqueue(encoder.encode(ssePayload({ type: "error", errorText: err?.message ?? String(err) })));
|
|
2097
|
+
controller.close();
|
|
2098
|
+
}
|
|
2099
|
+
}
|
|
2100
|
+
});
|
|
2101
|
+
}
|
|
2102
|
+
function createAgentUIStreamResponse(agent, input, options = {}) {
|
|
2103
|
+
const body = agentUIStream(agent, input, options);
|
|
2104
|
+
return new Response(body, {
|
|
2105
|
+
headers: {
|
|
2106
|
+
"Content-Type": "text/event-stream",
|
|
2107
|
+
"Cache-Control": "no-cache",
|
|
2108
|
+
Connection: "keep-alive",
|
|
2109
|
+
"x-vercel-ai-ui-message-stream": "v1"
|
|
2110
|
+
}
|
|
2111
|
+
});
|
|
2112
|
+
}
|
|
2113
|
+
async function pipeAgentUIStreamToResponse(agent, input, res, options = {}) {
|
|
2114
|
+
res.writeHead(200, {
|
|
2115
|
+
"Content-Type": "text/event-stream",
|
|
2116
|
+
"Cache-Control": "no-cache",
|
|
2117
|
+
Connection: "keep-alive",
|
|
2118
|
+
"x-vercel-ai-ui-message-stream": "v1"
|
|
2119
|
+
});
|
|
2120
|
+
const stream = agentUIStream(agent, input, options);
|
|
2121
|
+
const reader = stream.getReader();
|
|
2122
|
+
try {
|
|
2123
|
+
while (true) {
|
|
2124
|
+
const { done, value } = await reader.read();
|
|
2125
|
+
if (done) break;
|
|
2126
|
+
res.write(Buffer.from(value));
|
|
2127
|
+
}
|
|
2128
|
+
} finally {
|
|
2129
|
+
res.end();
|
|
2130
|
+
}
|
|
2131
|
+
}
|
|
2132
|
+
|
|
1996
2133
|
// src/socketio/browser-gateway.ts
|
|
1997
2134
|
function createBrowserGateway(opts) {
|
|
1998
2135
|
const ns = opts.io.of(opts.namespace ?? "/agentium-browser");
|
|
@@ -2530,13 +2667,72 @@ function createVoiceGateway(opts) {
|
|
|
2530
2667
|
});
|
|
2531
2668
|
});
|
|
2532
2669
|
}
|
|
2670
|
+
|
|
2671
|
+
// src/sse-event-log.ts
|
|
2672
|
+
var InMemoryEventLog = class {
|
|
2673
|
+
buffers = /* @__PURE__ */ new Map();
|
|
2674
|
+
nextIds = /* @__PURE__ */ new Map();
|
|
2675
|
+
finalizeTimers = /* @__PURE__ */ new Map();
|
|
2676
|
+
maxEvents;
|
|
2677
|
+
ttlMs;
|
|
2678
|
+
constructor(config = {}) {
|
|
2679
|
+
this.maxEvents = config.maxEventsPerRun ?? 1024;
|
|
2680
|
+
this.ttlMs = config.ttlMs ?? 5 * 6e4;
|
|
2681
|
+
}
|
|
2682
|
+
record(runId, event) {
|
|
2683
|
+
const buf = this.buffers.get(runId) ?? [];
|
|
2684
|
+
const nextId = (this.nextIds.get(runId) ?? 0) + 1;
|
|
2685
|
+
const full = { id: nextId, recordedAt: Date.now(), ...event };
|
|
2686
|
+
buf.push(full);
|
|
2687
|
+
if (buf.length > this.maxEvents) buf.splice(0, buf.length - this.maxEvents);
|
|
2688
|
+
this.buffers.set(runId, buf);
|
|
2689
|
+
this.nextIds.set(runId, nextId);
|
|
2690
|
+
return full;
|
|
2691
|
+
}
|
|
2692
|
+
since(runId, afterId) {
|
|
2693
|
+
const buf = this.buffers.get(runId) ?? [];
|
|
2694
|
+
return buf.filter((e) => e.id > afterId);
|
|
2695
|
+
}
|
|
2696
|
+
all(runId) {
|
|
2697
|
+
return this.buffers.get(runId) ?? [];
|
|
2698
|
+
}
|
|
2699
|
+
finalize(runId) {
|
|
2700
|
+
const existing = this.finalizeTimers.get(runId);
|
|
2701
|
+
if (existing) clearTimeout(existing);
|
|
2702
|
+
const t = setTimeout(() => this.drop(runId), this.ttlMs);
|
|
2703
|
+
t.unref?.();
|
|
2704
|
+
this.finalizeTimers.set(runId, t);
|
|
2705
|
+
}
|
|
2706
|
+
drop(runId) {
|
|
2707
|
+
this.buffers.delete(runId);
|
|
2708
|
+
this.nextIds.delete(runId);
|
|
2709
|
+
const t = this.finalizeTimers.get(runId);
|
|
2710
|
+
if (t) clearTimeout(t);
|
|
2711
|
+
this.finalizeTimers.delete(runId);
|
|
2712
|
+
}
|
|
2713
|
+
};
|
|
2714
|
+
function formatSSEEvent(ev) {
|
|
2715
|
+
let out = `id: ${ev.id}
|
|
2716
|
+
`;
|
|
2717
|
+
if (ev.event) out += `event: ${ev.event}
|
|
2718
|
+
`;
|
|
2719
|
+
const data = typeof ev.payload === "string" ? ev.payload : JSON.stringify(ev.payload);
|
|
2720
|
+
out += `data: ${data}
|
|
2721
|
+
|
|
2722
|
+
`;
|
|
2723
|
+
return out;
|
|
2724
|
+
}
|
|
2725
|
+
var defaultEventLog = new InMemoryEventLog();
|
|
2533
2726
|
export {
|
|
2727
|
+
InMemoryEventLog,
|
|
2534
2728
|
MCPManager,
|
|
2729
|
+
agentUIStream,
|
|
2535
2730
|
buildMultiModalInput,
|
|
2536
2731
|
createA2AServer,
|
|
2537
2732
|
createAdminRouter,
|
|
2538
2733
|
createAgentGateway,
|
|
2539
2734
|
createAgentRouter,
|
|
2735
|
+
createAgentUIStreamResponse,
|
|
2540
2736
|
createBrowserGateway,
|
|
2541
2737
|
createFileUploadMiddleware,
|
|
2542
2738
|
createGatewayRouter,
|
|
@@ -2544,9 +2740,12 @@ export {
|
|
|
2544
2740
|
createRbacMiddleware,
|
|
2545
2741
|
createVisionGateway,
|
|
2546
2742
|
createVoiceGateway,
|
|
2743
|
+
defaultEventLog,
|
|
2547
2744
|
errorHandler,
|
|
2745
|
+
formatSSEEvent,
|
|
2548
2746
|
generateAgentCard,
|
|
2549
2747
|
generateMultiAgentCard,
|
|
2550
2748
|
generateOpenAPISpec,
|
|
2749
|
+
pipeAgentUIStreamToResponse,
|
|
2551
2750
|
requestLogger
|
|
2552
2751
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentium/transport",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "HTTP and WebSocket transport layer for Agentium agents",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"typescript": "^5.6.0"
|
|
43
43
|
},
|
|
44
44
|
"peerDependencies": {
|
|
45
|
-
"@agentium/core": "^
|
|
45
|
+
"@agentium/core": "^2.0.0",
|
|
46
46
|
"@types/express": "^4.0.0 || ^5.0.0",
|
|
47
47
|
"express": "^4.0.0 || ^5.0.0",
|
|
48
48
|
"multer": ">=2.0.0",
|