@alfe.ai/agent-api-client 0.14.0 → 0.16.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 +210 -63
- package/dist/index.d.cts +144 -56
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +144 -56
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +210 -63
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -19,7 +19,14 @@ function firstFrame(err) {
|
|
|
19
19
|
return frame ? ` (${frame.trim()})` : "";
|
|
20
20
|
}
|
|
21
21
|
function buildLine(plugin, tool, kind, message, frame = "") {
|
|
22
|
-
|
|
22
|
+
const safeToken = (value) => value.replace(/[^A-Za-z0-9_.@/-]+/g, "_").slice(0, 80);
|
|
23
|
+
const stripControls = (value) => Array.from(value, (character) => {
|
|
24
|
+
const code = character.charCodeAt(0);
|
|
25
|
+
return code < 32 || code >= 127 && code <= 159 ? " " : character;
|
|
26
|
+
}).join("");
|
|
27
|
+
const oneLine = stripControls(message).replace(/\s+/g, " ").trim();
|
|
28
|
+
const safeFrame = stripControls(frame).replace(/\s+/g, " ");
|
|
29
|
+
return `[ERROR] alfe-tool plugin=${safeToken(plugin)} tool=${safeToken(tool)} ${kind}: ${oneLine}${safeFrame}`.slice(0, 480);
|
|
23
30
|
}
|
|
24
31
|
function wrapExecute(tool, opts) {
|
|
25
32
|
const execute = tool.execute;
|
|
@@ -56,7 +63,6 @@ function installToolErrorCapture(api, options) {
|
|
|
56
63
|
try {
|
|
57
64
|
const markedApi = api;
|
|
58
65
|
if (markedApi[INSTALLED_MARKER]) return;
|
|
59
|
-
markedApi[INSTALLED_MARKER] = true;
|
|
60
66
|
const emit = options.emit ?? ((line) => {
|
|
61
67
|
process.stderr.write(`${line}\n`);
|
|
62
68
|
});
|
|
@@ -65,24 +71,29 @@ function installToolErrorCapture(api, options) {
|
|
|
65
71
|
emit
|
|
66
72
|
};
|
|
67
73
|
const original = api.registerTool.bind(api);
|
|
68
|
-
|
|
74
|
+
const wrappedRegisterTool = (...args) => {
|
|
75
|
+
let preparedArgs = args;
|
|
69
76
|
try {
|
|
70
77
|
const [first, ...rest] = args;
|
|
71
78
|
if (typeof first === "function") {
|
|
72
79
|
const factory = first;
|
|
73
80
|
const wrappedFactory = (...fa) => {
|
|
74
81
|
const tool = factory(...fa);
|
|
75
|
-
if (typeof tool === "object" && tool !== null)
|
|
82
|
+
if (typeof tool === "object" && tool !== null) try {
|
|
83
|
+
wrapExecute(tool, opts);
|
|
84
|
+
} catch {}
|
|
76
85
|
return tool;
|
|
77
86
|
};
|
|
78
|
-
|
|
87
|
+
preparedArgs = [wrappedFactory, ...rest];
|
|
79
88
|
}
|
|
80
89
|
if (typeof first === "object" && first !== null) wrapExecute(first, opts);
|
|
81
|
-
return original(first, ...rest);
|
|
82
90
|
} catch {
|
|
83
|
-
|
|
91
|
+
preparedArgs = args;
|
|
84
92
|
}
|
|
93
|
+
return original(...preparedArgs);
|
|
85
94
|
};
|
|
95
|
+
api.registerTool = wrappedRegisterTool;
|
|
96
|
+
markedApi[INSTALLED_MARKER] = true;
|
|
86
97
|
} catch {}
|
|
87
98
|
}
|
|
88
99
|
//#endregion
|
|
@@ -121,6 +132,10 @@ const RETRYABLE_STATUS = new Set([
|
|
|
121
132
|
504
|
|
122
133
|
]);
|
|
123
134
|
const RETRY_DELAY_MS = 500;
|
|
135
|
+
function isSafeRetryMethod(method) {
|
|
136
|
+
const normalized = (method ?? "GET").toUpperCase();
|
|
137
|
+
return normalized === "GET" || normalized === "HEAD" || normalized === "OPTIONS";
|
|
138
|
+
}
|
|
124
139
|
function sleep(ms) {
|
|
125
140
|
return new Promise((resolve) => {
|
|
126
141
|
setTimeout(resolve, ms);
|
|
@@ -132,6 +147,12 @@ function isRetryableNetworkError(err) {
|
|
|
132
147
|
if (err.name === "TypeError") return true;
|
|
133
148
|
return false;
|
|
134
149
|
}
|
|
150
|
+
/** Whether a completed request may be retried/polled without masking a real client error. */
|
|
151
|
+
function isTransientRequestError(err) {
|
|
152
|
+
if (isRetryableNetworkError(err)) return true;
|
|
153
|
+
const status = err?.status;
|
|
154
|
+
return typeof status === "number" && RETRYABLE_STATUS.has(status);
|
|
155
|
+
}
|
|
135
156
|
var AgentApiTransport = class {
|
|
136
157
|
apiKey;
|
|
137
158
|
apiUrl;
|
|
@@ -144,16 +165,17 @@ var AgentApiTransport = class {
|
|
|
144
165
|
* `Content-Type: application/json` and parses a `{ data: T }` envelope,
|
|
145
166
|
* neither of which fits a raw-audio flow (voice TTS/STT), so those go
|
|
146
167
|
* through this instead. Auth (Bearer), the request budget, and the single
|
|
147
|
-
* retry on transient 5xx / network errors
|
|
148
|
-
* `request()`.
|
|
149
|
-
*
|
|
150
|
-
*
|
|
168
|
+
* retry policy on transient 5xx / network errors is kept in sync with
|
|
169
|
+
* `request()`. Safe read methods retry once by default; mutation methods do
|
|
170
|
+
* not, because a response can be lost after a handler or provider call has
|
|
171
|
+
* already succeeded.
|
|
151
172
|
*/
|
|
152
|
-
async rawRequest(path, init) {
|
|
173
|
+
async rawRequest(path, init, extra) {
|
|
153
174
|
const url = `${this.apiUrl}${path}`;
|
|
154
175
|
init.headers.set("Authorization", `Bearer ${this.apiKey}`);
|
|
176
|
+
const maxAttempts = extra?.retry ?? isSafeRetryMethod(init.method) ? 2 : 1;
|
|
155
177
|
let lastError;
|
|
156
|
-
for (let attempt = 1; attempt <=
|
|
178
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) try {
|
|
157
179
|
const res = await fetch(url, {
|
|
158
180
|
method: init.method,
|
|
159
181
|
headers: init.headers,
|
|
@@ -164,7 +186,7 @@ var AgentApiTransport = class {
|
|
|
164
186
|
const errorBody = await res.text();
|
|
165
187
|
const error = new Error(formatErrorMessage(res.status, errorBody));
|
|
166
188
|
error.status = res.status;
|
|
167
|
-
if (attempt
|
|
189
|
+
if (attempt < maxAttempts && RETRYABLE_STATUS.has(res.status)) {
|
|
168
190
|
lastError = error;
|
|
169
191
|
await sleep(RETRY_DELAY_MS);
|
|
170
192
|
continue;
|
|
@@ -173,7 +195,7 @@ var AgentApiTransport = class {
|
|
|
173
195
|
}
|
|
174
196
|
return res;
|
|
175
197
|
} catch (err) {
|
|
176
|
-
if (attempt
|
|
198
|
+
if (attempt < maxAttempts && isRetryableNetworkError(err)) {
|
|
177
199
|
lastError = err;
|
|
178
200
|
await sleep(RETRY_DELAY_MS);
|
|
179
201
|
continue;
|
|
@@ -186,8 +208,11 @@ var AgentApiTransport = class {
|
|
|
186
208
|
* @param extra.timeoutMs Per-request abort timeout (default REQUEST_TIMEOUT_MS).
|
|
187
209
|
* Long endpoints (image generation) pass a larger value so the gateway's
|
|
188
210
|
* own timeout wins with a readable status instead of a client-side abort.
|
|
189
|
-
* @param extra.retry Whether to retry once on transient failures
|
|
190
|
-
*
|
|
211
|
+
* @param extra.retry Whether to retry once on transient failures. Safe reads
|
|
212
|
+
* (GET/HEAD/OPTIONS) default to true; mutations default to false. Set true
|
|
213
|
+
* only when the endpoint's server-side contract is explicitly idempotent.
|
|
214
|
+
* @param extra.signal Optional caller cancellation combined with the client's
|
|
215
|
+
* own timeout budget. Aborting either signal cancels the request.
|
|
191
216
|
*/
|
|
192
217
|
async request(path, options, extra) {
|
|
193
218
|
const url = `${this.apiUrl}${path}`;
|
|
@@ -195,13 +220,13 @@ var AgentApiTransport = class {
|
|
|
195
220
|
headers.set("Authorization", `Bearer ${this.apiKey}`);
|
|
196
221
|
headers.set("Content-Type", "application/json");
|
|
197
222
|
const timeoutMs = extra?.timeoutMs ?? 2e4;
|
|
198
|
-
const maxAttempts = extra?.retry
|
|
223
|
+
const maxAttempts = extra?.retry ?? isSafeRetryMethod(options?.method) ? 2 : 1;
|
|
199
224
|
let lastError;
|
|
200
225
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) try {
|
|
201
226
|
const res = await fetch(url, {
|
|
202
227
|
...options,
|
|
203
228
|
headers,
|
|
204
|
-
signal: AbortSignal.timeout(timeoutMs)
|
|
229
|
+
signal: extra?.signal ? AbortSignal.any([extra.signal, AbortSignal.timeout(timeoutMs)]) : AbortSignal.timeout(timeoutMs)
|
|
205
230
|
});
|
|
206
231
|
if (!res.ok) {
|
|
207
232
|
const errorBody = await res.text();
|
|
@@ -370,8 +395,9 @@ var ConnectCredentialsApi = class extends ApiBase {
|
|
|
370
395
|
* selector arg (e.g. `xeroTenantId`) on every credential-touching tool
|
|
371
396
|
* and look up the matching account by that selector at dispatch time.
|
|
372
397
|
*
|
|
373
|
-
*
|
|
374
|
-
*
|
|
398
|
+
* `xeroTenantId` is the model-facing organisation selector. The separate
|
|
399
|
+
* `accountIdentifier` is the Connect persistence key used for refresh and
|
|
400
|
+
* may be an email; never substitute one for the other.
|
|
375
401
|
*/
|
|
376
402
|
async getXeroAccounts() {
|
|
377
403
|
return { accounts: (await this.transport.request("/agent/connect/xero/accounts")).accounts.map((a) => ({
|
|
@@ -381,21 +407,21 @@ var ConnectCredentialsApi = class extends ApiBase {
|
|
|
381
407
|
connectedAt: a.connectedAt,
|
|
382
408
|
accessToken: a.accessToken,
|
|
383
409
|
accessTokenExpiresAt: a.accessTokenExpiresAt ?? "",
|
|
384
|
-
xeroTenantId: a.xeroTenantId ??
|
|
410
|
+
xeroTenantId: a.xeroTenantId ?? ""
|
|
385
411
|
})) };
|
|
386
412
|
}
|
|
387
413
|
async refreshXeroToken() {
|
|
388
|
-
return this.transport.request("/agent/connect/xero/refresh", { method: "POST" });
|
|
414
|
+
return this.transport.request("/agent/connect/xero/refresh", { method: "POST" }, { retry: true });
|
|
389
415
|
}
|
|
390
416
|
/**
|
|
391
|
-
*
|
|
392
|
-
* (
|
|
393
|
-
* the
|
|
394
|
-
*
|
|
417
|
+
* Refresh a specific Xero Connection by its exact `accountIdentifier` from
|
|
418
|
+
* `getXeroAccounts()`. Do not substitute `xeroTenantId`: current Xero OAuth
|
|
419
|
+
* rows may use the account email as their persistence key even when a sole
|
|
420
|
+
* organisation tenant ID is available in provider metadata.
|
|
395
421
|
*/
|
|
396
|
-
async refreshXeroAccountToken(
|
|
397
|
-
const path = `/agent/connect/xero/accounts/${encodeURIComponent(
|
|
398
|
-
const raw = await this.transport.request(path, { method: "POST" });
|
|
422
|
+
async refreshXeroAccountToken(accountIdentifier) {
|
|
423
|
+
const path = `/agent/connect/xero/accounts/${encodeURIComponent(accountIdentifier)}/refresh`;
|
|
424
|
+
const raw = await this.transport.request(path, { method: "POST" }, { retry: true });
|
|
399
425
|
return {
|
|
400
426
|
accessToken: raw.accessToken,
|
|
401
427
|
accessTokenExpiresAt: raw.accessTokenExpiresAt ?? "",
|
|
@@ -457,7 +483,7 @@ var ConnectCredentialsApi = class extends ApiBase {
|
|
|
457
483
|
};
|
|
458
484
|
}
|
|
459
485
|
async refreshAtlassianToken() {
|
|
460
|
-
return this.transport.request("/agent/connect/atlassian/refresh", { method: "POST" });
|
|
486
|
+
return this.transport.request("/agent/connect/atlassian/refresh", { method: "POST" }, { retry: true });
|
|
461
487
|
}
|
|
462
488
|
/**
|
|
463
489
|
* Pattern A: multi-account / multi-site credential fetch for Atlassian.
|
|
@@ -513,7 +539,7 @@ var ConnectCredentialsApi = class extends ApiBase {
|
|
|
513
539
|
*/
|
|
514
540
|
async refreshAtlassianAccountToken(accountIdentifier) {
|
|
515
541
|
const path = `/agent/connect/atlassian/accounts/${encodeURIComponent(accountIdentifier)}/refresh`;
|
|
516
|
-
const raw = await this.transport.request(path, { method: "POST" });
|
|
542
|
+
const raw = await this.transport.request(path, { method: "POST" }, { retry: true });
|
|
517
543
|
return {
|
|
518
544
|
accessToken: raw.accessToken,
|
|
519
545
|
accessTokenExpiresAt: raw.accessTokenExpiresAt ?? "",
|
|
@@ -555,7 +581,26 @@ var ConnectCredentialsApi = class extends ApiBase {
|
|
|
555
581
|
})) };
|
|
556
582
|
}
|
|
557
583
|
async refreshMYOBToken() {
|
|
558
|
-
return this.transport.request("/agent/connect/myob/refresh", { method: "POST" });
|
|
584
|
+
return this.transport.request("/agent/connect/myob/refresh", { method: "POST" }, { retry: true });
|
|
585
|
+
}
|
|
586
|
+
/**
|
|
587
|
+
* Pattern A: refresh one MYOB Connection by its stable
|
|
588
|
+
* `accountIdentifier` (the MYOB business id returned by
|
|
589
|
+
* `getMYOBAccounts()`).
|
|
590
|
+
*
|
|
591
|
+
* MYOB refresh tokens belong to individual Connection rows. A
|
|
592
|
+
* multi-business client must use this method instead of refreshing the
|
|
593
|
+
* primary Connection and copying that access token into every cached
|
|
594
|
+
* business client.
|
|
595
|
+
*/
|
|
596
|
+
async refreshMYOBAccountToken(accountIdentifier) {
|
|
597
|
+
const path = `/agent/connect/myob/accounts/${encodeURIComponent(accountIdentifier)}/refresh`;
|
|
598
|
+
const raw = await this.transport.request(path, { method: "POST" }, { retry: true });
|
|
599
|
+
return {
|
|
600
|
+
accessToken: raw.accessToken,
|
|
601
|
+
accessTokenExpiresAt: raw.accessTokenExpiresAt ?? "",
|
|
602
|
+
expiresAt: raw.expiresAt ?? ""
|
|
603
|
+
};
|
|
559
604
|
}
|
|
560
605
|
/**
|
|
561
606
|
* @deprecated Returns a single primary credential blob. Use
|
|
@@ -596,7 +641,7 @@ var ConnectCredentialsApi = class extends ApiBase {
|
|
|
596
641
|
*/
|
|
597
642
|
async refreshSalesforceAccountToken(orgId) {
|
|
598
643
|
const path = `/agent/connect/salesforce/accounts/${encodeURIComponent(orgId)}/refresh`;
|
|
599
|
-
const raw = await this.transport.request(path, { method: "POST" });
|
|
644
|
+
const raw = await this.transport.request(path, { method: "POST" }, { retry: true });
|
|
600
645
|
return {
|
|
601
646
|
accessToken: raw.accessToken,
|
|
602
647
|
accessTokenExpiresAt: raw.accessTokenExpiresAt ?? "",
|
|
@@ -626,9 +671,6 @@ var ConnectCredentialsApi = class extends ApiBase {
|
|
|
626
671
|
connectedAt: a.connectedAt,
|
|
627
672
|
accessToken: a.accessToken ?? "",
|
|
628
673
|
accessTokenExpiresAt: a.accessTokenExpiresAt ?? "",
|
|
629
|
-
refreshToken: a.refreshToken ?? "",
|
|
630
|
-
clientId: a.clientId ?? "",
|
|
631
|
-
clientSecret: a.clientSecret ?? "",
|
|
632
674
|
email: a.email ?? a.accountIdentifier,
|
|
633
675
|
microsoftTenantId: a.microsoftTenantId ?? "",
|
|
634
676
|
workspaceDomain: a.workspaceDomain ?? ""
|
|
@@ -650,7 +692,7 @@ var ConnectCredentialsApi = class extends ApiBase {
|
|
|
650
692
|
*/
|
|
651
693
|
async refreshMicrosoftAccountToken(accountIdentifier) {
|
|
652
694
|
const path = `/agent/connect/microsoft/accounts/${encodeURIComponent(accountIdentifier)}/refresh`;
|
|
653
|
-
const raw = await this.transport.request(path, { method: "POST" });
|
|
695
|
+
const raw = await this.transport.request(path, { method: "POST" }, { retry: true });
|
|
654
696
|
return {
|
|
655
697
|
accessToken: raw.accessToken,
|
|
656
698
|
accessTokenExpiresAt: raw.accessTokenExpiresAt ?? "",
|
|
@@ -751,6 +793,7 @@ var ConnectCredentialsApi = class extends ApiBase {
|
|
|
751
793
|
const accounts = [];
|
|
752
794
|
for (const row of raw.accounts) {
|
|
753
795
|
const rowToken = row.accessToken ?? "";
|
|
796
|
+
const rowAccountIdentifier = row.accountIdentifier ?? "";
|
|
754
797
|
for (const a of row.availableAccounts ?? []) {
|
|
755
798
|
const id = a.ctidTraderAccountId != null ? String(a.ctidTraderAccountId) : a.accountId != null ? String(a.accountId) : "";
|
|
756
799
|
if (id.length === 0 || seen.has(id)) continue;
|
|
@@ -762,7 +805,8 @@ var ConnectCredentialsApi = class extends ApiBase {
|
|
|
762
805
|
isLive,
|
|
763
806
|
...a.brokerName != null ? { brokerName: a.brokerName } : {},
|
|
764
807
|
...a.accountNumber != null ? { accountNumber: String(a.accountNumber) } : {},
|
|
765
|
-
accessToken: rowToken
|
|
808
|
+
accessToken: rowToken,
|
|
809
|
+
accountIdentifier: rowAccountIdentifier
|
|
766
810
|
});
|
|
767
811
|
}
|
|
768
812
|
}
|
|
@@ -773,6 +817,31 @@ var ConnectCredentialsApi = class extends ApiBase {
|
|
|
773
817
|
};
|
|
774
818
|
}
|
|
775
819
|
/**
|
|
820
|
+
* Pattern A: refresh a specific cTrader grant by its stable
|
|
821
|
+
* `accountIdentifier` (`ctid:<userId>` from `getCTraderAccounts()`).
|
|
822
|
+
*
|
|
823
|
+
* cTrader access tokens live ~30 days; the `getCTraderAccounts()` /
|
|
824
|
+
* credentials reads serve the STORED token without refreshing, so refresh is
|
|
825
|
+
* the consumer's job. `@alfe.ai/ctrader-mcp` calls this when the cTrader Open
|
|
826
|
+
* API rejects an account-auth with `CH_ACCESS_TOKEN_INVALID`, then re-runs
|
|
827
|
+
* the socket handshake with the returned `accessToken`.
|
|
828
|
+
*
|
|
829
|
+
* Refreshing one grant rotates the single OAuth token that covers EVERY
|
|
830
|
+
* trading account under that login. cTrader's refresh token itself does not
|
|
831
|
+
* expire but may rotate on refresh (`rotatesRefreshToken: true`); connect
|
|
832
|
+
* persists the rotated refresh token server-side, so the caller only needs
|
|
833
|
+
* the new `accessToken`. Mirrors `refreshXeroAccountToken`.
|
|
834
|
+
*/
|
|
835
|
+
async refreshCTraderAccount(accountIdentifier) {
|
|
836
|
+
const path = `/agent/connect/ctrader/accounts/${encodeURIComponent(accountIdentifier)}/refresh`;
|
|
837
|
+
const raw = await this.transport.request(path, { method: "POST" }, { retry: true });
|
|
838
|
+
return {
|
|
839
|
+
accessToken: raw.accessToken,
|
|
840
|
+
accessTokenExpiresAt: raw.accessTokenExpiresAt ?? "",
|
|
841
|
+
expiresAt: raw.expiresAt ?? ""
|
|
842
|
+
};
|
|
843
|
+
}
|
|
844
|
+
/**
|
|
776
845
|
* @deprecated Returns a single primary credential blob. Use
|
|
777
846
|
* `getShopifyAccounts()` for the multi-account shape required by Pattern A
|
|
778
847
|
* (`@alfe.ai/shopify-mcp` keys per-shop on the myshopify domain).
|
|
@@ -874,7 +943,7 @@ var ConnectCredentialsApi = class extends ApiBase {
|
|
|
874
943
|
*/
|
|
875
944
|
async refreshSocialAccount(provider, accountIdentifier) {
|
|
876
945
|
const path = `/agent/connect/${encodeURIComponent(provider)}/accounts/${encodeURIComponent(accountIdentifier)}/refresh`;
|
|
877
|
-
const raw = await this.transport.request(path, { method: "POST" });
|
|
946
|
+
const raw = await this.transport.request(path, { method: "POST" }, { retry: true });
|
|
878
947
|
return {
|
|
879
948
|
accountIdentifier: raw.accountIdentifier,
|
|
880
949
|
accessToken: raw.accessToken,
|
|
@@ -940,11 +1009,8 @@ var IdentityApi = class extends ApiBase {
|
|
|
940
1009
|
body: JSON.stringify(args)
|
|
941
1010
|
});
|
|
942
1011
|
}
|
|
943
|
-
async unmergeIdentity(identityId
|
|
944
|
-
return this.transport.request(`/agent/identity/${encodeURIComponent(identityId)}/unmerge`, {
|
|
945
|
-
method: "POST",
|
|
946
|
-
body: JSON.stringify(args)
|
|
947
|
-
});
|
|
1012
|
+
async unmergeIdentity(identityId) {
|
|
1013
|
+
return this.transport.request(`/agent/identity/${encodeURIComponent(identityId)}/unmerge`, { method: "POST" });
|
|
948
1014
|
}
|
|
949
1015
|
async addIdentityNote(identityId, args) {
|
|
950
1016
|
return this.transport.request(`/agent/identity/${encodeURIComponent(identityId)}/notes`, {
|
|
@@ -961,6 +1027,7 @@ var IdentityApi = class extends ApiBase {
|
|
|
961
1027
|
async getIdentityChangelog(identityId, args) {
|
|
962
1028
|
const qs = new URLSearchParams();
|
|
963
1029
|
if (args?.limit) qs.set("limit", String(args.limit));
|
|
1030
|
+
if (args?.cursor) qs.set("cursor", args.cursor);
|
|
964
1031
|
const query = qs.toString();
|
|
965
1032
|
return this.transport.request(`/agent/identity/${encodeURIComponent(identityId)}/changelog${query ? `?${query}` : ""}`);
|
|
966
1033
|
}
|
|
@@ -1037,8 +1104,9 @@ var ImagesApi = class extends ApiBase {
|
|
|
1037
1104
|
let job;
|
|
1038
1105
|
try {
|
|
1039
1106
|
job = await this.transport.request(`/agent/images/${jobId}`);
|
|
1040
|
-
} catch {
|
|
1041
|
-
continue;
|
|
1107
|
+
} catch (error) {
|
|
1108
|
+
if (isTransientRequestError(error)) continue;
|
|
1109
|
+
throw error;
|
|
1042
1110
|
}
|
|
1043
1111
|
if (job.status === "completed") {
|
|
1044
1112
|
if (!job.imageUrl) throw new Error("Image generation completed without a URL");
|
|
@@ -1111,6 +1179,8 @@ var IntegrationsApi = class extends ApiBase {
|
|
|
1111
1179
|
* Knowledge resource methods (org/team/project scoped docs, profiles,
|
|
1112
1180
|
* change requests + RAG search) for the Agent API client.
|
|
1113
1181
|
*/
|
|
1182
|
+
/** Matches services/knowledge's maximum indexed document size. */
|
|
1183
|
+
const MAX_KNOWLEDGE_DOCUMENT_BYTES = 2 * 1024 * 1024;
|
|
1114
1184
|
var KnowledgeApi = class extends ApiBase {
|
|
1115
1185
|
/**
|
|
1116
1186
|
* Semantic search across the agent's member scopes. Fan-out is gated
|
|
@@ -1199,16 +1269,18 @@ var KnowledgeApi = class extends ApiBase {
|
|
|
1199
1269
|
* from `services/org`, then fetches the bytes directly from S3 (the one
|
|
1200
1270
|
* legitimate raw fetch in a plugin — same pattern as sync).
|
|
1201
1271
|
*/
|
|
1202
|
-
async readScopeDoc(scopeType, scopeId, filePath) {
|
|
1272
|
+
async readScopeDoc(scopeType, scopeId, filePath, opts) {
|
|
1273
|
+
const maxBytes = opts?.maxBytes ?? 2097152;
|
|
1274
|
+
if (!Number.isInteger(maxBytes) || maxBytes < 1 || maxBytes > 2097152) throw new RangeError(`maxBytes must be an integer from 1 to ${String(MAX_KNOWLEDGE_DOCUMENT_BYTES)}`);
|
|
1203
1275
|
const { downloadUrl } = await this.transport.request(`/agent/org/files/${encodeURIComponent(scopeType)}/${encodeURIComponent(scopeId)}/download/${encodeFilePath(filePath)}`);
|
|
1204
1276
|
const res = await fetch(downloadUrl, { signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS) });
|
|
1205
1277
|
if (!res.ok) {
|
|
1206
|
-
await res.
|
|
1278
|
+
await res.body?.cancel().catch(() => void 0);
|
|
1207
1279
|
throw new Error(`Doc download failed (${String(res.status)})`);
|
|
1208
1280
|
}
|
|
1209
1281
|
return {
|
|
1210
1282
|
filePath,
|
|
1211
|
-
text: await res
|
|
1283
|
+
text: await readBoundedUtf8(res, maxBytes)
|
|
1212
1284
|
};
|
|
1213
1285
|
}
|
|
1214
1286
|
/**
|
|
@@ -1242,6 +1314,52 @@ var KnowledgeApi = class extends ApiBase {
|
|
|
1242
1314
|
return { filePath: presign.filePath };
|
|
1243
1315
|
}
|
|
1244
1316
|
};
|
|
1317
|
+
async function readBoundedUtf8(response, maxBytes) {
|
|
1318
|
+
const declaredLength = response.headers.get("content-length");
|
|
1319
|
+
if (declaredLength !== null && /^\d+$/u.test(declaredLength) && Number(declaredLength) > maxBytes) {
|
|
1320
|
+
await response.body?.cancel().catch(() => void 0);
|
|
1321
|
+
throw documentTooLargeError(maxBytes);
|
|
1322
|
+
}
|
|
1323
|
+
if (response.body === null) return "";
|
|
1324
|
+
const reader = response.body.getReader();
|
|
1325
|
+
const chunks = [];
|
|
1326
|
+
let total = 0;
|
|
1327
|
+
let complete = false;
|
|
1328
|
+
try {
|
|
1329
|
+
while (!complete) {
|
|
1330
|
+
const { done, value } = await reader.read();
|
|
1331
|
+
if (done) {
|
|
1332
|
+
complete = true;
|
|
1333
|
+
continue;
|
|
1334
|
+
}
|
|
1335
|
+
total += value.byteLength;
|
|
1336
|
+
if (total > maxBytes) {
|
|
1337
|
+
await reader.cancel().catch(() => void 0);
|
|
1338
|
+
throw documentTooLargeError(maxBytes);
|
|
1339
|
+
}
|
|
1340
|
+
chunks.push(value);
|
|
1341
|
+
}
|
|
1342
|
+
} finally {
|
|
1343
|
+
reader.releaseLock();
|
|
1344
|
+
}
|
|
1345
|
+
const bytes = new Uint8Array(total);
|
|
1346
|
+
let offset = 0;
|
|
1347
|
+
for (const chunk of chunks) {
|
|
1348
|
+
bytes.set(chunk, offset);
|
|
1349
|
+
offset += chunk.byteLength;
|
|
1350
|
+
}
|
|
1351
|
+
try {
|
|
1352
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
1353
|
+
} catch {
|
|
1354
|
+
throw new Error("Knowledge document is not valid UTF-8 text");
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
function documentTooLargeError(maxBytes) {
|
|
1358
|
+
const error = /* @__PURE__ */ new Error(`Knowledge document exceeds the ${String(maxBytes)} byte read limit`);
|
|
1359
|
+
error.name = "KnowledgeDocumentTooLargeError";
|
|
1360
|
+
error.code = "KNOWLEDGE_DOCUMENT_TOO_LARGE";
|
|
1361
|
+
return error;
|
|
1362
|
+
}
|
|
1245
1363
|
//#endregion
|
|
1246
1364
|
//#region src/domains/memory.ts
|
|
1247
1365
|
/**
|
|
@@ -1416,23 +1534,23 @@ var RemoteApi = class extends ApiBase {
|
|
|
1416
1534
|
* Web/image/news search methods (services/search) for the Agent API client.
|
|
1417
1535
|
*/
|
|
1418
1536
|
var SearchApi = class extends ApiBase {
|
|
1419
|
-
async searchWeb(params) {
|
|
1537
|
+
async searchWeb(params, options) {
|
|
1420
1538
|
return this.transport.request("/agent/search/web", {
|
|
1421
1539
|
method: "POST",
|
|
1422
1540
|
body: JSON.stringify(params)
|
|
1423
|
-
});
|
|
1541
|
+
}, { signal: options?.signal });
|
|
1424
1542
|
}
|
|
1425
|
-
async searchImages(params) {
|
|
1543
|
+
async searchImages(params, options) {
|
|
1426
1544
|
return this.transport.request("/agent/search/images", {
|
|
1427
1545
|
method: "POST",
|
|
1428
1546
|
body: JSON.stringify(params)
|
|
1429
|
-
});
|
|
1547
|
+
}, { signal: options?.signal });
|
|
1430
1548
|
}
|
|
1431
|
-
async searchNews(params) {
|
|
1549
|
+
async searchNews(params, options) {
|
|
1432
1550
|
return this.transport.request("/agent/search/news", {
|
|
1433
1551
|
method: "POST",
|
|
1434
1552
|
body: JSON.stringify(params)
|
|
1435
|
-
});
|
|
1553
|
+
}, { signal: options?.signal });
|
|
1436
1554
|
}
|
|
1437
1555
|
/** Search news across the selected provider's corpus. → POST /agent/news/search */
|
|
1438
1556
|
async newsSearch(params) {
|
|
@@ -1583,8 +1701,9 @@ var SelfApi = class extends ApiBase {
|
|
|
1583
1701
|
let job;
|
|
1584
1702
|
try {
|
|
1585
1703
|
job = await this.transport.request(`/agent/avatar/${jobId}`);
|
|
1586
|
-
} catch {
|
|
1587
|
-
continue;
|
|
1704
|
+
} catch (error) {
|
|
1705
|
+
if (isTransientRequestError(error)) continue;
|
|
1706
|
+
throw error;
|
|
1588
1707
|
}
|
|
1589
1708
|
if (job.status === "completed") {
|
|
1590
1709
|
if (!job.agent) throw new Error("Avatar generation completed without an agent");
|
|
@@ -1729,7 +1848,11 @@ var SyncApi = class extends ApiBase {
|
|
|
1729
1848
|
return this.transport.request(`/agent/sync/files/${encodeFilePath(filePath)}`, { method: "DELETE" });
|
|
1730
1849
|
}
|
|
1731
1850
|
async sharedListFiles(args) {
|
|
1732
|
-
|
|
1851
|
+
const params = new URLSearchParams();
|
|
1852
|
+
if (args.limit !== void 0) params.set("limit", String(args.limit));
|
|
1853
|
+
if (args.cursor) params.set("cursor", args.cursor);
|
|
1854
|
+
const query = params.toString();
|
|
1855
|
+
return this.transport.request(`/agent/org/files/${encodeURIComponent(args.scope)}/${encodeURIComponent(args.scopeId)}${query ? `?${query}` : ""}`);
|
|
1733
1856
|
}
|
|
1734
1857
|
async sharedDownloadUrl(args) {
|
|
1735
1858
|
return this.transport.request(`/agent/org/files/${encodeURIComponent(args.scope)}/${encodeURIComponent(args.scopeId)}/download/${encodeFilePath(args.filePath)}`);
|
|
@@ -1762,11 +1885,11 @@ var TeamsApi = class extends ApiBase {
|
|
|
1762
1885
|
*/
|
|
1763
1886
|
var WorkspaceApi = class extends ApiBase {
|
|
1764
1887
|
/**
|
|
1765
|
-
* GET /
|
|
1888
|
+
* GET /agent/workspace — workspace config for the authenticated agent
|
|
1766
1889
|
* (template assignment, default model, org roster).
|
|
1767
1890
|
*/
|
|
1768
1891
|
async getWorkspace() {
|
|
1769
|
-
return this.transport.request("/
|
|
1892
|
+
return this.transport.request("/agent/workspace");
|
|
1770
1893
|
}
|
|
1771
1894
|
/**
|
|
1772
1895
|
* GET /templates/{key}/files — persona/workspace file contents for a
|
|
@@ -1775,7 +1898,30 @@ var WorkspaceApi = class extends ApiBase {
|
|
|
1775
1898
|
*/
|
|
1776
1899
|
async getTemplateFiles(templateKey, opts) {
|
|
1777
1900
|
const query = opts?.version !== void 0 ? `?version=${String(opts.version)}` : "";
|
|
1778
|
-
return this.transport.request(`/templates/${encodeURIComponent(templateKey)}/files${query}`);
|
|
1901
|
+
return this.transport.request(`/agent/templates/${encodeURIComponent(templateKey)}/files${query}`);
|
|
1902
|
+
}
|
|
1903
|
+
};
|
|
1904
|
+
//#endregion
|
|
1905
|
+
//#region src/domains/webhooks.ts
|
|
1906
|
+
/** Agent self-service webhook management methods. */
|
|
1907
|
+
var WebhooksApi = class extends ApiBase {
|
|
1908
|
+
async createWebhook(args) {
|
|
1909
|
+
return this.transport.request("/agent/webhooks", {
|
|
1910
|
+
method: "POST",
|
|
1911
|
+
body: JSON.stringify(args)
|
|
1912
|
+
}, { retry: false });
|
|
1913
|
+
}
|
|
1914
|
+
async listWebhooks() {
|
|
1915
|
+
return (await this.transport.request("/agent/webhooks")).webhooks;
|
|
1916
|
+
}
|
|
1917
|
+
async deleteWebhook(webhookId) {
|
|
1918
|
+
return this.transport.request(`/agent/webhooks/${encodeURIComponent(webhookId)}`, { method: "DELETE" });
|
|
1919
|
+
}
|
|
1920
|
+
async rotateWebhookSecret(webhookId) {
|
|
1921
|
+
return this.transport.request(`/agent/webhooks/${encodeURIComponent(webhookId)}/rotate`, { method: "POST" }, { retry: false });
|
|
1922
|
+
}
|
|
1923
|
+
async listWebhookDeliveries(webhookId) {
|
|
1924
|
+
return (await this.transport.request(`/agent/webhooks/${encodeURIComponent(webhookId)}/deliveries`)).deliveries;
|
|
1779
1925
|
}
|
|
1780
1926
|
};
|
|
1781
1927
|
//#endregion
|
|
@@ -1810,7 +1956,8 @@ applyMixins(AgentApiClient, [
|
|
|
1810
1956
|
RemoteApi,
|
|
1811
1957
|
SelfApi,
|
|
1812
1958
|
VoiceApi,
|
|
1813
|
-
ImagesApi
|
|
1959
|
+
ImagesApi,
|
|
1960
|
+
WebhooksApi
|
|
1814
1961
|
]);
|
|
1815
1962
|
//#endregion
|
|
1816
1963
|
export { AgentApiClient, installToolErrorCapture };
|