@agentium/transport 1.0.0 → 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 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
- export { type A2AServerOptions, type AdminRouterOptions, type BrowserGatewayOptions, type FileUploadOptions, type GatewayConfig, type GatewayOptions, type JwtConfig, MCPManager, type MCPServerEntry, type MCPServerSummary, type RbacConfig, type RemoteEndpoint, type RouterOptions, type SwaggerOptions, type VisionGatewayOptions, type VoiceGatewayOptions, buildMultiModalInput, createA2AServer, createAdminRouter, createAgentGateway, createAgentRouter, createBrowserGateway, createFileUploadMiddleware, createGatewayRouter, createJwtMiddleware, createRbacMiddleware, createVisionGateway, createVoiceGateway, errorHandler, generateAgentCard, generateMultiAgentCard, generateOpenAPISpec, requestLogger };
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
- export { type A2AServerOptions, type AdminRouterOptions, type BrowserGatewayOptions, type FileUploadOptions, type GatewayConfig, type GatewayOptions, type JwtConfig, MCPManager, type MCPServerEntry, type MCPServerSummary, type RbacConfig, type RemoteEndpoint, type RouterOptions, type SwaggerOptions, type VisionGatewayOptions, type VoiceGatewayOptions, buildMultiModalInput, createA2AServer, createAdminRouter, createAgentGateway, createAgentRouter, createBrowserGateway, createFileUploadMiddleware, createGatewayRouter, createJwtMiddleware, createRbacMiddleware, createVisionGateway, createVoiceGateway, errorHandler, generateAgentCard, generateMultiAgentCard, generateOpenAPISpec, requestLogger };
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
@@ -925,7 +925,12 @@ function routeMatches(actual, pattern) {
925
925
 
926
926
  // src/express/router-factory.ts
927
927
  import { createRequire as createRequire7 } from "module";
928
- import { classifyServables, collectToolkitTools, describeToolLibrary, registry as globalRegistry } from "@agentium/core";
928
+ import {
929
+ classifyServables,
930
+ collectToolkitTools,
931
+ describeToolLibrary,
932
+ registry as globalRegistry
933
+ } from "@agentium/core";
929
934
 
930
935
  // src/express/swagger.ts
931
936
  import { createRequire as createRequire6 } from "module";
@@ -1988,6 +1993,143 @@ function createAgentRouter(opts) {
1988
1993
  return router;
1989
1994
  }
1990
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
+
1991
2133
  // src/socketio/browser-gateway.ts
1992
2134
  function createBrowserGateway(opts) {
1993
2135
  const ns = opts.io.of(opts.namespace ?? "/agentium-browser");
@@ -2102,7 +2244,12 @@ function createBrowserGateway(opts) {
2102
2244
  }
2103
2245
 
2104
2246
  // src/socketio/gateway.ts
2105
- import { classifyServables as classifyServables2, collectToolkitTools as collectToolkitTools2, describeToolLibrary as describeToolLibrary2, registry as globalRegistry2 } from "@agentium/core";
2247
+ import {
2248
+ classifyServables as classifyServables2,
2249
+ collectToolkitTools as collectToolkitTools2,
2250
+ describeToolLibrary as describeToolLibrary2,
2251
+ registry as globalRegistry2
2252
+ } from "@agentium/core";
2106
2253
  function createSocketRateLimiter(maxPerMinute = 60) {
2107
2254
  return () => {
2108
2255
  let count = 0;
@@ -2520,13 +2667,72 @@ function createVoiceGateway(opts) {
2520
2667
  });
2521
2668
  });
2522
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();
2523
2726
  export {
2727
+ InMemoryEventLog,
2524
2728
  MCPManager,
2729
+ agentUIStream,
2525
2730
  buildMultiModalInput,
2526
2731
  createA2AServer,
2527
2732
  createAdminRouter,
2528
2733
  createAgentGateway,
2529
2734
  createAgentRouter,
2735
+ createAgentUIStreamResponse,
2530
2736
  createBrowserGateway,
2531
2737
  createFileUploadMiddleware,
2532
2738
  createGatewayRouter,
@@ -2534,9 +2740,12 @@ export {
2534
2740
  createRbacMiddleware,
2535
2741
  createVisionGateway,
2536
2742
  createVoiceGateway,
2743
+ defaultEventLog,
2537
2744
  errorHandler,
2745
+ formatSSEEvent,
2538
2746
  generateAgentCard,
2539
2747
  generateMultiAgentCard,
2540
2748
  generateOpenAPISpec,
2749
+ pipeAgentUIStreamToResponse,
2541
2750
  requestLogger
2542
2751
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentium/transport",
3
- "version": "1.0.0",
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": "^1.0.0",
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",