@couplet/agent-ui 1.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/chunk-LQB4Y7J4.js +135 -0
- package/dist/chunk-MJFUPOSB.js +432 -0
- package/dist/chunk-PVUMYYWU.js +194 -0
- package/dist/client.d.ts +11 -0
- package/dist/client.js +15 -0
- package/dist/core.d.ts +51 -0
- package/dist/core.js +58 -0
- package/dist/index.d.ts +162 -0
- package/dist/index.js +1659 -0
- package/dist/types-rklAgjcr.d.ts +219 -0
- package/package.json +52 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import {
|
|
2
|
+
consumeAgentSseStream,
|
|
3
|
+
createAgentMessage,
|
|
4
|
+
createAgentSession,
|
|
5
|
+
extractClarifyFromChain
|
|
6
|
+
} from "./chunk-MJFUPOSB.js";
|
|
7
|
+
|
|
8
|
+
// src/client/endpoints.ts
|
|
9
|
+
var DEFAULT_AGENT_ENDPOINTS = {
|
|
10
|
+
createSession: "/api/chat/session",
|
|
11
|
+
sessions: "/api/chat/sessions",
|
|
12
|
+
completions: "/api/chat/completions"
|
|
13
|
+
};
|
|
14
|
+
function resolveAgentEndpoints(overrides) {
|
|
15
|
+
return {
|
|
16
|
+
...DEFAULT_AGENT_ENDPOINTS,
|
|
17
|
+
...overrides
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function getSessionDetailEndpoint(endpoints, sessionId) {
|
|
21
|
+
return `${endpoints.sessions}/${encodeURIComponent(sessionId)}`;
|
|
22
|
+
}
|
|
23
|
+
function getContextUsageEndpoint(endpoints, sessionId) {
|
|
24
|
+
return `${getSessionDetailEndpoint(endpoints, sessionId)}/context-usage`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// src/client/create-client.ts
|
|
28
|
+
function readTimestamp(value, fallback) {
|
|
29
|
+
if (!value) return fallback;
|
|
30
|
+
const timestamp = new Date(value).getTime();
|
|
31
|
+
return Number.isNaN(timestamp) ? fallback : timestamp;
|
|
32
|
+
}
|
|
33
|
+
function toAgentMessage(msg) {
|
|
34
|
+
const chain = msg.chain ?? void 0;
|
|
35
|
+
const clarify = msg.type === "clarify" && chain ? extractClarifyFromChain(chain) : void 0;
|
|
36
|
+
return createAgentMessage(msg.role, msg.content ?? "", {
|
|
37
|
+
id: msg.id ?? void 0,
|
|
38
|
+
type: msg.type || void 0,
|
|
39
|
+
chain,
|
|
40
|
+
clarify,
|
|
41
|
+
createdAt: readTimestamp(msg.created_at, Date.now())
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
function toAgentSession(data) {
|
|
45
|
+
const now = Date.now();
|
|
46
|
+
const createdAt = readTimestamp(data.created_at, now);
|
|
47
|
+
const baseSession = createAgentSession(data.session_id, createdAt);
|
|
48
|
+
return {
|
|
49
|
+
...baseSession,
|
|
50
|
+
title: data.title?.trim() || baseSession.title,
|
|
51
|
+
updatedAt: readTimestamp(data.updated_at, now),
|
|
52
|
+
messages: data.messages.map(toAgentMessage)
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function normalizeRequestBody(body) {
|
|
56
|
+
if (body == null) return body;
|
|
57
|
+
if (typeof body === "string" || body instanceof Blob || body instanceof ArrayBuffer || ArrayBuffer.isView(body) || body instanceof FormData || body instanceof URLSearchParams || body instanceof ReadableStream) {
|
|
58
|
+
return body;
|
|
59
|
+
}
|
|
60
|
+
return JSON.stringify(body);
|
|
61
|
+
}
|
|
62
|
+
async function requestStream(transport, url, init = {}) {
|
|
63
|
+
const body = normalizeRequestBody(init.body);
|
|
64
|
+
const headers = new Headers(init.headers);
|
|
65
|
+
if (!headers.has("Content-Type") && typeof body === "string") {
|
|
66
|
+
headers.set("Content-Type", "application/json");
|
|
67
|
+
}
|
|
68
|
+
if (!headers.has("Accept")) {
|
|
69
|
+
headers.set("Accept", "text/event-stream");
|
|
70
|
+
}
|
|
71
|
+
return transport.requestStream(url, { ...init, headers, body });
|
|
72
|
+
}
|
|
73
|
+
function createAgentClient(options) {
|
|
74
|
+
const { transport } = options;
|
|
75
|
+
const endpoints = resolveAgentEndpoints(options.endpoints);
|
|
76
|
+
return {
|
|
77
|
+
endpoints,
|
|
78
|
+
async createSession() {
|
|
79
|
+
const data = await transport.request(endpoints.createSession, {
|
|
80
|
+
method: "POST"
|
|
81
|
+
});
|
|
82
|
+
return data.session_id;
|
|
83
|
+
},
|
|
84
|
+
async listSessions() {
|
|
85
|
+
const data = await transport.request(endpoints.sessions);
|
|
86
|
+
return data.sessions;
|
|
87
|
+
},
|
|
88
|
+
async loadLatestSession() {
|
|
89
|
+
const sessions = await this.listSessions();
|
|
90
|
+
if (sessions.length === 0) {
|
|
91
|
+
const sessionId = await this.createSession();
|
|
92
|
+
return { sessionId, messages: [] };
|
|
93
|
+
}
|
|
94
|
+
const detail = await this.getSessionDetail(sessions[0].id);
|
|
95
|
+
return { sessionId: detail.id, messages: [...detail.messages] };
|
|
96
|
+
},
|
|
97
|
+
async getSessionDetail(sessionId) {
|
|
98
|
+
const data = await transport.request(
|
|
99
|
+
getSessionDetailEndpoint(endpoints, sessionId)
|
|
100
|
+
);
|
|
101
|
+
return toAgentSession(data);
|
|
102
|
+
},
|
|
103
|
+
async fetchContextUsage(sessionId) {
|
|
104
|
+
const data = await transport.request(
|
|
105
|
+
getContextUsageEndpoint(endpoints, sessionId)
|
|
106
|
+
);
|
|
107
|
+
return {
|
|
108
|
+
budget_tokens: data.budget_tokens,
|
|
109
|
+
used_tokens: data.used_tokens,
|
|
110
|
+
used_percent: data.used_percent,
|
|
111
|
+
categories: data.categories,
|
|
112
|
+
source: data.source,
|
|
113
|
+
estimated_tokens: data.estimated_tokens,
|
|
114
|
+
prompt_tokens: data.prompt_tokens
|
|
115
|
+
};
|
|
116
|
+
},
|
|
117
|
+
async sendMessageStream({ request, signal, onEvent }) {
|
|
118
|
+
const response = await requestStream(transport, endpoints.completions, {
|
|
119
|
+
method: "POST",
|
|
120
|
+
body: JSON.stringify(request),
|
|
121
|
+
signal
|
|
122
|
+
});
|
|
123
|
+
if (!response.body) throw new Error("Agent response body is empty");
|
|
124
|
+
await consumeAgentSseStream(response.body, onEvent);
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export {
|
|
130
|
+
DEFAULT_AGENT_ENDPOINTS,
|
|
131
|
+
resolveAgentEndpoints,
|
|
132
|
+
getSessionDetailEndpoint,
|
|
133
|
+
getContextUsageEndpoint,
|
|
134
|
+
createAgentClient
|
|
135
|
+
};
|
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
// src/core/session-helpers.ts
|
|
2
|
+
var DEFAULT_PROJECT_NAME = "agent";
|
|
3
|
+
var DEFAULT_SESSION_TITLE = "New conversation";
|
|
4
|
+
var TITLE_MAX_LENGTH = 56;
|
|
5
|
+
function createId(prefix) {
|
|
6
|
+
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
7
|
+
return `${prefix}-${crypto.randomUUID()}`;
|
|
8
|
+
}
|
|
9
|
+
return `${prefix}-${Date.now().toString(36)}`;
|
|
10
|
+
}
|
|
11
|
+
function createAgentSession(id, now = Date.now()) {
|
|
12
|
+
return {
|
|
13
|
+
id: id ?? createId("session"),
|
|
14
|
+
title: DEFAULT_SESSION_TITLE,
|
|
15
|
+
project: DEFAULT_PROJECT_NAME,
|
|
16
|
+
createdAt: now,
|
|
17
|
+
updatedAt: now,
|
|
18
|
+
messages: []
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function createAgentMessage(role, content, extras = {}) {
|
|
22
|
+
return {
|
|
23
|
+
id: createId("message"),
|
|
24
|
+
role,
|
|
25
|
+
content,
|
|
26
|
+
createdAt: Date.now(),
|
|
27
|
+
...extras
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function createClarifyMessage(question, options) {
|
|
31
|
+
return {
|
|
32
|
+
id: createId("message"),
|
|
33
|
+
role: "assistant",
|
|
34
|
+
content: question,
|
|
35
|
+
type: "clarify",
|
|
36
|
+
clarify: { question, options },
|
|
37
|
+
createdAt: Date.now()
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function findLastClarifyCall(chain) {
|
|
41
|
+
return [...chain].reverse().find((node) => node.type === "tool_call" && node.tool_name === "clarify");
|
|
42
|
+
}
|
|
43
|
+
function isClarifyCallAnswered(chain, clarifyCall) {
|
|
44
|
+
return chain.some((node) => node.type === "user_reply" && node.parent_id === clarifyCall.id);
|
|
45
|
+
}
|
|
46
|
+
function extractClarifyFromChain(chain) {
|
|
47
|
+
const lastClarifyCall = findLastClarifyCall(chain);
|
|
48
|
+
if (!lastClarifyCall) return void 0;
|
|
49
|
+
const input = lastClarifyCall.tool_input ?? {};
|
|
50
|
+
const question = typeof input.question === "string" ? input.question : "";
|
|
51
|
+
const options = Array.isArray(input.options) ? input.options.map((option) => String(option)) : void 0;
|
|
52
|
+
if (!question) return void 0;
|
|
53
|
+
return { question, options };
|
|
54
|
+
}
|
|
55
|
+
function getPendingClarify(messages) {
|
|
56
|
+
const message = [...messages].reverse().find((item) => item.role === "assistant" && Boolean(findLastClarifyCall(item.chain ?? [])));
|
|
57
|
+
const chain = message?.chain ?? [];
|
|
58
|
+
const lastClarifyCall = findLastClarifyCall(chain);
|
|
59
|
+
if (!lastClarifyCall || isClarifyCallAnswered(chain, lastClarifyCall)) return void 0;
|
|
60
|
+
const clarify = message?.clarify ?? extractClarifyFromChain(chain);
|
|
61
|
+
if (!clarify?.question || !clarify.options?.length) return void 0;
|
|
62
|
+
return {
|
|
63
|
+
question: clarify.question,
|
|
64
|
+
options: clarify.options,
|
|
65
|
+
chainReplyTo: lastClarifyCall.id
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function updateMessageType(session, messageId, type) {
|
|
69
|
+
return {
|
|
70
|
+
...session,
|
|
71
|
+
updatedAt: Date.now(),
|
|
72
|
+
messages: session.messages.map(
|
|
73
|
+
(message) => message.id === messageId ? { ...message, type } : message
|
|
74
|
+
)
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
function updateClarifyMessage(session, messageId, clarify) {
|
|
78
|
+
return {
|
|
79
|
+
...session,
|
|
80
|
+
updatedAt: Date.now(),
|
|
81
|
+
messages: session.messages.map(
|
|
82
|
+
(message) => message.id === messageId ? { ...message, content: "", type: "clarify", clarify } : message
|
|
83
|
+
)
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function getSessionTitle(input) {
|
|
87
|
+
const normalized = input.trim().replace(/\s+/g, " ");
|
|
88
|
+
if (!normalized) return DEFAULT_SESSION_TITLE;
|
|
89
|
+
if (normalized.length <= TITLE_MAX_LENGTH) return normalized;
|
|
90
|
+
return `${normalized.slice(0, TITLE_MAX_LENGTH - 1)}...`;
|
|
91
|
+
}
|
|
92
|
+
function appendSessionMessages(session, messages) {
|
|
93
|
+
return {
|
|
94
|
+
...session,
|
|
95
|
+
title: session.messages.length ? session.title : getSessionTitle(messages[0]?.content ?? ""),
|
|
96
|
+
updatedAt: Date.now(),
|
|
97
|
+
messages: [...session.messages, ...messages]
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
function updateMessageContent(session, messageId, content, error = false) {
|
|
101
|
+
return {
|
|
102
|
+
...session,
|
|
103
|
+
updatedAt: Date.now(),
|
|
104
|
+
messages: session.messages.map(
|
|
105
|
+
(message) => message.id === messageId ? { ...message, content, error } : message
|
|
106
|
+
)
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
function updateMessageReasoning(session, messageId, reasoning) {
|
|
110
|
+
return {
|
|
111
|
+
...session,
|
|
112
|
+
updatedAt: Date.now(),
|
|
113
|
+
messages: session.messages.map(
|
|
114
|
+
(message) => message.id === messageId ? { ...message, reasoning } : message
|
|
115
|
+
)
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
function appendUserReplyChainNode(session, parentNodeId, content) {
|
|
119
|
+
const parentMessage = session.messages.find(
|
|
120
|
+
(message) => message.chain?.some((node2) => node2.id === parentNodeId)
|
|
121
|
+
);
|
|
122
|
+
if (!parentMessage) return session;
|
|
123
|
+
const node = {
|
|
124
|
+
id: createId("reply"),
|
|
125
|
+
type: "user_reply",
|
|
126
|
+
title: void 0,
|
|
127
|
+
content,
|
|
128
|
+
parent_id: parentNodeId
|
|
129
|
+
};
|
|
130
|
+
return appendMessageChainNode(session, parentMessage.id, node);
|
|
131
|
+
}
|
|
132
|
+
function appendMessageChainNode(session, messageId, node) {
|
|
133
|
+
return {
|
|
134
|
+
...session,
|
|
135
|
+
updatedAt: Date.now(),
|
|
136
|
+
messages: session.messages.map((message) => {
|
|
137
|
+
if (message.id !== messageId) return message;
|
|
138
|
+
const existing = message.chain ?? [];
|
|
139
|
+
const index = existing.findIndex((n) => n.id === node.id);
|
|
140
|
+
const nextChain = index >= 0 ? existing.map((n, i) => i === index ? node : n) : [...existing, node];
|
|
141
|
+
return { ...message, chain: nextChain };
|
|
142
|
+
})
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function mergeToolCalls(existing, deltas) {
|
|
146
|
+
const map = /* @__PURE__ */ new Map();
|
|
147
|
+
for (const item of existing ?? []) {
|
|
148
|
+
map.set(item.index, item);
|
|
149
|
+
}
|
|
150
|
+
for (const delta of deltas) {
|
|
151
|
+
const current = map.get(delta.index);
|
|
152
|
+
if (current) {
|
|
153
|
+
map.set(delta.index, {
|
|
154
|
+
index: delta.index,
|
|
155
|
+
id: delta.id || current.id,
|
|
156
|
+
type: delta.type || current.type,
|
|
157
|
+
function: {
|
|
158
|
+
name: delta.function.name || current.function.name,
|
|
159
|
+
arguments: current.function.arguments + delta.function.arguments
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
} else {
|
|
163
|
+
map.set(delta.index, delta);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return Array.from(map.entries()).sort(([a], [b]) => a - b).map(([, item]) => item);
|
|
167
|
+
}
|
|
168
|
+
function appendMessageToolCalls(session, messageId, deltas) {
|
|
169
|
+
return {
|
|
170
|
+
...session,
|
|
171
|
+
updatedAt: Date.now(),
|
|
172
|
+
messages: session.messages.map(
|
|
173
|
+
(message) => message.id === messageId ? { ...message, toolCalls: mergeToolCalls(message.toolCalls, deltas) } : message
|
|
174
|
+
)
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
function removeSessionMessage(session, messageId) {
|
|
178
|
+
return {
|
|
179
|
+
...session,
|
|
180
|
+
updatedAt: Date.now(),
|
|
181
|
+
messages: session.messages.filter((message) => message.id !== messageId)
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// src/core/stream.ts
|
|
186
|
+
var STREAM_DONE_MARKER = "[DONE]";
|
|
187
|
+
var SSE_DATA_PREFIX = "data:";
|
|
188
|
+
function assertJsonObject(payload) {
|
|
189
|
+
if (typeof payload !== "object" || payload === null) {
|
|
190
|
+
throw new Error("Agent stream payload must be a JSON object");
|
|
191
|
+
}
|
|
192
|
+
return payload;
|
|
193
|
+
}
|
|
194
|
+
function readJsonObject(value) {
|
|
195
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
196
|
+
return value;
|
|
197
|
+
}
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
function readString(value) {
|
|
201
|
+
return typeof value === "string" ? value : null;
|
|
202
|
+
}
|
|
203
|
+
function readStringArray(value) {
|
|
204
|
+
if (!Array.isArray(value)) return [];
|
|
205
|
+
return value.map((item) => String(item));
|
|
206
|
+
}
|
|
207
|
+
function readContextUsageSource(value) {
|
|
208
|
+
return value === "estimated" || value === "provider" ? value : void 0;
|
|
209
|
+
}
|
|
210
|
+
function parseContextUsageCategories(value) {
|
|
211
|
+
if (!Array.isArray(value)) return [];
|
|
212
|
+
return value.map((item) => {
|
|
213
|
+
const row = readJsonObject(item);
|
|
214
|
+
if (!row) return null;
|
|
215
|
+
const id = readString(row.id);
|
|
216
|
+
const label = readString(row.label);
|
|
217
|
+
const tokens = typeof row.tokens === "number" ? row.tokens : 0;
|
|
218
|
+
if (!id || !label) return null;
|
|
219
|
+
return { id, label, tokens };
|
|
220
|
+
}).filter((item) => item !== null);
|
|
221
|
+
}
|
|
222
|
+
function parseStreamEvent(eventPayload) {
|
|
223
|
+
const kind = readString(eventPayload.kind);
|
|
224
|
+
if (!kind) return null;
|
|
225
|
+
switch (kind) {
|
|
226
|
+
case "MessageChunk":
|
|
227
|
+
return { kind, text: readString(eventPayload.text) ?? "" };
|
|
228
|
+
case "MessageStop":
|
|
229
|
+
return { kind, final: Boolean(eventPayload.final) };
|
|
230
|
+
case "ThinkingChunk":
|
|
231
|
+
return {
|
|
232
|
+
kind,
|
|
233
|
+
content: readString(eventPayload.content) ?? "",
|
|
234
|
+
node_id: readString(eventPayload.node_id)
|
|
235
|
+
};
|
|
236
|
+
case "ToolCallFinished":
|
|
237
|
+
return {
|
|
238
|
+
kind,
|
|
239
|
+
tool_name: readString(eventPayload.tool_name) ?? "",
|
|
240
|
+
duration: typeof eventPayload.duration === "number" ? eventPayload.duration : 0,
|
|
241
|
+
ok: Boolean(eventPayload.ok),
|
|
242
|
+
index: typeof eventPayload.index === "number" ? eventPayload.index : 0
|
|
243
|
+
};
|
|
244
|
+
case "ToolCallEvent":
|
|
245
|
+
return {
|
|
246
|
+
kind,
|
|
247
|
+
node_id: readString(eventPayload.node_id) ?? "",
|
|
248
|
+
tool_name: readString(eventPayload.tool_name) ?? "",
|
|
249
|
+
tool_input: readJsonObject(eventPayload.tool_input),
|
|
250
|
+
title: readString(eventPayload.title)
|
|
251
|
+
};
|
|
252
|
+
case "ToolResultEvent":
|
|
253
|
+
return {
|
|
254
|
+
kind,
|
|
255
|
+
node_id: readString(eventPayload.node_id) ?? "",
|
|
256
|
+
tool_name: readString(eventPayload.tool_name) ?? "",
|
|
257
|
+
tool_output: readJsonObject(eventPayload.tool_output),
|
|
258
|
+
title: readString(eventPayload.title)
|
|
259
|
+
};
|
|
260
|
+
case "UserReplyEvent":
|
|
261
|
+
return {
|
|
262
|
+
kind,
|
|
263
|
+
node_id: readString(eventPayload.node_id) ?? "",
|
|
264
|
+
content: readString(eventPayload.content) ?? "",
|
|
265
|
+
parent_id: readString(eventPayload.parent_id)
|
|
266
|
+
};
|
|
267
|
+
case "ClarifyEvent":
|
|
268
|
+
return {
|
|
269
|
+
kind,
|
|
270
|
+
node_id: readString(eventPayload.node_id) ?? "",
|
|
271
|
+
question: readString(eventPayload.question) ?? "",
|
|
272
|
+
options: readStringArray(eventPayload.options)
|
|
273
|
+
};
|
|
274
|
+
case "FinalEvent":
|
|
275
|
+
return {
|
|
276
|
+
kind,
|
|
277
|
+
node_id: readString(eventPayload.node_id) ?? "",
|
|
278
|
+
content: readString(eventPayload.content) ?? ""
|
|
279
|
+
};
|
|
280
|
+
case "Retrying":
|
|
281
|
+
return {
|
|
282
|
+
kind,
|
|
283
|
+
attempt: typeof eventPayload.attempt === "number" ? eventPayload.attempt : 0,
|
|
284
|
+
max_retries: typeof eventPayload.max_retries === "number" ? eventPayload.max_retries : 0,
|
|
285
|
+
delay: typeof eventPayload.delay === "number" ? eventPayload.delay : 0,
|
|
286
|
+
reason: readString(eventPayload.reason) ?? ""
|
|
287
|
+
};
|
|
288
|
+
case "ContextCompressed":
|
|
289
|
+
return { kind };
|
|
290
|
+
case "ContextUsageEvent":
|
|
291
|
+
return {
|
|
292
|
+
kind,
|
|
293
|
+
budget_tokens: typeof eventPayload.budget_tokens === "number" ? eventPayload.budget_tokens : 0,
|
|
294
|
+
used_tokens: typeof eventPayload.used_tokens === "number" ? eventPayload.used_tokens : 0,
|
|
295
|
+
used_percent: typeof eventPayload.used_percent === "number" ? eventPayload.used_percent : 0,
|
|
296
|
+
categories: parseContextUsageCategories(eventPayload.categories),
|
|
297
|
+
source: readContextUsageSource(eventPayload.source),
|
|
298
|
+
estimated_tokens: typeof eventPayload.estimated_tokens === "number" ? eventPayload.estimated_tokens : void 0,
|
|
299
|
+
prompt_tokens: typeof eventPayload.prompt_tokens === "number" ? eventPayload.prompt_tokens : void 0
|
|
300
|
+
};
|
|
301
|
+
case "LongToolHint":
|
|
302
|
+
return {
|
|
303
|
+
kind,
|
|
304
|
+
tool_name: readString(eventPayload.tool_name) ?? "",
|
|
305
|
+
duration: typeof eventPayload.duration === "number" ? eventPayload.duration : 0
|
|
306
|
+
};
|
|
307
|
+
case "FatalError":
|
|
308
|
+
return { kind, message: readString(eventPayload.message) ?? "" };
|
|
309
|
+
case "OpenPageEvent":
|
|
310
|
+
return {
|
|
311
|
+
kind,
|
|
312
|
+
url: readString(eventPayload.url) ?? "",
|
|
313
|
+
title: readString(eventPayload.title)
|
|
314
|
+
};
|
|
315
|
+
case "OpenExternalLinkEvent":
|
|
316
|
+
return {
|
|
317
|
+
kind,
|
|
318
|
+
url: readString(eventPayload.url) ?? "",
|
|
319
|
+
title: readString(eventPayload.title)
|
|
320
|
+
};
|
|
321
|
+
default:
|
|
322
|
+
return null;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
function streamEventToChainNode(event) {
|
|
326
|
+
switch (event.kind) {
|
|
327
|
+
case "ThinkingChunk":
|
|
328
|
+
return {
|
|
329
|
+
id: event.node_id ?? `thinking-${Date.now()}`,
|
|
330
|
+
type: "thinking",
|
|
331
|
+
content: event.content
|
|
332
|
+
};
|
|
333
|
+
case "ToolCallEvent":
|
|
334
|
+
return {
|
|
335
|
+
id: event.node_id,
|
|
336
|
+
type: "tool_call",
|
|
337
|
+
title: event.title ?? void 0,
|
|
338
|
+
tool_name: event.tool_name,
|
|
339
|
+
tool_input: event.tool_input ?? void 0
|
|
340
|
+
};
|
|
341
|
+
case "ToolResultEvent":
|
|
342
|
+
return {
|
|
343
|
+
id: event.node_id,
|
|
344
|
+
type: "tool_result",
|
|
345
|
+
title: event.title ?? void 0,
|
|
346
|
+
tool_name: event.tool_name,
|
|
347
|
+
tool_output: event.tool_output ?? void 0
|
|
348
|
+
};
|
|
349
|
+
case "UserReplyEvent":
|
|
350
|
+
return {
|
|
351
|
+
id: event.node_id,
|
|
352
|
+
type: "user_reply",
|
|
353
|
+
content: event.content,
|
|
354
|
+
parent_id: event.parent_id ?? void 0
|
|
355
|
+
};
|
|
356
|
+
case "FinalEvent":
|
|
357
|
+
return {
|
|
358
|
+
id: event.node_id,
|
|
359
|
+
type: "final",
|
|
360
|
+
content: event.content
|
|
361
|
+
};
|
|
362
|
+
default:
|
|
363
|
+
return null;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
function parseSseEnvelope(rawEvent) {
|
|
367
|
+
const dataLines = [];
|
|
368
|
+
for (const line of rawEvent.split(/\r?\n/)) {
|
|
369
|
+
if (!line || line.startsWith(":")) continue;
|
|
370
|
+
if (line.startsWith(SSE_DATA_PREFIX)) {
|
|
371
|
+
dataLines.push(line.slice(SSE_DATA_PREFIX.length).trimStart());
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
if (!dataLines.length) return null;
|
|
375
|
+
const payload = dataLines.join("\n").trim();
|
|
376
|
+
if (payload === STREAM_DONE_MARKER) return null;
|
|
377
|
+
const parsed = assertJsonObject(JSON.parse(payload));
|
|
378
|
+
if (parsed.type !== "event") {
|
|
379
|
+
throw new Error(`Unexpected agent stream payload type: ${String(parsed.type)}`);
|
|
380
|
+
}
|
|
381
|
+
const eventPayload = readJsonObject(parsed.event);
|
|
382
|
+
if (!eventPayload) return null;
|
|
383
|
+
return parseStreamEvent(eventPayload);
|
|
384
|
+
}
|
|
385
|
+
function processBufferedEvents(buffered, onEvent) {
|
|
386
|
+
const events = buffered.split(/\r?\n\r?\n/);
|
|
387
|
+
const rest = events.pop() ?? "";
|
|
388
|
+
for (const raw of events) {
|
|
389
|
+
const streamEvent = parseSseEnvelope(raw);
|
|
390
|
+
if (streamEvent) onEvent(streamEvent);
|
|
391
|
+
}
|
|
392
|
+
return rest;
|
|
393
|
+
}
|
|
394
|
+
async function consumeAgentSseStream(body, onEvent) {
|
|
395
|
+
const reader = body.getReader();
|
|
396
|
+
const decoder = new TextDecoder();
|
|
397
|
+
let buffered = "";
|
|
398
|
+
while (true) {
|
|
399
|
+
const { value, done } = await reader.read();
|
|
400
|
+
if (done) break;
|
|
401
|
+
buffered = processBufferedEvents(buffered + decoder.decode(value, { stream: true }), onEvent);
|
|
402
|
+
}
|
|
403
|
+
buffered = processBufferedEvents(buffered + decoder.decode(), onEvent);
|
|
404
|
+
if (buffered.trim()) {
|
|
405
|
+
const streamEvent = parseSseEnvelope(buffered);
|
|
406
|
+
if (streamEvent) onEvent(streamEvent);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
export {
|
|
411
|
+
createAgentSession,
|
|
412
|
+
createAgentMessage,
|
|
413
|
+
createClarifyMessage,
|
|
414
|
+
findLastClarifyCall,
|
|
415
|
+
isClarifyCallAnswered,
|
|
416
|
+
extractClarifyFromChain,
|
|
417
|
+
getPendingClarify,
|
|
418
|
+
updateMessageType,
|
|
419
|
+
updateClarifyMessage,
|
|
420
|
+
getSessionTitle,
|
|
421
|
+
appendSessionMessages,
|
|
422
|
+
updateMessageContent,
|
|
423
|
+
updateMessageReasoning,
|
|
424
|
+
appendUserReplyChainNode,
|
|
425
|
+
appendMessageChainNode,
|
|
426
|
+
appendMessageToolCalls,
|
|
427
|
+
removeSessionMessage,
|
|
428
|
+
parseStreamEvent,
|
|
429
|
+
streamEventToChainNode,
|
|
430
|
+
parseSseEnvelope,
|
|
431
|
+
consumeAgentSseStream
|
|
432
|
+
};
|