@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/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 ?? "",
|
|
@@ -752,6 +794,7 @@ var ConnectCredentialsApi = class extends ApiBase {
|
|
|
752
794
|
const accounts = [];
|
|
753
795
|
for (const row of raw.accounts) {
|
|
754
796
|
const rowToken = row.accessToken ?? "";
|
|
797
|
+
const rowAccountIdentifier = row.accountIdentifier ?? "";
|
|
755
798
|
for (const a of row.availableAccounts ?? []) {
|
|
756
799
|
const id = a.ctidTraderAccountId != null ? String(a.ctidTraderAccountId) : a.accountId != null ? String(a.accountId) : "";
|
|
757
800
|
if (id.length === 0 || seen.has(id)) continue;
|
|
@@ -763,7 +806,8 @@ var ConnectCredentialsApi = class extends ApiBase {
|
|
|
763
806
|
isLive,
|
|
764
807
|
...a.brokerName != null ? { brokerName: a.brokerName } : {},
|
|
765
808
|
...a.accountNumber != null ? { accountNumber: String(a.accountNumber) } : {},
|
|
766
|
-
accessToken: rowToken
|
|
809
|
+
accessToken: rowToken,
|
|
810
|
+
accountIdentifier: rowAccountIdentifier
|
|
767
811
|
});
|
|
768
812
|
}
|
|
769
813
|
}
|
|
@@ -774,6 +818,31 @@ var ConnectCredentialsApi = class extends ApiBase {
|
|
|
774
818
|
};
|
|
775
819
|
}
|
|
776
820
|
/**
|
|
821
|
+
* Pattern A: refresh a specific cTrader grant by its stable
|
|
822
|
+
* `accountIdentifier` (`ctid:<userId>` from `getCTraderAccounts()`).
|
|
823
|
+
*
|
|
824
|
+
* cTrader access tokens live ~30 days; the `getCTraderAccounts()` /
|
|
825
|
+
* credentials reads serve the STORED token without refreshing, so refresh is
|
|
826
|
+
* the consumer's job. `@alfe.ai/ctrader-mcp` calls this when the cTrader Open
|
|
827
|
+
* API rejects an account-auth with `CH_ACCESS_TOKEN_INVALID`, then re-runs
|
|
828
|
+
* the socket handshake with the returned `accessToken`.
|
|
829
|
+
*
|
|
830
|
+
* Refreshing one grant rotates the single OAuth token that covers EVERY
|
|
831
|
+
* trading account under that login. cTrader's refresh token itself does not
|
|
832
|
+
* expire but may rotate on refresh (`rotatesRefreshToken: true`); connect
|
|
833
|
+
* persists the rotated refresh token server-side, so the caller only needs
|
|
834
|
+
* the new `accessToken`. Mirrors `refreshXeroAccountToken`.
|
|
835
|
+
*/
|
|
836
|
+
async refreshCTraderAccount(accountIdentifier) {
|
|
837
|
+
const path = `/agent/connect/ctrader/accounts/${encodeURIComponent(accountIdentifier)}/refresh`;
|
|
838
|
+
const raw = await this.transport.request(path, { method: "POST" }, { retry: true });
|
|
839
|
+
return {
|
|
840
|
+
accessToken: raw.accessToken,
|
|
841
|
+
accessTokenExpiresAt: raw.accessTokenExpiresAt ?? "",
|
|
842
|
+
expiresAt: raw.expiresAt ?? ""
|
|
843
|
+
};
|
|
844
|
+
}
|
|
845
|
+
/**
|
|
777
846
|
* @deprecated Returns a single primary credential blob. Use
|
|
778
847
|
* `getShopifyAccounts()` for the multi-account shape required by Pattern A
|
|
779
848
|
* (`@alfe.ai/shopify-mcp` keys per-shop on the myshopify domain).
|
|
@@ -875,7 +944,7 @@ var ConnectCredentialsApi = class extends ApiBase {
|
|
|
875
944
|
*/
|
|
876
945
|
async refreshSocialAccount(provider, accountIdentifier) {
|
|
877
946
|
const path = `/agent/connect/${encodeURIComponent(provider)}/accounts/${encodeURIComponent(accountIdentifier)}/refresh`;
|
|
878
|
-
const raw = await this.transport.request(path, { method: "POST" });
|
|
947
|
+
const raw = await this.transport.request(path, { method: "POST" }, { retry: true });
|
|
879
948
|
return {
|
|
880
949
|
accountIdentifier: raw.accountIdentifier,
|
|
881
950
|
accessToken: raw.accessToken,
|
|
@@ -941,11 +1010,8 @@ var IdentityApi = class extends ApiBase {
|
|
|
941
1010
|
body: JSON.stringify(args)
|
|
942
1011
|
});
|
|
943
1012
|
}
|
|
944
|
-
async unmergeIdentity(identityId
|
|
945
|
-
return this.transport.request(`/agent/identity/${encodeURIComponent(identityId)}/unmerge`, {
|
|
946
|
-
method: "POST",
|
|
947
|
-
body: JSON.stringify(args)
|
|
948
|
-
});
|
|
1013
|
+
async unmergeIdentity(identityId) {
|
|
1014
|
+
return this.transport.request(`/agent/identity/${encodeURIComponent(identityId)}/unmerge`, { method: "POST" });
|
|
949
1015
|
}
|
|
950
1016
|
async addIdentityNote(identityId, args) {
|
|
951
1017
|
return this.transport.request(`/agent/identity/${encodeURIComponent(identityId)}/notes`, {
|
|
@@ -962,6 +1028,7 @@ var IdentityApi = class extends ApiBase {
|
|
|
962
1028
|
async getIdentityChangelog(identityId, args) {
|
|
963
1029
|
const qs = new URLSearchParams();
|
|
964
1030
|
if (args?.limit) qs.set("limit", String(args.limit));
|
|
1031
|
+
if (args?.cursor) qs.set("cursor", args.cursor);
|
|
965
1032
|
const query = qs.toString();
|
|
966
1033
|
return this.transport.request(`/agent/identity/${encodeURIComponent(identityId)}/changelog${query ? `?${query}` : ""}`);
|
|
967
1034
|
}
|
|
@@ -1038,8 +1105,9 @@ var ImagesApi = class extends ApiBase {
|
|
|
1038
1105
|
let job;
|
|
1039
1106
|
try {
|
|
1040
1107
|
job = await this.transport.request(`/agent/images/${jobId}`);
|
|
1041
|
-
} catch {
|
|
1042
|
-
continue;
|
|
1108
|
+
} catch (error) {
|
|
1109
|
+
if (isTransientRequestError(error)) continue;
|
|
1110
|
+
throw error;
|
|
1043
1111
|
}
|
|
1044
1112
|
if (job.status === "completed") {
|
|
1045
1113
|
if (!job.imageUrl) throw new Error("Image generation completed without a URL");
|
|
@@ -1112,6 +1180,8 @@ var IntegrationsApi = class extends ApiBase {
|
|
|
1112
1180
|
* Knowledge resource methods (org/team/project scoped docs, profiles,
|
|
1113
1181
|
* change requests + RAG search) for the Agent API client.
|
|
1114
1182
|
*/
|
|
1183
|
+
/** Matches services/knowledge's maximum indexed document size. */
|
|
1184
|
+
const MAX_KNOWLEDGE_DOCUMENT_BYTES = 2 * 1024 * 1024;
|
|
1115
1185
|
var KnowledgeApi = class extends ApiBase {
|
|
1116
1186
|
/**
|
|
1117
1187
|
* Semantic search across the agent's member scopes. Fan-out is gated
|
|
@@ -1200,16 +1270,18 @@ var KnowledgeApi = class extends ApiBase {
|
|
|
1200
1270
|
* from `services/org`, then fetches the bytes directly from S3 (the one
|
|
1201
1271
|
* legitimate raw fetch in a plugin — same pattern as sync).
|
|
1202
1272
|
*/
|
|
1203
|
-
async readScopeDoc(scopeType, scopeId, filePath) {
|
|
1273
|
+
async readScopeDoc(scopeType, scopeId, filePath, opts) {
|
|
1274
|
+
const maxBytes = opts?.maxBytes ?? 2097152;
|
|
1275
|
+
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
1276
|
const { downloadUrl } = await this.transport.request(`/agent/org/files/${encodeURIComponent(scopeType)}/${encodeURIComponent(scopeId)}/download/${encodeFilePath(filePath)}`);
|
|
1205
1277
|
const res = await fetch(downloadUrl, { signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS) });
|
|
1206
1278
|
if (!res.ok) {
|
|
1207
|
-
await res.
|
|
1279
|
+
await res.body?.cancel().catch(() => void 0);
|
|
1208
1280
|
throw new Error(`Doc download failed (${String(res.status)})`);
|
|
1209
1281
|
}
|
|
1210
1282
|
return {
|
|
1211
1283
|
filePath,
|
|
1212
|
-
text: await res
|
|
1284
|
+
text: await readBoundedUtf8(res, maxBytes)
|
|
1213
1285
|
};
|
|
1214
1286
|
}
|
|
1215
1287
|
/**
|
|
@@ -1243,6 +1315,52 @@ var KnowledgeApi = class extends ApiBase {
|
|
|
1243
1315
|
return { filePath: presign.filePath };
|
|
1244
1316
|
}
|
|
1245
1317
|
};
|
|
1318
|
+
async function readBoundedUtf8(response, maxBytes) {
|
|
1319
|
+
const declaredLength = response.headers.get("content-length");
|
|
1320
|
+
if (declaredLength !== null && /^\d+$/u.test(declaredLength) && Number(declaredLength) > maxBytes) {
|
|
1321
|
+
await response.body?.cancel().catch(() => void 0);
|
|
1322
|
+
throw documentTooLargeError(maxBytes);
|
|
1323
|
+
}
|
|
1324
|
+
if (response.body === null) return "";
|
|
1325
|
+
const reader = response.body.getReader();
|
|
1326
|
+
const chunks = [];
|
|
1327
|
+
let total = 0;
|
|
1328
|
+
let complete = false;
|
|
1329
|
+
try {
|
|
1330
|
+
while (!complete) {
|
|
1331
|
+
const { done, value } = await reader.read();
|
|
1332
|
+
if (done) {
|
|
1333
|
+
complete = true;
|
|
1334
|
+
continue;
|
|
1335
|
+
}
|
|
1336
|
+
total += value.byteLength;
|
|
1337
|
+
if (total > maxBytes) {
|
|
1338
|
+
await reader.cancel().catch(() => void 0);
|
|
1339
|
+
throw documentTooLargeError(maxBytes);
|
|
1340
|
+
}
|
|
1341
|
+
chunks.push(value);
|
|
1342
|
+
}
|
|
1343
|
+
} finally {
|
|
1344
|
+
reader.releaseLock();
|
|
1345
|
+
}
|
|
1346
|
+
const bytes = new Uint8Array(total);
|
|
1347
|
+
let offset = 0;
|
|
1348
|
+
for (const chunk of chunks) {
|
|
1349
|
+
bytes.set(chunk, offset);
|
|
1350
|
+
offset += chunk.byteLength;
|
|
1351
|
+
}
|
|
1352
|
+
try {
|
|
1353
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
1354
|
+
} catch {
|
|
1355
|
+
throw new Error("Knowledge document is not valid UTF-8 text");
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
function documentTooLargeError(maxBytes) {
|
|
1359
|
+
const error = /* @__PURE__ */ new Error(`Knowledge document exceeds the ${String(maxBytes)} byte read limit`);
|
|
1360
|
+
error.name = "KnowledgeDocumentTooLargeError";
|
|
1361
|
+
error.code = "KNOWLEDGE_DOCUMENT_TOO_LARGE";
|
|
1362
|
+
return error;
|
|
1363
|
+
}
|
|
1246
1364
|
//#endregion
|
|
1247
1365
|
//#region src/domains/memory.ts
|
|
1248
1366
|
/**
|
|
@@ -1417,23 +1535,23 @@ var RemoteApi = class extends ApiBase {
|
|
|
1417
1535
|
* Web/image/news search methods (services/search) for the Agent API client.
|
|
1418
1536
|
*/
|
|
1419
1537
|
var SearchApi = class extends ApiBase {
|
|
1420
|
-
async searchWeb(params) {
|
|
1538
|
+
async searchWeb(params, options) {
|
|
1421
1539
|
return this.transport.request("/agent/search/web", {
|
|
1422
1540
|
method: "POST",
|
|
1423
1541
|
body: JSON.stringify(params)
|
|
1424
|
-
});
|
|
1542
|
+
}, { signal: options?.signal });
|
|
1425
1543
|
}
|
|
1426
|
-
async searchImages(params) {
|
|
1544
|
+
async searchImages(params, options) {
|
|
1427
1545
|
return this.transport.request("/agent/search/images", {
|
|
1428
1546
|
method: "POST",
|
|
1429
1547
|
body: JSON.stringify(params)
|
|
1430
|
-
});
|
|
1548
|
+
}, { signal: options?.signal });
|
|
1431
1549
|
}
|
|
1432
|
-
async searchNews(params) {
|
|
1550
|
+
async searchNews(params, options) {
|
|
1433
1551
|
return this.transport.request("/agent/search/news", {
|
|
1434
1552
|
method: "POST",
|
|
1435
1553
|
body: JSON.stringify(params)
|
|
1436
|
-
});
|
|
1554
|
+
}, { signal: options?.signal });
|
|
1437
1555
|
}
|
|
1438
1556
|
/** Search news across the selected provider's corpus. → POST /agent/news/search */
|
|
1439
1557
|
async newsSearch(params) {
|
|
@@ -1584,8 +1702,9 @@ var SelfApi = class extends ApiBase {
|
|
|
1584
1702
|
let job;
|
|
1585
1703
|
try {
|
|
1586
1704
|
job = await this.transport.request(`/agent/avatar/${jobId}`);
|
|
1587
|
-
} catch {
|
|
1588
|
-
continue;
|
|
1705
|
+
} catch (error) {
|
|
1706
|
+
if (isTransientRequestError(error)) continue;
|
|
1707
|
+
throw error;
|
|
1589
1708
|
}
|
|
1590
1709
|
if (job.status === "completed") {
|
|
1591
1710
|
if (!job.agent) throw new Error("Avatar generation completed without an agent");
|
|
@@ -1730,7 +1849,11 @@ var SyncApi = class extends ApiBase {
|
|
|
1730
1849
|
return this.transport.request(`/agent/sync/files/${encodeFilePath(filePath)}`, { method: "DELETE" });
|
|
1731
1850
|
}
|
|
1732
1851
|
async sharedListFiles(args) {
|
|
1733
|
-
|
|
1852
|
+
const params = new URLSearchParams();
|
|
1853
|
+
if (args.limit !== void 0) params.set("limit", String(args.limit));
|
|
1854
|
+
if (args.cursor) params.set("cursor", args.cursor);
|
|
1855
|
+
const query = params.toString();
|
|
1856
|
+
return this.transport.request(`/agent/org/files/${encodeURIComponent(args.scope)}/${encodeURIComponent(args.scopeId)}${query ? `?${query}` : ""}`);
|
|
1734
1857
|
}
|
|
1735
1858
|
async sharedDownloadUrl(args) {
|
|
1736
1859
|
return this.transport.request(`/agent/org/files/${encodeURIComponent(args.scope)}/${encodeURIComponent(args.scopeId)}/download/${encodeFilePath(args.filePath)}`);
|
|
@@ -1763,11 +1886,11 @@ var TeamsApi = class extends ApiBase {
|
|
|
1763
1886
|
*/
|
|
1764
1887
|
var WorkspaceApi = class extends ApiBase {
|
|
1765
1888
|
/**
|
|
1766
|
-
* GET /
|
|
1889
|
+
* GET /agent/workspace — workspace config for the authenticated agent
|
|
1767
1890
|
* (template assignment, default model, org roster).
|
|
1768
1891
|
*/
|
|
1769
1892
|
async getWorkspace() {
|
|
1770
|
-
return this.transport.request("/
|
|
1893
|
+
return this.transport.request("/agent/workspace");
|
|
1771
1894
|
}
|
|
1772
1895
|
/**
|
|
1773
1896
|
* GET /templates/{key}/files — persona/workspace file contents for a
|
|
@@ -1776,7 +1899,30 @@ var WorkspaceApi = class extends ApiBase {
|
|
|
1776
1899
|
*/
|
|
1777
1900
|
async getTemplateFiles(templateKey, opts) {
|
|
1778
1901
|
const query = opts?.version !== void 0 ? `?version=${String(opts.version)}` : "";
|
|
1779
|
-
return this.transport.request(`/templates/${encodeURIComponent(templateKey)}/files${query}`);
|
|
1902
|
+
return this.transport.request(`/agent/templates/${encodeURIComponent(templateKey)}/files${query}`);
|
|
1903
|
+
}
|
|
1904
|
+
};
|
|
1905
|
+
//#endregion
|
|
1906
|
+
//#region src/domains/webhooks.ts
|
|
1907
|
+
/** Agent self-service webhook management methods. */
|
|
1908
|
+
var WebhooksApi = class extends ApiBase {
|
|
1909
|
+
async createWebhook(args) {
|
|
1910
|
+
return this.transport.request("/agent/webhooks", {
|
|
1911
|
+
method: "POST",
|
|
1912
|
+
body: JSON.stringify(args)
|
|
1913
|
+
}, { retry: false });
|
|
1914
|
+
}
|
|
1915
|
+
async listWebhooks() {
|
|
1916
|
+
return (await this.transport.request("/agent/webhooks")).webhooks;
|
|
1917
|
+
}
|
|
1918
|
+
async deleteWebhook(webhookId) {
|
|
1919
|
+
return this.transport.request(`/agent/webhooks/${encodeURIComponent(webhookId)}`, { method: "DELETE" });
|
|
1920
|
+
}
|
|
1921
|
+
async rotateWebhookSecret(webhookId) {
|
|
1922
|
+
return this.transport.request(`/agent/webhooks/${encodeURIComponent(webhookId)}/rotate`, { method: "POST" }, { retry: false });
|
|
1923
|
+
}
|
|
1924
|
+
async listWebhookDeliveries(webhookId) {
|
|
1925
|
+
return (await this.transport.request(`/agent/webhooks/${encodeURIComponent(webhookId)}/deliveries`)).deliveries;
|
|
1780
1926
|
}
|
|
1781
1927
|
};
|
|
1782
1928
|
//#endregion
|
|
@@ -1811,7 +1957,8 @@ applyMixins(AgentApiClient, [
|
|
|
1811
1957
|
RemoteApi,
|
|
1812
1958
|
SelfApi,
|
|
1813
1959
|
VoiceApi,
|
|
1814
|
-
ImagesApi
|
|
1960
|
+
ImagesApi,
|
|
1961
|
+
WebhooksApi
|
|
1815
1962
|
]);
|
|
1816
1963
|
//#endregion
|
|
1817
1964
|
exports.AgentApiClient = AgentApiClient;
|