@better-agent/client 0.1.0-canary.6 → 0.2.0-beta.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/controller-BkCxCUpn.d.mts +365 -0
- package/dist/controller-BkCxCUpn.d.mts.map +1 -0
- package/dist/controller-xMlzSCc7.mjs +1342 -0
- package/dist/controller-xMlzSCc7.mjs.map +1 -0
- package/dist/index.d.mts +8 -262
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +240 -596
- package/dist/index.mjs.map +1 -1
- package/dist/preact/index.d.mts +21 -89
- package/dist/preact/index.d.mts.map +1 -1
- package/dist/preact/index.mjs +48 -122
- package/dist/preact/index.mjs.map +1 -1
- package/dist/react/index.d.mts +21 -89
- package/dist/react/index.d.mts.map +1 -1
- package/dist/react/index.mjs +47 -111
- package/dist/react/index.mjs.map +1 -1
- package/dist/solid/index.d.mts +27 -87
- package/dist/solid/index.d.mts.map +1 -1
- package/dist/solid/index.mjs +40 -108
- package/dist/solid/index.mjs.map +1 -1
- package/dist/svelte/index.d.mts +22 -75
- package/dist/svelte/index.d.mts.map +1 -1
- package/dist/svelte/index.mjs +18 -92
- package/dist/svelte/index.mjs.map +1 -1
- package/dist/vue/index.d.mts +27 -84
- package/dist/vue/index.d.mts.map +1 -1
- package/dist/vue/index.mjs +43 -136
- package/dist/vue/index.mjs.map +1 -1
- package/package.json +5 -9
- package/README.md +0 -3
- package/dist/controller-BrBUfjhZ.mjs +0 -2124
- package/dist/controller-BrBUfjhZ.mjs.map +0 -1
- package/dist/controller-CJ79_cSR.d.mts +0 -657
- package/dist/controller-CJ79_cSR.d.mts.map +0 -1
- package/dist/utils-CiHUj_BW.mjs +0 -34
- package/dist/utils-CiHUj_BW.mjs.map +0 -1
|
@@ -0,0 +1,1342 @@
|
|
|
1
|
+
import { EventType, RuntimeInterruptReason } from "@better-agent/core";
|
|
2
|
+
import { createRuntimeStateControl } from "@better-agent/core/runtime";
|
|
3
|
+
|
|
4
|
+
//#region src/core/errors.ts
|
|
5
|
+
var BetterAgentClientError = class extends Error {
|
|
6
|
+
status;
|
|
7
|
+
code;
|
|
8
|
+
details;
|
|
9
|
+
constructor(message, options = {}) {
|
|
10
|
+
super(message, { cause: options.cause });
|
|
11
|
+
this.name = "BetterAgentClientError";
|
|
12
|
+
this.status = options.status;
|
|
13
|
+
this.code = options.code;
|
|
14
|
+
this.details = options.details;
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
const toBetterAgentClientError = (error) => {
|
|
18
|
+
if (error instanceof BetterAgentClientError) return error;
|
|
19
|
+
if (error instanceof Error) return new BetterAgentClientError(error.message, { cause: error });
|
|
20
|
+
return new BetterAgentClientError("An unknown error occurred.", { cause: error });
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
//#endregion
|
|
24
|
+
//#region src/ui/helpers.ts
|
|
25
|
+
const toMessageRole = (role) => {
|
|
26
|
+
switch (role) {
|
|
27
|
+
case "user": return "user";
|
|
28
|
+
case "system": return "system";
|
|
29
|
+
case "developer": return "system";
|
|
30
|
+
default: return "assistant";
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
const sourceToUrl = (source) => {
|
|
34
|
+
if (source.type === "data") return `data:${source.mimeType ?? "application/octet-stream"};base64,${source.value}`;
|
|
35
|
+
return source.value;
|
|
36
|
+
};
|
|
37
|
+
const urlToSource = (url, mimeType) => {
|
|
38
|
+
const match = /^data:([^;,]+)?;base64,(.*)$/.exec(url);
|
|
39
|
+
if (match) return {
|
|
40
|
+
type: "data",
|
|
41
|
+
value: match[2] ?? "",
|
|
42
|
+
mimeType: mimeType ?? match[1] ?? "application/octet-stream"
|
|
43
|
+
};
|
|
44
|
+
return {
|
|
45
|
+
type: "url",
|
|
46
|
+
value: url,
|
|
47
|
+
...mimeType ? { mimeType } : {}
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
const contentToParts = (content) => {
|
|
51
|
+
if (typeof content === "string") return [{
|
|
52
|
+
type: "text",
|
|
53
|
+
text: content
|
|
54
|
+
}];
|
|
55
|
+
if (!Array.isArray(content)) return [];
|
|
56
|
+
return content.flatMap((part) => {
|
|
57
|
+
if (part.type === "text") return [{
|
|
58
|
+
type: "text",
|
|
59
|
+
text: part.text
|
|
60
|
+
}];
|
|
61
|
+
if (part.type === "image" || part.type === "audio" || part.type === "video") return [{
|
|
62
|
+
type: part.type,
|
|
63
|
+
url: sourceToUrl(part.source),
|
|
64
|
+
mimeType: part.source.mimeType
|
|
65
|
+
}];
|
|
66
|
+
return [];
|
|
67
|
+
});
|
|
68
|
+
};
|
|
69
|
+
const partsToContent = (parts) => {
|
|
70
|
+
const content = [];
|
|
71
|
+
for (const part of parts) {
|
|
72
|
+
if (part.type === "text") {
|
|
73
|
+
if (part.text) content.push({
|
|
74
|
+
type: "text",
|
|
75
|
+
text: part.text
|
|
76
|
+
});
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (part.type === "image" || part.type === "audio" || part.type === "video") {
|
|
80
|
+
content.push({
|
|
81
|
+
type: part.type,
|
|
82
|
+
source: urlToSource(part.url, part.mimeType)
|
|
83
|
+
});
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (part.type === "file") content.push({
|
|
87
|
+
type: "document",
|
|
88
|
+
source: urlToSource(part.url, part.mimeType)
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
if (content.length === 1 && content[0]?.type === "text") return content[0].text;
|
|
92
|
+
return content;
|
|
93
|
+
};
|
|
94
|
+
const contentToText = (content) => {
|
|
95
|
+
if (typeof content === "string") return content;
|
|
96
|
+
return content.filter((part) => part.type === "text").map((part) => part.text).join("");
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
//#endregion
|
|
100
|
+
//#region src/ui/convert.ts
|
|
101
|
+
const isAgentMessageContent = (content) => {
|
|
102
|
+
return content === void 0 || typeof content === "string" || Array.isArray(content);
|
|
103
|
+
};
|
|
104
|
+
const fromAgentMessages = (messages) => {
|
|
105
|
+
const parseToolInput = (input) => {
|
|
106
|
+
try {
|
|
107
|
+
return JSON.parse(input);
|
|
108
|
+
} catch {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
const parseToolContent = (content) => {
|
|
113
|
+
try {
|
|
114
|
+
return JSON.parse(content);
|
|
115
|
+
} catch {
|
|
116
|
+
return content;
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
const toolResults = /* @__PURE__ */ new Map();
|
|
120
|
+
for (const message of messages) if (message.role === "tool") toolResults.set(message.toolCallId, {
|
|
121
|
+
content: message.content,
|
|
122
|
+
error: message.error,
|
|
123
|
+
status: message.status,
|
|
124
|
+
approval: message.approval
|
|
125
|
+
});
|
|
126
|
+
const uiMessages = [];
|
|
127
|
+
let pendingReasoning = [];
|
|
128
|
+
for (const message of messages) {
|
|
129
|
+
const parts = isAgentMessageContent(message.content) ? contentToParts(message.content) : [];
|
|
130
|
+
if (message.role === "reasoning") {
|
|
131
|
+
if (typeof message.content === "string") pendingReasoning = [...pendingReasoning, {
|
|
132
|
+
id: message.id,
|
|
133
|
+
part: {
|
|
134
|
+
type: "reasoning",
|
|
135
|
+
text: message.content
|
|
136
|
+
}
|
|
137
|
+
}];
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
if (message.role === "assistant") {
|
|
141
|
+
const assistantParts = [...pendingReasoning.map((reasoning) => reasoning.part), ...parts];
|
|
142
|
+
pendingReasoning = [];
|
|
143
|
+
if (Array.isArray(message.toolCalls)) for (const toolCall of message.toolCalls) {
|
|
144
|
+
const result = toolResults.get(toolCall.id);
|
|
145
|
+
const toolPart = {
|
|
146
|
+
inputText: toolCall.function.arguments,
|
|
147
|
+
input: parseToolInput(toolCall.function.arguments),
|
|
148
|
+
toolCallId: toolCall.id,
|
|
149
|
+
toolName: toolCall.function.name,
|
|
150
|
+
type: "tool-call",
|
|
151
|
+
...toolCall.providerExecuted ? { providerExecuted: true } : {},
|
|
152
|
+
state: "input-available",
|
|
153
|
+
...result?.approval ? {
|
|
154
|
+
approval: {
|
|
155
|
+
interruptId: `${toolCall.id}:approval`,
|
|
156
|
+
needsApproval: true,
|
|
157
|
+
approved: result.approval.approved,
|
|
158
|
+
...result.approval.metadata ? { metadata: result.approval.metadata } : {}
|
|
159
|
+
},
|
|
160
|
+
state: "approval-responded"
|
|
161
|
+
} : {}
|
|
162
|
+
};
|
|
163
|
+
assistantParts.push(toolPart);
|
|
164
|
+
if (result) {
|
|
165
|
+
const deniedResult = result.content.length > 0 ? (() => {
|
|
166
|
+
const parsed = parseToolContent(result.content);
|
|
167
|
+
return typeof parsed === "string" ? parsed : void 0;
|
|
168
|
+
})() : void 0;
|
|
169
|
+
const resultPart = result.status === "denied" ? {
|
|
170
|
+
type: "tool-result",
|
|
171
|
+
toolCallId: toolCall.id,
|
|
172
|
+
state: "output-denied",
|
|
173
|
+
result: deniedResult
|
|
174
|
+
} : result.status === "error" || result.error ? {
|
|
175
|
+
type: "tool-result",
|
|
176
|
+
toolCallId: toolCall.id,
|
|
177
|
+
state: "output-error",
|
|
178
|
+
error: result.error ?? result.content
|
|
179
|
+
} : {
|
|
180
|
+
type: "tool-result",
|
|
181
|
+
toolCallId: toolCall.id,
|
|
182
|
+
state: "output-available",
|
|
183
|
+
result: parseToolContent(result.content)
|
|
184
|
+
};
|
|
185
|
+
assistantParts.push(resultPart);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
for (const source of message.sources ?? []) assistantParts.push({
|
|
189
|
+
type: "source",
|
|
190
|
+
sourceId: source.id,
|
|
191
|
+
sourceType: "url",
|
|
192
|
+
url: source.url,
|
|
193
|
+
...source.title ? { title: source.title } : {}
|
|
194
|
+
});
|
|
195
|
+
uiMessages.push({
|
|
196
|
+
id: message.id,
|
|
197
|
+
role: message.role,
|
|
198
|
+
parts: assistantParts
|
|
199
|
+
});
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
if (message.role === "user" || message.role === "system" || message.role === "developer") uiMessages.push({
|
|
203
|
+
id: message.id,
|
|
204
|
+
role: toMessageRole(message.role),
|
|
205
|
+
parts
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
if (pendingReasoning.length > 0) for (const reasoning of pendingReasoning) uiMessages.push({
|
|
209
|
+
id: reasoning.id,
|
|
210
|
+
role: "assistant",
|
|
211
|
+
parts: [reasoning.part]
|
|
212
|
+
});
|
|
213
|
+
return uiMessages;
|
|
214
|
+
};
|
|
215
|
+
const toAgentMessages = (messages) => {
|
|
216
|
+
return messages.map((message) => {
|
|
217
|
+
const content = partsToContent(message.parts);
|
|
218
|
+
if (message.role === "assistant") {
|
|
219
|
+
const toolCalls = message.parts.filter((part) => part.type === "tool-call").map((part) => ({
|
|
220
|
+
id: part.toolCallId,
|
|
221
|
+
type: "function",
|
|
222
|
+
function: {
|
|
223
|
+
name: part.toolName,
|
|
224
|
+
arguments: part.inputText
|
|
225
|
+
},
|
|
226
|
+
...part.providerExecuted ? { providerExecuted: true } : {}
|
|
227
|
+
}));
|
|
228
|
+
return {
|
|
229
|
+
id: message.id,
|
|
230
|
+
role: "assistant",
|
|
231
|
+
content,
|
|
232
|
+
...toolCalls.length > 0 ? { toolCalls } : {}
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
if (message.role === "system") return {
|
|
236
|
+
id: message.id,
|
|
237
|
+
role: "system",
|
|
238
|
+
content: contentToText(content)
|
|
239
|
+
};
|
|
240
|
+
return {
|
|
241
|
+
id: message.id,
|
|
242
|
+
role: "user",
|
|
243
|
+
content
|
|
244
|
+
};
|
|
245
|
+
});
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
//#endregion
|
|
249
|
+
//#region src/ui/messages.ts
|
|
250
|
+
const createMessageId = () => {
|
|
251
|
+
const webCrypto = globalThis.crypto;
|
|
252
|
+
if (typeof webCrypto?.randomUUID === "function") return `msg_${webCrypto.randomUUID()}`;
|
|
253
|
+
if (typeof webCrypto?.getRandomValues === "function") {
|
|
254
|
+
const bytes = webCrypto.getRandomValues(new Uint8Array(16));
|
|
255
|
+
return `msg_${Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
|
|
256
|
+
}
|
|
257
|
+
return `msg_${Date.now().toString(36)}_${Math.random().toString(36).slice(2)}`;
|
|
258
|
+
};
|
|
259
|
+
const createUserUIMessage = (content) => ({
|
|
260
|
+
id: createMessageId(),
|
|
261
|
+
role: "user",
|
|
262
|
+
parts: [{
|
|
263
|
+
type: "text",
|
|
264
|
+
text: content
|
|
265
|
+
}]
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
//#endregion
|
|
269
|
+
//#region src/ui/reducer.ts
|
|
270
|
+
const updateMessage = (messages, messageId, update) => {
|
|
271
|
+
return messages.map((message) => message.id === messageId ? update(message) : message);
|
|
272
|
+
};
|
|
273
|
+
const upsertMessage = (messages, message, update) => {
|
|
274
|
+
return messages.some((current) => current.id === message.id) ? updateMessage(messages, message.id, update) : [...messages, update(message)];
|
|
275
|
+
};
|
|
276
|
+
const updateToolPart = (messages, toolCallId, update) => {
|
|
277
|
+
return messages.map((message) => ({
|
|
278
|
+
...message,
|
|
279
|
+
parts: message.parts.map((part) => part.type === "tool-call" && part.toolCallId === toolCallId ? update(part) : part)
|
|
280
|
+
}));
|
|
281
|
+
};
|
|
282
|
+
const upsertToolResultPartByToolCallId = (messages, toolCallId, update) => {
|
|
283
|
+
return messages.map((message) => {
|
|
284
|
+
const existingResultIndex = message.parts.findIndex((part) => part.type === "tool-result" && part.toolCallId === toolCallId);
|
|
285
|
+
if (existingResultIndex !== -1) return {
|
|
286
|
+
...message,
|
|
287
|
+
parts: message.parts.map((part, index) => index === existingResultIndex && part.type === "tool-result" ? update(part) : part)
|
|
288
|
+
};
|
|
289
|
+
const toolCallIndex = message.parts.findIndex((part) => part.type === "tool-call" && part.toolCallId === toolCallId);
|
|
290
|
+
if (toolCallIndex === -1) return message;
|
|
291
|
+
const parts = [...message.parts];
|
|
292
|
+
parts.splice(toolCallIndex + 1, 0, update({
|
|
293
|
+
type: "tool-result",
|
|
294
|
+
toolCallId,
|
|
295
|
+
state: "output-available"
|
|
296
|
+
}));
|
|
297
|
+
return {
|
|
298
|
+
...message,
|
|
299
|
+
parts
|
|
300
|
+
};
|
|
301
|
+
});
|
|
302
|
+
};
|
|
303
|
+
const upsertSourcePart = (messages, messageId, part) => {
|
|
304
|
+
return upsertMessage(messages, {
|
|
305
|
+
id: messageId,
|
|
306
|
+
role: "assistant",
|
|
307
|
+
parts: []
|
|
308
|
+
}, (message) => {
|
|
309
|
+
return message.parts.some((current) => current.type === "source" && current.sourceId === part.sourceId) ? message : {
|
|
310
|
+
...message,
|
|
311
|
+
parts: [...message.parts, part]
|
|
312
|
+
};
|
|
313
|
+
});
|
|
314
|
+
};
|
|
315
|
+
const parseToolContent = (content) => {
|
|
316
|
+
if (!content.trim()) return "";
|
|
317
|
+
try {
|
|
318
|
+
return JSON.parse(content);
|
|
319
|
+
} catch {
|
|
320
|
+
return content;
|
|
321
|
+
}
|
|
322
|
+
};
|
|
323
|
+
const createUIReducerState = (messages = []) => {
|
|
324
|
+
return { messages };
|
|
325
|
+
};
|
|
326
|
+
const applyUIEvent = (state, event) => {
|
|
327
|
+
if (event.type === EventType.RUN_STARTED) {
|
|
328
|
+
const messages = event.input?.messages?.filter((message) => message.role !== "system");
|
|
329
|
+
if (!messages || messages.length === 0) return state;
|
|
330
|
+
return {
|
|
331
|
+
...state,
|
|
332
|
+
messages: fromAgentMessages(messages)
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
if (event.type === EventType.TEXT_MESSAGE_START) {
|
|
336
|
+
if (state.messages.some((message) => message.id === event.messageId)) return state;
|
|
337
|
+
return {
|
|
338
|
+
...state,
|
|
339
|
+
messages: [...state.messages, {
|
|
340
|
+
id: event.messageId,
|
|
341
|
+
role: toMessageRole(event.role),
|
|
342
|
+
parts: []
|
|
343
|
+
}]
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
if (event.type === EventType.TEXT_MESSAGE_CONTENT) {
|
|
347
|
+
const messageIndex = state.messages.findIndex((message) => message.id === event.messageId);
|
|
348
|
+
const targetMessage = messageIndex === -1 ? {
|
|
349
|
+
id: event.messageId,
|
|
350
|
+
role: "assistant",
|
|
351
|
+
parts: []
|
|
352
|
+
} : state.messages[messageIndex];
|
|
353
|
+
const parts = [...targetMessage.parts];
|
|
354
|
+
const lastPart = parts[parts.length - 1];
|
|
355
|
+
if (lastPart?.type === "text") parts[parts.length - 1] = {
|
|
356
|
+
...lastPart,
|
|
357
|
+
text: lastPart.text + event.delta
|
|
358
|
+
};
|
|
359
|
+
else parts.push({
|
|
360
|
+
type: "text",
|
|
361
|
+
text: event.delta
|
|
362
|
+
});
|
|
363
|
+
const nextMessage = {
|
|
364
|
+
...targetMessage,
|
|
365
|
+
parts
|
|
366
|
+
};
|
|
367
|
+
return {
|
|
368
|
+
...state,
|
|
369
|
+
messages: messageIndex === -1 ? [...state.messages, nextMessage] : state.messages.map((message, index) => index === messageIndex ? nextMessage : message)
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
if (event.type === EventType.REASONING_MESSAGE_START) {
|
|
373
|
+
if (state.messages.some((message) => message.id === event.messageId)) return state;
|
|
374
|
+
return {
|
|
375
|
+
...state,
|
|
376
|
+
messages: [...state.messages, {
|
|
377
|
+
id: event.messageId,
|
|
378
|
+
role: "assistant",
|
|
379
|
+
parts: []
|
|
380
|
+
}]
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
if (event.type === EventType.REASONING_MESSAGE_CONTENT) {
|
|
384
|
+
const messageIndex = state.messages.findIndex((message) => message.id === event.messageId);
|
|
385
|
+
const targetMessage = messageIndex === -1 ? {
|
|
386
|
+
id: event.messageId,
|
|
387
|
+
role: "assistant",
|
|
388
|
+
parts: []
|
|
389
|
+
} : state.messages[messageIndex];
|
|
390
|
+
const parts = [...targetMessage.parts];
|
|
391
|
+
const lastPart = parts[parts.length - 1];
|
|
392
|
+
if (lastPart?.type === "reasoning") parts[parts.length - 1] = {
|
|
393
|
+
...lastPart,
|
|
394
|
+
text: lastPart.text + event.delta
|
|
395
|
+
};
|
|
396
|
+
else parts.push({
|
|
397
|
+
type: "reasoning",
|
|
398
|
+
text: event.delta
|
|
399
|
+
});
|
|
400
|
+
const nextMessage = {
|
|
401
|
+
...targetMessage,
|
|
402
|
+
parts
|
|
403
|
+
};
|
|
404
|
+
return {
|
|
405
|
+
...state,
|
|
406
|
+
messages: messageIndex === -1 ? [...state.messages, nextMessage] : state.messages.map((message, index) => index === messageIndex ? nextMessage : message)
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
if (event.type === EventType.REASONING_MESSAGE_END) return state;
|
|
410
|
+
if (event.type === EventType.MESSAGES_SNAPSHOT) return {
|
|
411
|
+
...state,
|
|
412
|
+
messages: fromAgentMessages(event.messages)
|
|
413
|
+
};
|
|
414
|
+
if (event.type === EventType.CUSTOM && event.name === "source" && event.value) {
|
|
415
|
+
const value = event.value;
|
|
416
|
+
return {
|
|
417
|
+
...state,
|
|
418
|
+
messages: upsertSourcePart(state.messages, value.messageId, {
|
|
419
|
+
type: "source",
|
|
420
|
+
sourceId: value.source.id,
|
|
421
|
+
sourceType: "url",
|
|
422
|
+
url: value.source.url,
|
|
423
|
+
...value.source.title ? { title: value.source.title } : {}
|
|
424
|
+
})
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
if (event.type === EventType.TOOL_CALL_START) {
|
|
428
|
+
if (!event.parentMessageId) return state;
|
|
429
|
+
if (state.messages.some((message) => message.parts.some((part) => part.type === "tool-call" && part.toolCallId === event.toolCallId))) return state;
|
|
430
|
+
return {
|
|
431
|
+
...state,
|
|
432
|
+
messages: upsertMessage(state.messages, {
|
|
433
|
+
id: event.parentMessageId,
|
|
434
|
+
role: "assistant",
|
|
435
|
+
parts: []
|
|
436
|
+
}, (message) => ({
|
|
437
|
+
...message,
|
|
438
|
+
parts: [...message.parts, {
|
|
439
|
+
type: "tool-call",
|
|
440
|
+
state: "input-streaming",
|
|
441
|
+
toolCallId: event.toolCallId,
|
|
442
|
+
toolName: event.toolCallName,
|
|
443
|
+
inputText: "",
|
|
444
|
+
...event.providerExecuted !== void 0 ? { providerExecuted: event.providerExecuted } : {}
|
|
445
|
+
}]
|
|
446
|
+
}))
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
if (event.type === EventType.TOOL_CALL_ARGS) return {
|
|
450
|
+
...state,
|
|
451
|
+
messages: updateToolPart(state.messages, event.toolCallId, (part) => ({
|
|
452
|
+
...part,
|
|
453
|
+
inputText: part.inputText.length === 0 ? event.delta : event.delta.startsWith(part.inputText) ? event.delta : part.inputText.endsWith(event.delta) ? part.inputText : part.inputText + event.delta
|
|
454
|
+
}))
|
|
455
|
+
};
|
|
456
|
+
if (event.type === EventType.TOOL_CALL_CHUNK) {
|
|
457
|
+
if (!event.toolCallId || !event.input || typeof event.input !== "string") return state;
|
|
458
|
+
return {
|
|
459
|
+
...state,
|
|
460
|
+
messages: updateToolPart(state.messages, event.toolCallId, (part) => ({
|
|
461
|
+
...part,
|
|
462
|
+
inputText: part.inputText.length === 0 ? event.input : event.input.startsWith(part.inputText) ? event.input : part.inputText.startsWith(event.input) ? part.inputText : event.input
|
|
463
|
+
}))
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
if (event.type === EventType.TOOL_CALL_END) return {
|
|
467
|
+
...state,
|
|
468
|
+
messages: updateToolPart(state.messages, event.toolCallId, (part) => ({
|
|
469
|
+
...part,
|
|
470
|
+
state: "input-available",
|
|
471
|
+
input: typeof part.inputText === "string" && part.inputText.length > 0 ? (() => {
|
|
472
|
+
try {
|
|
473
|
+
return JSON.parse(part.inputText);
|
|
474
|
+
} catch {
|
|
475
|
+
return part.input;
|
|
476
|
+
}
|
|
477
|
+
})() : part.input
|
|
478
|
+
}))
|
|
479
|
+
};
|
|
480
|
+
if (event.type === EventType.TOOL_CALL_RESULT) {
|
|
481
|
+
const status = "status" in event ? event.status : void 0;
|
|
482
|
+
const parsedContent = parseToolContent(event.content);
|
|
483
|
+
const deniedResult = status === "denied" && typeof parsedContent !== "string" ? void 0 : parsedContent;
|
|
484
|
+
return {
|
|
485
|
+
...state,
|
|
486
|
+
messages: upsertToolResultPartByToolCallId(state.messages, event.toolCallId, (part) => ({
|
|
487
|
+
...part,
|
|
488
|
+
state: status === "denied" ? "output-denied" : status === "error" ? "output-error" : "output-available",
|
|
489
|
+
...status === "error" ? {
|
|
490
|
+
error: event.content,
|
|
491
|
+
result: void 0
|
|
492
|
+
} : {
|
|
493
|
+
result: deniedResult,
|
|
494
|
+
error: void 0
|
|
495
|
+
}
|
|
496
|
+
}))
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
return { ...state };
|
|
500
|
+
};
|
|
501
|
+
|
|
502
|
+
//#endregion
|
|
503
|
+
//#region src/core/browser-lifecycle.ts
|
|
504
|
+
const markPageTeardown = () => {
|
|
505
|
+
if (typeof window !== "undefined") window.__baPageTeardown = true;
|
|
506
|
+
};
|
|
507
|
+
const ensureBrowserTeardownTracking = () => {
|
|
508
|
+
if (typeof window === "undefined") return;
|
|
509
|
+
window.__baPageTeardown ??= false;
|
|
510
|
+
if (window.__baPageTeardownInstalled) return;
|
|
511
|
+
window.__baPageTeardownInstalled = true;
|
|
512
|
+
window.addEventListener("pagehide", markPageTeardown);
|
|
513
|
+
window.addEventListener("beforeunload", markPageTeardown);
|
|
514
|
+
};
|
|
515
|
+
const isBrowserPageTearingDown = () => typeof window !== "undefined" && window.__baPageTeardown === true;
|
|
516
|
+
|
|
517
|
+
//#endregion
|
|
518
|
+
//#region src/core/controller.ts
|
|
519
|
+
var AgentController = class {
|
|
520
|
+
messages;
|
|
521
|
+
stateControl;
|
|
522
|
+
status = "ready";
|
|
523
|
+
error;
|
|
524
|
+
runId;
|
|
525
|
+
threadId;
|
|
526
|
+
pendingClientTools = [];
|
|
527
|
+
pendingToolApprovals = [];
|
|
528
|
+
stateRevision = 0;
|
|
529
|
+
snapshot;
|
|
530
|
+
listeners = /* @__PURE__ */ new Set();
|
|
531
|
+
activeAbortController;
|
|
532
|
+
finishMessageStartIndex = 0;
|
|
533
|
+
stopRequested = false;
|
|
534
|
+
stopWaitTimer;
|
|
535
|
+
abortRequestedRunId;
|
|
536
|
+
started = false;
|
|
537
|
+
constructor(agent, options) {
|
|
538
|
+
this.agent = agent;
|
|
539
|
+
this.options = options;
|
|
540
|
+
ensureBrowserTeardownTracking();
|
|
541
|
+
this.messages = options.initialMessages ?? [];
|
|
542
|
+
this.stateControl = createRuntimeStateControl(options.initialState);
|
|
543
|
+
this.threadId = options.threadId;
|
|
544
|
+
this.runId = options.initialInterruptState?.runId;
|
|
545
|
+
this.pendingClientTools = [...options.initialInterruptState?.pendingClientTools ?? []];
|
|
546
|
+
this.pendingToolApprovals = [...options.initialInterruptState?.pendingToolApprovals ?? []];
|
|
547
|
+
this.status = options.initialInterruptState?.status ?? (this.pendingClientTools.length > 0 || this.pendingToolApprovals.length > 0 ? "interrupted" : "ready");
|
|
548
|
+
for (const approval of this.pendingToolApprovals) this.updateToolCallPart(approval.toolCallId, {
|
|
549
|
+
state: "approval-requested",
|
|
550
|
+
approval: {
|
|
551
|
+
interruptId: approval.interruptId,
|
|
552
|
+
needsApproval: true,
|
|
553
|
+
...approval.metadata ? { metadata: approval.metadata } : {}
|
|
554
|
+
}
|
|
555
|
+
});
|
|
556
|
+
this.snapshot = this.createSnapshot();
|
|
557
|
+
}
|
|
558
|
+
start() {
|
|
559
|
+
if (this.started) return;
|
|
560
|
+
this.started = true;
|
|
561
|
+
const shouldAutoHydrateThread = this.threadId && this.options.initialMessages === void 0 && "memory" in this.agent;
|
|
562
|
+
if (this.options.resume) (async () => {
|
|
563
|
+
try {
|
|
564
|
+
if (shouldAutoHydrateThread) await this.loadMessages(this.threadId, { beforeRunId: this.options.resume?.runId });
|
|
565
|
+
await this.resume(this.options.resume);
|
|
566
|
+
} catch {}
|
|
567
|
+
})();
|
|
568
|
+
else if (this.pendingClientTools.length > 0 && !this.hasUndecidedApprovals()) this.runPendingInterruptsWithLifecycle();
|
|
569
|
+
else if (shouldAutoHydrateThread && this.threadId) this.loadThread(this.threadId).catch(() => {});
|
|
570
|
+
}
|
|
571
|
+
getSnapshot() {
|
|
572
|
+
return this.snapshot;
|
|
573
|
+
}
|
|
574
|
+
createSnapshot() {
|
|
575
|
+
return {
|
|
576
|
+
messages: this.messages,
|
|
577
|
+
state: this.stateControl.get(),
|
|
578
|
+
status: this.status,
|
|
579
|
+
error: this.error,
|
|
580
|
+
runId: this.runId,
|
|
581
|
+
threadId: this.threadId,
|
|
582
|
+
isRunning: this.status === "submitted" || this.status === "streaming",
|
|
583
|
+
pendingClientTools: this.pendingClientTools,
|
|
584
|
+
pendingToolApprovals: this.getPendingToolApprovalsSnapshot()
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
getPendingToolApprovalsSnapshot() {
|
|
588
|
+
return this.pendingToolApprovals.filter((approval) => approval.approved === void 0);
|
|
589
|
+
}
|
|
590
|
+
isClientToolInterrupt(interrupt) {
|
|
591
|
+
return Boolean(interrupt?.reason === RuntimeInterruptReason.ClientToolPending && interrupt.toolCallId);
|
|
592
|
+
}
|
|
593
|
+
isApprovalInterrupt(interrupt) {
|
|
594
|
+
return Boolean(interrupt?.reason === RuntimeInterruptReason.ToolApprovalPending && interrupt.toolCallId);
|
|
595
|
+
}
|
|
596
|
+
getToolCallPart(toolCallId) {
|
|
597
|
+
for (const message of this.messages) {
|
|
598
|
+
const part = message.parts.find((candidate) => candidate.type === "tool-call" && candidate.toolCallId === toolCallId);
|
|
599
|
+
if (part) return part;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
combineAbortSignals(primary, secondary) {
|
|
603
|
+
if (!secondary) return primary;
|
|
604
|
+
return AbortSignal.any([primary, secondary]);
|
|
605
|
+
}
|
|
606
|
+
hasUndecidedApprovals() {
|
|
607
|
+
return this.pendingToolApprovals.some((approval) => approval.approved === void 0);
|
|
608
|
+
}
|
|
609
|
+
subscribe(listener) {
|
|
610
|
+
this.listeners.add(listener);
|
|
611
|
+
return () => {
|
|
612
|
+
this.listeners.delete(listener);
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
setMessages(messages) {
|
|
616
|
+
this.messages = messages;
|
|
617
|
+
this.notify();
|
|
618
|
+
}
|
|
619
|
+
async loadMessages(threadId = this.threadId, options) {
|
|
620
|
+
if (!threadId) throw new BetterAgentClientError("Cannot load messages without a threadId.");
|
|
621
|
+
const memory = "memory" in this.agent ? this.agent.memory : void 0;
|
|
622
|
+
if (!memory) throw new BetterAgentClientError("Agent memory is not available.");
|
|
623
|
+
try {
|
|
624
|
+
const messages = await memory.messages.list(threadId, options);
|
|
625
|
+
this.threadId = threadId;
|
|
626
|
+
this.messages = fromAgentMessages(messages.map((message) => {
|
|
627
|
+
const { threadId: _threadId, runId: _runId, createdAt: _createdAt, ...rest } = message;
|
|
628
|
+
return rest;
|
|
629
|
+
}));
|
|
630
|
+
for (const approval of this.pendingToolApprovals) this.updateToolCallPart(approval.toolCallId, {
|
|
631
|
+
state: "approval-requested",
|
|
632
|
+
approval: {
|
|
633
|
+
interruptId: approval.interruptId,
|
|
634
|
+
needsApproval: true,
|
|
635
|
+
...approval.metadata ? { metadata: approval.metadata } : {}
|
|
636
|
+
}
|
|
637
|
+
});
|
|
638
|
+
this.error = void 0;
|
|
639
|
+
this.notify();
|
|
640
|
+
} catch (error) {
|
|
641
|
+
this.error = toBetterAgentClientError(error);
|
|
642
|
+
this.status = "error";
|
|
643
|
+
this.notify();
|
|
644
|
+
throw this.error;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
async loadThread(threadId) {
|
|
648
|
+
const memory = "memory" in this.agent ? this.agent.memory : void 0;
|
|
649
|
+
if (!memory) throw new BetterAgentClientError("Agent memory is not available.");
|
|
650
|
+
const runtime = await memory.threads.runtime(threadId);
|
|
651
|
+
if (runtime.resumable) {
|
|
652
|
+
await this.loadMessages(threadId, { beforeRunId: runtime.resumable.runId });
|
|
653
|
+
await this.resume(runtime.resumable);
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
await this.loadMessages(threadId);
|
|
657
|
+
if (runtime.interrupted) {
|
|
658
|
+
this.hydrateInterrupts(runtime.interrupted.runId, runtime.interrupted.interrupts);
|
|
659
|
+
if (this.pendingClientTools.length > 0 && !this.hasUndecidedApprovals()) await this.runPendingInterruptsWithLifecycle();
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
hydrateInterrupts(runId, interrupts) {
|
|
663
|
+
const approvalInterrupts = interrupts.filter((interrupt) => this.isApprovalInterrupt(interrupt));
|
|
664
|
+
const clientToolInterrupts = interrupts.filter((interrupt) => this.isClientToolInterrupt(interrupt));
|
|
665
|
+
const approvals = [];
|
|
666
|
+
for (const interrupt of approvalInterrupts) {
|
|
667
|
+
const pending = this.createPendingApproval(runId, interrupt);
|
|
668
|
+
if ("isError" in pending) return;
|
|
669
|
+
approvals.push(pending);
|
|
670
|
+
this.updateToolCallPart(interrupt.toolCallId, {
|
|
671
|
+
state: "approval-requested",
|
|
672
|
+
approval: {
|
|
673
|
+
interruptId: interrupt.id,
|
|
674
|
+
needsApproval: true,
|
|
675
|
+
...interrupt.metadata ? { metadata: interrupt.metadata } : {}
|
|
676
|
+
}
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
const clientTools = [];
|
|
680
|
+
for (const interrupt of clientToolInterrupts) {
|
|
681
|
+
const pending = this.createPendingClientTool(runId, interrupt);
|
|
682
|
+
if ("isError" in pending) return;
|
|
683
|
+
clientTools.push(pending);
|
|
684
|
+
}
|
|
685
|
+
this.runId = runId;
|
|
686
|
+
this.pendingToolApprovals = approvals;
|
|
687
|
+
this.pendingClientTools = clientTools;
|
|
688
|
+
this.status = "interrupted";
|
|
689
|
+
this.error = void 0;
|
|
690
|
+
this.notify();
|
|
691
|
+
}
|
|
692
|
+
async selectThread(threadId) {
|
|
693
|
+
this.stop();
|
|
694
|
+
await this.loadThread(threadId);
|
|
695
|
+
}
|
|
696
|
+
clearThread() {
|
|
697
|
+
this.stop();
|
|
698
|
+
this.messages = [];
|
|
699
|
+
this.threadId = void 0;
|
|
700
|
+
this.runId = void 0;
|
|
701
|
+
this.error = void 0;
|
|
702
|
+
this.pendingClientTools = [];
|
|
703
|
+
this.pendingToolApprovals = [];
|
|
704
|
+
this.status = "ready";
|
|
705
|
+
this.notify();
|
|
706
|
+
}
|
|
707
|
+
clearStopWaitTimer() {
|
|
708
|
+
if (this.stopWaitTimer) {
|
|
709
|
+
clearTimeout(this.stopWaitTimer);
|
|
710
|
+
this.stopWaitTimer = void 0;
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
resetStopRequest() {
|
|
714
|
+
this.stopRequested = false;
|
|
715
|
+
this.abortRequestedRunId = void 0;
|
|
716
|
+
this.clearStopWaitTimer();
|
|
717
|
+
}
|
|
718
|
+
abortServerRun(runId) {
|
|
719
|
+
if (this.abortRequestedRunId === runId) return;
|
|
720
|
+
this.abortRequestedRunId = runId;
|
|
721
|
+
this.agent.runs.abort(runId).catch((error) => {
|
|
722
|
+
console.error("[better-agent] abortRun failed", error);
|
|
723
|
+
});
|
|
724
|
+
}
|
|
725
|
+
abortActiveRequest() {
|
|
726
|
+
this.activeAbortController?.abort();
|
|
727
|
+
this.activeAbortController = void 0;
|
|
728
|
+
}
|
|
729
|
+
stop() {
|
|
730
|
+
this.stopRequested = true;
|
|
731
|
+
const runId = this.runId;
|
|
732
|
+
if (runId) {
|
|
733
|
+
this.clearStopWaitTimer();
|
|
734
|
+
this.abortServerRun(runId);
|
|
735
|
+
this.abortActiveRequest();
|
|
736
|
+
} else if (this.activeAbortController && !this.stopWaitTimer) {
|
|
737
|
+
const activeAbortController = this.activeAbortController;
|
|
738
|
+
this.stopWaitTimer = setTimeout(() => {
|
|
739
|
+
if (this.stopRequested && this.activeAbortController === activeAbortController) this.abortActiveRequest();
|
|
740
|
+
this.clearStopWaitTimer();
|
|
741
|
+
}, 2e3);
|
|
742
|
+
}
|
|
743
|
+
if (this.status !== "ready") {
|
|
744
|
+
this.status = "ready";
|
|
745
|
+
this.notify();
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
safeLifecycle(name, fn) {
|
|
749
|
+
try {
|
|
750
|
+
const result = fn();
|
|
751
|
+
if (result !== void 0 && typeof result === "object" && "then" in result && typeof result.then === "function") result.catch((err) => {
|
|
752
|
+
console.error(`[better-agent] ${name} callback failed`, err);
|
|
753
|
+
});
|
|
754
|
+
} catch (err) {
|
|
755
|
+
console.error(`[better-agent] ${name} callback failed`, err);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
emitEvent(hooks, event) {
|
|
759
|
+
const onEvent = hooks.onEvent;
|
|
760
|
+
if (!onEvent) return;
|
|
761
|
+
this.safeLifecycle("onEvent", () => onEvent(event));
|
|
762
|
+
}
|
|
763
|
+
invokeLifecycleFinish(hooks, finish) {
|
|
764
|
+
this.safeLifecycle("onFinish", () => hooks.onFinish?.(finish));
|
|
765
|
+
const error = finish.error;
|
|
766
|
+
if (finish.isError && error) {
|
|
767
|
+
const onError = hooks.onError;
|
|
768
|
+
if (onError) this.safeLifecycle("onError", () => onError(error));
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
createFinish(finish) {
|
|
772
|
+
const messages = [...this.messages];
|
|
773
|
+
const generatedMessages = messages.slice(this.finishMessageStartIndex);
|
|
774
|
+
const message = generatedMessages[generatedMessages.length - 1];
|
|
775
|
+
return {
|
|
776
|
+
...message ? { message } : {},
|
|
777
|
+
generatedMessages,
|
|
778
|
+
messages,
|
|
779
|
+
runId: this.runId,
|
|
780
|
+
threadId: this.threadId,
|
|
781
|
+
pendingClientTools: finish.pendingClientTools ?? [...this.pendingClientTools],
|
|
782
|
+
pendingToolApprovals: finish.pendingToolApprovals ?? this.getPendingToolApprovalsSnapshot(),
|
|
783
|
+
...finish
|
|
784
|
+
};
|
|
785
|
+
}
|
|
786
|
+
async sendMessage(input, sendOptions) {
|
|
787
|
+
const hooks = {
|
|
788
|
+
onEvent: this.options.onEvent,
|
|
789
|
+
onFinish: this.options.onFinish,
|
|
790
|
+
onError: this.options.onError
|
|
791
|
+
};
|
|
792
|
+
let terminal;
|
|
793
|
+
this.stop();
|
|
794
|
+
this.resetStopRequest();
|
|
795
|
+
this.error = void 0;
|
|
796
|
+
this.pendingClientTools = [];
|
|
797
|
+
this.pendingToolApprovals = [];
|
|
798
|
+
this.status = "submitted";
|
|
799
|
+
const inputMessages = typeof input === "string" ? [createUserUIMessage(input)] : input;
|
|
800
|
+
this.messages = [...this.messages, ...inputMessages];
|
|
801
|
+
this.finishMessageStartIndex = this.messages.length;
|
|
802
|
+
this.notify();
|
|
803
|
+
const abortController = new AbortController();
|
|
804
|
+
this.activeAbortController = abortController;
|
|
805
|
+
const streamSignal = this.combineAbortSignals(abortController.signal, sendOptions?.signal ?? null);
|
|
806
|
+
const context = sendOptions?.context ?? this.options.context;
|
|
807
|
+
try {
|
|
808
|
+
const stream = this.agent.stream({
|
|
809
|
+
messages: toAgentMessages(this.threadId ? inputMessages : this.messages).map(({ id: _id, ...message }) => message),
|
|
810
|
+
...context !== void 0 ? { context } : {},
|
|
811
|
+
...this.stateControl.get() !== void 0 ? { state: this.stateControl.get() } : {},
|
|
812
|
+
...this.threadId !== void 0 ? { threadId: this.threadId } : {}
|
|
813
|
+
}, { signal: streamSignal });
|
|
814
|
+
terminal = await this.consumeStream(stream, hooks, streamSignal);
|
|
815
|
+
} catch (error) {
|
|
816
|
+
if (streamSignal.aborted || isBrowserPageTearingDown()) {
|
|
817
|
+
this.status = "ready";
|
|
818
|
+
this.notify();
|
|
819
|
+
terminal = this.createFinish({
|
|
820
|
+
isAbort: true,
|
|
821
|
+
isDisconnect: false,
|
|
822
|
+
isError: false,
|
|
823
|
+
isInterrupted: false
|
|
824
|
+
});
|
|
825
|
+
} else {
|
|
826
|
+
this.error = toBetterAgentClientError(error);
|
|
827
|
+
this.status = "error";
|
|
828
|
+
this.notify();
|
|
829
|
+
terminal = this.createFinish({
|
|
830
|
+
isAbort: false,
|
|
831
|
+
isDisconnect: false,
|
|
832
|
+
isError: true,
|
|
833
|
+
isInterrupted: false,
|
|
834
|
+
error: this.error
|
|
835
|
+
});
|
|
836
|
+
}
|
|
837
|
+
} finally {
|
|
838
|
+
if (this.activeAbortController === abortController) this.activeAbortController = void 0;
|
|
839
|
+
if (terminal !== void 0) this.invokeLifecycleFinish(hooks, terminal);
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
async approveToolCall(interruptId, metadata) {
|
|
843
|
+
await this.resolveApprovalDecision(interruptId, true, metadata);
|
|
844
|
+
}
|
|
845
|
+
async rejectToolCall(interruptId, metadata) {
|
|
846
|
+
await this.resolveApprovalDecision(interruptId, false, metadata);
|
|
847
|
+
}
|
|
848
|
+
async resume(resume) {
|
|
849
|
+
const nextResume = resume ?? (this.runId ? { runId: this.runId } : void 0);
|
|
850
|
+
if (!nextResume) throw new BetterAgentClientError("Cannot resume without a runId.");
|
|
851
|
+
const hooks = {
|
|
852
|
+
onEvent: this.options.onEvent,
|
|
853
|
+
onFinish: this.options.onFinish,
|
|
854
|
+
onError: this.options.onError
|
|
855
|
+
};
|
|
856
|
+
const abortController = new AbortController();
|
|
857
|
+
this.resetStopRequest();
|
|
858
|
+
this.activeAbortController?.abort();
|
|
859
|
+
this.activeAbortController = abortController;
|
|
860
|
+
this.runId = nextResume.runId;
|
|
861
|
+
this.status = "streaming";
|
|
862
|
+
this.finishMessageStartIndex = this.messages.length;
|
|
863
|
+
this.notify();
|
|
864
|
+
let terminal;
|
|
865
|
+
try {
|
|
866
|
+
const stream = this.agent.runs.resumeStream({
|
|
867
|
+
runId: nextResume.runId,
|
|
868
|
+
afterSequence: nextResume.afterSequence
|
|
869
|
+
}, { signal: abortController.signal });
|
|
870
|
+
terminal = await this.consumeStream(stream, hooks, abortController.signal);
|
|
871
|
+
} catch (error) {
|
|
872
|
+
if (abortController.signal.aborted || isBrowserPageTearingDown()) {
|
|
873
|
+
this.status = "ready";
|
|
874
|
+
this.notify();
|
|
875
|
+
terminal = this.createFinish({
|
|
876
|
+
isAbort: true,
|
|
877
|
+
isDisconnect: false,
|
|
878
|
+
isError: false,
|
|
879
|
+
isInterrupted: false
|
|
880
|
+
});
|
|
881
|
+
} else {
|
|
882
|
+
this.error = toBetterAgentClientError(error);
|
|
883
|
+
this.status = "error";
|
|
884
|
+
this.notify();
|
|
885
|
+
terminal = this.createFinish({
|
|
886
|
+
isAbort: false,
|
|
887
|
+
isDisconnect: false,
|
|
888
|
+
isError: true,
|
|
889
|
+
isInterrupted: false,
|
|
890
|
+
error: this.error
|
|
891
|
+
});
|
|
892
|
+
}
|
|
893
|
+
} finally {
|
|
894
|
+
if (this.activeAbortController === abortController) this.activeAbortController = void 0;
|
|
895
|
+
if (terminal !== void 0) this.invokeLifecycleFinish(hooks, terminal);
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
async consumeStream(stream, hooks, signal, beforeSuccessFinish) {
|
|
899
|
+
for await (const event of stream) {
|
|
900
|
+
if (signal.aborted) break;
|
|
901
|
+
const terminal = await this.processStreamEvent(event, hooks, signal);
|
|
902
|
+
if (terminal) return terminal;
|
|
903
|
+
}
|
|
904
|
+
if (signal.aborted) return this.createFinish({
|
|
905
|
+
isAbort: true,
|
|
906
|
+
isDisconnect: false,
|
|
907
|
+
isError: false,
|
|
908
|
+
isInterrupted: false
|
|
909
|
+
});
|
|
910
|
+
beforeSuccessFinish?.();
|
|
911
|
+
this.status = "ready";
|
|
912
|
+
this.notify();
|
|
913
|
+
return this.createFinish({
|
|
914
|
+
isAbort: false,
|
|
915
|
+
isDisconnect: false,
|
|
916
|
+
isError: false,
|
|
917
|
+
isInterrupted: false
|
|
918
|
+
});
|
|
919
|
+
}
|
|
920
|
+
async processStreamEvent(event, hooks, signal) {
|
|
921
|
+
if (event.type === EventType.RUN_ERROR) {
|
|
922
|
+
this.error = new BetterAgentClientError("message" in event && typeof event.message === "string" ? event.message : "Run failed.", {
|
|
923
|
+
code: "code" in event && typeof event.code === "string" ? event.code : void 0,
|
|
924
|
+
details: event
|
|
925
|
+
});
|
|
926
|
+
this.status = "error";
|
|
927
|
+
this.notify();
|
|
928
|
+
this.emitEvent(hooks, event);
|
|
929
|
+
return this.createFinish({
|
|
930
|
+
isAbort: false,
|
|
931
|
+
isDisconnect: false,
|
|
932
|
+
isError: true,
|
|
933
|
+
isInterrupted: false,
|
|
934
|
+
error: this.error
|
|
935
|
+
});
|
|
936
|
+
}
|
|
937
|
+
if (event.type === EventType.RUN_FINISHED && event.outcome === "interrupt") {
|
|
938
|
+
this.applyEvent(event);
|
|
939
|
+
this.notify();
|
|
940
|
+
this.emitEvent(hooks, event);
|
|
941
|
+
const interrupts = event.interrupts ?? [];
|
|
942
|
+
const runId = event.runId ?? this.runId;
|
|
943
|
+
if (!runId && interrupts.length > 0) {
|
|
944
|
+
this.error = new BetterAgentClientError("Interrupt missing runId.");
|
|
945
|
+
this.status = "error";
|
|
946
|
+
this.notify();
|
|
947
|
+
return this.createFinish({
|
|
948
|
+
isAbort: false,
|
|
949
|
+
isDisconnect: false,
|
|
950
|
+
isError: true,
|
|
951
|
+
isInterrupted: false,
|
|
952
|
+
error: this.error
|
|
953
|
+
});
|
|
954
|
+
}
|
|
955
|
+
if (runId && interrupts.length > 0) {
|
|
956
|
+
this.runId = runId;
|
|
957
|
+
return await this.resolveInterrupts({
|
|
958
|
+
runId,
|
|
959
|
+
interrupts,
|
|
960
|
+
hooks,
|
|
961
|
+
signal
|
|
962
|
+
});
|
|
963
|
+
}
|
|
964
|
+
this.status = "interrupted";
|
|
965
|
+
this.notify();
|
|
966
|
+
return this.createFinish({
|
|
967
|
+
isAbort: false,
|
|
968
|
+
isDisconnect: false,
|
|
969
|
+
isError: false,
|
|
970
|
+
isInterrupted: true,
|
|
971
|
+
interruptReason: "other"
|
|
972
|
+
});
|
|
973
|
+
}
|
|
974
|
+
this.applyEvent(event);
|
|
975
|
+
if (this.stopRequested) return this.createFinish({
|
|
976
|
+
isAbort: true,
|
|
977
|
+
isDisconnect: false,
|
|
978
|
+
isError: false,
|
|
979
|
+
isInterrupted: false
|
|
980
|
+
});
|
|
981
|
+
this.status = "streaming";
|
|
982
|
+
this.notify();
|
|
983
|
+
this.emitEvent(hooks, event);
|
|
984
|
+
}
|
|
985
|
+
createPendingClientTool(runId, interrupt) {
|
|
986
|
+
const toolCall = this.getToolCallPart(interrupt.toolCallId);
|
|
987
|
+
if (!toolCall) {
|
|
988
|
+
this.error = new BetterAgentClientError(`Client tool call not found for toolCallId: ${interrupt.toolCallId}`);
|
|
989
|
+
this.status = "error";
|
|
990
|
+
this.notify();
|
|
991
|
+
return this.createFinish({
|
|
992
|
+
isAbort: false,
|
|
993
|
+
isDisconnect: false,
|
|
994
|
+
isError: true,
|
|
995
|
+
isInterrupted: false,
|
|
996
|
+
error: this.error
|
|
997
|
+
});
|
|
998
|
+
}
|
|
999
|
+
return {
|
|
1000
|
+
interruptId: interrupt.id,
|
|
1001
|
+
runId,
|
|
1002
|
+
toolCallId: interrupt.toolCallId,
|
|
1003
|
+
toolName: toolCall.toolName,
|
|
1004
|
+
input: toolCall.input,
|
|
1005
|
+
...interrupt.expiresAt !== void 0 ? { expiresAt: interrupt.expiresAt } : {}
|
|
1006
|
+
};
|
|
1007
|
+
}
|
|
1008
|
+
createPendingApproval(runId, interrupt) {
|
|
1009
|
+
const toolCall = this.getToolCallPart(interrupt.toolCallId);
|
|
1010
|
+
if (!toolCall) {
|
|
1011
|
+
this.error = new BetterAgentClientError(`Approval tool call not found for toolCallId: ${interrupt.toolCallId}`);
|
|
1012
|
+
this.status = "error";
|
|
1013
|
+
this.notify();
|
|
1014
|
+
return this.createFinish({
|
|
1015
|
+
isAbort: false,
|
|
1016
|
+
isDisconnect: false,
|
|
1017
|
+
isError: true,
|
|
1018
|
+
isInterrupted: false,
|
|
1019
|
+
error: this.error
|
|
1020
|
+
});
|
|
1021
|
+
}
|
|
1022
|
+
return {
|
|
1023
|
+
interruptId: interrupt.id,
|
|
1024
|
+
runId,
|
|
1025
|
+
toolCallId: interrupt.toolCallId,
|
|
1026
|
+
toolName: toolCall.toolName,
|
|
1027
|
+
input: toolCall.input,
|
|
1028
|
+
metadata: interrupt.metadata,
|
|
1029
|
+
...interrupt.expiresAt !== void 0 ? { expiresAt: interrupt.expiresAt } : {}
|
|
1030
|
+
};
|
|
1031
|
+
}
|
|
1032
|
+
async createClientToolResumeEntry(pending) {
|
|
1033
|
+
const handler = this.getToolHandler(pending.toolName);
|
|
1034
|
+
if (!handler) return;
|
|
1035
|
+
try {
|
|
1036
|
+
const payload = await handler(pending.input);
|
|
1037
|
+
const resumePayload = payload === void 0 ? {
|
|
1038
|
+
status: "success",
|
|
1039
|
+
result: {}
|
|
1040
|
+
} : {
|
|
1041
|
+
status: "success",
|
|
1042
|
+
result: payload
|
|
1043
|
+
};
|
|
1044
|
+
this.upsertToolResultPart(pending.toolCallId, {
|
|
1045
|
+
state: "output-available",
|
|
1046
|
+
result: typeof resumePayload.result === "string" ? resumePayload.result : JSON.stringify(resumePayload.result)
|
|
1047
|
+
});
|
|
1048
|
+
this.notify();
|
|
1049
|
+
return {
|
|
1050
|
+
interruptId: pending.interruptId,
|
|
1051
|
+
status: "resolved",
|
|
1052
|
+
payload: resumePayload
|
|
1053
|
+
};
|
|
1054
|
+
} catch (error) {
|
|
1055
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1056
|
+
const resumePayload = {
|
|
1057
|
+
status: "error",
|
|
1058
|
+
error: message
|
|
1059
|
+
};
|
|
1060
|
+
this.upsertToolResultPart(pending.toolCallId, {
|
|
1061
|
+
state: "output-error",
|
|
1062
|
+
error: message
|
|
1063
|
+
});
|
|
1064
|
+
this.notify();
|
|
1065
|
+
return {
|
|
1066
|
+
interruptId: pending.interruptId,
|
|
1067
|
+
status: "resolved",
|
|
1068
|
+
payload: resumePayload
|
|
1069
|
+
};
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
async resolveInterrupts(params) {
|
|
1073
|
+
const { hooks, runId, signal } = params;
|
|
1074
|
+
const approvalInterrupts = params.interrupts.filter((interrupt) => this.isApprovalInterrupt(interrupt));
|
|
1075
|
+
const clientToolInterrupts = params.interrupts.filter((interrupt) => this.isClientToolInterrupt(interrupt));
|
|
1076
|
+
if (approvalInterrupts.length > 0) {
|
|
1077
|
+
const approvals = [];
|
|
1078
|
+
for (const interrupt of approvalInterrupts) {
|
|
1079
|
+
const pending = this.createPendingApproval(runId, interrupt);
|
|
1080
|
+
if ("isError" in pending) return pending;
|
|
1081
|
+
approvals.push(pending);
|
|
1082
|
+
this.updateToolCallPart(interrupt.toolCallId, {
|
|
1083
|
+
state: "approval-requested",
|
|
1084
|
+
approval: {
|
|
1085
|
+
interruptId: interrupt.id,
|
|
1086
|
+
needsApproval: true,
|
|
1087
|
+
...interrupt.metadata ? { metadata: interrupt.metadata } : {}
|
|
1088
|
+
}
|
|
1089
|
+
});
|
|
1090
|
+
}
|
|
1091
|
+
const clientTools = [];
|
|
1092
|
+
for (const interrupt of clientToolInterrupts) {
|
|
1093
|
+
const pending = this.createPendingClientTool(runId, interrupt);
|
|
1094
|
+
if ("isError" in pending) return pending;
|
|
1095
|
+
clientTools.push(pending);
|
|
1096
|
+
}
|
|
1097
|
+
this.pendingToolApprovals = approvals;
|
|
1098
|
+
this.pendingClientTools = clientTools;
|
|
1099
|
+
this.status = "interrupted";
|
|
1100
|
+
this.notify();
|
|
1101
|
+
return this.createFinish({
|
|
1102
|
+
isAbort: false,
|
|
1103
|
+
isDisconnect: false,
|
|
1104
|
+
isError: false,
|
|
1105
|
+
isInterrupted: true,
|
|
1106
|
+
interruptReason: "tool_approval_pending"
|
|
1107
|
+
});
|
|
1108
|
+
}
|
|
1109
|
+
if (clientToolInterrupts.length > 0) {
|
|
1110
|
+
const clientTools = [];
|
|
1111
|
+
for (const interrupt of clientToolInterrupts) {
|
|
1112
|
+
const pending = this.createPendingClientTool(runId, interrupt);
|
|
1113
|
+
if ("isError" in pending) return pending;
|
|
1114
|
+
clientTools.push(pending);
|
|
1115
|
+
}
|
|
1116
|
+
this.pendingClientTools = clientTools;
|
|
1117
|
+
this.pendingToolApprovals = [];
|
|
1118
|
+
return this.resolvePendingInterruptBatch(hooks, signal);
|
|
1119
|
+
}
|
|
1120
|
+
this.pendingClientTools = [];
|
|
1121
|
+
this.pendingToolApprovals = [];
|
|
1122
|
+
this.status = "interrupted";
|
|
1123
|
+
this.notify();
|
|
1124
|
+
return this.createFinish({
|
|
1125
|
+
isAbort: false,
|
|
1126
|
+
isDisconnect: false,
|
|
1127
|
+
isError: false,
|
|
1128
|
+
isInterrupted: true,
|
|
1129
|
+
interruptReason: "other"
|
|
1130
|
+
});
|
|
1131
|
+
}
|
|
1132
|
+
async streamWithResume(resume, hooks, signal) {
|
|
1133
|
+
const stream = this.agent.stream({
|
|
1134
|
+
resume,
|
|
1135
|
+
...this.stateControl.get() !== void 0 ? { state: this.stateControl.get() } : {},
|
|
1136
|
+
...this.threadId !== void 0 ? { threadId: this.threadId } : {}
|
|
1137
|
+
}, { signal });
|
|
1138
|
+
return this.consumeStream(stream, hooks, signal, () => {
|
|
1139
|
+
this.pendingClientTools = [];
|
|
1140
|
+
this.pendingToolApprovals = [];
|
|
1141
|
+
});
|
|
1142
|
+
}
|
|
1143
|
+
async runPendingInterruptsWithLifecycle() {
|
|
1144
|
+
const hooks = {
|
|
1145
|
+
onEvent: this.options.onEvent,
|
|
1146
|
+
onFinish: this.options.onFinish,
|
|
1147
|
+
onError: this.options.onError
|
|
1148
|
+
};
|
|
1149
|
+
const abortController = new AbortController();
|
|
1150
|
+
this.resetStopRequest();
|
|
1151
|
+
this.activeAbortController?.abort();
|
|
1152
|
+
this.activeAbortController = abortController;
|
|
1153
|
+
this.error = void 0;
|
|
1154
|
+
this.finishMessageStartIndex = this.messages.length;
|
|
1155
|
+
this.status = "streaming";
|
|
1156
|
+
this.notify();
|
|
1157
|
+
let terminal;
|
|
1158
|
+
try {
|
|
1159
|
+
terminal = await this.resolvePendingInterruptBatch(hooks, abortController.signal);
|
|
1160
|
+
} catch (e) {
|
|
1161
|
+
if (abortController.signal.aborted) terminal = this.createFinish({
|
|
1162
|
+
isAbort: true,
|
|
1163
|
+
isDisconnect: false,
|
|
1164
|
+
isError: false,
|
|
1165
|
+
isInterrupted: false
|
|
1166
|
+
});
|
|
1167
|
+
else {
|
|
1168
|
+
this.error = toBetterAgentClientError(e);
|
|
1169
|
+
this.status = "error";
|
|
1170
|
+
this.notify();
|
|
1171
|
+
terminal = this.createFinish({
|
|
1172
|
+
isAbort: false,
|
|
1173
|
+
isDisconnect: false,
|
|
1174
|
+
isError: true,
|
|
1175
|
+
isInterrupted: false,
|
|
1176
|
+
error: this.error
|
|
1177
|
+
});
|
|
1178
|
+
}
|
|
1179
|
+
} finally {
|
|
1180
|
+
if (this.activeAbortController === abortController) this.activeAbortController = void 0;
|
|
1181
|
+
if (terminal !== void 0) this.invokeLifecycleFinish(hooks, terminal);
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
async resolvePendingInterruptBatch(hooks, signal) {
|
|
1185
|
+
if (this.hasUndecidedApprovals()) {
|
|
1186
|
+
this.status = "interrupted";
|
|
1187
|
+
this.notify();
|
|
1188
|
+
return this.createFinish({
|
|
1189
|
+
isAbort: false,
|
|
1190
|
+
isDisconnect: false,
|
|
1191
|
+
isError: false,
|
|
1192
|
+
isInterrupted: true,
|
|
1193
|
+
interruptReason: "tool_approval_pending"
|
|
1194
|
+
});
|
|
1195
|
+
}
|
|
1196
|
+
const runId = this.runId ?? this.pendingToolApprovals[0]?.runId ?? this.pendingClientTools[0]?.runId;
|
|
1197
|
+
if (!runId) {
|
|
1198
|
+
this.error = new BetterAgentClientError("Pending interrupt missing runId.");
|
|
1199
|
+
this.status = "error";
|
|
1200
|
+
this.notify();
|
|
1201
|
+
return this.createFinish({
|
|
1202
|
+
isAbort: false,
|
|
1203
|
+
isDisconnect: false,
|
|
1204
|
+
isError: true,
|
|
1205
|
+
isInterrupted: false,
|
|
1206
|
+
error: this.error
|
|
1207
|
+
});
|
|
1208
|
+
}
|
|
1209
|
+
const resume = this.pendingToolApprovals.map((approval) => ({
|
|
1210
|
+
interruptId: approval.interruptId,
|
|
1211
|
+
status: "resolved",
|
|
1212
|
+
payload: {
|
|
1213
|
+
approved: approval.approved,
|
|
1214
|
+
...approval.responseMetadata ? { metadata: approval.responseMetadata } : {}
|
|
1215
|
+
}
|
|
1216
|
+
}));
|
|
1217
|
+
if (this.pendingClientTools.some((pending) => !this.getToolHandler(pending.toolName))) {
|
|
1218
|
+
this.status = "interrupted";
|
|
1219
|
+
this.notify();
|
|
1220
|
+
return this.createFinish({
|
|
1221
|
+
isAbort: false,
|
|
1222
|
+
isDisconnect: false,
|
|
1223
|
+
isError: false,
|
|
1224
|
+
isInterrupted: true,
|
|
1225
|
+
interruptReason: "client_tool_pending"
|
|
1226
|
+
});
|
|
1227
|
+
}
|
|
1228
|
+
for (const pending of this.pendingClientTools) {
|
|
1229
|
+
const entry = await this.createClientToolResumeEntry(pending);
|
|
1230
|
+
if (entry) resume.push(entry);
|
|
1231
|
+
}
|
|
1232
|
+
if (resume.length === 0) {
|
|
1233
|
+
this.status = "interrupted";
|
|
1234
|
+
this.notify();
|
|
1235
|
+
return this.createFinish({
|
|
1236
|
+
isAbort: false,
|
|
1237
|
+
isDisconnect: false,
|
|
1238
|
+
isError: false,
|
|
1239
|
+
isInterrupted: true,
|
|
1240
|
+
interruptReason: "other"
|
|
1241
|
+
});
|
|
1242
|
+
}
|
|
1243
|
+
this.runId = runId;
|
|
1244
|
+
return this.streamWithResume(resume, hooks, signal);
|
|
1245
|
+
}
|
|
1246
|
+
getToolHandler(toolName) {
|
|
1247
|
+
return this.options.toolHandlers?.[toolName];
|
|
1248
|
+
}
|
|
1249
|
+
updateToolCallPart(toolCallId, update) {
|
|
1250
|
+
this.messages = this.messages.map((message) => ({
|
|
1251
|
+
...message,
|
|
1252
|
+
parts: message.parts.map((part) => part.type === "tool-call" && part.toolCallId === toolCallId ? {
|
|
1253
|
+
...part,
|
|
1254
|
+
...update
|
|
1255
|
+
} : part)
|
|
1256
|
+
}));
|
|
1257
|
+
}
|
|
1258
|
+
upsertToolResultPart(toolCallId, update) {
|
|
1259
|
+
this.messages = this.messages.map((message) => {
|
|
1260
|
+
const existingResultIndex = message.parts.findIndex((part) => part.type === "tool-result" && part.toolCallId === toolCallId);
|
|
1261
|
+
const existingToolCallIndex = message.parts.findIndex((part) => part.type === "tool-call" && part.toolCallId === toolCallId);
|
|
1262
|
+
if (existingResultIndex === -1 && existingToolCallIndex === -1) return message;
|
|
1263
|
+
if (existingResultIndex !== -1) return {
|
|
1264
|
+
...message,
|
|
1265
|
+
parts: message.parts.map((part, index) => index === existingResultIndex && part.type === "tool-result" ? {
|
|
1266
|
+
...part,
|
|
1267
|
+
...update
|
|
1268
|
+
} : part)
|
|
1269
|
+
};
|
|
1270
|
+
const parts = [...message.parts];
|
|
1271
|
+
const insertIndex = existingToolCallIndex + 1;
|
|
1272
|
+
parts.splice(insertIndex, 0, {
|
|
1273
|
+
type: "tool-result",
|
|
1274
|
+
toolCallId,
|
|
1275
|
+
state: "output-available",
|
|
1276
|
+
...update
|
|
1277
|
+
});
|
|
1278
|
+
return {
|
|
1279
|
+
...message,
|
|
1280
|
+
parts
|
|
1281
|
+
};
|
|
1282
|
+
});
|
|
1283
|
+
}
|
|
1284
|
+
async resolveApprovalDecision(interruptId, approved, metadata) {
|
|
1285
|
+
const approvalIndex = this.pendingToolApprovals.findIndex((approval) => approval.interruptId === interruptId);
|
|
1286
|
+
if (approvalIndex === -1) throw new BetterAgentClientError(`Pending approval not found for interruptId: ${interruptId}`);
|
|
1287
|
+
const approval = this.pendingToolApprovals[approvalIndex];
|
|
1288
|
+
if (!approval) return;
|
|
1289
|
+
const normalizedMetadata = typeof metadata === "string" ? { note: metadata } : metadata;
|
|
1290
|
+
this.error = void 0;
|
|
1291
|
+
this.pendingToolApprovals = this.pendingToolApprovals.map((pending, index) => index === approvalIndex ? {
|
|
1292
|
+
...pending,
|
|
1293
|
+
approved,
|
|
1294
|
+
responseMetadata: normalizedMetadata
|
|
1295
|
+
} : pending);
|
|
1296
|
+
this.updateToolCallPart(approval.toolCallId, {
|
|
1297
|
+
state: "approval-responded",
|
|
1298
|
+
approval: {
|
|
1299
|
+
interruptId: approval.interruptId,
|
|
1300
|
+
needsApproval: true,
|
|
1301
|
+
approved,
|
|
1302
|
+
...normalizedMetadata ? { metadata: normalizedMetadata } : {}
|
|
1303
|
+
}
|
|
1304
|
+
});
|
|
1305
|
+
this.notify();
|
|
1306
|
+
if (this.hasUndecidedApprovals()) {
|
|
1307
|
+
this.status = "interrupted";
|
|
1308
|
+
this.notify();
|
|
1309
|
+
return;
|
|
1310
|
+
}
|
|
1311
|
+
await this.runPendingInterruptsWithLifecycle();
|
|
1312
|
+
}
|
|
1313
|
+
applyEvent(event) {
|
|
1314
|
+
const rid = "runId" in event && typeof event.runId === "string" ? event.runId : void 0;
|
|
1315
|
+
const tid = "threadId" in event && typeof event.threadId === "string" && event.threadId.length > 0 ? event.threadId : void 0;
|
|
1316
|
+
if (rid) {
|
|
1317
|
+
this.runId = rid;
|
|
1318
|
+
if (this.stopRequested) {
|
|
1319
|
+
this.clearStopWaitTimer();
|
|
1320
|
+
this.abortServerRun(rid);
|
|
1321
|
+
this.abortActiveRequest();
|
|
1322
|
+
return;
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
if (tid) this.threadId = tid;
|
|
1326
|
+
if (this.stopRequested) return;
|
|
1327
|
+
this.stateControl.apply(event);
|
|
1328
|
+
if (event.type === EventType.STATE_SNAPSHOT || event.type === EventType.STATE_DELTA) this.stateRevision += 1;
|
|
1329
|
+
this.messages = applyUIEvent(createUIReducerState(this.messages), event).messages;
|
|
1330
|
+
}
|
|
1331
|
+
notify() {
|
|
1332
|
+
this.snapshot = this.createSnapshot();
|
|
1333
|
+
for (const listener of this.listeners) listener();
|
|
1334
|
+
}
|
|
1335
|
+
};
|
|
1336
|
+
function createAgentController(agent, options) {
|
|
1337
|
+
return new AgentController(agent, options);
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
//#endregion
|
|
1341
|
+
export { BetterAgentClientError as a, toAgentMessages as i, createAgentController as n, toBetterAgentClientError as o, fromAgentMessages as r, AgentController as t };
|
|
1342
|
+
//# sourceMappingURL=controller-xMlzSCc7.mjs.map
|