@better-agent/client 0.1.0-canary.6 → 0.2.0-beta.1
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-B7G0xSYW.d.mts +365 -0
- package/dist/controller-B7G0xSYW.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
package/dist/index.mjs
CHANGED
|
@@ -1,643 +1,287 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
import { Events } from "@better-agent/core/events";
|
|
3
|
-
import { BetterAgentError } from "@better-agent/shared/errors";
|
|
4
|
-
import { pruneInputByCapabilities } from "@better-agent/core";
|
|
1
|
+
import { a as BetterAgentClientError, i as toAgentMessages, n as createAgentController, o as toBetterAgentClientError, r as fromAgentMessages, t as AgentController } from "./controller-xMlzSCc7.mjs";
|
|
5
2
|
|
|
6
3
|
//#region src/core/sse.ts
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
const
|
|
4
|
+
const createFrame = () => ({ data: "" });
|
|
5
|
+
const getErrorPayload = (data) => {
|
|
6
|
+
const fallbackMessage = data.trim().length > 0 ? data.trim() : "Stream failed.";
|
|
10
7
|
try {
|
|
11
8
|
const parsed = JSON.parse(data);
|
|
12
|
-
if (typeof parsed
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
};
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
options.onId?.(frame.id);
|
|
27
|
-
}
|
|
28
|
-
return parsed;
|
|
9
|
+
if (!parsed || typeof parsed !== "object") return {
|
|
10
|
+
message: fallbackMessage,
|
|
11
|
+
details: parsed
|
|
12
|
+
};
|
|
13
|
+
const messageField = "message" in parsed && typeof parsed.message === "string" && parsed.message.trim().length > 0 ? parsed.message : void 0;
|
|
14
|
+
const detailField = "detail" in parsed && typeof parsed.detail === "string" && parsed.detail.trim().length > 0 ? parsed.detail : void 0;
|
|
15
|
+
const codeField = "code" in parsed && typeof parsed.code === "string" ? parsed.code : void 0;
|
|
16
|
+
const statusField = "status" in parsed && typeof parsed.status === "number" && Number.isFinite(parsed.status) ? parsed.status : void 0;
|
|
17
|
+
return {
|
|
18
|
+
message: messageField ?? detailField ?? fallbackMessage,
|
|
19
|
+
code: codeField,
|
|
20
|
+
status: statusField,
|
|
21
|
+
details: parsed
|
|
22
|
+
};
|
|
29
23
|
} catch {
|
|
30
|
-
return
|
|
24
|
+
return { message: fallbackMessage };
|
|
31
25
|
}
|
|
32
26
|
};
|
|
33
|
-
|
|
27
|
+
const toSseError = (data) => {
|
|
28
|
+
const payload = getErrorPayload(data);
|
|
29
|
+
return new BetterAgentClientError(payload.message, {
|
|
30
|
+
code: payload.code,
|
|
31
|
+
status: payload.status,
|
|
32
|
+
details: payload.details
|
|
33
|
+
});
|
|
34
|
+
};
|
|
35
|
+
const parseFrame = (frame) => {
|
|
36
|
+
if (frame.data.length === 0) return null;
|
|
37
|
+
const data = frame.data.replace(/\n$/, "");
|
|
38
|
+
if (frame.event === "error") throw toSseError(data);
|
|
39
|
+
const parsed = JSON.parse(data);
|
|
40
|
+
if (frame.id !== void 0 && parsed && typeof parsed === "object") return {
|
|
41
|
+
...parsed,
|
|
42
|
+
seq: frame.id
|
|
43
|
+
};
|
|
44
|
+
return parsed;
|
|
45
|
+
};
|
|
46
|
+
async function* parseSse(body) {
|
|
34
47
|
const reader = body.getReader();
|
|
35
48
|
const decoder = new TextDecoder();
|
|
36
49
|
let buffer = "";
|
|
37
|
-
let frame =
|
|
38
|
-
|
|
50
|
+
let frame = createFrame();
|
|
51
|
+
for (;;) {
|
|
39
52
|
const { done, value } = await reader.read();
|
|
40
53
|
if (done) break;
|
|
41
54
|
buffer += decoder.decode(value, { stream: true });
|
|
42
55
|
for (;;) {
|
|
43
|
-
const
|
|
44
|
-
if (
|
|
45
|
-
const line = buffer.slice(0,
|
|
46
|
-
buffer = buffer.slice(
|
|
56
|
+
const newlineIndex = buffer.indexOf("\n");
|
|
57
|
+
if (newlineIndex < 0) break;
|
|
58
|
+
const line = buffer.slice(0, newlineIndex).replace(/\r$/, "");
|
|
59
|
+
buffer = buffer.slice(newlineIndex + 1);
|
|
47
60
|
if (line === "") {
|
|
48
|
-
const event = parseFrame(frame
|
|
49
|
-
frame =
|
|
61
|
+
const event = parseFrame(frame);
|
|
62
|
+
frame = createFrame();
|
|
50
63
|
if (event) yield event;
|
|
51
64
|
continue;
|
|
52
65
|
}
|
|
53
66
|
if (line.startsWith("event:")) {
|
|
54
|
-
frame.
|
|
67
|
+
frame.event = line.slice(6).trim();
|
|
55
68
|
continue;
|
|
56
69
|
}
|
|
57
70
|
if (line.startsWith("id:")) {
|
|
58
|
-
const
|
|
59
|
-
frame.id = Number.isFinite(
|
|
71
|
+
const id = Number(line.slice(3).trim());
|
|
72
|
+
frame.id = Number.isFinite(id) ? id : void 0;
|
|
60
73
|
continue;
|
|
61
74
|
}
|
|
62
75
|
if (line.startsWith("data:")) frame.data += `${line.slice(5).trim()}\n`;
|
|
63
76
|
}
|
|
64
77
|
}
|
|
65
|
-
const
|
|
66
|
-
if (
|
|
78
|
+
const event = parseFrame(frame);
|
|
79
|
+
if (event) yield event;
|
|
67
80
|
}
|
|
68
81
|
|
|
69
82
|
//#endregion
|
|
70
|
-
//#region src/core/
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
};
|
|
83
|
+
//#region src/core/url.ts
|
|
84
|
+
const ABSOLUTE_URL_PATTERN = /^[a-zA-Z][a-zA-Z\d+\-.]*:\/\//;
|
|
85
|
+
const isAbsoluteURL = (url) => ABSOLUTE_URL_PATTERN.test(url);
|
|
86
|
+
async function resolveRequestURL(url) {
|
|
87
|
+
if (isAbsoluteURL(url)) return url;
|
|
88
|
+
if (typeof window !== "undefined") return url;
|
|
89
|
+
throw new Error("Relative Better Agent URLs work only in browser contexts. Use an absolute URL on the server or call the app directly.");
|
|
90
|
+
}
|
|
176
91
|
|
|
177
92
|
//#endregion
|
|
178
|
-
//#region src/core/
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
const
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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);
|
|
93
|
+
//#region src/core/request.ts
|
|
94
|
+
function joinURL(baseURL, path) {
|
|
95
|
+
return `${baseURL.replace(/\/$/, "")}/${path.replace(/^\//, "")}`;
|
|
96
|
+
}
|
|
97
|
+
async function resolveHeaders(configHeaders, requestHeaders) {
|
|
98
|
+
const headers = new Headers(typeof configHeaders === "function" ? await configHeaders() : configHeaders);
|
|
99
|
+
if (requestHeaders) for (const [key, value] of new Headers(requestHeaders)) headers.set(key, value);
|
|
100
|
+
return headers;
|
|
101
|
+
}
|
|
102
|
+
async function prepareRequest(config, request) {
|
|
103
|
+
return await config.prepareRequest?.(request) ?? request;
|
|
104
|
+
}
|
|
105
|
+
async function throwRequestError(response) {
|
|
106
|
+
let details;
|
|
107
|
+
let code;
|
|
108
|
+
let message = `Better Agent request failed with ${response.status}.`;
|
|
109
|
+
try {
|
|
110
|
+
const body = await response.json();
|
|
111
|
+
details = body;
|
|
112
|
+
if (body && typeof body === "object") {
|
|
113
|
+
if ("message" in body && typeof body.message === "string") message = body.message;
|
|
114
|
+
else if ("detail" in body && typeof body.detail === "string") message = body.detail;
|
|
115
|
+
if ("code" in body && typeof body.code === "string") code = body.code;
|
|
266
116
|
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
};
|
|
117
|
+
} catch {}
|
|
118
|
+
throw new BetterAgentClientError(message, {
|
|
119
|
+
status: response.status,
|
|
120
|
+
code,
|
|
121
|
+
details
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
async function requestJson(config, request, options) {
|
|
125
|
+
const headers = await resolveHeaders(config.headers, options?.headers);
|
|
126
|
+
if (!headers.has("content-type") && request.body !== void 0) headers.set("content-type", "application/json");
|
|
127
|
+
const prepared = await prepareRequest(config, {
|
|
128
|
+
url: request.url,
|
|
129
|
+
method: request.method,
|
|
130
|
+
headers,
|
|
131
|
+
body: request.body === void 0 ? void 0 : JSON.stringify(request.body)
|
|
132
|
+
});
|
|
133
|
+
const url = await resolveRequestURL(prepared.url);
|
|
134
|
+
const response = await (config.fetch ?? fetch)(url, {
|
|
135
|
+
method: prepared.method,
|
|
136
|
+
headers: prepared.headers,
|
|
137
|
+
body: prepared.body,
|
|
138
|
+
credentials: config.credentials,
|
|
139
|
+
signal: options?.signal ?? null
|
|
140
|
+
});
|
|
141
|
+
if (!response.ok) await throwRequestError(response);
|
|
142
|
+
if (response.status === 204) return;
|
|
143
|
+
return response.json();
|
|
144
|
+
}
|
|
145
|
+
async function requestSse(config, request, options) {
|
|
146
|
+
const headers = await resolveHeaders(config.headers, options?.headers);
|
|
147
|
+
headers.set("accept", "text/event-stream");
|
|
148
|
+
if (!headers.has("content-type") && request.body !== void 0) headers.set("content-type", "application/json");
|
|
149
|
+
const prepared = await prepareRequest(config, {
|
|
150
|
+
url: request.url,
|
|
151
|
+
method: request.method,
|
|
152
|
+
headers,
|
|
153
|
+
body: request.body === void 0 ? void 0 : JSON.stringify(request.body)
|
|
154
|
+
});
|
|
155
|
+
const url = await resolveRequestURL(prepared.url);
|
|
156
|
+
const response = await (config.fetch ?? fetch)(url, {
|
|
157
|
+
method: prepared.method,
|
|
158
|
+
headers: prepared.headers,
|
|
159
|
+
body: prepared.body,
|
|
160
|
+
credentials: config.credentials,
|
|
161
|
+
signal: options?.signal ?? null
|
|
162
|
+
});
|
|
163
|
+
if (!response.ok) await throwRequestError(response);
|
|
164
|
+
if (!response.body) return (async function* () {})();
|
|
165
|
+
return parseSse(response.body);
|
|
166
|
+
}
|
|
273
167
|
|
|
274
168
|
//#endregion
|
|
275
|
-
//#region src/
|
|
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) => {
|
|
169
|
+
//#region src/create-client.ts
|
|
170
|
+
function createClient(config) {
|
|
317
171
|
const baseURL = config.baseURL.replace(/\/$/, "");
|
|
318
|
-
const
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
const
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
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
|
-
})();
|
|
172
|
+
const withLimit = (url, limit) => {
|
|
173
|
+
if (typeof limit !== "number") return url;
|
|
174
|
+
return `${url}${url.includes("?") ? "&" : "?"}limit=${encodeURIComponent(String(limit))}`;
|
|
175
|
+
};
|
|
176
|
+
const withParam = (url, key, value) => {
|
|
177
|
+
if (value === void 0) return url;
|
|
178
|
+
return `${url}${url.includes("?") ? "&" : "?"}${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
|
|
179
|
+
};
|
|
180
|
+
const runs = {
|
|
181
|
+
async abort(runId, options) {
|
|
182
|
+
await requestJson(config, {
|
|
183
|
+
url: joinURL(baseURL, `/runs/${encodeURIComponent(runId)}/abort`),
|
|
184
|
+
method: "POST"
|
|
185
|
+
}, options);
|
|
536
186
|
},
|
|
537
|
-
|
|
187
|
+
resumeStream(input, options) {
|
|
538
188
|
return (async function* () {
|
|
539
|
-
const headers =
|
|
540
|
-
if (typeof input.
|
|
541
|
-
const
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
189
|
+
const headers = new Headers(options?.headers);
|
|
190
|
+
if (typeof input.afterSequence === "number") headers.set("last-event-id", String(input.afterSequence));
|
|
191
|
+
const events = await requestSse(config, {
|
|
192
|
+
url: joinURL(baseURL, `/runs/${encodeURIComponent(input.runId)}/stream`),
|
|
193
|
+
method: "GET"
|
|
194
|
+
}, {
|
|
195
|
+
...options,
|
|
545
196
|
headers
|
|
546
197
|
});
|
|
547
|
-
const
|
|
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;
|
|
198
|
+
for await (const event of events) yield event;
|
|
566
199
|
})();
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
const
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
const agentHandles = /* @__PURE__ */ new Map();
|
|
203
|
+
return {
|
|
204
|
+
agent(agentName) {
|
|
205
|
+
const agentKey = String(agentName);
|
|
206
|
+
const cached = agentHandles.get(agentKey);
|
|
207
|
+
if (cached) return cached;
|
|
208
|
+
const encodedAgentName = encodeURIComponent(agentKey);
|
|
209
|
+
const runURL = joinURL(baseURL, `/${encodedAgentName}/run`);
|
|
210
|
+
const handle = {
|
|
211
|
+
runs,
|
|
212
|
+
run(input, options) {
|
|
213
|
+
return requestJson(config, {
|
|
214
|
+
url: runURL,
|
|
215
|
+
method: "POST",
|
|
216
|
+
body: input
|
|
217
|
+
}, options);
|
|
218
|
+
},
|
|
219
|
+
stream(input, options) {
|
|
220
|
+
return (async function* () {
|
|
221
|
+
const events = await requestSse(config, {
|
|
222
|
+
url: runURL,
|
|
223
|
+
method: "POST",
|
|
224
|
+
body: input
|
|
225
|
+
}, options);
|
|
226
|
+
for await (const event of events) yield event;
|
|
227
|
+
})();
|
|
228
|
+
},
|
|
229
|
+
memory: {
|
|
230
|
+
threads: {
|
|
231
|
+
async list(input, options) {
|
|
232
|
+
return (await requestJson(config, {
|
|
233
|
+
url: withLimit(joinURL(baseURL, `/${encodedAgentName}/threads`), input?.limit),
|
|
234
|
+
method: "GET"
|
|
235
|
+
}, options)).threads;
|
|
236
|
+
},
|
|
237
|
+
create(input, options) {
|
|
238
|
+
return requestJson(config, {
|
|
239
|
+
url: joinURL(baseURL, `/${encodedAgentName}/threads`),
|
|
240
|
+
method: "POST",
|
|
241
|
+
body: input ?? {}
|
|
242
|
+
}, options);
|
|
243
|
+
},
|
|
244
|
+
get(threadId, options) {
|
|
245
|
+
return requestJson(config, {
|
|
246
|
+
url: joinURL(baseURL, `/${encodedAgentName}/threads/${encodeURIComponent(threadId)}`),
|
|
247
|
+
method: "GET"
|
|
248
|
+
}, options);
|
|
249
|
+
},
|
|
250
|
+
update(threadId, input, options) {
|
|
251
|
+
return requestJson(config, {
|
|
252
|
+
url: joinURL(baseURL, `/${encodedAgentName}/threads/${encodeURIComponent(threadId)}`),
|
|
253
|
+
method: "PATCH",
|
|
254
|
+
body: input
|
|
255
|
+
}, options);
|
|
256
|
+
},
|
|
257
|
+
async delete(threadId, options) {
|
|
258
|
+
await requestJson(config, {
|
|
259
|
+
url: joinURL(baseURL, `/${encodedAgentName}/threads/${encodeURIComponent(threadId)}`),
|
|
260
|
+
method: "DELETE"
|
|
261
|
+
}, options);
|
|
262
|
+
},
|
|
263
|
+
runtime(threadId, options) {
|
|
264
|
+
return requestJson(config, {
|
|
265
|
+
url: joinURL(baseURL, `/${encodedAgentName}/threads/${encodeURIComponent(threadId)}/runtime`),
|
|
266
|
+
method: "GET"
|
|
267
|
+
}, options);
|
|
268
|
+
}
|
|
269
|
+
},
|
|
270
|
+
messages: { async list(threadId, input, options) {
|
|
271
|
+
return (await requestJson(config, {
|
|
272
|
+
url: withParam(withLimit(joinURL(baseURL, `/${encodedAgentName}/threads/${encodeURIComponent(threadId)}/messages`), input?.limit), "beforeRunId", input?.beforeRunId),
|
|
273
|
+
method: "GET"
|
|
274
|
+
}, options)).messages;
|
|
275
|
+
} }
|
|
616
276
|
}
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
await submitToolResult({
|
|
621
|
-
...req,
|
|
622
|
-
agent: String(req.agent)
|
|
623
|
-
});
|
|
277
|
+
};
|
|
278
|
+
agentHandles.set(agentKey, handle);
|
|
279
|
+
return handle;
|
|
624
280
|
},
|
|
625
|
-
|
|
626
|
-
await submitToolApproval({
|
|
627
|
-
...req,
|
|
628
|
-
agent: String(req.agent)
|
|
629
|
-
});
|
|
630
|
-
}
|
|
281
|
+
runs
|
|
631
282
|
};
|
|
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
|
-
};
|
|
283
|
+
}
|
|
640
284
|
|
|
641
285
|
//#endregion
|
|
642
|
-
export {
|
|
286
|
+
export { AgentController, BetterAgentClientError, createAgentController, createClient, fromAgentMessages, toAgentMessages, toBetterAgentClientError };
|
|
643
287
|
//# sourceMappingURL=index.mjs.map
|