@agentium/transport 1.1.2 → 2.0.2

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
@@ -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": "1.1.2",
3
+ "version": "2.0.2",
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.1.2",
45
+ "@agentium/core": "^2.0.2",
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",
@@ -65,5 +65,9 @@
65
65
  "swagger-ui-express": {
66
66
  "optional": true
67
67
  }
68
+ },
69
+ "homepage": "https://agentium.in",
70
+ "bugs": {
71
+ "url": "https://github.com/agentiumOS/agentium/issues"
68
72
  }
69
73
  }