@cuylabs/agent-http 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/dist/index.d.ts +8 -68
- package/dist/index.js +128 -38
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @cuylabs/agent-http
|
|
2
2
|
|
|
3
|
-
HTTP streaming adapter for
|
|
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
|
|
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
|
|
10
|
-
* Bridges agent
|
|
11
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
224
|
+
function createAgentStream(source, options) {
|
|
120
225
|
let fullResponse = "";
|
|
121
|
-
|
|
226
|
+
return createUIMessageStream({
|
|
122
227
|
execute: async ({ writer }) => {
|
|
123
|
-
fullResponse = await writeAgentEventsToStream(
|
|
228
|
+
fullResponse = await writeAgentEventsToStream(source, options, writer);
|
|
124
229
|
},
|
|
125
|
-
onFinish: async ({
|
|
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
|
|
140
|
-
|
|
141
|
-
|
|
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.
|
|
3
|
+
"version": "0.11.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.
|
|
20
|
+
"@cuylabs/agent-core": "^0.11.0",
|
|
21
|
+
"@cuylabs/agent-server": "^0.11.0"
|
|
21
22
|
},
|
|
22
23
|
"devDependencies": {
|
|
23
24
|
"@types/node": "^22.0.0",
|