@alfe.ai/agent-api-client 0.14.0 → 0.15.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 +23 -1
- package/dist/index.cjs +182 -62
- package/dist/index.d.cts +113 -56
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +113 -56
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +182 -62
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# @alfe.ai/agent-api-client
|
|
2
2
|
|
|
3
|
-
Agent self-service API client
|
|
3
|
+
Agent self-service API client for Alfe's agent-authenticated endpoints. It
|
|
4
|
+
resolves identity from the bearer token, so callers never place their own
|
|
5
|
+
`agentId` in a self-service path.
|
|
4
6
|
|
|
5
7
|
Part of [**Alfe**](https://alfe.ai) — the operating system for AI agents: build, deploy, and run agents with persistent memory, identity, integrations, and channels. See the [documentation](https://docs.alfe.ai) to get started.
|
|
6
8
|
|
|
@@ -10,6 +12,26 @@ Part of [**Alfe**](https://alfe.ai) — the operating system for AI agents: buil
|
|
|
10
12
|
npm install @alfe.ai/agent-api-client
|
|
11
13
|
```
|
|
12
14
|
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { AgentApiClient } from "@alfe.ai/agent-api-client";
|
|
19
|
+
|
|
20
|
+
const client = new AgentApiClient({ apiKey, apiUrl });
|
|
21
|
+
const manifest = await client.syncGetManifest();
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Construct one client for the daemon/plugin lifetime and reuse it. JSON methods
|
|
25
|
+
unwrap Alfe's `{ data }` response envelope and attach the HTTP status to thrown
|
|
26
|
+
errors. Safe reads retry one transient failure; mutations do not retry unless a
|
|
27
|
+
method has an explicit server-side idempotency contract. S3 presigned URLs and
|
|
28
|
+
raw voice audio use their dedicated binary/direct-transfer paths.
|
|
29
|
+
|
|
30
|
+
The flat `AgentApiClient` surface is assembled from domain modules for chat,
|
|
31
|
+
Connect credentials, database, identity, integrations, knowledge, memory,
|
|
32
|
+
mobile, search, secrets, sync/shared files, voice, webhooks, and workspace
|
|
33
|
+
configuration.
|
|
34
|
+
|
|
13
35
|
## Links
|
|
14
36
|
|
|
15
37
|
- 🌐 Website: <https://alfe.ai>
|
package/dist/index.cjs
CHANGED
|
@@ -20,7 +20,14 @@ function firstFrame(err) {
|
|
|
20
20
|
return frame ? ` (${frame.trim()})` : "";
|
|
21
21
|
}
|
|
22
22
|
function buildLine(plugin, tool, kind, message, frame = "") {
|
|
23
|
-
|
|
23
|
+
const safeToken = (value) => value.replace(/[^A-Za-z0-9_.@/-]+/g, "_").slice(0, 80);
|
|
24
|
+
const stripControls = (value) => Array.from(value, (character) => {
|
|
25
|
+
const code = character.charCodeAt(0);
|
|
26
|
+
return code < 32 || code >= 127 && code <= 159 ? " " : character;
|
|
27
|
+
}).join("");
|
|
28
|
+
const oneLine = stripControls(message).replace(/\s+/g, " ").trim();
|
|
29
|
+
const safeFrame = stripControls(frame).replace(/\s+/g, " ");
|
|
30
|
+
return `[ERROR] alfe-tool plugin=${safeToken(plugin)} tool=${safeToken(tool)} ${kind}: ${oneLine}${safeFrame}`.slice(0, 480);
|
|
24
31
|
}
|
|
25
32
|
function wrapExecute(tool, opts) {
|
|
26
33
|
const execute = tool.execute;
|
|
@@ -57,7 +64,6 @@ function installToolErrorCapture(api, options) {
|
|
|
57
64
|
try {
|
|
58
65
|
const markedApi = api;
|
|
59
66
|
if (markedApi[INSTALLED_MARKER]) return;
|
|
60
|
-
markedApi[INSTALLED_MARKER] = true;
|
|
61
67
|
const emit = options.emit ?? ((line) => {
|
|
62
68
|
process.stderr.write(`${line}\n`);
|
|
63
69
|
});
|
|
@@ -66,24 +72,29 @@ function installToolErrorCapture(api, options) {
|
|
|
66
72
|
emit
|
|
67
73
|
};
|
|
68
74
|
const original = api.registerTool.bind(api);
|
|
69
|
-
|
|
75
|
+
const wrappedRegisterTool = (...args) => {
|
|
76
|
+
let preparedArgs = args;
|
|
70
77
|
try {
|
|
71
78
|
const [first, ...rest] = args;
|
|
72
79
|
if (typeof first === "function") {
|
|
73
80
|
const factory = first;
|
|
74
81
|
const wrappedFactory = (...fa) => {
|
|
75
82
|
const tool = factory(...fa);
|
|
76
|
-
if (typeof tool === "object" && tool !== null)
|
|
83
|
+
if (typeof tool === "object" && tool !== null) try {
|
|
84
|
+
wrapExecute(tool, opts);
|
|
85
|
+
} catch {}
|
|
77
86
|
return tool;
|
|
78
87
|
};
|
|
79
|
-
|
|
88
|
+
preparedArgs = [wrappedFactory, ...rest];
|
|
80
89
|
}
|
|
81
90
|
if (typeof first === "object" && first !== null) wrapExecute(first, opts);
|
|
82
|
-
return original(first, ...rest);
|
|
83
91
|
} catch {
|
|
84
|
-
|
|
92
|
+
preparedArgs = args;
|
|
85
93
|
}
|
|
94
|
+
return original(...preparedArgs);
|
|
86
95
|
};
|
|
96
|
+
api.registerTool = wrappedRegisterTool;
|
|
97
|
+
markedApi[INSTALLED_MARKER] = true;
|
|
87
98
|
} catch {}
|
|
88
99
|
}
|
|
89
100
|
//#endregion
|
|
@@ -122,6 +133,10 @@ const RETRYABLE_STATUS = new Set([
|
|
|
122
133
|
504
|
|
123
134
|
]);
|
|
124
135
|
const RETRY_DELAY_MS = 500;
|
|
136
|
+
function isSafeRetryMethod(method) {
|
|
137
|
+
const normalized = (method ?? "GET").toUpperCase();
|
|
138
|
+
return normalized === "GET" || normalized === "HEAD" || normalized === "OPTIONS";
|
|
139
|
+
}
|
|
125
140
|
function sleep(ms) {
|
|
126
141
|
return new Promise((resolve) => {
|
|
127
142
|
setTimeout(resolve, ms);
|
|
@@ -133,6 +148,12 @@ function isRetryableNetworkError(err) {
|
|
|
133
148
|
if (err.name === "TypeError") return true;
|
|
134
149
|
return false;
|
|
135
150
|
}
|
|
151
|
+
/** Whether a completed request may be retried/polled without masking a real client error. */
|
|
152
|
+
function isTransientRequestError(err) {
|
|
153
|
+
if (isRetryableNetworkError(err)) return true;
|
|
154
|
+
const status = err?.status;
|
|
155
|
+
return typeof status === "number" && RETRYABLE_STATUS.has(status);
|
|
156
|
+
}
|
|
136
157
|
var AgentApiTransport = class {
|
|
137
158
|
apiKey;
|
|
138
159
|
apiUrl;
|
|
@@ -145,16 +166,17 @@ var AgentApiTransport = class {
|
|
|
145
166
|
* `Content-Type: application/json` and parses a `{ data: T }` envelope,
|
|
146
167
|
* neither of which fits a raw-audio flow (voice TTS/STT), so those go
|
|
147
168
|
* through this instead. Auth (Bearer), the request budget, and the single
|
|
148
|
-
* retry on transient 5xx / network errors
|
|
149
|
-
* `request()`.
|
|
150
|
-
*
|
|
151
|
-
*
|
|
169
|
+
* retry policy on transient 5xx / network errors is kept in sync with
|
|
170
|
+
* `request()`. Safe read methods retry once by default; mutation methods do
|
|
171
|
+
* not, because a response can be lost after a handler or provider call has
|
|
172
|
+
* already succeeded.
|
|
152
173
|
*/
|
|
153
|
-
async rawRequest(path, init) {
|
|
174
|
+
async rawRequest(path, init, extra) {
|
|
154
175
|
const url = `${this.apiUrl}${path}`;
|
|
155
176
|
init.headers.set("Authorization", `Bearer ${this.apiKey}`);
|
|
177
|
+
const maxAttempts = extra?.retry ?? isSafeRetryMethod(init.method) ? 2 : 1;
|
|
156
178
|
let lastError;
|
|
157
|
-
for (let attempt = 1; attempt <=
|
|
179
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) try {
|
|
158
180
|
const res = await fetch(url, {
|
|
159
181
|
method: init.method,
|
|
160
182
|
headers: init.headers,
|
|
@@ -165,7 +187,7 @@ var AgentApiTransport = class {
|
|
|
165
187
|
const errorBody = await res.text();
|
|
166
188
|
const error = new Error(formatErrorMessage(res.status, errorBody));
|
|
167
189
|
error.status = res.status;
|
|
168
|
-
if (attempt
|
|
190
|
+
if (attempt < maxAttempts && RETRYABLE_STATUS.has(res.status)) {
|
|
169
191
|
lastError = error;
|
|
170
192
|
await sleep(RETRY_DELAY_MS);
|
|
171
193
|
continue;
|
|
@@ -174,7 +196,7 @@ var AgentApiTransport = class {
|
|
|
174
196
|
}
|
|
175
197
|
return res;
|
|
176
198
|
} catch (err) {
|
|
177
|
-
if (attempt
|
|
199
|
+
if (attempt < maxAttempts && isRetryableNetworkError(err)) {
|
|
178
200
|
lastError = err;
|
|
179
201
|
await sleep(RETRY_DELAY_MS);
|
|
180
202
|
continue;
|
|
@@ -187,8 +209,11 @@ var AgentApiTransport = class {
|
|
|
187
209
|
* @param extra.timeoutMs Per-request abort timeout (default REQUEST_TIMEOUT_MS).
|
|
188
210
|
* Long endpoints (image generation) pass a larger value so the gateway's
|
|
189
211
|
* own timeout wins with a readable status instead of a client-side abort.
|
|
190
|
-
* @param extra.retry Whether to retry once on transient failures
|
|
191
|
-
*
|
|
212
|
+
* @param extra.retry Whether to retry once on transient failures. Safe reads
|
|
213
|
+
* (GET/HEAD/OPTIONS) default to true; mutations default to false. Set true
|
|
214
|
+
* only when the endpoint's server-side contract is explicitly idempotent.
|
|
215
|
+
* @param extra.signal Optional caller cancellation combined with the client's
|
|
216
|
+
* own timeout budget. Aborting either signal cancels the request.
|
|
192
217
|
*/
|
|
193
218
|
async request(path, options, extra) {
|
|
194
219
|
const url = `${this.apiUrl}${path}`;
|
|
@@ -196,13 +221,13 @@ var AgentApiTransport = class {
|
|
|
196
221
|
headers.set("Authorization", `Bearer ${this.apiKey}`);
|
|
197
222
|
headers.set("Content-Type", "application/json");
|
|
198
223
|
const timeoutMs = extra?.timeoutMs ?? 2e4;
|
|
199
|
-
const maxAttempts = extra?.retry
|
|
224
|
+
const maxAttempts = extra?.retry ?? isSafeRetryMethod(options?.method) ? 2 : 1;
|
|
200
225
|
let lastError;
|
|
201
226
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) try {
|
|
202
227
|
const res = await fetch(url, {
|
|
203
228
|
...options,
|
|
204
229
|
headers,
|
|
205
|
-
signal: AbortSignal.timeout(timeoutMs)
|
|
230
|
+
signal: extra?.signal ? AbortSignal.any([extra.signal, AbortSignal.timeout(timeoutMs)]) : AbortSignal.timeout(timeoutMs)
|
|
206
231
|
});
|
|
207
232
|
if (!res.ok) {
|
|
208
233
|
const errorBody = await res.text();
|
|
@@ -371,8 +396,9 @@ var ConnectCredentialsApi = class extends ApiBase {
|
|
|
371
396
|
* selector arg (e.g. `xeroTenantId`) on every credential-touching tool
|
|
372
397
|
* and look up the matching account by that selector at dispatch time.
|
|
373
398
|
*
|
|
374
|
-
*
|
|
375
|
-
*
|
|
399
|
+
* `xeroTenantId` is the model-facing organisation selector. The separate
|
|
400
|
+
* `accountIdentifier` is the Connect persistence key used for refresh and
|
|
401
|
+
* may be an email; never substitute one for the other.
|
|
376
402
|
*/
|
|
377
403
|
async getXeroAccounts() {
|
|
378
404
|
return { accounts: (await this.transport.request("/agent/connect/xero/accounts")).accounts.map((a) => ({
|
|
@@ -382,21 +408,21 @@ var ConnectCredentialsApi = class extends ApiBase {
|
|
|
382
408
|
connectedAt: a.connectedAt,
|
|
383
409
|
accessToken: a.accessToken,
|
|
384
410
|
accessTokenExpiresAt: a.accessTokenExpiresAt ?? "",
|
|
385
|
-
xeroTenantId: a.xeroTenantId ??
|
|
411
|
+
xeroTenantId: a.xeroTenantId ?? ""
|
|
386
412
|
})) };
|
|
387
413
|
}
|
|
388
414
|
async refreshXeroToken() {
|
|
389
|
-
return this.transport.request("/agent/connect/xero/refresh", { method: "POST" });
|
|
415
|
+
return this.transport.request("/agent/connect/xero/refresh", { method: "POST" }, { retry: true });
|
|
390
416
|
}
|
|
391
417
|
/**
|
|
392
|
-
*
|
|
393
|
-
* (
|
|
394
|
-
* the
|
|
395
|
-
*
|
|
418
|
+
* Refresh a specific Xero Connection by its exact `accountIdentifier` from
|
|
419
|
+
* `getXeroAccounts()`. Do not substitute `xeroTenantId`: current Xero OAuth
|
|
420
|
+
* rows may use the account email as their persistence key even when a sole
|
|
421
|
+
* organisation tenant ID is available in provider metadata.
|
|
396
422
|
*/
|
|
397
|
-
async refreshXeroAccountToken(
|
|
398
|
-
const path = `/agent/connect/xero/accounts/${encodeURIComponent(
|
|
399
|
-
const raw = await this.transport.request(path, { method: "POST" });
|
|
423
|
+
async refreshXeroAccountToken(accountIdentifier) {
|
|
424
|
+
const path = `/agent/connect/xero/accounts/${encodeURIComponent(accountIdentifier)}/refresh`;
|
|
425
|
+
const raw = await this.transport.request(path, { method: "POST" }, { retry: true });
|
|
400
426
|
return {
|
|
401
427
|
accessToken: raw.accessToken,
|
|
402
428
|
accessTokenExpiresAt: raw.accessTokenExpiresAt ?? "",
|
|
@@ -458,7 +484,7 @@ var ConnectCredentialsApi = class extends ApiBase {
|
|
|
458
484
|
};
|
|
459
485
|
}
|
|
460
486
|
async refreshAtlassianToken() {
|
|
461
|
-
return this.transport.request("/agent/connect/atlassian/refresh", { method: "POST" });
|
|
487
|
+
return this.transport.request("/agent/connect/atlassian/refresh", { method: "POST" }, { retry: true });
|
|
462
488
|
}
|
|
463
489
|
/**
|
|
464
490
|
* Pattern A: multi-account / multi-site credential fetch for Atlassian.
|
|
@@ -514,7 +540,7 @@ var ConnectCredentialsApi = class extends ApiBase {
|
|
|
514
540
|
*/
|
|
515
541
|
async refreshAtlassianAccountToken(accountIdentifier) {
|
|
516
542
|
const path = `/agent/connect/atlassian/accounts/${encodeURIComponent(accountIdentifier)}/refresh`;
|
|
517
|
-
const raw = await this.transport.request(path, { method: "POST" });
|
|
543
|
+
const raw = await this.transport.request(path, { method: "POST" }, { retry: true });
|
|
518
544
|
return {
|
|
519
545
|
accessToken: raw.accessToken,
|
|
520
546
|
accessTokenExpiresAt: raw.accessTokenExpiresAt ?? "",
|
|
@@ -556,7 +582,26 @@ var ConnectCredentialsApi = class extends ApiBase {
|
|
|
556
582
|
})) };
|
|
557
583
|
}
|
|
558
584
|
async refreshMYOBToken() {
|
|
559
|
-
return this.transport.request("/agent/connect/myob/refresh", { method: "POST" });
|
|
585
|
+
return this.transport.request("/agent/connect/myob/refresh", { method: "POST" }, { retry: true });
|
|
586
|
+
}
|
|
587
|
+
/**
|
|
588
|
+
* Pattern A: refresh one MYOB Connection by its stable
|
|
589
|
+
* `accountIdentifier` (the MYOB business id returned by
|
|
590
|
+
* `getMYOBAccounts()`).
|
|
591
|
+
*
|
|
592
|
+
* MYOB refresh tokens belong to individual Connection rows. A
|
|
593
|
+
* multi-business client must use this method instead of refreshing the
|
|
594
|
+
* primary Connection and copying that access token into every cached
|
|
595
|
+
* business client.
|
|
596
|
+
*/
|
|
597
|
+
async refreshMYOBAccountToken(accountIdentifier) {
|
|
598
|
+
const path = `/agent/connect/myob/accounts/${encodeURIComponent(accountIdentifier)}/refresh`;
|
|
599
|
+
const raw = await this.transport.request(path, { method: "POST" }, { retry: true });
|
|
600
|
+
return {
|
|
601
|
+
accessToken: raw.accessToken,
|
|
602
|
+
accessTokenExpiresAt: raw.accessTokenExpiresAt ?? "",
|
|
603
|
+
expiresAt: raw.expiresAt ?? ""
|
|
604
|
+
};
|
|
560
605
|
}
|
|
561
606
|
/**
|
|
562
607
|
* @deprecated Returns a single primary credential blob. Use
|
|
@@ -597,7 +642,7 @@ var ConnectCredentialsApi = class extends ApiBase {
|
|
|
597
642
|
*/
|
|
598
643
|
async refreshSalesforceAccountToken(orgId) {
|
|
599
644
|
const path = `/agent/connect/salesforce/accounts/${encodeURIComponent(orgId)}/refresh`;
|
|
600
|
-
const raw = await this.transport.request(path, { method: "POST" });
|
|
645
|
+
const raw = await this.transport.request(path, { method: "POST" }, { retry: true });
|
|
601
646
|
return {
|
|
602
647
|
accessToken: raw.accessToken,
|
|
603
648
|
accessTokenExpiresAt: raw.accessTokenExpiresAt ?? "",
|
|
@@ -627,9 +672,6 @@ var ConnectCredentialsApi = class extends ApiBase {
|
|
|
627
672
|
connectedAt: a.connectedAt,
|
|
628
673
|
accessToken: a.accessToken ?? "",
|
|
629
674
|
accessTokenExpiresAt: a.accessTokenExpiresAt ?? "",
|
|
630
|
-
refreshToken: a.refreshToken ?? "",
|
|
631
|
-
clientId: a.clientId ?? "",
|
|
632
|
-
clientSecret: a.clientSecret ?? "",
|
|
633
675
|
email: a.email ?? a.accountIdentifier,
|
|
634
676
|
microsoftTenantId: a.microsoftTenantId ?? "",
|
|
635
677
|
workspaceDomain: a.workspaceDomain ?? ""
|
|
@@ -651,7 +693,7 @@ var ConnectCredentialsApi = class extends ApiBase {
|
|
|
651
693
|
*/
|
|
652
694
|
async refreshMicrosoftAccountToken(accountIdentifier) {
|
|
653
695
|
const path = `/agent/connect/microsoft/accounts/${encodeURIComponent(accountIdentifier)}/refresh`;
|
|
654
|
-
const raw = await this.transport.request(path, { method: "POST" });
|
|
696
|
+
const raw = await this.transport.request(path, { method: "POST" }, { retry: true });
|
|
655
697
|
return {
|
|
656
698
|
accessToken: raw.accessToken,
|
|
657
699
|
accessTokenExpiresAt: raw.accessTokenExpiresAt ?? "",
|
|
@@ -875,7 +917,7 @@ var ConnectCredentialsApi = class extends ApiBase {
|
|
|
875
917
|
*/
|
|
876
918
|
async refreshSocialAccount(provider, accountIdentifier) {
|
|
877
919
|
const path = `/agent/connect/${encodeURIComponent(provider)}/accounts/${encodeURIComponent(accountIdentifier)}/refresh`;
|
|
878
|
-
const raw = await this.transport.request(path, { method: "POST" });
|
|
920
|
+
const raw = await this.transport.request(path, { method: "POST" }, { retry: true });
|
|
879
921
|
return {
|
|
880
922
|
accountIdentifier: raw.accountIdentifier,
|
|
881
923
|
accessToken: raw.accessToken,
|
|
@@ -941,11 +983,8 @@ var IdentityApi = class extends ApiBase {
|
|
|
941
983
|
body: JSON.stringify(args)
|
|
942
984
|
});
|
|
943
985
|
}
|
|
944
|
-
async unmergeIdentity(identityId
|
|
945
|
-
return this.transport.request(`/agent/identity/${encodeURIComponent(identityId)}/unmerge`, {
|
|
946
|
-
method: "POST",
|
|
947
|
-
body: JSON.stringify(args)
|
|
948
|
-
});
|
|
986
|
+
async unmergeIdentity(identityId) {
|
|
987
|
+
return this.transport.request(`/agent/identity/${encodeURIComponent(identityId)}/unmerge`, { method: "POST" });
|
|
949
988
|
}
|
|
950
989
|
async addIdentityNote(identityId, args) {
|
|
951
990
|
return this.transport.request(`/agent/identity/${encodeURIComponent(identityId)}/notes`, {
|
|
@@ -962,6 +1001,7 @@ var IdentityApi = class extends ApiBase {
|
|
|
962
1001
|
async getIdentityChangelog(identityId, args) {
|
|
963
1002
|
const qs = new URLSearchParams();
|
|
964
1003
|
if (args?.limit) qs.set("limit", String(args.limit));
|
|
1004
|
+
if (args?.cursor) qs.set("cursor", args.cursor);
|
|
965
1005
|
const query = qs.toString();
|
|
966
1006
|
return this.transport.request(`/agent/identity/${encodeURIComponent(identityId)}/changelog${query ? `?${query}` : ""}`);
|
|
967
1007
|
}
|
|
@@ -1038,8 +1078,9 @@ var ImagesApi = class extends ApiBase {
|
|
|
1038
1078
|
let job;
|
|
1039
1079
|
try {
|
|
1040
1080
|
job = await this.transport.request(`/agent/images/${jobId}`);
|
|
1041
|
-
} catch {
|
|
1042
|
-
continue;
|
|
1081
|
+
} catch (error) {
|
|
1082
|
+
if (isTransientRequestError(error)) continue;
|
|
1083
|
+
throw error;
|
|
1043
1084
|
}
|
|
1044
1085
|
if (job.status === "completed") {
|
|
1045
1086
|
if (!job.imageUrl) throw new Error("Image generation completed without a URL");
|
|
@@ -1112,6 +1153,8 @@ var IntegrationsApi = class extends ApiBase {
|
|
|
1112
1153
|
* Knowledge resource methods (org/team/project scoped docs, profiles,
|
|
1113
1154
|
* change requests + RAG search) for the Agent API client.
|
|
1114
1155
|
*/
|
|
1156
|
+
/** Matches services/knowledge's maximum indexed document size. */
|
|
1157
|
+
const MAX_KNOWLEDGE_DOCUMENT_BYTES = 2 * 1024 * 1024;
|
|
1115
1158
|
var KnowledgeApi = class extends ApiBase {
|
|
1116
1159
|
/**
|
|
1117
1160
|
* Semantic search across the agent's member scopes. Fan-out is gated
|
|
@@ -1200,16 +1243,18 @@ var KnowledgeApi = class extends ApiBase {
|
|
|
1200
1243
|
* from `services/org`, then fetches the bytes directly from S3 (the one
|
|
1201
1244
|
* legitimate raw fetch in a plugin — same pattern as sync).
|
|
1202
1245
|
*/
|
|
1203
|
-
async readScopeDoc(scopeType, scopeId, filePath) {
|
|
1246
|
+
async readScopeDoc(scopeType, scopeId, filePath, opts) {
|
|
1247
|
+
const maxBytes = opts?.maxBytes ?? 2097152;
|
|
1248
|
+
if (!Number.isInteger(maxBytes) || maxBytes < 1 || maxBytes > 2097152) throw new RangeError(`maxBytes must be an integer from 1 to ${String(MAX_KNOWLEDGE_DOCUMENT_BYTES)}`);
|
|
1204
1249
|
const { downloadUrl } = await this.transport.request(`/agent/org/files/${encodeURIComponent(scopeType)}/${encodeURIComponent(scopeId)}/download/${encodeFilePath(filePath)}`);
|
|
1205
1250
|
const res = await fetch(downloadUrl, { signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS) });
|
|
1206
1251
|
if (!res.ok) {
|
|
1207
|
-
await res.
|
|
1252
|
+
await res.body?.cancel().catch(() => void 0);
|
|
1208
1253
|
throw new Error(`Doc download failed (${String(res.status)})`);
|
|
1209
1254
|
}
|
|
1210
1255
|
return {
|
|
1211
1256
|
filePath,
|
|
1212
|
-
text: await res
|
|
1257
|
+
text: await readBoundedUtf8(res, maxBytes)
|
|
1213
1258
|
};
|
|
1214
1259
|
}
|
|
1215
1260
|
/**
|
|
@@ -1243,6 +1288,52 @@ var KnowledgeApi = class extends ApiBase {
|
|
|
1243
1288
|
return { filePath: presign.filePath };
|
|
1244
1289
|
}
|
|
1245
1290
|
};
|
|
1291
|
+
async function readBoundedUtf8(response, maxBytes) {
|
|
1292
|
+
const declaredLength = response.headers.get("content-length");
|
|
1293
|
+
if (declaredLength !== null && /^\d+$/u.test(declaredLength) && Number(declaredLength) > maxBytes) {
|
|
1294
|
+
await response.body?.cancel().catch(() => void 0);
|
|
1295
|
+
throw documentTooLargeError(maxBytes);
|
|
1296
|
+
}
|
|
1297
|
+
if (response.body === null) return "";
|
|
1298
|
+
const reader = response.body.getReader();
|
|
1299
|
+
const chunks = [];
|
|
1300
|
+
let total = 0;
|
|
1301
|
+
let complete = false;
|
|
1302
|
+
try {
|
|
1303
|
+
while (!complete) {
|
|
1304
|
+
const { done, value } = await reader.read();
|
|
1305
|
+
if (done) {
|
|
1306
|
+
complete = true;
|
|
1307
|
+
continue;
|
|
1308
|
+
}
|
|
1309
|
+
total += value.byteLength;
|
|
1310
|
+
if (total > maxBytes) {
|
|
1311
|
+
await reader.cancel().catch(() => void 0);
|
|
1312
|
+
throw documentTooLargeError(maxBytes);
|
|
1313
|
+
}
|
|
1314
|
+
chunks.push(value);
|
|
1315
|
+
}
|
|
1316
|
+
} finally {
|
|
1317
|
+
reader.releaseLock();
|
|
1318
|
+
}
|
|
1319
|
+
const bytes = new Uint8Array(total);
|
|
1320
|
+
let offset = 0;
|
|
1321
|
+
for (const chunk of chunks) {
|
|
1322
|
+
bytes.set(chunk, offset);
|
|
1323
|
+
offset += chunk.byteLength;
|
|
1324
|
+
}
|
|
1325
|
+
try {
|
|
1326
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
1327
|
+
} catch {
|
|
1328
|
+
throw new Error("Knowledge document is not valid UTF-8 text");
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
function documentTooLargeError(maxBytes) {
|
|
1332
|
+
const error = /* @__PURE__ */ new Error(`Knowledge document exceeds the ${String(maxBytes)} byte read limit`);
|
|
1333
|
+
error.name = "KnowledgeDocumentTooLargeError";
|
|
1334
|
+
error.code = "KNOWLEDGE_DOCUMENT_TOO_LARGE";
|
|
1335
|
+
return error;
|
|
1336
|
+
}
|
|
1246
1337
|
//#endregion
|
|
1247
1338
|
//#region src/domains/memory.ts
|
|
1248
1339
|
/**
|
|
@@ -1417,23 +1508,23 @@ var RemoteApi = class extends ApiBase {
|
|
|
1417
1508
|
* Web/image/news search methods (services/search) for the Agent API client.
|
|
1418
1509
|
*/
|
|
1419
1510
|
var SearchApi = class extends ApiBase {
|
|
1420
|
-
async searchWeb(params) {
|
|
1511
|
+
async searchWeb(params, options) {
|
|
1421
1512
|
return this.transport.request("/agent/search/web", {
|
|
1422
1513
|
method: "POST",
|
|
1423
1514
|
body: JSON.stringify(params)
|
|
1424
|
-
});
|
|
1515
|
+
}, { signal: options?.signal });
|
|
1425
1516
|
}
|
|
1426
|
-
async searchImages(params) {
|
|
1517
|
+
async searchImages(params, options) {
|
|
1427
1518
|
return this.transport.request("/agent/search/images", {
|
|
1428
1519
|
method: "POST",
|
|
1429
1520
|
body: JSON.stringify(params)
|
|
1430
|
-
});
|
|
1521
|
+
}, { signal: options?.signal });
|
|
1431
1522
|
}
|
|
1432
|
-
async searchNews(params) {
|
|
1523
|
+
async searchNews(params, options) {
|
|
1433
1524
|
return this.transport.request("/agent/search/news", {
|
|
1434
1525
|
method: "POST",
|
|
1435
1526
|
body: JSON.stringify(params)
|
|
1436
|
-
});
|
|
1527
|
+
}, { signal: options?.signal });
|
|
1437
1528
|
}
|
|
1438
1529
|
/** Search news across the selected provider's corpus. → POST /agent/news/search */
|
|
1439
1530
|
async newsSearch(params) {
|
|
@@ -1584,8 +1675,9 @@ var SelfApi = class extends ApiBase {
|
|
|
1584
1675
|
let job;
|
|
1585
1676
|
try {
|
|
1586
1677
|
job = await this.transport.request(`/agent/avatar/${jobId}`);
|
|
1587
|
-
} catch {
|
|
1588
|
-
continue;
|
|
1678
|
+
} catch (error) {
|
|
1679
|
+
if (isTransientRequestError(error)) continue;
|
|
1680
|
+
throw error;
|
|
1589
1681
|
}
|
|
1590
1682
|
if (job.status === "completed") {
|
|
1591
1683
|
if (!job.agent) throw new Error("Avatar generation completed without an agent");
|
|
@@ -1730,7 +1822,11 @@ var SyncApi = class extends ApiBase {
|
|
|
1730
1822
|
return this.transport.request(`/agent/sync/files/${encodeFilePath(filePath)}`, { method: "DELETE" });
|
|
1731
1823
|
}
|
|
1732
1824
|
async sharedListFiles(args) {
|
|
1733
|
-
|
|
1825
|
+
const params = new URLSearchParams();
|
|
1826
|
+
if (args.limit !== void 0) params.set("limit", String(args.limit));
|
|
1827
|
+
if (args.cursor) params.set("cursor", args.cursor);
|
|
1828
|
+
const query = params.toString();
|
|
1829
|
+
return this.transport.request(`/agent/org/files/${encodeURIComponent(args.scope)}/${encodeURIComponent(args.scopeId)}${query ? `?${query}` : ""}`);
|
|
1734
1830
|
}
|
|
1735
1831
|
async sharedDownloadUrl(args) {
|
|
1736
1832
|
return this.transport.request(`/agent/org/files/${encodeURIComponent(args.scope)}/${encodeURIComponent(args.scopeId)}/download/${encodeFilePath(args.filePath)}`);
|
|
@@ -1763,11 +1859,11 @@ var TeamsApi = class extends ApiBase {
|
|
|
1763
1859
|
*/
|
|
1764
1860
|
var WorkspaceApi = class extends ApiBase {
|
|
1765
1861
|
/**
|
|
1766
|
-
* GET /
|
|
1862
|
+
* GET /agent/workspace — workspace config for the authenticated agent
|
|
1767
1863
|
* (template assignment, default model, org roster).
|
|
1768
1864
|
*/
|
|
1769
1865
|
async getWorkspace() {
|
|
1770
|
-
return this.transport.request("/
|
|
1866
|
+
return this.transport.request("/agent/workspace");
|
|
1771
1867
|
}
|
|
1772
1868
|
/**
|
|
1773
1869
|
* GET /templates/{key}/files — persona/workspace file contents for a
|
|
@@ -1776,7 +1872,30 @@ var WorkspaceApi = class extends ApiBase {
|
|
|
1776
1872
|
*/
|
|
1777
1873
|
async getTemplateFiles(templateKey, opts) {
|
|
1778
1874
|
const query = opts?.version !== void 0 ? `?version=${String(opts.version)}` : "";
|
|
1779
|
-
return this.transport.request(`/templates/${encodeURIComponent(templateKey)}/files${query}`);
|
|
1875
|
+
return this.transport.request(`/agent/templates/${encodeURIComponent(templateKey)}/files${query}`);
|
|
1876
|
+
}
|
|
1877
|
+
};
|
|
1878
|
+
//#endregion
|
|
1879
|
+
//#region src/domains/webhooks.ts
|
|
1880
|
+
/** Agent self-service webhook management methods. */
|
|
1881
|
+
var WebhooksApi = class extends ApiBase {
|
|
1882
|
+
async createWebhook(args) {
|
|
1883
|
+
return this.transport.request("/agent/webhooks", {
|
|
1884
|
+
method: "POST",
|
|
1885
|
+
body: JSON.stringify(args)
|
|
1886
|
+
}, { retry: false });
|
|
1887
|
+
}
|
|
1888
|
+
async listWebhooks() {
|
|
1889
|
+
return (await this.transport.request("/agent/webhooks")).webhooks;
|
|
1890
|
+
}
|
|
1891
|
+
async deleteWebhook(webhookId) {
|
|
1892
|
+
return this.transport.request(`/agent/webhooks/${encodeURIComponent(webhookId)}`, { method: "DELETE" });
|
|
1893
|
+
}
|
|
1894
|
+
async rotateWebhookSecret(webhookId) {
|
|
1895
|
+
return this.transport.request(`/agent/webhooks/${encodeURIComponent(webhookId)}/rotate`, { method: "POST" }, { retry: false });
|
|
1896
|
+
}
|
|
1897
|
+
async listWebhookDeliveries(webhookId) {
|
|
1898
|
+
return (await this.transport.request(`/agent/webhooks/${encodeURIComponent(webhookId)}/deliveries`)).deliveries;
|
|
1780
1899
|
}
|
|
1781
1900
|
};
|
|
1782
1901
|
//#endregion
|
|
@@ -1811,7 +1930,8 @@ applyMixins(AgentApiClient, [
|
|
|
1811
1930
|
RemoteApi,
|
|
1812
1931
|
SelfApi,
|
|
1813
1932
|
VoiceApi,
|
|
1814
|
-
ImagesApi
|
|
1933
|
+
ImagesApi,
|
|
1934
|
+
WebhooksApi
|
|
1815
1935
|
]);
|
|
1816
1936
|
//#endregion
|
|
1817
1937
|
exports.AgentApiClient = AgentApiClient;
|