@better-agent/client 0.1.0-canary.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 -0
- package/dist/controller-CJ79_cSR.d.mts +657 -0
- package/dist/controller-CJ79_cSR.d.mts.map +1 -0
- package/dist/controller-Cf_JhTdJ.mjs +2123 -0
- package/dist/controller-Cf_JhTdJ.mjs.map +1 -0
- package/dist/index.d.mts +266 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +643 -0
- package/dist/index.mjs.map +1 -0
- package/dist/preact/index.d.mts +96 -0
- package/dist/preact/index.d.mts.map +1 -0
- package/dist/preact/index.mjs +134 -0
- package/dist/preact/index.mjs.map +1 -0
- package/dist/react/index.d.mts +96 -0
- package/dist/react/index.d.mts.map +1 -0
- package/dist/react/index.mjs +123 -0
- package/dist/react/index.mjs.map +1 -0
- package/dist/solid/index.d.mts +99 -0
- package/dist/solid/index.d.mts.map +1 -0
- package/dist/solid/index.mjs +130 -0
- package/dist/solid/index.mjs.map +1 -0
- package/dist/svelte/index.d.mts +83 -0
- package/dist/svelte/index.d.mts.map +1 -0
- package/dist/svelte/index.mjs +107 -0
- package/dist/svelte/index.mjs.map +1 -0
- package/dist/utils-CiHUj_BW.mjs +34 -0
- package/dist/utils-CiHUj_BW.mjs.map +1 -0
- package/dist/vue/index.d.mts +95 -0
- package/dist/vue/index.d.mts.map +1 -0
- package/dist/vue/index.mjs +155 -0
- package/dist/vue/index.mjs.map +1 -0
- package/package.json +117 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,643 @@
|
|
|
1
|
+
import { a as toModelMessages, i as fromModelMessages, n as createAgentChatController, o as getEventErrorMessage, r as fromConversationItems, s as toAgentClientError } from "./controller-Cf_JhTdJ.mjs";
|
|
2
|
+
import { Events } from "@better-agent/core/events";
|
|
3
|
+
import { BetterAgentError } from "@better-agent/shared/errors";
|
|
4
|
+
import { pruneInputByCapabilities } from "@better-agent/core";
|
|
5
|
+
|
|
6
|
+
//#region src/core/sse.ts
|
|
7
|
+
const isEventType = (value) => Object.values(Events).includes(value);
|
|
8
|
+
const resetFrame = () => ({ data: "" });
|
|
9
|
+
const getSseErrorMessage = (data) => {
|
|
10
|
+
try {
|
|
11
|
+
const parsed = JSON.parse(data);
|
|
12
|
+
if (typeof parsed.message === "string" && parsed.message.trim().length > 0) return parsed.message;
|
|
13
|
+
} catch {}
|
|
14
|
+
return data.trim().length > 0 ? data.trim() : "Stream failed";
|
|
15
|
+
};
|
|
16
|
+
const parseFrame = (frame, options) => {
|
|
17
|
+
if (!frame.data) return null;
|
|
18
|
+
const data = frame.data.replace(/\n$/, "");
|
|
19
|
+
if (frame.eventName === "error") throw new Error(getSseErrorMessage(data));
|
|
20
|
+
try {
|
|
21
|
+
const parsed = JSON.parse(data);
|
|
22
|
+
if (!(parsed && typeof parsed === "object" && typeof parsed.type === "string")) return null;
|
|
23
|
+
if (!isEventType(parsed.type)) return null;
|
|
24
|
+
if (typeof frame.id === "number") {
|
|
25
|
+
parsed.seq = frame.id;
|
|
26
|
+
options.onId?.(frame.id);
|
|
27
|
+
}
|
|
28
|
+
return parsed;
|
|
29
|
+
} catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
async function* parseSse(body, options = {}) {
|
|
34
|
+
const reader = body.getReader();
|
|
35
|
+
const decoder = new TextDecoder();
|
|
36
|
+
let buffer = "";
|
|
37
|
+
let frame = resetFrame();
|
|
38
|
+
while (true) {
|
|
39
|
+
const { done, value } = await reader.read();
|
|
40
|
+
if (done) break;
|
|
41
|
+
buffer += decoder.decode(value, { stream: true });
|
|
42
|
+
for (;;) {
|
|
43
|
+
const nl = buffer.indexOf("\n");
|
|
44
|
+
if (nl < 0) break;
|
|
45
|
+
const line = buffer.slice(0, nl).replace(/\r$/, "");
|
|
46
|
+
buffer = buffer.slice(nl + 1);
|
|
47
|
+
if (line === "") {
|
|
48
|
+
const event = parseFrame(frame, options);
|
|
49
|
+
frame = resetFrame();
|
|
50
|
+
if (event) yield event;
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (line.startsWith("event:")) {
|
|
54
|
+
frame.eventName = line.slice(6).trim();
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (line.startsWith("id:")) {
|
|
58
|
+
const idValue = Number(line.slice(3).trim());
|
|
59
|
+
frame.id = Number.isFinite(idValue) ? idValue : void 0;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if (line.startsWith("data:")) frame.data += `${line.slice(5).trim()}\n`;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const finalEvent = parseFrame(frame, options);
|
|
66
|
+
if (finalEvent) yield finalEvent;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
//#endregion
|
|
70
|
+
//#region src/core/client/errors.ts
|
|
71
|
+
const tryParseJson = (bodyText) => {
|
|
72
|
+
try {
|
|
73
|
+
return JSON.parse(bodyText);
|
|
74
|
+
} catch {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
const parseErrorBody = (bodyText) => {
|
|
79
|
+
let errorCodeFromBody;
|
|
80
|
+
let errorMessageFromBody;
|
|
81
|
+
let errorTitleFromBody;
|
|
82
|
+
let errorStatusFromBody;
|
|
83
|
+
let retryableFromBody;
|
|
84
|
+
let traceIdFromBody;
|
|
85
|
+
let issuesFromBody;
|
|
86
|
+
let contextFromBody;
|
|
87
|
+
let traceFromBody;
|
|
88
|
+
if (bodyText.length > 0) {
|
|
89
|
+
const parsed = tryParseJson(bodyText);
|
|
90
|
+
if (parsed) {
|
|
91
|
+
if (typeof parsed.code === "string") errorCodeFromBody = parsed.code;
|
|
92
|
+
else if (typeof parsed.error === "string") errorCodeFromBody = parsed.error;
|
|
93
|
+
if (typeof parsed.message === "string") errorMessageFromBody = parsed.message;
|
|
94
|
+
else if (typeof parsed.detail === "string") errorMessageFromBody = parsed.detail;
|
|
95
|
+
if (typeof parsed.title === "string") errorTitleFromBody = parsed.title;
|
|
96
|
+
if (typeof parsed.status === "number") errorStatusFromBody = parsed.status;
|
|
97
|
+
if (typeof parsed.retryable === "boolean") retryableFromBody = parsed.retryable;
|
|
98
|
+
if (typeof parsed.traceId === "string") traceIdFromBody = parsed.traceId;
|
|
99
|
+
if (Array.isArray(parsed.issues)) issuesFromBody = parsed.issues;
|
|
100
|
+
if (typeof parsed.context === "object" && parsed.context !== null) contextFromBody = parsed.context;
|
|
101
|
+
if (Array.isArray(parsed.trace)) traceFromBody = parsed.trace;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
errorCodeFromBody,
|
|
106
|
+
errorMessageFromBody,
|
|
107
|
+
errorTitleFromBody,
|
|
108
|
+
errorStatusFromBody,
|
|
109
|
+
retryableFromBody,
|
|
110
|
+
traceIdFromBody,
|
|
111
|
+
issuesFromBody,
|
|
112
|
+
contextFromBody,
|
|
113
|
+
traceFromBody
|
|
114
|
+
};
|
|
115
|
+
};
|
|
116
|
+
const getContentType = (response) => response.headers.get("content-type")?.toLowerCase() ?? "";
|
|
117
|
+
const isHtmlResponse = (response) => getContentType(response).includes("text/html");
|
|
118
|
+
const truncateForContext = (value, maxLength = 300) => value.length <= maxLength ? value : `${value.slice(0, maxLength)}...`;
|
|
119
|
+
const throwRequestError = async (params) => {
|
|
120
|
+
const bodyText = (await params.response.text().catch(() => "")).trim();
|
|
121
|
+
const { errorCodeFromBody, errorMessageFromBody, errorTitleFromBody, errorStatusFromBody, retryableFromBody, traceIdFromBody, issuesFromBody, contextFromBody, traceFromBody } = parseErrorBody(bodyText);
|
|
122
|
+
const fallbackDetail = isHtmlResponse(params.response) ? "Server returned an HTML error page. This usually means a framework or dev-server failure before the API handler ran." : params.response.statusText || "Unknown error";
|
|
123
|
+
const detail = errorMessageFromBody ?? (bodyText.length > 0 && !isHtmlResponse(params.response) ? bodyText : void 0) ?? fallbackDetail;
|
|
124
|
+
throw Object.assign(BetterAgentError.fromCode(errorCodeFromBody ?? (params.response.status >= 500 || params.response.status === 429 ? "UPSTREAM_FAILED" : "BAD_REQUEST"), detail, {
|
|
125
|
+
title: errorTitleFromBody,
|
|
126
|
+
status: errorStatusFromBody ?? params.response.status,
|
|
127
|
+
retryable: retryableFromBody,
|
|
128
|
+
traceId: traceIdFromBody,
|
|
129
|
+
context: {
|
|
130
|
+
...params.context,
|
|
131
|
+
operation: params.operation,
|
|
132
|
+
status: params.response.status,
|
|
133
|
+
...contextFromBody ?? {},
|
|
134
|
+
error: errorCodeFromBody,
|
|
135
|
+
issues: issuesFromBody,
|
|
136
|
+
...isHtmlResponse(params.response) && bodyText.length > 0 ? {
|
|
137
|
+
responseContentType: getContentType(params.response),
|
|
138
|
+
responseBodySnippet: truncateForContext(bodyText)
|
|
139
|
+
} : {}
|
|
140
|
+
},
|
|
141
|
+
trace: [...traceFromBody ?? [], { at: params.at }]
|
|
142
|
+
}), issuesFromBody ? { issues: issuesFromBody } : {});
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
//#endregion
|
|
146
|
+
//#region src/core/client/request.ts
|
|
147
|
+
const mergeHeaders = (...parts) => {
|
|
148
|
+
const merged = new Headers();
|
|
149
|
+
for (const part of parts) {
|
|
150
|
+
if (!part) continue;
|
|
151
|
+
new Headers(part).forEach((value, key) => merged.set(key, value));
|
|
152
|
+
}
|
|
153
|
+
return merged;
|
|
154
|
+
};
|
|
155
|
+
const prepareRequest = async (advanced, context) => {
|
|
156
|
+
if (!advanced?.prepareRequest) return {
|
|
157
|
+
url: context.url,
|
|
158
|
+
method: context.method,
|
|
159
|
+
headers: context.headers,
|
|
160
|
+
body: context.body
|
|
161
|
+
};
|
|
162
|
+
const prepared = await advanced.prepareRequest(context);
|
|
163
|
+
if (!prepared) return {
|
|
164
|
+
url: context.url,
|
|
165
|
+
method: context.method,
|
|
166
|
+
headers: context.headers,
|
|
167
|
+
body: context.body
|
|
168
|
+
};
|
|
169
|
+
return {
|
|
170
|
+
url: prepared.url ?? context.url,
|
|
171
|
+
method: prepared.method ?? context.method,
|
|
172
|
+
headers: prepared.headers ?? context.headers,
|
|
173
|
+
...prepared.body !== void 0 ? { body: prepared.body } : context.body !== void 0 ? { body: context.body } : {}
|
|
174
|
+
};
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
//#endregion
|
|
178
|
+
//#region src/core/client/tool-submission.ts
|
|
179
|
+
const sleep = async (ms) => {
|
|
180
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
181
|
+
};
|
|
182
|
+
const createToolSubmissionHandlers = (deps) => {
|
|
183
|
+
const doFetch = deps.doFetch;
|
|
184
|
+
const authHeaders = deps.secret ? { authorization: `Bearer ${deps.secret}` } : void 0;
|
|
185
|
+
const submitToolResult = async (params) => {
|
|
186
|
+
const payload = JSON.stringify({
|
|
187
|
+
runId: params.runId,
|
|
188
|
+
toolCallId: params.toolCallId,
|
|
189
|
+
...params.error !== void 0 ? {
|
|
190
|
+
status: "error",
|
|
191
|
+
error: params.error
|
|
192
|
+
} : {
|
|
193
|
+
status: "success",
|
|
194
|
+
result: params.result
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
for (let attempt = 1; attempt <= deps.toolSubmissionMaxAttempts; attempt++) try {
|
|
198
|
+
const prepared = await prepareRequest(deps.advanced, {
|
|
199
|
+
operation: "tool-result",
|
|
200
|
+
url: `${deps.baseURL}/${encodeURIComponent(params.agent)}/run/tool-result`,
|
|
201
|
+
method: "POST",
|
|
202
|
+
headers: mergeHeaders(authHeaders, { "content-type": "application/json" }, deps.defaultHeaders),
|
|
203
|
+
body: payload
|
|
204
|
+
});
|
|
205
|
+
const res = await doFetch(prepared.url, {
|
|
206
|
+
method: prepared.method,
|
|
207
|
+
headers: prepared.headers,
|
|
208
|
+
body: prepared.body
|
|
209
|
+
});
|
|
210
|
+
if (res.ok) return;
|
|
211
|
+
if (!(res.status >= 500 || res.status === 429) || attempt === deps.toolSubmissionMaxAttempts) await throwRequestError({
|
|
212
|
+
response: res,
|
|
213
|
+
operation: "tool-result",
|
|
214
|
+
at: "client.core.submitToolResult",
|
|
215
|
+
context: {
|
|
216
|
+
agentName: params.agent,
|
|
217
|
+
runId: params.runId,
|
|
218
|
+
toolCallId: params.toolCallId
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
await sleep(deps.toolSubmissionRetryDelayMs * attempt);
|
|
222
|
+
} catch (error) {
|
|
223
|
+
const wrapped = error instanceof Error ? error : new Error("Tool result submission failed.", { cause: error });
|
|
224
|
+
if (attempt === deps.toolSubmissionMaxAttempts) throw wrapped;
|
|
225
|
+
await sleep(deps.toolSubmissionRetryDelayMs * attempt);
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
const submitToolApproval = async (params) => {
|
|
229
|
+
const payload = JSON.stringify({
|
|
230
|
+
runId: params.runId,
|
|
231
|
+
toolCallId: params.toolCallId,
|
|
232
|
+
decision: params.decision,
|
|
233
|
+
...params.note !== void 0 ? { note: params.note } : {},
|
|
234
|
+
...params.actorId !== void 0 ? { actorId: params.actorId } : {}
|
|
235
|
+
});
|
|
236
|
+
for (let attempt = 1; attempt <= deps.toolSubmissionMaxAttempts; attempt++) try {
|
|
237
|
+
const prepared = await prepareRequest(deps.advanced, {
|
|
238
|
+
operation: "tool-approval",
|
|
239
|
+
url: `${deps.baseURL}/${encodeURIComponent(params.agent)}/run/tool-approval`,
|
|
240
|
+
method: "POST",
|
|
241
|
+
headers: mergeHeaders(authHeaders, { "content-type": "application/json" }, deps.defaultHeaders),
|
|
242
|
+
body: payload
|
|
243
|
+
});
|
|
244
|
+
const res = await doFetch(prepared.url, {
|
|
245
|
+
method: prepared.method,
|
|
246
|
+
headers: prepared.headers,
|
|
247
|
+
body: prepared.body
|
|
248
|
+
});
|
|
249
|
+
if (res.ok) return;
|
|
250
|
+
if (!(res.status >= 500 || res.status === 429) || attempt === deps.toolSubmissionMaxAttempts) await throwRequestError({
|
|
251
|
+
response: res,
|
|
252
|
+
operation: "tool-approval",
|
|
253
|
+
at: "client.core.submitToolApproval",
|
|
254
|
+
context: {
|
|
255
|
+
agentName: params.agent,
|
|
256
|
+
runId: params.runId,
|
|
257
|
+
toolCallId: params.toolCallId,
|
|
258
|
+
decision: params.decision
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
await sleep(deps.toolSubmissionRetryDelayMs * attempt);
|
|
262
|
+
} catch (error) {
|
|
263
|
+
const wrapped = error instanceof Error ? error : new Error("Tool approval submission failed.", { cause: error });
|
|
264
|
+
if (attempt === deps.toolSubmissionMaxAttempts) throw wrapped;
|
|
265
|
+
await sleep(deps.toolSubmissionRetryDelayMs * attempt);
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
return {
|
|
269
|
+
submitToolApproval,
|
|
270
|
+
submitToolResult
|
|
271
|
+
};
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
//#endregion
|
|
275
|
+
//#region src/core/client/index.ts
|
|
276
|
+
/**
|
|
277
|
+
* Creates a Better Agent client.
|
|
278
|
+
*
|
|
279
|
+
* @param config Client configuration.
|
|
280
|
+
* @returns A typed client.
|
|
281
|
+
*
|
|
282
|
+
* @example
|
|
283
|
+
* ```ts
|
|
284
|
+
* import { createClient } from "@better-agent/client";
|
|
285
|
+
*
|
|
286
|
+
* const client = createClient({
|
|
287
|
+
* baseURL: "http://localhost:3000/api",
|
|
288
|
+
* secret: "dev_secret",
|
|
289
|
+
* });
|
|
290
|
+
*
|
|
291
|
+
* const result = await client.run("helloAgent", {
|
|
292
|
+
* input: "Write one short sentence about TypeScript.",
|
|
293
|
+
* });
|
|
294
|
+
* ```
|
|
295
|
+
*
|
|
296
|
+
* @example
|
|
297
|
+
* ```ts
|
|
298
|
+
* import type ba from "./better-agent/server";
|
|
299
|
+
* import { createClient } from "@better-agent/client";
|
|
300
|
+
*
|
|
301
|
+
* const client = createClient<typeof ba>({
|
|
302
|
+
* baseURL: "http://localhost:3000/api",
|
|
303
|
+
* secret: "dev_secret",
|
|
304
|
+
* toolHandlers: {
|
|
305
|
+
* getClientTime: () => ({ now: new Date().toISOString() }),
|
|
306
|
+
* },
|
|
307
|
+
* });
|
|
308
|
+
*
|
|
309
|
+
* for await (const event of client.stream("helloAgent", {
|
|
310
|
+
* input: "Use tools if needed.",
|
|
311
|
+
* })) {
|
|
312
|
+
* console.log(event.type);
|
|
313
|
+
* }
|
|
314
|
+
* ```
|
|
315
|
+
*/
|
|
316
|
+
const createClient = (config) => {
|
|
317
|
+
const baseURL = config.baseURL.replace(/\/$/, "");
|
|
318
|
+
const doFetch = config.fetch ?? fetch;
|
|
319
|
+
const advanced = config.advanced;
|
|
320
|
+
const authHeaders = config.secret ? { authorization: `Bearer ${config.secret}` } : void 0;
|
|
321
|
+
const toolSubmissionMaxAttempts = Math.max(1, Math.floor(advanced?.toolSubmissionMaxAttempts ?? 3));
|
|
322
|
+
const toolSubmissionRetryDelayMs = Math.max(0, advanced?.toolSubmissionRetryDelayMs ?? 150);
|
|
323
|
+
const { submitToolResult, submitToolApproval } = createToolSubmissionHandlers({
|
|
324
|
+
advanced,
|
|
325
|
+
baseURL,
|
|
326
|
+
defaultHeaders: config.headers,
|
|
327
|
+
doFetch,
|
|
328
|
+
secret: config.secret,
|
|
329
|
+
toolSubmissionMaxAttempts,
|
|
330
|
+
toolSubmissionRetryDelayMs
|
|
331
|
+
});
|
|
332
|
+
return {
|
|
333
|
+
async run(agent, input, options) {
|
|
334
|
+
const request = {
|
|
335
|
+
agent,
|
|
336
|
+
...input
|
|
337
|
+
};
|
|
338
|
+
const prepared = await prepareRequest(advanced, {
|
|
339
|
+
operation: "run",
|
|
340
|
+
url: `${baseURL}/${encodeURIComponent(String(agent))}/run`,
|
|
341
|
+
method: "POST",
|
|
342
|
+
headers: mergeHeaders(authHeaders, { "content-type": "application/json" }, config.headers, options?.headers),
|
|
343
|
+
body: JSON.stringify(request)
|
|
344
|
+
});
|
|
345
|
+
const res = await doFetch(prepared.url, {
|
|
346
|
+
method: prepared.method,
|
|
347
|
+
headers: prepared.headers,
|
|
348
|
+
...prepared.body !== void 0 ? { body: prepared.body } : {},
|
|
349
|
+
signal: options?.signal ?? null
|
|
350
|
+
});
|
|
351
|
+
options?.onResponse?.(res);
|
|
352
|
+
if (!res.ok) await throwRequestError({
|
|
353
|
+
response: res,
|
|
354
|
+
operation: "run",
|
|
355
|
+
at: "client.core.run",
|
|
356
|
+
context: { agentName: agent }
|
|
357
|
+
});
|
|
358
|
+
return await res.json();
|
|
359
|
+
},
|
|
360
|
+
stream(agent, input, options) {
|
|
361
|
+
const request = {
|
|
362
|
+
agent,
|
|
363
|
+
...input
|
|
364
|
+
};
|
|
365
|
+
return (async function* () {
|
|
366
|
+
const pendingToolCalls = /* @__PURE__ */ new Map();
|
|
367
|
+
const toolCallController = new AbortController();
|
|
368
|
+
const abortToolCalls = () => {
|
|
369
|
+
if (!toolCallController.signal.aborted) toolCallController.abort();
|
|
370
|
+
};
|
|
371
|
+
const externalSignal = options?.signal;
|
|
372
|
+
const onExternalAbort = () => abortToolCalls();
|
|
373
|
+
if (externalSignal) if (externalSignal.aborted) abortToolCalls();
|
|
374
|
+
else externalSignal.addEventListener("abort", onExternalAbort, { once: true });
|
|
375
|
+
const processClientToolCall = async (toolCallKey, runId, toolCallId) => {
|
|
376
|
+
const pending = pendingToolCalls.get(toolCallKey);
|
|
377
|
+
if (!pending || pending.submitted || !pending.ended || pending.approvalState !== "waiting" && pending.approvalState !== "approved") return;
|
|
378
|
+
pending.submitted = true;
|
|
379
|
+
let parsedArgs = {};
|
|
380
|
+
try {
|
|
381
|
+
parsedArgs = pending.argsJson ? JSON.parse(pending.argsJson) : {};
|
|
382
|
+
} catch {
|
|
383
|
+
await submitToolResult({
|
|
384
|
+
agent: String(agent),
|
|
385
|
+
runId,
|
|
386
|
+
toolCallId,
|
|
387
|
+
error: `Invalid client tool arguments for '${pending.toolName}'.`
|
|
388
|
+
});
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
const context = {
|
|
392
|
+
agent,
|
|
393
|
+
runId,
|
|
394
|
+
toolCallId,
|
|
395
|
+
signal: toolCallController.signal
|
|
396
|
+
};
|
|
397
|
+
const handler = resolveToolHandler(pending.toolName, options, config.toolHandlers);
|
|
398
|
+
if (!handler) {
|
|
399
|
+
await submitToolResult({
|
|
400
|
+
agent: String(agent),
|
|
401
|
+
runId,
|
|
402
|
+
toolCallId,
|
|
403
|
+
error: `Missing client tool handler for '${pending.toolName}'.`
|
|
404
|
+
});
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
try {
|
|
408
|
+
const result = await Promise.resolve(handler({
|
|
409
|
+
toolName: pending.toolName,
|
|
410
|
+
input: parsedArgs,
|
|
411
|
+
context
|
|
412
|
+
}));
|
|
413
|
+
await submitToolResult({
|
|
414
|
+
agent: String(agent),
|
|
415
|
+
runId,
|
|
416
|
+
toolCallId,
|
|
417
|
+
result
|
|
418
|
+
});
|
|
419
|
+
} catch (error) {
|
|
420
|
+
await submitToolResult({
|
|
421
|
+
agent: String(agent),
|
|
422
|
+
runId,
|
|
423
|
+
toolCallId,
|
|
424
|
+
error: error instanceof Error ? error.message : "Client tool handler failed."
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
};
|
|
428
|
+
try {
|
|
429
|
+
const prepared = await prepareRequest(advanced, {
|
|
430
|
+
operation: "stream",
|
|
431
|
+
url: `${baseURL}/${encodeURIComponent(String(agent))}/run`,
|
|
432
|
+
method: "POST",
|
|
433
|
+
headers: mergeHeaders(authHeaders, {
|
|
434
|
+
"content-type": "application/json",
|
|
435
|
+
accept: "text/event-stream"
|
|
436
|
+
}, config.headers, options?.headers),
|
|
437
|
+
body: JSON.stringify({
|
|
438
|
+
...request,
|
|
439
|
+
stream: true
|
|
440
|
+
})
|
|
441
|
+
});
|
|
442
|
+
const res = await doFetch(prepared.url, {
|
|
443
|
+
method: prepared.method,
|
|
444
|
+
headers: prepared.headers,
|
|
445
|
+
body: prepared.body,
|
|
446
|
+
signal: options?.signal ?? null
|
|
447
|
+
});
|
|
448
|
+
options?.onResponse?.(res);
|
|
449
|
+
if (!res.ok) await throwRequestError({
|
|
450
|
+
response: res,
|
|
451
|
+
operation: "stream",
|
|
452
|
+
at: "client.core.stream",
|
|
453
|
+
context: { agentName: agent }
|
|
454
|
+
});
|
|
455
|
+
if (!res.body) return;
|
|
456
|
+
for await (const ev of parseSse(res.body)) {
|
|
457
|
+
if (ev.type === "TOOL_CALL_START") {
|
|
458
|
+
if (ev.toolTarget !== "client") {
|
|
459
|
+
yield ev;
|
|
460
|
+
continue;
|
|
461
|
+
}
|
|
462
|
+
if (typeof ev.runId === "string") pendingToolCalls.set(getToolCallKey(ev.runId, ev.toolCallId), {
|
|
463
|
+
toolName: ev.toolCallName,
|
|
464
|
+
argsJson: "",
|
|
465
|
+
ended: false,
|
|
466
|
+
submitted: false,
|
|
467
|
+
approvalState: "waiting"
|
|
468
|
+
});
|
|
469
|
+
} else if (ev.type === "TOOL_CALL_ARGS") {
|
|
470
|
+
if (typeof ev.runId !== "string") {
|
|
471
|
+
yield ev;
|
|
472
|
+
continue;
|
|
473
|
+
}
|
|
474
|
+
const pending = pendingToolCalls.get(getToolCallKey(ev.runId, ev.toolCallId));
|
|
475
|
+
if (pending) pending.argsJson += ev.delta;
|
|
476
|
+
} else if (ev.type === "TOOL_CALL_END" && typeof ev.runId === "string") {
|
|
477
|
+
const toolCallKey = getToolCallKey(ev.runId, ev.toolCallId);
|
|
478
|
+
const pending = pendingToolCalls.get(toolCallKey);
|
|
479
|
+
if (pending) {
|
|
480
|
+
pending.ended = true;
|
|
481
|
+
await processClientToolCall(toolCallKey, ev.runId, ev.toolCallId);
|
|
482
|
+
}
|
|
483
|
+
} else if ((ev.type === "TOOL_APPROVAL_REQUIRED" || ev.type === "TOOL_APPROVAL_UPDATED") && ev.toolTarget === "client" && typeof ev.runId === "string") {
|
|
484
|
+
const toolCallKey = getToolCallKey(ev.runId, ev.toolCallId);
|
|
485
|
+
const pending = pendingToolCalls.get(toolCallKey);
|
|
486
|
+
if (pending) {
|
|
487
|
+
pending.approvalState = ev.state === "approved" ? "approved" : ev.state === "requested" ? "requested" : ev.state === "denied" ? "denied" : "expired";
|
|
488
|
+
if (pending.approvalState === "denied" || pending.approvalState === "expired") {
|
|
489
|
+
pending.submitted = true;
|
|
490
|
+
pendingToolCalls.delete(toolCallKey);
|
|
491
|
+
} else if (pending.approvalState === "approved") await processClientToolCall(toolCallKey, ev.runId, ev.toolCallId);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
if (ev.type === "RUN_ABORTED" || ev.type === "RUN_ERROR" || ev.type === "RUN_FINISHED") {
|
|
495
|
+
for (const [toolCallKey, pending] of pendingToolCalls) if (pending.submitted) pendingToolCalls.delete(toolCallKey);
|
|
496
|
+
abortToolCalls();
|
|
497
|
+
}
|
|
498
|
+
yield ev;
|
|
499
|
+
}
|
|
500
|
+
} finally {
|
|
501
|
+
abortToolCalls();
|
|
502
|
+
if (externalSignal) externalSignal.removeEventListener("abort", onExternalAbort);
|
|
503
|
+
pendingToolCalls.clear();
|
|
504
|
+
}
|
|
505
|
+
})();
|
|
506
|
+
},
|
|
507
|
+
resumeStream(agent, input, options) {
|
|
508
|
+
return (async function* () {
|
|
509
|
+
const searchParams = new URLSearchParams();
|
|
510
|
+
searchParams.set("streamId", input.streamId);
|
|
511
|
+
const headers = mergeHeaders(authHeaders, { accept: "text/event-stream" }, config.headers, options?.headers);
|
|
512
|
+
if (typeof input.afterSeq === "number") headers.set("last-event-id", String(input.afterSeq));
|
|
513
|
+
const prepared = await prepareRequest(advanced, {
|
|
514
|
+
operation: "resume-stream",
|
|
515
|
+
url: `${baseURL}/${encodeURIComponent(String(agent))}/stream-events/resume?${searchParams.toString()}`,
|
|
516
|
+
method: "GET",
|
|
517
|
+
headers
|
|
518
|
+
});
|
|
519
|
+
const res = await doFetch(prepared.url, {
|
|
520
|
+
method: prepared.method,
|
|
521
|
+
headers: prepared.headers,
|
|
522
|
+
...prepared.body !== void 0 ? { body: prepared.body } : {},
|
|
523
|
+
signal: options?.signal ?? null
|
|
524
|
+
});
|
|
525
|
+
options?.onResponse?.(res);
|
|
526
|
+
if (res.status === 204) return;
|
|
527
|
+
if (!res.ok) await throwRequestError({
|
|
528
|
+
response: res,
|
|
529
|
+
operation: "resume-stream",
|
|
530
|
+
at: "client.core.resumeStream",
|
|
531
|
+
context: { agentName: agent }
|
|
532
|
+
});
|
|
533
|
+
if (!res.body) return;
|
|
534
|
+
for await (const ev of parseSse(res.body)) yield ev;
|
|
535
|
+
})();
|
|
536
|
+
},
|
|
537
|
+
resumeConversation(agent, input, options) {
|
|
538
|
+
return (async function* () {
|
|
539
|
+
const headers = mergeHeaders(authHeaders, { accept: "text/event-stream" }, config.headers, options?.headers);
|
|
540
|
+
if (typeof input.afterSeq === "number") headers.set("last-event-id", String(input.afterSeq));
|
|
541
|
+
const prepared = await prepareRequest(advanced, {
|
|
542
|
+
operation: "resume-conversation",
|
|
543
|
+
url: `${baseURL}/${encodeURIComponent(String(agent))}/conversations/${encodeURIComponent(input.conversationId)}/resume`,
|
|
544
|
+
method: "GET",
|
|
545
|
+
headers
|
|
546
|
+
});
|
|
547
|
+
const res = await doFetch(prepared.url, {
|
|
548
|
+
method: prepared.method,
|
|
549
|
+
headers: prepared.headers,
|
|
550
|
+
...prepared.body !== void 0 ? { body: prepared.body } : {},
|
|
551
|
+
signal: options?.signal ?? null
|
|
552
|
+
});
|
|
553
|
+
options?.onResponse?.(res);
|
|
554
|
+
if (res.status === 204) return;
|
|
555
|
+
if (!res.ok) await throwRequestError({
|
|
556
|
+
response: res,
|
|
557
|
+
operation: "resume-conversation",
|
|
558
|
+
at: "client.core.resumeConversation",
|
|
559
|
+
context: {
|
|
560
|
+
agentName: agent,
|
|
561
|
+
conversationId: input.conversationId
|
|
562
|
+
}
|
|
563
|
+
});
|
|
564
|
+
if (!res.body) return;
|
|
565
|
+
for await (const ev of parseSse(res.body)) yield ev;
|
|
566
|
+
})();
|
|
567
|
+
},
|
|
568
|
+
async loadConversation(agent, conversationId, options) {
|
|
569
|
+
const prepared = await prepareRequest(advanced, {
|
|
570
|
+
operation: "load-conversation",
|
|
571
|
+
url: `${baseURL}/${encodeURIComponent(String(agent))}/conversations/${encodeURIComponent(conversationId)}`,
|
|
572
|
+
method: "GET",
|
|
573
|
+
headers: mergeHeaders(authHeaders, config.headers, options?.headers)
|
|
574
|
+
});
|
|
575
|
+
const res = await doFetch(prepared.url, {
|
|
576
|
+
method: prepared.method,
|
|
577
|
+
headers: prepared.headers,
|
|
578
|
+
...prepared.body !== void 0 ? { body: prepared.body } : {},
|
|
579
|
+
signal: options?.signal ?? null
|
|
580
|
+
});
|
|
581
|
+
options?.onResponse?.(res);
|
|
582
|
+
if (res.status === 204) return null;
|
|
583
|
+
if (!res.ok) await throwRequestError({
|
|
584
|
+
response: res,
|
|
585
|
+
operation: "load-conversation",
|
|
586
|
+
at: "client.core.loadConversation",
|
|
587
|
+
context: {
|
|
588
|
+
agentName: agent,
|
|
589
|
+
conversationId
|
|
590
|
+
}
|
|
591
|
+
});
|
|
592
|
+
return await res.json();
|
|
593
|
+
},
|
|
594
|
+
async abortRun(req, options) {
|
|
595
|
+
const prepared = await prepareRequest(advanced, {
|
|
596
|
+
operation: "abort-run",
|
|
597
|
+
url: `${baseURL}/${encodeURIComponent(String(req.agent))}/runs/${encodeURIComponent(req.runId)}/abort`,
|
|
598
|
+
method: "POST",
|
|
599
|
+
headers: mergeHeaders(authHeaders, config.headers, options?.headers)
|
|
600
|
+
});
|
|
601
|
+
const res = await doFetch(prepared.url, {
|
|
602
|
+
method: prepared.method,
|
|
603
|
+
headers: prepared.headers,
|
|
604
|
+
...prepared.body !== void 0 ? { body: prepared.body } : {},
|
|
605
|
+
signal: options?.signal ?? null
|
|
606
|
+
});
|
|
607
|
+
options?.onResponse?.(res);
|
|
608
|
+
if (res.status === 204) return;
|
|
609
|
+
if (!res.ok) await throwRequestError({
|
|
610
|
+
response: res,
|
|
611
|
+
operation: "abort-run",
|
|
612
|
+
at: "client.core.abortRun",
|
|
613
|
+
context: {
|
|
614
|
+
agentName: req.agent,
|
|
615
|
+
runId: req.runId
|
|
616
|
+
}
|
|
617
|
+
});
|
|
618
|
+
},
|
|
619
|
+
async submitToolResult(req) {
|
|
620
|
+
await submitToolResult({
|
|
621
|
+
...req,
|
|
622
|
+
agent: String(req.agent)
|
|
623
|
+
});
|
|
624
|
+
},
|
|
625
|
+
async submitToolApproval(req) {
|
|
626
|
+
await submitToolApproval({
|
|
627
|
+
...req,
|
|
628
|
+
agent: String(req.agent)
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
};
|
|
632
|
+
};
|
|
633
|
+
const getToolCallKey = (runId, toolCallId) => `${runId}:${toolCallId}`;
|
|
634
|
+
const resolveToolHandler = (toolName, options, staticToolHandlers) => {
|
|
635
|
+
const requestHandler = options?.onToolCall;
|
|
636
|
+
const requestToolHandler = (options?.toolHandlers)?.[toolName];
|
|
637
|
+
const staticHandler = staticToolHandlers?.[toolName];
|
|
638
|
+
return requestHandler ?? (requestToolHandler ? (params) => requestToolHandler(params.input, params.context) : void 0) ?? (staticHandler ? (params) => staticHandler(params.input, params.context) : void 0);
|
|
639
|
+
};
|
|
640
|
+
|
|
641
|
+
//#endregion
|
|
642
|
+
export { createAgentChatController, createClient, fromConversationItems, fromModelMessages, getEventErrorMessage, pruneInputByCapabilities, toAgentClientError, toModelMessages };
|
|
643
|
+
//# sourceMappingURL=index.mjs.map
|