@cuylabs/agent-http 0.9.0 → 0.10.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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @cuylabs/agent-http
2
2
 
3
- HTTP streaming adapter for [@cuylabs/agent-core](https://github.com/cuylabs-ai/agents-ts/tree/main/packages/agent-core). Bridges agent events to AI SDK v6 `UIMessageStream` for use with `useChat()`.
3
+ HTTP streaming adapter for the CuyLabs agent stack.
4
4
 
5
5
  It maps the UI-facing streaming events (`text-*`, `reasoning-*`, `tool-*`, `error`) and intentionally skips advanced agent-core events that do not have a direct `UIMessageStream` equivalent (`turn-boundary`, `turn-summary`, approvals, interventions, retries, etc.).
6
6
 
@@ -8,7 +8,8 @@ This package is intentionally narrow:
8
8
 
9
9
  - It is a good way to expose an agent over HTTP when your client already speaks the AI SDK v6 chat stream protocol.
10
10
  - It is not a full generic agent hosting layer, REST API surface, or control plane.
11
- - For broader host/runtime concerns, keep those in your app or use the runtime packages.
11
+ - For broader host/runtime concerns, keep those in your app, use
12
+ `@cuylabs/agent-server`, or use the runtime packages.
12
13
 
13
14
  ## Installation
14
15
 
package/dist/index.d.ts CHANGED
@@ -1,91 +1,31 @@
1
1
  import { Agent } from '@cuylabs/agent-core';
2
2
  export { AgentEvent } from '@cuylabs/agent-core';
3
+ import { AgentServerAdapter, AgentServer, AgentServerClient } from '@cuylabs/agent-server';
3
4
  import { createUIMessageStream } from 'ai';
4
5
  export { createUIMessageStream, createUIMessageStreamResponse } from 'ai';
5
6
 
6
7
  /**
7
8
  * @cuylabs/agent-http
8
9
  *
9
- * HTTP streaming adapter for @cuylabs/agent-core.
10
- * Bridges agent events to AI SDK v6 compatible UIMessageStream for use with useChat().
11
- * Advanced agent-core events without a direct UIMessageStream equivalent are
12
- * intentionally skipped here.
13
- *
14
- * @example
15
- * ```typescript
16
- * // In a Next.js API route
17
- * import { createAgent } from "@cuylabs/agent-core";
18
- * import { createAgentStreamResponse } from "@cuylabs/agent-http";
19
- * import { anthropic } from "@ai-sdk/anthropic";
20
- *
21
- * const agent = createAgent({
22
- * model: anthropic("claude-sonnet-4-20250514"),
23
- * systemPrompt: "You are a helpful assistant.",
24
- * });
25
- *
26
- * export async function POST(req: Request) {
27
- * const { messages, id } = await req.json();
28
- * const lastMessage = messages[messages.length - 1];
29
- *
30
- * return createAgentStreamResponse(agent, {
31
- * sessionId: id,
32
- * message: lastMessage.content,
33
- * });
34
- * }
35
- * ```
10
+ * HTTP streaming adapter for the CuyLabs agent stack.
11
+ * Bridges direct agents and agent-server-backed runtimes to AI SDK v6
12
+ * `UIMessageStream` for use with `useChat()`.
36
13
  *
37
14
  * @packageDocumentation
38
15
  */
39
16
 
40
- /**
41
- * Options for creating an agent stream response
42
- */
43
17
  interface AgentStreamOptions {
44
- /** Session ID for conversation history */
45
18
  sessionId: string;
46
- /** User message to send */
47
19
  message: string;
48
- /** Optional abort signal */
49
20
  abortSignal?: AbortSignal;
50
- /** Optional system prompt override for this message */
51
21
  system?: string;
52
- /** Callback when streaming completes - use for persistence */
53
22
  onFinish?: (result: {
54
23
  response: string;
55
24
  messageId?: string;
56
25
  }) => void | Promise<void>;
57
26
  }
58
- /**
59
- * Create an HTTP streaming response from an agent.
60
- *
61
- * This bridges @cuylabs/agent-core's event-based streaming to the
62
- * AI SDK v6 UIMessageStream format that useChat() understands.
63
- *
64
- * @example
65
- * ```typescript
66
- * // Next.js API route
67
- * export async function POST(req: Request) {
68
- * const { messages, id } = await req.json();
69
- * const lastMessage = messages[messages.length - 1];
70
- *
71
- * return createAgentStreamResponse(agent, {
72
- * sessionId: id,
73
- * message: lastMessage.content,
74
- * onFinish: async ({ response }) => {
75
- * // Persist the response to database
76
- * await saveMessage({ chatId: id, content: response, role: "assistant" });
77
- * },
78
- * });
79
- * }
80
- * ```
81
- */
82
- declare function createAgentStreamResponse(agent: Agent, options: AgentStreamOptions): Response;
83
- /**
84
- * Create a ReadableStream of UIMessageChunks from agent events.
85
- *
86
- * Lower-level than createAgentStreamResponse - useful when you need
87
- * to compose streams or add custom middleware.
88
- */
89
- declare function createAgentStream(agent: Agent, options: AgentStreamOptions): ReturnType<typeof createUIMessageStream>;
27
+ type AgentStreamSource = Agent | AgentServerAdapter | AgentServer | AgentServerClient;
28
+ declare function createAgentStream(source: AgentStreamSource, options: AgentStreamOptions): ReturnType<typeof createUIMessageStream>;
29
+ declare function createAgentStreamResponse(source: AgentStreamSource, options: AgentStreamOptions): Response;
90
30
 
91
- export { type AgentStreamOptions, createAgentStream, createAgentStreamResponse };
31
+ export { type AgentStreamOptions, type AgentStreamSource, createAgentStream, createAgentStreamResponse };
package/dist/index.js CHANGED
@@ -1,4 +1,7 @@
1
1
  // src/index.ts
2
+ import {
3
+ createAgentServerAdapter
4
+ } from "@cuylabs/agent-server";
2
5
  import {
3
6
  createUIMessageStream,
4
7
  createUIMessageStreamResponse
@@ -7,15 +10,122 @@ var idCounter = 0;
7
10
  function generatePartId() {
8
11
  return `part-${Date.now()}-${++idCounter}`;
9
12
  }
10
- async function writeAgentEventsToStream(agent, options, writer) {
13
+ function isServerSource(source) {
14
+ return typeof source.startTurn === "function" && typeof source.subscribe === "function";
15
+ }
16
+ function isAdapterSource(source) {
17
+ return typeof source.chat === "function" && typeof source.listSessions === "function" && typeof source.getSessionStorage === "function";
18
+ }
19
+ async function* iterateAgentEvents(source, options) {
20
+ if (isServerSource(source)) {
21
+ yield* streamServerAgentEvents(source, options);
22
+ return;
23
+ }
24
+ const adapter = isAdapterSource(source) ? source : createAgentServerAdapter(source);
25
+ for await (const event of adapter.chat(options.sessionId, options.message, {
26
+ ...options.abortSignal ? { abort: options.abortSignal } : {},
27
+ ...options.system ? { system: options.system } : {}
28
+ })) {
29
+ yield event;
30
+ }
31
+ }
32
+ async function* streamServerAgentEvents(server, options) {
33
+ for await (const notification of streamServerTurn(server, options)) {
34
+ if (notification.type === "turn/event") {
35
+ yield notification.event;
36
+ }
37
+ }
38
+ }
39
+ async function* streamServerTurn(server, options) {
40
+ const queue = [];
41
+ let waiter = null;
42
+ let turnId = null;
43
+ const unsubscribe = server.subscribe(
44
+ (notification) => {
45
+ if (waiter) {
46
+ const resolve = waiter;
47
+ waiter = null;
48
+ resolve(notification);
49
+ return;
50
+ }
51
+ queue.push(notification);
52
+ },
53
+ { sessionId: options.sessionId }
54
+ );
55
+ const abortListener = () => {
56
+ if (turnId !== null) {
57
+ server.interruptTurn(turnId);
58
+ }
59
+ };
60
+ try {
61
+ const turn = await server.startTurn(options.sessionId, options.message, {
62
+ ...options.system ? { system: options.system } : {}
63
+ });
64
+ turnId = turn.id;
65
+ if (options.abortSignal) {
66
+ if (options.abortSignal.aborted) {
67
+ server.interruptTurn(turnId);
68
+ } else {
69
+ options.abortSignal.addEventListener("abort", abortListener, {
70
+ once: true
71
+ });
72
+ }
73
+ }
74
+ while (true) {
75
+ const notification = queue.shift() ?? await new Promise((resolve) => {
76
+ waiter = resolve;
77
+ });
78
+ if (!matchesTurnStreamNotification(notification, options.sessionId, turnId)) {
79
+ continue;
80
+ }
81
+ if (notification.type === "turn/started") {
82
+ turnId = notification.turn.id;
83
+ if (options.abortSignal?.aborted) {
84
+ server.interruptTurn(turnId);
85
+ }
86
+ }
87
+ yield notification;
88
+ if (notification.type === "turn/completed" && notification.turn.id === turnId) {
89
+ break;
90
+ }
91
+ }
92
+ } finally {
93
+ unsubscribe();
94
+ options.abortSignal?.removeEventListener("abort", abortListener);
95
+ }
96
+ }
97
+ function matchesTurnStreamNotification(notification, sessionId, turnId) {
98
+ switch (notification.type) {
99
+ case "session/created":
100
+ return notification.session.id === sessionId;
101
+ case "session/deleted":
102
+ return notification.sessionId === sessionId;
103
+ case "session/branched":
104
+ case "session/runtime":
105
+ return notification.sessionId === sessionId;
106
+ case "turn/started":
107
+ case "input/request":
108
+ case "input/resolved":
109
+ case "turn/completed":
110
+ if (notification.type === "turn/started" || notification.type === "turn/completed") {
111
+ return turnId !== null && notification.turn.id === turnId;
112
+ }
113
+ return turnId !== null && (notification.type === "input/request" ? notification.request.turnId === turnId : notification.turnId === turnId);
114
+ case "turn/event":
115
+ return turnId !== null && notification.turnId === turnId;
116
+ case "team/notification":
117
+ return false;
118
+ default: {
119
+ const _exhaustive = notification;
120
+ return false;
121
+ }
122
+ }
123
+ }
124
+ async function writeAgentEventsToStream(source, options, writer) {
11
125
  let currentTextId = null;
12
126
  let fullResponse = "";
13
- for await (const event of agent.chat(options.sessionId, options.message, {
14
- abort: options.abortSignal,
15
- system: options.system
16
- })) {
127
+ for await (const event of iterateAgentEvents(source, options)) {
17
128
  switch (event.type) {
18
- // Text streaming
19
129
  case "text-start":
20
130
  currentTextId = generatePartId();
21
131
  writer.write({
@@ -42,7 +152,6 @@ async function writeAgentEventsToStream(agent, options, writer) {
42
152
  currentTextId = null;
43
153
  }
44
154
  break;
45
- // Reasoning/thinking streaming
46
155
  case "reasoning-start":
47
156
  writer.write({
48
157
  type: "reasoning-start",
@@ -62,7 +171,6 @@ async function writeAgentEventsToStream(agent, options, writer) {
62
171
  id: event.id
63
172
  });
64
173
  break;
65
- // Tool invocations
66
174
  case "tool-start":
67
175
  writer.write({
68
176
  type: "tool-input-available",
@@ -85,15 +193,12 @@ async function writeAgentEventsToStream(agent, options, writer) {
85
193
  output: { error: event.error, isError: true }
86
194
  });
87
195
  break;
88
- // Error handling
89
196
  case "error":
90
197
  writer.write({
91
198
  type: "error",
92
199
  errorText: event.error.message
93
200
  });
94
201
  break;
95
- // Step/message/runtime lifecycle events - no direct mapping in AI SDK v6.
96
- // These can be exposed later via custom data parts if the UI needs them.
97
202
  case "step-start":
98
203
  case "step-finish":
99
204
  case "turn-boundary":
@@ -101,8 +206,6 @@ async function writeAgentEventsToStream(agent, options, writer) {
101
206
  case "intervention-applied":
102
207
  case "turn-summary":
103
208
  case "complete":
104
- break;
105
- // Events we don't need to stream
106
209
  case "status":
107
210
  case "retry":
108
211
  case "doom-loop":
@@ -111,18 +214,22 @@ async function writeAgentEventsToStream(agent, options, writer) {
111
214
  case "computer-result":
112
215
  case "approval-request":
113
216
  case "approval-resolved":
217
+ case "human-input-request":
218
+ case "human-input-resolved":
114
219
  break;
115
220
  }
116
221
  }
117
222
  return fullResponse;
118
223
  }
119
- function createAgentStreamResponse(agent, options) {
224
+ function createAgentStream(source, options) {
120
225
  let fullResponse = "";
121
- const stream = createUIMessageStream({
226
+ return createUIMessageStream({
122
227
  execute: async ({ writer }) => {
123
- fullResponse = await writeAgentEventsToStream(agent, options, writer);
228
+ fullResponse = await writeAgentEventsToStream(source, options, writer);
124
229
  },
125
- onFinish: async ({ responseMessage }) => {
230
+ onFinish: async ({
231
+ responseMessage
232
+ }) => {
126
233
  if (options.onFinish) {
127
234
  await options.onFinish({
128
235
  response: fullResponse,
@@ -130,29 +237,12 @@ function createAgentStreamResponse(agent, options) {
130
237
  });
131
238
  }
132
239
  },
133
- onError: (error) => {
134
- return error instanceof Error ? error.message : "Unknown error";
135
- }
240
+ onError: (error) => error instanceof Error ? error.message : "Unknown error"
136
241
  });
137
- return createUIMessageStreamResponse({ stream });
138
242
  }
139
- function createAgentStream(agent, options) {
140
- let fullResponse = "";
141
- return createUIMessageStream({
142
- execute: async ({ writer }) => {
143
- fullResponse = await writeAgentEventsToStream(agent, options, writer);
144
- },
145
- onFinish: async ({ responseMessage }) => {
146
- if (options.onFinish) {
147
- await options.onFinish({
148
- response: fullResponse,
149
- messageId: responseMessage?.id
150
- });
151
- }
152
- },
153
- onError: (error) => {
154
- return error instanceof Error ? error.message : "Unknown error";
155
- }
243
+ function createAgentStreamResponse(source, options) {
244
+ return createUIMessageStreamResponse({
245
+ stream: createAgentStream(source, options)
156
246
  });
157
247
  }
158
248
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cuylabs/agent-http",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "HTTP streaming adapter for @cuylabs/agent-core - bridges agent events to AI SDK compatible streams",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -17,7 +17,8 @@
17
17
  "README.md"
18
18
  ],
19
19
  "dependencies": {
20
- "@cuylabs/agent-core": "^0.9.0"
20
+ "@cuylabs/agent-core": "^0.10.0",
21
+ "@cuylabs/agent-server": "^0.10.0"
21
22
  },
22
23
  "devDependencies": {
23
24
  "@types/node": "^22.0.0",