@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 CHANGED
@@ -1,6 +1,8 @@
1
1
  # @alfe.ai/agent-api-client
2
2
 
3
- Agent self-service API client agents calling /agents/ endpoints
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
- return `[ERROR] alfe-tool plugin=${plugin} tool=${tool} ${kind}: ${message.replace(/\s+/g, " ").trim()}${frame}`.slice(0, 480);
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
- api.registerTool = (...args) => {
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) wrapExecute(tool, opts);
83
+ if (typeof tool === "object" && tool !== null) try {
84
+ wrapExecute(tool, opts);
85
+ } catch {}
77
86
  return tool;
78
87
  };
79
- return original(wrappedFactory, ...rest);
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
- return original(...args);
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 are kept in sync with
149
- * `request()`. Retries fire only on statuses produced BEFORE the route
150
- * handler runs (authorizer-timeout 500 + LB 502/503/504), so re-issuing a
151
- * POST does not risk a duplicate side effect.
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 <= 2; attempt++) try {
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 === 1 && RETRYABLE_STATUS.has(res.status)) {
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 === 1 && isRetryableNetworkError(err)) {
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 (default
191
- * true). Expensive/non-idempotent endpoints pass false.
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 === false ? 1 : 2;
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
- * Returned `accounts[i].accountIdentifier` is the Xero tenantId the
375
- * stable cross-session identifier the LLM should pass.
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 ?? a.accountIdentifier
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
- * Pattern A: refresh a specific Xero connection by its `accountIdentifier`
393
- * (the Xero `tenantId`). The legacy `refreshXeroToken()` only refreshes
394
- * the *primary* connection, which is wrong for multi-tenant Xero where
395
- * each tenant has its own non-interchangeable access token.
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(xeroTenantId) {
398
- const path = `/agent/connect/xero/accounts/${encodeURIComponent(xeroTenantId)}/refresh`;
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, args) {
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.text();
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.text()
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
- return this.transport.request(`/agent/org/files/${encodeURIComponent(args.scope)}/${encodeURIComponent(args.scopeId)}`);
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 /agents/me/workspace — workspace config for the authenticated agent
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("/agents/me/workspace");
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;