@alfe.ai/agent-api-client 0.13.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/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,28 +72,33 @@ 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
90
- //#region src/index.ts
101
+ //#region src/transport.ts
91
102
  /**
92
103
  * Encode each path segment but keep the `/` separators — `encodeURIComponent`
93
104
  * would escape the slashes too, breaking greedy proxy routes.
@@ -95,6 +106,25 @@ function installToolErrorCapture(api, options) {
95
106
  function encodeFilePath(filePath) {
96
107
  return filePath.split("/").map(encodeURIComponent).join("/");
97
108
  }
109
+ /**
110
+ * Build the thrown Error message for a non-2xx response. The
111
+ * @alfe/api-core error envelope carries the server's detail as
112
+ * `{ message }` (Zod failures add `{ issues }`); some handlers use
113
+ * `{ error }`. Surfacing that detail matters for tool-facing callers —
114
+ * e.g. GET /mobile/numbers 404s with "No phone number assigned…
115
+ * use mobile_search_numbers", which guides the agent's next tool call.
116
+ */
117
+ function formatErrorMessage(status, rawBody) {
118
+ const prefix = `Agent API request failed (${String(status)})`;
119
+ try {
120
+ const json = JSON.parse(rawBody);
121
+ const issues = json.issues;
122
+ if (Array.isArray(issues) && issues.length > 0) return `${prefix}: validation failed — ${issues.map((i) => `${i.path?.join(".") ?? "input"}: ${i.message ?? "invalid"}`).join("; ")}`;
123
+ const detail = typeof json.error === "string" ? json.error : typeof json.message === "string" ? json.message : void 0;
124
+ if (detail) return `${prefix}: ${detail}`;
125
+ } catch {}
126
+ return prefix;
127
+ }
98
128
  const REQUEST_TIMEOUT_MS = 2e4;
99
129
  const RETRYABLE_STATUS = new Set([
100
130
  500,
@@ -103,8 +133,10 @@ const RETRYABLE_STATUS = new Set([
103
133
  504
104
134
  ]);
105
135
  const RETRY_DELAY_MS = 500;
106
- const IMAGE_POLL_INTERVAL_MS = 2e3;
107
- const IMAGE_JOB_TIMEOUT_MS = 18e4;
136
+ function isSafeRetryMethod(method) {
137
+ const normalized = (method ?? "GET").toUpperCase();
138
+ return normalized === "GET" || normalized === "HEAD" || normalized === "OPTIONS";
139
+ }
108
140
  function sleep(ms) {
109
141
  return new Promise((resolve) => {
110
142
  setTimeout(resolve, ms);
@@ -116,30 +148,92 @@ function isRetryableNetworkError(err) {
116
148
  if (err.name === "TypeError") return true;
117
149
  return false;
118
150
  }
119
- var AgentApiClient = class {
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
+ }
157
+ var AgentApiTransport = class {
120
158
  apiKey;
121
159
  apiUrl;
122
160
  constructor(config) {
123
161
  this.apiKey = config.apiKey;
124
162
  this.apiUrl = config.apiUrl;
125
163
  }
126
- async request(path, options) {
164
+ /**
165
+ * Binary sibling of `request<T>()`. `request()` forces
166
+ * `Content-Type: application/json` and parses a `{ data: T }` envelope,
167
+ * neither of which fits a raw-audio flow (voice TTS/STT), so those go
168
+ * through this instead. Auth (Bearer), the request budget, and the single
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.
173
+ */
174
+ async rawRequest(path, init, extra) {
175
+ const url = `${this.apiUrl}${path}`;
176
+ init.headers.set("Authorization", `Bearer ${this.apiKey}`);
177
+ const maxAttempts = extra?.retry ?? isSafeRetryMethod(init.method) ? 2 : 1;
178
+ let lastError;
179
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) try {
180
+ const res = await fetch(url, {
181
+ method: init.method,
182
+ headers: init.headers,
183
+ body: init.body,
184
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
185
+ });
186
+ if (!res.ok) {
187
+ const errorBody = await res.text();
188
+ const error = new Error(formatErrorMessage(res.status, errorBody));
189
+ error.status = res.status;
190
+ if (attempt < maxAttempts && RETRYABLE_STATUS.has(res.status)) {
191
+ lastError = error;
192
+ await sleep(RETRY_DELAY_MS);
193
+ continue;
194
+ }
195
+ throw error;
196
+ }
197
+ return res;
198
+ } catch (err) {
199
+ if (attempt < maxAttempts && isRetryableNetworkError(err)) {
200
+ lastError = err;
201
+ await sleep(RETRY_DELAY_MS);
202
+ continue;
203
+ }
204
+ throw err;
205
+ }
206
+ throw lastError instanceof Error ? lastError : new Error(String(lastError));
207
+ }
208
+ /**
209
+ * @param extra.timeoutMs Per-request abort timeout (default REQUEST_TIMEOUT_MS).
210
+ * Long endpoints (image generation) pass a larger value so the gateway's
211
+ * own timeout wins with a readable status instead of a client-side abort.
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.
217
+ */
218
+ async request(path, options, extra) {
127
219
  const url = `${this.apiUrl}${path}`;
128
220
  const headers = new Headers(options?.headers);
129
221
  headers.set("Authorization", `Bearer ${this.apiKey}`);
130
222
  headers.set("Content-Type", "application/json");
223
+ const timeoutMs = extra?.timeoutMs ?? 2e4;
224
+ const maxAttempts = extra?.retry ?? isSafeRetryMethod(options?.method) ? 2 : 1;
131
225
  let lastError;
132
- for (let attempt = 1; attempt <= 2; attempt++) try {
226
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) try {
133
227
  const res = await fetch(url, {
134
228
  ...options,
135
229
  headers,
136
- signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
230
+ signal: extra?.signal ? AbortSignal.any([extra.signal, AbortSignal.timeout(timeoutMs)]) : AbortSignal.timeout(timeoutMs)
137
231
  });
138
232
  if (!res.ok) {
139
- await res.text();
140
- const error = /* @__PURE__ */ new Error(`Agent API request failed (${String(res.status)})`);
233
+ const errorBody = await res.text();
234
+ const error = new Error(formatErrorMessage(res.status, errorBody));
141
235
  error.status = res.status;
142
- if (attempt === 1 && RETRYABLE_STATUS.has(res.status)) {
236
+ if (attempt < maxAttempts && RETRYABLE_STATUS.has(res.status)) {
143
237
  lastError = error;
144
238
  await sleep(RETRY_DELAY_MS);
145
239
  continue;
@@ -148,7 +242,7 @@ var AgentApiClient = class {
148
242
  }
149
243
  return (await res.json()).data;
150
244
  } catch (err) {
151
- if (attempt === 1 && isRetryableNetworkError(err)) {
245
+ if (attempt < maxAttempts && isRetryableNetworkError(err)) {
152
246
  lastError = err;
153
247
  await sleep(RETRY_DELAY_MS);
154
248
  continue;
@@ -157,103 +251,45 @@ var AgentApiClient = class {
157
251
  }
158
252
  throw lastError instanceof Error ? lastError : new Error(String(lastError));
159
253
  }
160
- async syncRegister(args) {
161
- return this.request("/agent/sync/register", {
162
- method: "POST",
163
- body: JSON.stringify(args ?? {})
164
- });
165
- }
166
- async syncGetManifest() {
167
- return this.request("/agent/sync/manifest");
168
- }
169
- async syncPresign(args) {
170
- return this.request("/agent/sync/presign", {
171
- method: "POST",
172
- body: JSON.stringify(args)
173
- });
174
- }
175
- async syncConfirmUpload(args) {
176
- return this.request("/agent/sync/confirm", {
177
- method: "POST",
178
- body: JSON.stringify(args)
179
- });
254
+ };
255
+ /**
256
+ * Base class for the domain method groups. Holds the shared transport;
257
+ * `AgentApiClient` assembles the groups onto one class via `applyMixins`
258
+ * (prototype copy), so methods keep their original `this`-on-the-client
259
+ * call shape.
260
+ */
261
+ var ApiBase = class {
262
+ transport;
263
+ constructor(transport) {
264
+ this.transport = transport;
180
265
  }
181
- async syncReconstruct(args) {
182
- return this.request("/agent/sync/reconstruct", {
266
+ };
267
+ //#endregion
268
+ //#region src/domains/chat.ts
269
+ /**
270
+ * Chat attachment + activity methods for the Agent API client.
271
+ */
272
+ var ChatApi = class extends ApiBase {
273
+ async presignAttachments(files) {
274
+ return this.transport.request("/agent/chat/attachments/presign", {
183
275
  method: "POST",
184
- body: JSON.stringify(args)
185
- });
186
- }
187
- async syncGetStats() {
188
- return this.request("/agent/sync/stats");
189
- }
190
- async syncListFiles(args) {
191
- const qs = new URLSearchParams();
192
- if (args?.prefix) qs.set("prefix", args.prefix);
193
- const query = qs.toString();
194
- return this.request(`/agent/sync/files${query ? `?${query}` : ""}`);
195
- }
196
- async syncListSessions() {
197
- return this.request("/agent/sync/sessions");
198
- }
199
- async syncGetSession(sessionId) {
200
- return this.request(`/agent/sync/sessions/${encodeURIComponent(sessionId)}`);
201
- }
202
- async syncDeleteFile(filePath) {
203
- return this.request(`/agent/sync/files/${encodeFilePath(filePath)}`, { method: "DELETE" });
204
- }
205
- async sharedListFiles(args) {
206
- return this.request(`/agent/org/files/${encodeURIComponent(args.scope)}/${encodeURIComponent(args.scopeId)}`);
207
- }
208
- async sharedDownloadUrl(args) {
209
- return this.request(`/agent/org/files/${encodeURIComponent(args.scope)}/${encodeURIComponent(args.scopeId)}/download/${encodeFilePath(args.filePath)}`);
210
- }
211
- async listIntegrations() {
212
- return this.request("/agent/integrations");
213
- }
214
- async getIntegrationConfig(integrationId) {
215
- try {
216
- return await this.request(`/agent/integrations/${encodeURIComponent(integrationId)}/config`);
217
- } catch (err) {
218
- if (err.status === 404) return {
219
- integrationId,
220
- config: {},
221
- configSchema: [],
222
- installed: false
223
- };
224
- throw err;
225
- }
226
- }
227
- async updateIntegrationConfig(integrationId, config) {
228
- await this.request(`/agent/integrations/${encodeURIComponent(integrationId)}`, {
229
- method: "PATCH",
230
- body: JSON.stringify({ config })
276
+ body: JSON.stringify({ files })
231
277
  });
232
278
  }
233
- async installIntegration(integrationId, options) {
234
- return this.request("/agent/integrations", {
279
+ async recordActivity(data) {
280
+ return this.transport.request("/agent/activity", {
235
281
  method: "POST",
236
- body: JSON.stringify({
237
- integrationId,
238
- version: options?.version,
239
- config: options?.config
240
- })
282
+ body: JSON.stringify(data)
241
283
  });
242
284
  }
243
- async removeIntegration(integrationId) {
244
- return this.request(`/agent/integrations/${encodeURIComponent(integrationId)}`, { method: "DELETE" });
245
- }
246
- async getOAuthUrl(provider, scopes) {
247
- const params = new URLSearchParams({ provider });
248
- if (scopes?.length) params.set("scopes", scopes.join(","));
249
- return this.request(`/agent/integrations/oauth/url?${params.toString()}`);
250
- }
251
- async getOAuthStatus(provider) {
252
- return this.request(`/agent/integrations/oauth/status?provider=${encodeURIComponent(provider)}`);
253
- }
254
- async getRegistry() {
255
- return this.request("/integrations/registry");
256
- }
285
+ };
286
+ //#endregion
287
+ //#region src/domains/connect-credentials.ts
288
+ /**
289
+ * services/connect credential + account methods (Google, GitHub, Xero,
290
+ * Notion, Atlassian, MYOB, Salesforce, Microsoft 365) for the Agent API client.
291
+ */
292
+ var ConnectCredentialsApi = class extends ApiBase {
257
293
  /**
258
294
  * Returns every connected Google account for the agent. Multi-account by
259
295
  * design — the openclaw-google plugin requires the LLM to pass `email`
@@ -265,7 +301,7 @@ var AgentApiClient = class {
265
301
  * is gone. Iterate over `accounts`.
266
302
  */
267
303
  async getGoogleCredentials() {
268
- return { accounts: (await this.request("/agent/connect/google/accounts")).accounts.map((a) => ({
304
+ return { accounts: (await this.transport.request("/agent/connect/google/accounts")).accounts.map((a) => ({
269
305
  email: a.accountIdentifier,
270
306
  refreshToken: a.refreshToken ?? "",
271
307
  clientId: a.clientId ?? "",
@@ -275,14 +311,14 @@ var AgentApiClient = class {
275
311
  })) };
276
312
  }
277
313
  async disconnectGoogleAccount(email) {
278
- return { accounts: (await this.request(`/agent/connect/google/accounts/${encodeURIComponent(email)}`, { method: "DELETE" })).accounts.map((a) => ({
314
+ return { accounts: (await this.transport.request(`/agent/connect/google/accounts/${encodeURIComponent(email)}`, { method: "DELETE" })).accounts.map((a) => ({
279
315
  email: a.accountIdentifier,
280
316
  displayName: a.displayName ?? void 0,
281
317
  connectedAt: a.connectedAt
282
318
  })) };
283
319
  }
284
320
  async getGoogleChatCredentials() {
285
- return this.request("/agent/google-chat/credentials");
321
+ return this.transport.request("/agent/google-chat/credentials");
286
322
  }
287
323
  /**
288
324
  * Fetch decrypted credentials for ONE specific connection by its
@@ -298,100 +334,7 @@ var AgentApiClient = class {
298
334
  * the calling agent's effective scope (403 otherwise).
299
335
  */
300
336
  async getConnectionCredentials(connectionId) {
301
- return this.request(`/agent/connect/connections/${encodeURIComponent(connectionId)}/credentials`);
302
- }
303
- /**
304
- * Resolve the primary cTrader Connection's credentials for the calling
305
- * agent. Unlike most providers, the cTrader Open API needs app-level auth
306
- * (`clientId` + `clientSecret`) AND account auth (`accessToken` +
307
- * `accountId`) on the socket, so `@alfe.ai/ctrader-mcp` self-fetches the
308
- * full set here at startup (the atlassian/google pattern). `clientId` /
309
- * `clientSecret` are the SST-sourced global app credentials the connect
310
- * endpoint injects — they are never persisted on the connection. `host` is
311
- * the resolved TLS endpoint (`live.ctraderapi.com` / `demo.ctraderapi.com`)
312
- * derived from the selected account's live/demo flag.
313
- */
314
- async getCTraderCredentials() {
315
- const raw = await this.request("/agent/connect/ctrader/credentials");
316
- return {
317
- accessToken: raw.accessToken ?? "",
318
- refreshToken: raw.refreshToken ?? "",
319
- accountId: raw.accountId != null ? String(raw.accountId) : "",
320
- host: raw.host ?? "",
321
- clientId: raw.clientId ?? "",
322
- clientSecret: raw.clientSecret ?? ""
323
- };
324
- }
325
- /**
326
- * Pattern A: multi-account credential fetch for cTrader.
327
- *
328
- * Unlike atlassian/salesforce (one Connection row per account/site), a
329
- * cTrader is MULTI-grant per agent: an agent may connect several distinct
330
- * cTrader logins, each its own Connection row keyed on `accountIdentifier =
331
- * ctid:<userId>` (Phase 1). This aggregates the *trading accounts* across
332
- * ALL of those Connection rows — each row contributes its `availableAccounts`
333
- * flattened, and every account carries ITS OWN grant's `accessToken` (the
334
- * token that authenticates that account against the cTrader Open API). One
335
- * OAuth grant still covers all accounts under that single login on one shared
336
- * token; only the `ctidTraderAccountId` and the protobuf socket `host` (live
337
- * vs demo) differ within a grant. Across grants the tokens differ, so the
338
- * token is now PER-ACCOUNT rather than hoisted to the top level.
339
- *
340
- * `host` per account is derived from the account's `isLive` flag
341
- * (`live.ctraderapi.com` / `demo.ctraderapi.com`) — the same mapping the
342
- * connect provider applies server-side when an account is auto-selected.
343
- *
344
- * `clientId` / `clientSecret` are the SST-sourced GLOBAL app credentials the
345
- * connect endpoint injects — identical across every Connection row (one
346
- * cTrader app), never persisted on a connection. We take them from the first
347
- * row that carries them.
348
- *
349
- * Accounts are deduped on `ctidTraderAccountId` first-wins: Spotware ids are
350
- * globally unique across logins, so a duplicate can only appear if the same
351
- * account somehow surfaced under two grants — first-wins keeps it
352
- * deterministic.
353
- *
354
- * `accounts` may be empty (no cTrader Connection at all), in which case we
355
- * return empty creds rather than throwing.
356
- */
357
- async getCTraderAccounts() {
358
- const raw = await this.request("/agent/connect/ctrader/accounts");
359
- if (raw.accounts.length === 0) return {
360
- accounts: [],
361
- clientId: "",
362
- clientSecret: ""
363
- };
364
- let clientId = "";
365
- let clientSecret = "";
366
- for (const row of raw.accounts) {
367
- if (!clientId && row.clientId) clientId = row.clientId;
368
- if (!clientSecret && row.clientSecret) clientSecret = row.clientSecret;
369
- if (clientId && clientSecret) break;
370
- }
371
- const seen = /* @__PURE__ */ new Set();
372
- const accounts = [];
373
- for (const row of raw.accounts) {
374
- const rowToken = row.accessToken ?? "";
375
- for (const a of row.availableAccounts ?? []) {
376
- const id = a.ctidTraderAccountId != null ? String(a.ctidTraderAccountId) : a.accountId != null ? String(a.accountId) : "";
377
- if (id.length === 0 || seen.has(id)) continue;
378
- seen.add(id);
379
- const isLive = a.isLive === true;
380
- accounts.push({
381
- ctidTraderAccountId: id,
382
- host: isLive ? "live.ctraderapi.com" : "demo.ctraderapi.com",
383
- isLive,
384
- ...a.brokerName != null ? { brokerName: a.brokerName } : {},
385
- ...a.accountNumber != null ? { accountNumber: String(a.accountNumber) } : {},
386
- accessToken: rowToken
387
- });
388
- }
389
- }
390
- return {
391
- accounts,
392
- clientId,
393
- clientSecret
394
- };
337
+ return this.transport.request(`/agent/connect/connections/${encodeURIComponent(connectionId)}/credentials`);
395
338
  }
396
339
  /**
397
340
  * @deprecated Returns a single primary credential blob (legacy "pick-the-
@@ -402,7 +345,7 @@ var AgentApiClient = class {
402
345
  * callers will keep hitting `/credentials` until they move to the proxy.
403
346
  */
404
347
  async getGithubCredentials() {
405
- const raw = await this.request("/agent/connect/github/credentials");
348
+ const raw = await this.transport.request("/agent/connect/github/credentials");
406
349
  return {
407
350
  login: raw.login,
408
351
  accessToken: raw.accessToken
@@ -423,7 +366,7 @@ var AgentApiClient = class {
423
366
  * cross-session identifier the LLM should pass.
424
367
  */
425
368
  async getGithubAccounts() {
426
- return { accounts: (await this.request("/agent/connect/github/accounts")).accounts.map((a) => ({
369
+ return { accounts: (await this.transport.request("/agent/connect/github/accounts")).accounts.map((a) => ({
427
370
  connectionId: a.connectionId,
428
371
  accountIdentifier: a.accountIdentifier,
429
372
  displayName: a.displayName,
@@ -434,81 +377,13 @@ var AgentApiClient = class {
434
377
  })) };
435
378
  }
436
379
  /**
437
- * Pattern A: provider-parameterized multi-account credential fetch for the
438
- * social connectors (Bluesky, and the approval-gated backlog: X, Meta,
439
- * Threads, LinkedIn, Pinterest, TikTok, Reddit, YouTube).
440
- *
441
- * Unlike the bespoke `getGithubAccounts()` / `getXeroAccounts()` shapes,
442
- * this returns a UNIFORM normalized account shape so `@alfe.ai/social-mcp`'s
443
- * shared driver can require a single `account` selector on every
444
- * credential-touching tool regardless of platform. The backend
445
- * `api-agents/{provider}/accounts` route is already provider-generic; this
446
- * is the client-side normalization the plan (`do-we-need-any-moonlit-toucan`
447
- * Phase 0, step 5) calls for.
448
- *
449
- * `accountIdentifier` is the stable per-account selector the LLM should
450
- * pass back (for Bluesky: the account DID). `accessToken` carries whatever
451
- * the provider's `buildCredentialsResponse` bundles (for Bluesky: the JSON
452
- * session bundle — the driver parses the `accessJwt` out of it, or reads the
453
- * top-level `accessJwt` from `providerMetadata`-adjacent fields). Everything
454
- * else the driver needs for routing (handle, pdsHost, did, …) is on
455
- * `providerMetadata`.
456
- *
457
- * Token refresh is delegated to connect (never done in-plugin) via the
458
- * per-account route `POST /agent/connect/{provider}/accounts/{accountIdentifier}/refresh`
459
- * — call `refreshSocialAccount(provider, accountIdentifier)`. (The non-account
460
- * `POST /agent/connect/{provider}/refresh` route refreshes the provider's
461
- * PRIMARY connection, which is wrong under multi-account Pattern A.)
380
+ * @deprecated Returns a single primary credential blob (legacy "pick-the-
381
+ * default-connection" shape). Use `getXeroAccounts()` for the multi-
382
+ * account shape required by Pattern A — explicit selector args on every
383
+ * tool. This method will be removed once all consumers migrate.
462
384
  */
463
- async getSocialAccounts(provider) {
464
- const raw = await this.request(`/agent/connect/${encodeURIComponent(provider)}/accounts`);
465
- return {
466
- provider: raw.provider ?? provider,
467
- accounts: raw.accounts.map((a) => ({
468
- connectionId: a.connectionId,
469
- accountIdentifier: a.accountIdentifier,
470
- displayName: a.displayName,
471
- accessToken: a.accessToken ?? "",
472
- providerMetadata: a.providerMetadata ?? {},
473
- connectedAt: a.connectedAt
474
- }))
475
- };
476
- }
477
- /**
478
- * Pattern A: refresh a specific social Connection by its stable
479
- * `accountIdentifier` (for Bluesky: the account DID) via the
480
- * provider-generic per-account refresh route. The counterpart to
481
- * `getSocialAccounts(provider)`; `@alfe.ai/social-mcp` calls this on a
482
- * 401/ExpiredToken from the platform PDS/API, then re-fetches accounts to
483
- * pick up the rotated bundle.
484
- *
485
- * Refresh itself is ALWAYS delegated to connect — the plugin never calls
486
- * the platform's own refresh XRPC (e.g. `com.atproto.server.refreshSession`)
487
- * because connect owns the encrypted refresh token + rotation persistence
488
- * (Bluesky rotates the refreshJwt; a missed rotation kills the connection
489
- * after one refresh). The returned `accessToken` is whatever the provider's
490
- * `refreshToken` hook re-bundled (for Bluesky: the JSON session bundle with
491
- * the fresh `accessJwt`) — callers typically ignore it and re-fetch via
492
- * `getSocialAccounts` for a consistent shape.
493
- */
494
- async refreshSocialAccount(provider, accountIdentifier) {
495
- const path = `/agent/connect/${encodeURIComponent(provider)}/accounts/${encodeURIComponent(accountIdentifier)}/refresh`;
496
- const raw = await this.request(path, { method: "POST" });
497
- return {
498
- accountIdentifier: raw.accountIdentifier,
499
- accessToken: raw.accessToken,
500
- accessTokenExpiresAt: raw.accessTokenExpiresAt ?? "",
501
- expiresAt: raw.expiresAt ?? ""
502
- };
503
- }
504
- /**
505
- * @deprecated Returns a single primary credential blob (legacy "pick-the-
506
- * default-connection" shape). Use `getXeroAccounts()` for the multi-
507
- * account shape required by Pattern A — explicit selector args on every
508
- * tool. This method will be removed once all consumers migrate.
509
- */
510
- async getXeroCredentials() {
511
- const raw = await this.request("/agent/connect/xero/credentials");
385
+ async getXeroCredentials() {
386
+ const raw = await this.transport.request("/agent/connect/xero/credentials");
512
387
  return {
513
388
  accessToken: raw.accessToken,
514
389
  accessTokenExpiresAt: raw.accessTokenExpiresAt ?? "",
@@ -521,32 +396,33 @@ var AgentApiClient = class {
521
396
  * selector arg (e.g. `xeroTenantId`) on every credential-touching tool
522
397
  * and look up the matching account by that selector at dispatch time.
523
398
  *
524
- * Returned `accounts[i].accountIdentifier` is the Xero tenantId the
525
- * 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.
526
402
  */
527
403
  async getXeroAccounts() {
528
- return { accounts: (await this.request("/agent/connect/xero/accounts")).accounts.map((a) => ({
404
+ return { accounts: (await this.transport.request("/agent/connect/xero/accounts")).accounts.map((a) => ({
529
405
  connectionId: a.connectionId,
530
406
  accountIdentifier: a.accountIdentifier,
531
407
  displayName: a.displayName,
532
408
  connectedAt: a.connectedAt,
533
409
  accessToken: a.accessToken,
534
410
  accessTokenExpiresAt: a.accessTokenExpiresAt ?? "",
535
- xeroTenantId: a.xeroTenantId ?? a.accountIdentifier
411
+ xeroTenantId: a.xeroTenantId ?? ""
536
412
  })) };
537
413
  }
538
414
  async refreshXeroToken() {
539
- return this.request("/agent/connect/xero/refresh", { method: "POST" });
415
+ return this.transport.request("/agent/connect/xero/refresh", { method: "POST" }, { retry: true });
540
416
  }
541
417
  /**
542
- * Pattern A: refresh a specific Xero connection by its `accountIdentifier`
543
- * (the Xero `tenantId`). The legacy `refreshXeroToken()` only refreshes
544
- * the *primary* connection, which is wrong for multi-tenant Xero where
545
- * 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.
546
422
  */
547
- async refreshXeroAccountToken(xeroTenantId) {
548
- const path = `/agent/connect/xero/accounts/${encodeURIComponent(xeroTenantId)}/refresh`;
549
- const raw = await this.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 });
550
426
  return {
551
427
  accessToken: raw.accessToken,
552
428
  accessTokenExpiresAt: raw.accessTokenExpiresAt ?? "",
@@ -559,7 +435,7 @@ var AgentApiClient = class {
559
435
  * account shape required by Pattern A.
560
436
  */
561
437
  async getNotionCredentials() {
562
- const raw = await this.request("/agent/connect/notion/credentials");
438
+ const raw = await this.transport.request("/agent/connect/notion/credentials");
563
439
  return {
564
440
  accessToken: raw.accessToken,
565
441
  workspaceId: raw.workspaceId ?? "",
@@ -574,7 +450,7 @@ var AgentApiClient = class {
574
450
  * Returned `accounts[i].accountIdentifier` is the Notion workspaceId.
575
451
  */
576
452
  async getNotionAccounts() {
577
- return { accounts: (await this.request("/agent/connect/notion/accounts")).accounts.map((a) => ({
453
+ return { accounts: (await this.transport.request("/agent/connect/notion/accounts")).accounts.map((a) => ({
578
454
  connectionId: a.connectionId,
579
455
  accountIdentifier: a.accountIdentifier,
580
456
  displayName: a.displayName,
@@ -593,7 +469,7 @@ var AgentApiClient = class {
593
469
  * the `cloudId` selector arg.
594
470
  */
595
471
  async getAtlassianCredentials() {
596
- const raw = await this.request("/agent/connect/atlassian/credentials");
472
+ const raw = await this.transport.request("/agent/connect/atlassian/credentials");
597
473
  return {
598
474
  accessToken: raw.accessToken,
599
475
  refreshToken: "",
@@ -608,7 +484,7 @@ var AgentApiClient = class {
608
484
  };
609
485
  }
610
486
  async refreshAtlassianToken() {
611
- return this.request("/agent/connect/atlassian/refresh", { method: "POST" });
487
+ return this.transport.request("/agent/connect/atlassian/refresh", { method: "POST" }, { retry: true });
612
488
  }
613
489
  /**
614
490
  * Pattern A: multi-account / multi-site credential fetch for Atlassian.
@@ -633,7 +509,7 @@ var AgentApiClient = class {
633
509
  * instead.
634
510
  */
635
511
  async getAtlassianAccounts() {
636
- return { accounts: (await this.request("/agent/connect/atlassian/accounts")).accounts.map((a) => ({
512
+ return { accounts: (await this.transport.request("/agent/connect/atlassian/accounts")).accounts.map((a) => ({
637
513
  connectionId: a.connectionId,
638
514
  accountIdentifier: a.accountIdentifier,
639
515
  displayName: a.displayName,
@@ -664,7 +540,7 @@ var AgentApiClient = class {
664
540
  */
665
541
  async refreshAtlassianAccountToken(accountIdentifier) {
666
542
  const path = `/agent/connect/atlassian/accounts/${encodeURIComponent(accountIdentifier)}/refresh`;
667
- const raw = await this.request(path, { method: "POST" });
543
+ const raw = await this.transport.request(path, { method: "POST" }, { retry: true });
668
544
  return {
669
545
  accessToken: raw.accessToken,
670
546
  accessTokenExpiresAt: raw.accessTokenExpiresAt ?? "",
@@ -677,7 +553,7 @@ var AgentApiClient = class {
677
553
  * account shape required by Pattern A.
678
554
  */
679
555
  async getMYOBCredentials() {
680
- const raw = await this.request("/agent/connect/myob/credentials");
556
+ const raw = await this.transport.request("/agent/connect/myob/credentials");
681
557
  return {
682
558
  accessToken: raw.accessToken,
683
559
  accessTokenExpiresAt: raw.accessTokenExpiresAt ?? "",
@@ -694,7 +570,7 @@ var AgentApiClient = class {
694
570
  * Returned `accounts[i].accountIdentifier` is the MYOB businessId.
695
571
  */
696
572
  async getMYOBAccounts() {
697
- return { accounts: (await this.request("/agent/connect/myob/accounts")).accounts.map((a) => ({
573
+ return { accounts: (await this.transport.request("/agent/connect/myob/accounts")).accounts.map((a) => ({
698
574
  connectionId: a.connectionId,
699
575
  accountIdentifier: a.accountIdentifier,
700
576
  displayName: a.displayName,
@@ -706,7 +582,26 @@ var AgentApiClient = class {
706
582
  })) };
707
583
  }
708
584
  async refreshMYOBToken() {
709
- return this.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
+ };
710
605
  }
711
606
  /**
712
607
  * @deprecated Returns a single primary credential blob. Use
@@ -714,7 +609,7 @@ var AgentApiClient = class {
714
609
  * Pattern A.
715
610
  */
716
611
  async getSalesforceCredentials() {
717
- const raw = await this.request("/agent/connect/salesforce/credentials");
612
+ const raw = await this.transport.request("/agent/connect/salesforce/credentials");
718
613
  return {
719
614
  accessToken: raw.accessToken,
720
615
  accessTokenExpiresAt: raw.accessTokenExpiresAt ?? "",
@@ -729,7 +624,7 @@ var AgentApiClient = class {
729
624
  * the selector every credential-touching tool requires.
730
625
  */
731
626
  async getSalesforceAccounts() {
732
- return { accounts: (await this.request("/agent/connect/salesforce/accounts")).accounts.map((a) => ({
627
+ return { accounts: (await this.transport.request("/agent/connect/salesforce/accounts")).accounts.map((a) => ({
733
628
  connectionId: a.connectionId,
734
629
  accountIdentifier: a.accountIdentifier,
735
630
  displayName: a.displayName,
@@ -747,7 +642,7 @@ var AgentApiClient = class {
747
642
  */
748
643
  async refreshSalesforceAccountToken(orgId) {
749
644
  const path = `/agent/connect/salesforce/accounts/${encodeURIComponent(orgId)}/refresh`;
750
- const raw = await this.request(path, { method: "POST" });
645
+ const raw = await this.transport.request(path, { method: "POST" }, { retry: true });
751
646
  return {
752
647
  accessToken: raw.accessToken,
753
648
  accessTokenExpiresAt: raw.accessTokenExpiresAt ?? "",
@@ -755,70 +650,6 @@ var AgentApiClient = class {
755
650
  };
756
651
  }
757
652
  /**
758
- * @deprecated Returns a single primary credential blob. Use
759
- * `getShopifyAccounts()` for the multi-account shape required by Pattern A
760
- * (`@alfe.ai/shopify-mcp` keys per-shop on the myshopify domain).
761
- */
762
- async getShopifyCredentials() {
763
- const raw = await this.request("/agent/connect/shopify/credentials");
764
- return {
765
- accessToken: raw.accessToken,
766
- shopDomain: raw.shopDomain ?? "",
767
- shopGid: raw.shopGid ?? "",
768
- shopName: raw.shopName ?? "",
769
- apiVersion: raw.apiVersion ?? ""
770
- };
771
- }
772
- /**
773
- * Pattern A: multi-account credential fetch for Shopify. Returns every
774
- * agent-scoped Shopify Connection. One OAuth grant maps to one store, so the
775
- * stable per-call selector is the store's myshopify domain (`shopDomain`),
776
- * NOT `accountIdentifier` — the connect provider keys `accountIdentifier` on
777
- * the immutable shop GID (falling back to the domain), so `shopDomain` is the
778
- * value the LLM passes and the plugin routes on.
779
- *
780
- * Each entry is shaped by the connect provider's `buildCredentialsResponse`:
781
- * `{ accessToken, shopDomain, shopGid, shopName, apiVersion }` — offline
782
- * Shopify tokens never expire, so there is NO token / expiry field and no
783
- * refresh method (unlike Salesforce). The GraphQL Admin API authenticates
784
- * purely on `X-Shopify-Access-Token`; no client credentials are on the wire.
785
- */
786
- async getShopifyAccounts() {
787
- return { accounts: (await this.request("/agent/connect/shopify/accounts")).accounts.map((a) => ({
788
- connectionId: a.connectionId,
789
- accountIdentifier: a.accountIdentifier,
790
- displayName: a.displayName,
791
- connectedAt: a.connectedAt,
792
- accessToken: a.accessToken,
793
- shopDomain: a.shopDomain ?? "",
794
- shopGid: a.shopGid ?? "",
795
- shopName: a.shopName ?? "",
796
- apiVersion: a.apiVersion ?? ""
797
- })) };
798
- }
799
- /**
800
- * Disconnects one connected Microsoft 365 account for the agent, by its
801
- * `accountIdentifier`. Hits the generic per-account disconnect route
802
- * (`DELETE /agent/connect/microsoft/accounts/{accountIdentifier}`), which
803
- * resolves across the agent's full effective scope chain and deletes the
804
- * matching Connection row. Returns the remaining accounts.
805
- *
806
- * IMPORTANT: pass the `accountIdentifier` from `getMicrosoftAccounts()`, NOT
807
- * a synthesised email. For Microsoft, `accountIdentifier` is the user's email
808
- * only when the Graph profile fetch succeeded at connect time; it falls back
809
- * to the Azure tenant id (`tid` claim) otherwise. The backend matches on
810
- * `accountIdentifier` exactly, so passing an email would 404 on those
811
- * fallback-identifier accounts. (This is why the param is not named `email`,
812
- * unlike `disconnectGoogleAccount` where the identifier is always the email.)
813
- */
814
- async disconnectMicrosoftAccount(accountIdentifier) {
815
- return { accounts: (await this.request(`/agent/connect/microsoft/accounts/${encodeURIComponent(accountIdentifier)}`, { method: "DELETE" })).accounts.map((a) => ({
816
- accountIdentifier: a.accountIdentifier,
817
- displayName: a.displayName ?? void 0,
818
- connectedAt: a.connectedAt
819
- })) };
820
- }
821
- /**
822
653
  * Pattern A: multi-account credential fetch for Microsoft 365.
823
654
  *
824
655
  * Returns every agent-scoped Microsoft connection. The caller is expected
@@ -834,16 +665,13 @@ var AgentApiClient = class {
834
665
  * interchangeable across (tenant, user) pairs.
835
666
  */
836
667
  async getMicrosoftAccounts() {
837
- return { accounts: (await this.request("/agent/connect/microsoft/accounts")).accounts.map((a) => ({
668
+ return { accounts: (await this.transport.request("/agent/connect/microsoft/accounts")).accounts.map((a) => ({
838
669
  connectionId: a.connectionId,
839
670
  accountIdentifier: a.accountIdentifier,
840
671
  displayName: a.displayName,
841
672
  connectedAt: a.connectedAt,
842
673
  accessToken: a.accessToken ?? "",
843
674
  accessTokenExpiresAt: a.accessTokenExpiresAt ?? "",
844
- refreshToken: a.refreshToken ?? "",
845
- clientId: a.clientId ?? "",
846
- clientSecret: a.clientSecret ?? "",
847
675
  email: a.email ?? a.accountIdentifier,
848
676
  microsoftTenantId: a.microsoftTenantId ?? "",
849
677
  workspaceDomain: a.workspaceDomain ?? ""
@@ -865,249 +693,261 @@ var AgentApiClient = class {
865
693
  */
866
694
  async refreshMicrosoftAccountToken(accountIdentifier) {
867
695
  const path = `/agent/connect/microsoft/accounts/${encodeURIComponent(accountIdentifier)}/refresh`;
868
- const raw = await this.request(path, { method: "POST" });
696
+ const raw = await this.transport.request(path, { method: "POST" }, { retry: true });
869
697
  return {
870
698
  accessToken: raw.accessToken,
871
699
  accessTokenExpiresAt: raw.accessTokenExpiresAt ?? "",
872
700
  expiresAt: raw.expiresAt ?? ""
873
701
  };
874
702
  }
875
- async getTeamsCredentials() {
876
- return this.request("/agent/microsoft/credentials");
877
- }
878
- async sendTeamsMessage(data) {
879
- return this.request("/agent/microsoft/send", {
880
- method: "POST",
881
- body: JSON.stringify(data)
882
- });
883
- }
884
- async listTeamsChannels() {
885
- return this.request("/agent/microsoft/channels");
886
- }
887
- async presignAttachments(files) {
888
- return this.request("/agent/chat/attachments/presign", {
889
- method: "POST",
890
- body: JSON.stringify({ files })
891
- });
892
- }
893
703
  /**
894
- * Generate an image from a text prompt and get back a STABLE, public URL
895
- * (served from the agent-assets CDN — it does not expire). Embed the returned
896
- * `imageUrl` in a reply as markdown to show it to the user.
897
- *
898
- * ASYNC: `gpt-image-1` routinely runs 30–60s, which exceeds the API Gateway
899
- * 30s ceiling, so this enqueues a job (`POST /agent/images/generate` →
900
- * `jobId`) then polls (`GET /agent/images/{jobId}`) until it completes. The
901
- * worker's real failure message (e.g. an unsupported `size`) surfaces via the
902
- * job's `error` field.
903
- */
904
- async generateImage(args) {
905
- const headers = new Headers();
906
- headers.set("Authorization", `Bearer ${this.apiKey}`);
907
- headers.set("Content-Type", "application/json");
908
- const enqueueRes = await fetch(`${this.apiUrl}/agent/images/generate`, {
909
- method: "POST",
910
- headers,
911
- body: JSON.stringify(args),
912
- signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
913
- });
914
- if (!enqueueRes.ok) throw new Error(`Image generation failed to start (${String(enqueueRes.status)})`);
915
- const { data: enqueued } = await enqueueRes.json();
916
- const jobId = enqueued.jobId;
917
- const deadline = Date.now() + IMAGE_JOB_TIMEOUT_MS;
918
- while (Date.now() < deadline) {
919
- await sleep(IMAGE_POLL_INTERVAL_MS);
920
- let job;
921
- try {
922
- job = await this.request(`/agent/images/${jobId}`);
923
- } catch {
924
- continue;
925
- }
926
- if (job.status === "completed") {
927
- if (!job.imageUrl) throw new Error("Image generation completed without a URL");
928
- return {
929
- imageUrl: job.imageUrl,
930
- model: job.model ?? args.model ?? "gpt-image-1"
931
- };
932
- }
933
- if (job.status === "failed") {
934
- const detail = job.error ? `: ${job.error.split("\n")[0]}` : "";
935
- throw new Error(`Image generation failed${detail}`);
936
- }
937
- }
938
- throw new Error("Image generation timed out");
939
- }
940
- async recordActivity(data) {
941
- return this.request("/agent/activity", {
942
- method: "POST",
943
- body: JSON.stringify(data)
944
- });
945
- }
946
- /** Update the agent's own name and/or voice config. Returns the updated agent. */
947
- async updateSelf(update) {
948
- return this.request("/agent/self", {
949
- method: "PATCH",
950
- body: JSON.stringify(update)
951
- });
952
- }
953
- /**
954
- * Generate the agent's own avatar from a text prompt. The image is generated,
955
- * stored, and set on the agent server-side; returns the updated agent.
704
+ * Disconnects one connected Microsoft 365 account for the agent, by its
705
+ * `accountIdentifier`. Hits the generic per-account disconnect route
706
+ * (`DELETE /agent/connect/microsoft/accounts/{accountIdentifier}`), which
707
+ * resolves across the agent's full effective scope chain and deletes the
708
+ * matching Connection row. Returns the remaining accounts.
956
709
  *
957
- * ASYNC (same reason as `generateImage`): avatar gen runs `gpt-image-1`
958
- * (30–60s) which exceeds the API Gateway 30s ceiling, so this enqueues a job
959
- * (`POST /agent/avatar/generate` `jobId`) then polls (`GET /agent/avatar/{jobId}`)
960
- * until the avatar is set. Signature unchanged the plugin is unaffected.
710
+ * IMPORTANT: pass the `accountIdentifier` from `getMicrosoftAccounts()`, NOT
711
+ * a synthesised email. For Microsoft, `accountIdentifier` is the user's email
712
+ * only when the Graph profile fetch succeeded at connect time; it falls back
713
+ * to the Azure tenant id (`tid` claim) otherwise. The backend matches on
714
+ * `accountIdentifier` exactly, so passing an email would 404 on those
715
+ * fallback-identifier accounts. (This is why the param is not named `email`,
716
+ * unlike `disconnectGoogleAccount` where the identifier is always the email.)
961
717
  */
962
- async generateAvatar(args) {
963
- const headers = new Headers();
964
- headers.set("Authorization", `Bearer ${this.apiKey}`);
965
- headers.set("Content-Type", "application/json");
966
- const enqueueRes = await fetch(`${this.apiUrl}/agent/avatar/generate`, {
967
- method: "POST",
968
- headers,
969
- body: JSON.stringify(args),
970
- signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
971
- });
972
- if (!enqueueRes.ok) throw new Error(`Avatar generation failed to start (${String(enqueueRes.status)})`);
973
- const { data: enqueued } = await enqueueRes.json();
974
- const jobId = enqueued.jobId;
975
- const deadline = Date.now() + IMAGE_JOB_TIMEOUT_MS;
976
- while (Date.now() < deadline) {
977
- await sleep(IMAGE_POLL_INTERVAL_MS);
978
- let job;
979
- try {
980
- job = await this.request(`/agent/avatar/${jobId}`);
981
- } catch {
982
- continue;
983
- }
984
- if (job.status === "completed") {
985
- if (!job.agent) throw new Error("Avatar generation completed without an agent");
986
- return job.agent;
987
- }
988
- if (job.status === "failed") {
989
- const detail = job.error ? `: ${job.error.split("\n")[0]}` : "";
990
- throw new Error(`Avatar generation failed${detail}`);
991
- }
992
- }
993
- throw new Error("Avatar generation timed out");
718
+ async disconnectMicrosoftAccount(accountIdentifier) {
719
+ return { accounts: (await this.transport.request(`/agent/connect/microsoft/accounts/${encodeURIComponent(accountIdentifier)}`, { method: "DELETE" })).accounts.map((a) => ({
720
+ accountIdentifier: a.accountIdentifier,
721
+ displayName: a.displayName ?? void 0,
722
+ connectedAt: a.connectedAt
723
+ })) };
994
724
  }
995
725
  /**
996
- * Get a presigned PUT URL to upload a new avatar image. Upload the bytes to
997
- * `uploadUrl`, then call `finalizeAvatar(s3Key)` to set it on the agent.
726
+ * Resolve the primary cTrader Connection's credentials for the calling
727
+ * agent. Unlike most providers, the cTrader Open API needs app-level auth
728
+ * (`clientId` + `clientSecret`) AND account auth (`accessToken` +
729
+ * `accountId`) on the socket, so `@alfe.ai/ctrader-mcp` self-fetches the
730
+ * full set here at startup (the atlassian/google pattern). `clientId` /
731
+ * `clientSecret` are the SST-sourced global app credentials the connect
732
+ * endpoint injects — they are never persisted on the connection. `host` is
733
+ * the resolved TLS endpoint (`live.ctraderapi.com` / `demo.ctraderapi.com`)
734
+ * derived from the selected account's live/demo flag.
998
735
  */
999
- async presignAvatar(args) {
1000
- return this.request("/agent/avatar/presign", {
1001
- method: "POST",
1002
- body: JSON.stringify(args)
1003
- });
736
+ async getCTraderCredentials() {
737
+ const raw = await this.transport.request("/agent/connect/ctrader/credentials");
738
+ return {
739
+ accessToken: raw.accessToken ?? "",
740
+ refreshToken: raw.refreshToken ?? "",
741
+ accountId: raw.accountId != null ? String(raw.accountId) : "",
742
+ host: raw.host ?? "",
743
+ clientId: raw.clientId ?? "",
744
+ clientSecret: raw.clientSecret ?? ""
745
+ };
1004
746
  }
1005
747
  /**
1006
- * Finalize an avatar upload validates ownership + size, then sets the
1007
- * agent's `avatarUrl` server-side. Returns the updated agent.
1008
- */
1009
- async finalizeAvatar(s3Key) {
1010
- return this.request("/agent/avatar", {
1011
- method: "POST",
1012
- body: JSON.stringify({ s3Key })
1013
- });
1014
- }
1015
- /** List the platform voice catalogue (ElevenLabs) so the agent can pick its own voice. */
1016
- async listVoices() {
1017
- return this.request("/agent/voices");
748
+ * Pattern A: multi-account credential fetch for cTrader.
749
+ *
750
+ * Unlike atlassian/salesforce (one Connection row per account/site), a
751
+ * cTrader is MULTI-grant per agent: an agent may connect several distinct
752
+ * cTrader logins, each its own Connection row keyed on `accountIdentifier =
753
+ * ctid:<userId>` (Phase 1). This aggregates the *trading accounts* across
754
+ * ALL of those Connection rows — each row contributes its `availableAccounts`
755
+ * flattened, and every account carries ITS OWN grant's `accessToken` (the
756
+ * token that authenticates that account against the cTrader Open API). One
757
+ * OAuth grant still covers all accounts under that single login on one shared
758
+ * token; only the `ctidTraderAccountId` and the protobuf socket `host` (live
759
+ * vs demo) differ within a grant. Across grants the tokens differ, so the
760
+ * token is now PER-ACCOUNT rather than hoisted to the top level.
761
+ *
762
+ * `host` per account is derived from the account's `isLive` flag
763
+ * (`live.ctraderapi.com` / `demo.ctraderapi.com`) — the same mapping the
764
+ * connect provider applies server-side when an account is auto-selected.
765
+ *
766
+ * `clientId` / `clientSecret` are the SST-sourced GLOBAL app credentials the
767
+ * connect endpoint injects — identical across every Connection row (one
768
+ * cTrader app), never persisted on a connection. We take them from the first
769
+ * row that carries them.
770
+ *
771
+ * Accounts are deduped on `ctidTraderAccountId` first-wins: Spotware ids are
772
+ * globally unique across logins, so a duplicate can only appear if the same
773
+ * account somehow surfaced under two grants — first-wins keeps it
774
+ * deterministic.
775
+ *
776
+ * `accounts` may be empty (no cTrader Connection at all), in which case we
777
+ * return empty creds rather than throwing.
778
+ */
779
+ async getCTraderAccounts() {
780
+ const raw = await this.transport.request("/agent/connect/ctrader/accounts");
781
+ if (raw.accounts.length === 0) return {
782
+ accounts: [],
783
+ clientId: "",
784
+ clientSecret: ""
785
+ };
786
+ let clientId = "";
787
+ let clientSecret = "";
788
+ for (const row of raw.accounts) {
789
+ if (!clientId && row.clientId) clientId = row.clientId;
790
+ if (!clientSecret && row.clientSecret) clientSecret = row.clientSecret;
791
+ if (clientId && clientSecret) break;
792
+ }
793
+ const seen = /* @__PURE__ */ new Set();
794
+ const accounts = [];
795
+ for (const row of raw.accounts) {
796
+ const rowToken = row.accessToken ?? "";
797
+ for (const a of row.availableAccounts ?? []) {
798
+ const id = a.ctidTraderAccountId != null ? String(a.ctidTraderAccountId) : a.accountId != null ? String(a.accountId) : "";
799
+ if (id.length === 0 || seen.has(id)) continue;
800
+ seen.add(id);
801
+ const isLive = a.isLive === true;
802
+ accounts.push({
803
+ ctidTraderAccountId: id,
804
+ host: isLive ? "live.ctraderapi.com" : "demo.ctraderapi.com",
805
+ isLive,
806
+ ...a.brokerName != null ? { brokerName: a.brokerName } : {},
807
+ ...a.accountNumber != null ? { accountNumber: String(a.accountNumber) } : {},
808
+ accessToken: rowToken
809
+ });
810
+ }
811
+ }
812
+ return {
813
+ accounts,
814
+ clientId,
815
+ clientSecret
816
+ };
1018
817
  }
1019
818
  /**
1020
- * Mint a fresh AES-256 data key for a specific (secret, field) pair. The
1021
- * encryption context is rebuilt server-side from `auth.tenantId` + the body
1022
- * fields including `fieldKey`; the agent cannot forge context for a scope
1023
- * or field it doesn't own. Legacy single-envelope secrets are migrated to
1024
- * `field#value` rows by the data migration, so call with `fieldKey: "value"`
1025
- * to reach them.
819
+ * @deprecated Returns a single primary credential blob. Use
820
+ * `getShopifyAccounts()` for the multi-account shape required by Pattern A
821
+ * (`@alfe.ai/shopify-mcp` keys per-shop on the myshopify domain).
1026
822
  */
1027
- async generateSecretDataKey(args) {
1028
- return this.request("/agent/secrets/generate-data-key", {
1029
- method: "POST",
1030
- body: JSON.stringify(args)
1031
- });
823
+ async getShopifyCredentials() {
824
+ const raw = await this.transport.request("/agent/connect/shopify/credentials");
825
+ return {
826
+ accessToken: raw.accessToken,
827
+ shopDomain: raw.shopDomain ?? "",
828
+ shopGid: raw.shopGid ?? "",
829
+ shopName: raw.shopName ?? "",
830
+ apiVersion: raw.apiVersion ?? ""
831
+ };
1032
832
  }
1033
833
  /**
1034
- * Unwrap a wrapped data key so the agent can decrypt the envelope locally.
1035
- * `fieldKey` MUST match the value supplied when the data key was generated
1036
- * (it's bound into KMS encryption context); mismatch fails with
1037
- * `InvalidCiphertextException`.
834
+ * Pattern A: multi-account credential fetch for Shopify. Returns every
835
+ * agent-scoped Shopify Connection. One OAuth grant maps to one store, so the
836
+ * stable per-call selector is the store's myshopify domain (`shopDomain`),
837
+ * NOT `accountIdentifier` — the connect provider keys `accountIdentifier` on
838
+ * the immutable shop GID (falling back to the domain), so `shopDomain` is the
839
+ * value the LLM passes and the plugin routes on.
840
+ *
841
+ * Each entry is shaped by the connect provider's `buildCredentialsResponse`:
842
+ * `{ accessToken, shopDomain, shopGid, shopName, apiVersion }` — offline
843
+ * Shopify tokens never expire, so there is NO token / expiry field and no
844
+ * refresh method (unlike Salesforce). The GraphQL Admin API authenticates
845
+ * purely on `X-Shopify-Access-Token`; no client credentials are on the wire.
1038
846
  */
1039
- async decryptSecretDataKey(args) {
1040
- return this.request("/agent/secrets/decrypt-data-key", {
1041
- method: "POST",
1042
- body: JSON.stringify(args)
1043
- });
847
+ async getShopifyAccounts() {
848
+ return { accounts: (await this.transport.request("/agent/connect/shopify/accounts")).accounts.map((a) => ({
849
+ connectionId: a.connectionId,
850
+ accountIdentifier: a.accountIdentifier,
851
+ displayName: a.displayName,
852
+ connectedAt: a.connectedAt,
853
+ accessToken: a.accessToken,
854
+ shopDomain: a.shopDomain ?? "",
855
+ shopGid: a.shopGid ?? "",
856
+ shopName: a.shopName ?? "",
857
+ apiVersion: a.apiVersion ?? ""
858
+ })) };
1044
859
  }
1045
860
  /**
1046
- * Create a new secret with one or more fields. Encrypted fields must arrive
1047
- * pre-sealed (the agent has already obtained per-field data keys via
1048
- * `generateSecretDataKey({ ..., fieldKey })` and AES-encrypted locally).
1049
- * Plaintext fields ship the value inline.
861
+ * Pattern A: provider-parameterized multi-account credential fetch for the
862
+ * social connectors (Bluesky, and the approval-gated backlog: X, Meta,
863
+ * Threads, LinkedIn, Pinterest, TikTok, Reddit, YouTube).
864
+ *
865
+ * Unlike the bespoke `getGithubAccounts()` / `getXeroAccounts()` shapes,
866
+ * this returns a UNIFORM normalized account shape so `@alfe.ai/social-mcp`'s
867
+ * shared driver can require a single `account` selector on every
868
+ * credential-touching tool regardless of platform. The backend
869
+ * `api-agents/{provider}/accounts` route is already provider-generic; this
870
+ * is the client-side normalization the plan (`do-we-need-any-moonlit-toucan`
871
+ * Phase 0, step 5) calls for.
872
+ *
873
+ * `accountIdentifier` is the stable per-account selector the LLM should
874
+ * pass back (for Bluesky: the account DID). `accessToken` carries whatever
875
+ * the provider's `buildCredentialsResponse` bundles (for Bluesky: the JSON
876
+ * session bundle — the driver parses the `accessJwt` out of it, or reads the
877
+ * top-level `accessJwt` from `providerMetadata`-adjacent fields). Everything
878
+ * else the driver needs for routing (handle, pdsHost, did, …) is on
879
+ * `providerMetadata`.
880
+ *
881
+ * Token refresh is delegated to connect (never done in-plugin) via the
882
+ * per-account route `POST /agent/connect/{provider}/accounts/{accountIdentifier}/refresh`
883
+ * — call `refreshSocialAccount(provider, accountIdentifier)`. (The non-account
884
+ * `POST /agent/connect/{provider}/refresh` route refreshes the provider's
885
+ * PRIMARY connection, which is wrong under multi-account Pattern A.)
1050
886
  */
1051
- async createSecret(args) {
1052
- const { scope, scopeId, secretId, ...body } = args;
1053
- return this.request(`/agent/secrets/${encodeURIComponent(scope)}/${encodeURIComponent(scopeId)}/${encodeURIComponent(secretId)}`, {
1054
- method: "PUT",
1055
- body: JSON.stringify(body)
1056
- });
1057
- }
1058
- /** Fetch the secret aggregate plus per-field encrypted envelopes. */
1059
- async getSecret(args) {
1060
- return this.request(`/agent/secrets/${encodeURIComponent(args.scope)}/${encodeURIComponent(args.scopeId)}/${encodeURIComponent(args.secretId)}`);
1061
- }
1062
- /** Fetch one field. Plaintext: value inline. Encrypted: envelope. */
1063
- async getSecretField(args) {
1064
- return this.request(`/agent/secrets/${encodeURIComponent(args.scope)}/${encodeURIComponent(args.scopeId)}/${encodeURIComponent(args.secretId)}/fields/${encodeURIComponent(args.fieldKey)}`);
1065
- }
1066
- /** Add OR rotate one field. */
1067
- async setSecretField(args) {
1068
- const { scope, scopeId, secretId, fieldKey, ...body } = args;
1069
- return this.request(`/agent/secrets/${encodeURIComponent(scope)}/${encodeURIComponent(scopeId)}/${encodeURIComponent(secretId)}/fields/${encodeURIComponent(fieldKey)}`, {
1070
- method: "PUT",
1071
- body: JSON.stringify(body)
1072
- });
1073
- }
1074
- /** Remove one field. */
1075
- async removeSecretField(args) {
1076
- await this.request(`/agent/secrets/${encodeURIComponent(args.scope)}/${encodeURIComponent(args.scopeId)}/${encodeURIComponent(args.secretId)}/fields/${encodeURIComponent(args.fieldKey)}`, { method: "DELETE" });
1077
- }
1078
- /** Update secret-level metadata (name/description/tags/category). */
1079
- async updateSecretMetadata(args) {
1080
- const { scope, scopeId, secretId, ...body } = args;
1081
- return this.request(`/agent/secrets/${encodeURIComponent(scope)}/${encodeURIComponent(scopeId)}/${encodeURIComponent(secretId)}`, {
1082
- method: "PATCH",
1083
- body: JSON.stringify(body)
1084
- });
1085
- }
1086
- /** List metadata for secrets in a scope. Optional filters route through the byFacet GSI. */
1087
- async listSecrets(args) {
1088
- const params = new URLSearchParams();
1089
- if (args.category) params.set("category", args.category);
1090
- if (args.tag) params.set("tag", args.tag);
1091
- if (args.fieldKey) params.set("fieldKey", args.fieldKey);
1092
- const qs = params.toString();
1093
- return (await this.request(`/agent/secrets/${encodeURIComponent(args.scope)}/${encodeURIComponent(args.scopeId)}${qs ? `?${qs}` : ""}`)).secrets;
887
+ async getSocialAccounts(provider) {
888
+ const raw = await this.transport.request(`/agent/connect/${encodeURIComponent(provider)}/accounts`);
889
+ return {
890
+ provider: raw.provider ?? provider,
891
+ accounts: raw.accounts.map((a) => ({
892
+ connectionId: a.connectionId,
893
+ accountIdentifier: a.accountIdentifier,
894
+ displayName: a.displayName,
895
+ accessToken: a.accessToken ?? "",
896
+ providerMetadata: a.providerMetadata ?? {},
897
+ connectedAt: a.connectedAt
898
+ }))
899
+ };
1094
900
  }
1095
- /** Bounded changelog read — metadata-only audit entries. */
1096
- async getSecretHistory(args) {
1097
- const params = new URLSearchParams();
1098
- if (args.limit) params.set("limit", String(args.limit));
1099
- if (args.cursor) params.set("cursor", args.cursor);
1100
- const qs = params.toString();
1101
- return this.request(`/agent/secrets/${encodeURIComponent(args.scope)}/${encodeURIComponent(args.scopeId)}/${encodeURIComponent(args.secretId)}/history${qs ? `?${qs}` : ""}`);
901
+ /**
902
+ * Pattern A: refresh a specific social Connection by its stable
903
+ * `accountIdentifier` (for Bluesky: the account DID) via the
904
+ * provider-generic per-account refresh route. The counterpart to
905
+ * `getSocialAccounts(provider)`; `@alfe.ai/social-mcp` calls this on a
906
+ * 401/ExpiredToken from the platform PDS/API, then re-fetches accounts to
907
+ * pick up the rotated bundle.
908
+ *
909
+ * Refresh itself is ALWAYS delegated to connect — the plugin never calls
910
+ * the platform's own refresh XRPC (e.g. `com.atproto.server.refreshSession`)
911
+ * because connect owns the encrypted refresh token + rotation persistence
912
+ * (Bluesky rotates the refreshJwt; a missed rotation kills the connection
913
+ * after one refresh). The returned `accessToken` is whatever the provider's
914
+ * `refreshToken` hook re-bundled (for Bluesky: the JSON session bundle with
915
+ * the fresh `accessJwt`) — callers typically ignore it and re-fetch via
916
+ * `getSocialAccounts` for a consistent shape.
917
+ */
918
+ async refreshSocialAccount(provider, accountIdentifier) {
919
+ const path = `/agent/connect/${encodeURIComponent(provider)}/accounts/${encodeURIComponent(accountIdentifier)}/refresh`;
920
+ const raw = await this.transport.request(path, { method: "POST" }, { retry: true });
921
+ return {
922
+ accountIdentifier: raw.accountIdentifier,
923
+ accessToken: raw.accessToken,
924
+ accessTokenExpiresAt: raw.accessTokenExpiresAt ?? "",
925
+ expiresAt: raw.expiresAt ?? ""
926
+ };
1102
927
  }
1103
- /** Delete a secret (and all its field rows + tag rows + changelog rows). */
1104
- async deleteSecret(args) {
1105
- await this.request(`/agent/secrets/${encodeURIComponent(args.scope)}/${encodeURIComponent(args.scopeId)}/${encodeURIComponent(args.secretId)}`, { method: "DELETE" });
928
+ };
929
+ //#endregion
930
+ //#region src/domains/database.ts
931
+ /**
932
+ * Per-tenant MongoDB methods (services/database) for the Agent API client.
933
+ */
934
+ var DatabaseApi = class extends ApiBase {
935
+ async registerDatabaseCredentials() {
936
+ return this.transport.request("/agent/database/register", { method: "POST" });
1106
937
  }
1107
- /** Enumerate scopes (org/team/project/agent) this agent can access. */
1108
- async listSecretScopes() {
1109
- return (await this.request("/agent/secrets/scopes")).scopes;
938
+ async reportDatabaseAudit(entry) {
939
+ await this.transport.request("/agent/database/audit", {
940
+ method: "POST",
941
+ body: JSON.stringify(entry)
942
+ }).catch(() => {});
1110
943
  }
944
+ };
945
+ //#endregion
946
+ //#region src/domains/identity.ts
947
+ /**
948
+ * Identity resolution, verification, and CRM methods for the Agent API client.
949
+ */
950
+ var IdentityApi = class extends ApiBase {
1111
951
  /**
1112
952
  * Returns the calling agent's own identity context — `{ agentId, tenantId }`
1113
953
  * decoded server-side from the agent API token. Used by the
@@ -1118,10 +958,10 @@ var AgentApiClient = class {
1118
958
  * per-call use.
1119
959
  */
1120
960
  async whoami() {
1121
- return this.request("/agent/identity/whoami");
961
+ return this.transport.request("/agent/identity/whoami");
1122
962
  }
1123
963
  async resolveIdentity(args) {
1124
- return this.request("/agent/identity/resolve", {
964
+ return this.transport.request("/agent/identity/resolve", {
1125
965
  method: "POST",
1126
966
  body: JSON.stringify(args)
1127
967
  });
@@ -1132,31 +972,28 @@ var AgentApiClient = class {
1132
972
  if (args?.status) qs.set("status", args.status);
1133
973
  if (args?.limit) qs.set("limit", String(args.limit));
1134
974
  const query = qs.toString();
1135
- return this.request(`/agent/identity/search${query ? `?${query}` : ""}`);
975
+ return this.transport.request(`/agent/identity/search${query ? `?${query}` : ""}`);
1136
976
  }
1137
977
  async getIdentityContext(identityId) {
1138
- return this.request(`/agent/identity/${encodeURIComponent(identityId)}/context`);
978
+ return this.transport.request(`/agent/identity/${encodeURIComponent(identityId)}/context`);
1139
979
  }
1140
980
  async mergeIdentities(survivorId, args) {
1141
- return this.request(`/agent/identity/${encodeURIComponent(survivorId)}/merge`, {
981
+ return this.transport.request(`/agent/identity/${encodeURIComponent(survivorId)}/merge`, {
1142
982
  method: "POST",
1143
983
  body: JSON.stringify(args)
1144
984
  });
1145
985
  }
1146
- async unmergeIdentity(identityId, args) {
1147
- return this.request(`/agent/identity/${encodeURIComponent(identityId)}/unmerge`, {
1148
- method: "POST",
1149
- body: JSON.stringify(args)
1150
- });
986
+ async unmergeIdentity(identityId) {
987
+ return this.transport.request(`/agent/identity/${encodeURIComponent(identityId)}/unmerge`, { method: "POST" });
1151
988
  }
1152
989
  async addIdentityNote(identityId, args) {
1153
- return this.request(`/agent/identity/${encodeURIComponent(identityId)}/notes`, {
990
+ return this.transport.request(`/agent/identity/${encodeURIComponent(identityId)}/notes`, {
1154
991
  method: "POST",
1155
992
  body: JSON.stringify(args)
1156
993
  });
1157
994
  }
1158
995
  async tagIdentity(identityId, args) {
1159
- return this.request(`/agent/identity/${encodeURIComponent(identityId)}/tags`, {
996
+ return this.transport.request(`/agent/identity/${encodeURIComponent(identityId)}/tags`, {
1160
997
  method: "POST",
1161
998
  body: JSON.stringify(args)
1162
999
  });
@@ -1164,23 +1001,24 @@ var AgentApiClient = class {
1164
1001
  async getIdentityChangelog(identityId, args) {
1165
1002
  const qs = new URLSearchParams();
1166
1003
  if (args?.limit) qs.set("limit", String(args.limit));
1004
+ if (args?.cursor) qs.set("cursor", args.cursor);
1167
1005
  const query = qs.toString();
1168
- return this.request(`/agent/identity/${encodeURIComponent(identityId)}/changelog${query ? `?${query}` : ""}`);
1006
+ return this.transport.request(`/agent/identity/${encodeURIComponent(identityId)}/changelog${query ? `?${query}` : ""}`);
1169
1007
  }
1170
1008
  async rollbackIdentity(identityId, args) {
1171
- return this.request(`/agent/identity/${encodeURIComponent(identityId)}/rollback`, {
1009
+ return this.transport.request(`/agent/identity/${encodeURIComponent(identityId)}/rollback`, {
1172
1010
  method: "POST",
1173
1011
  body: JSON.stringify(args)
1174
1012
  });
1175
1013
  }
1176
1014
  async requestIdentityVerification(args) {
1177
- return this.request("/agent/identity/verify/request", {
1015
+ return this.transport.request("/agent/identity/verify/request", {
1178
1016
  method: "POST",
1179
1017
  body: JSON.stringify(args)
1180
1018
  });
1181
1019
  }
1182
1020
  async confirmIdentityVerification(args) {
1183
- return this.request("/agent/identity/verify/confirm", {
1021
+ return this.transport.request("/agent/identity/verify/confirm", {
1184
1022
  method: "POST",
1185
1023
  body: JSON.stringify(args)
1186
1024
  });
@@ -1192,7 +1030,7 @@ var AgentApiClient = class {
1192
1030
  * not agent-writable.
1193
1031
  */
1194
1032
  async updateIdentity(identityId, args) {
1195
- return this.request(`/agent/identity/${encodeURIComponent(identityId)}/update`, {
1033
+ return this.transport.request(`/agent/identity/${encodeURIComponent(identityId)}/update`, {
1196
1034
  method: "POST",
1197
1035
  body: JSON.stringify(args)
1198
1036
  });
@@ -1203,128 +1041,121 @@ var AgentApiClient = class {
1203
1041
  * identity (created or matched via Scenario-B email enrichment).
1204
1042
  */
1205
1043
  async resolveGoogleChatSender(args) {
1206
- return this.request("/agent/google/resolve-sender", {
1044
+ return this.transport.request("/agent/google/resolve-sender", {
1207
1045
  method: "POST",
1208
1046
  body: JSON.stringify(args)
1209
1047
  });
1210
1048
  }
1211
- async memorySearch(query, opts) {
1212
- return this.request("/agent/memory/search", {
1213
- method: "POST",
1214
- body: JSON.stringify({
1215
- query,
1216
- limit: opts?.limit ?? 10,
1217
- topic: opts?.topic,
1218
- subtopic: opts?.subtopic,
1219
- tag: opts?.tag,
1220
- includeKnowledge: opts?.includeKnowledge ?? true
1221
- })
1222
- });
1223
- }
1224
- async memoryStore(text, opts) {
1225
- return this.request("/agent/memory/store", {
1226
- method: "POST",
1227
- body: JSON.stringify({
1228
- text,
1229
- topic: opts?.topic ?? "general",
1230
- subtopic: opts?.subtopic ?? "general",
1231
- tag: opts?.tag ?? "fact",
1232
- importance: opts?.importance ?? .7
1233
- })
1234
- });
1235
- }
1236
- async memoryIngest(sessionKey, messages, metadata, ingestEpoch) {
1237
- return this.request("/agent/memory/ingest", {
1049
+ };
1050
+ //#endregion
1051
+ //#region src/domains/images.ts
1052
+ /**
1053
+ * Image-generation method — text prompt → a stable, public CDN image URL.
1054
+ * Ported from main's monolith addition into the domain-split layout.
1055
+ */
1056
+ const IMAGE_POLL_INTERVAL_MS = 2e3;
1057
+ const IMAGE_JOB_TIMEOUT_MS = 18e4;
1058
+ var ImagesApi = class extends ApiBase {
1059
+ /**
1060
+ * Generate an image from a text prompt and get back a STABLE, public URL
1061
+ * (served from the agent-assets CDN — it does not expire). Embed the returned
1062
+ * `imageUrl` in a reply as markdown to show it to the user.
1063
+ *
1064
+ * ASYNC: `gpt-image-1` routinely runs 30–60s, which exceeds the API Gateway
1065
+ * 30s ceiling, so this enqueues a job (`POST /agent/images/generate` →
1066
+ * `jobId`) then polls (`GET /agent/images/{jobId}`) until it completes. The
1067
+ * worker's real failure message (e.g. an unsupported `size`) surfaces via the
1068
+ * job's `error` field.
1069
+ */
1070
+ async generateImage(args) {
1071
+ const { jobId } = await this.transport.request("/agent/images/generate", {
1238
1072
  method: "POST",
1239
- body: JSON.stringify({
1240
- sessionKey,
1241
- lastProcessedIndex: messages.length > 0 ? messages[messages.length - 1].index : -1,
1242
- ...ingestEpoch !== void 0 ? { ingestEpoch } : {},
1243
- messages,
1244
- metadata
1245
- })
1246
- });
1247
- }
1248
- async memoryLoadContext(tier, topicHint) {
1249
- const params = new URLSearchParams();
1250
- if (tier !== void 0) params.set("tier", String(tier));
1251
- if (topicHint) params.set("topicHint", topicHint);
1252
- const qs = params.toString();
1253
- return this.request(`/agent/memory/context${qs ? `?${qs}` : ""}`);
1254
- }
1255
- async memoryLookupEntity(subject) {
1256
- return this.request(`/agent/memory/knowledge/entities?subject=${encodeURIComponent(subject)}`);
1257
- }
1258
- async memoryNavigate() {
1259
- return this.request("/agent/memory/navigate");
1260
- }
1261
- async memoryDelete(memoryId) {
1262
- return this.request(`/agent/memory/${encodeURIComponent(memoryId)}`, { method: "DELETE" });
1263
- }
1264
- async memoryStats() {
1265
- return this.request("/agent/memory/stats");
1073
+ body: JSON.stringify(args)
1074
+ }, { retry: false });
1075
+ const deadline = Date.now() + IMAGE_JOB_TIMEOUT_MS;
1076
+ while (Date.now() < deadline) {
1077
+ await sleep(IMAGE_POLL_INTERVAL_MS);
1078
+ let job;
1079
+ try {
1080
+ job = await this.transport.request(`/agent/images/${jobId}`);
1081
+ } catch (error) {
1082
+ if (isTransientRequestError(error)) continue;
1083
+ throw error;
1084
+ }
1085
+ if (job.status === "completed") {
1086
+ if (!job.imageUrl) throw new Error("Image generation completed without a URL");
1087
+ return {
1088
+ imageUrl: job.imageUrl,
1089
+ model: job.model ?? args.model ?? "gpt-image-1"
1090
+ };
1091
+ }
1092
+ if (job.status === "failed") {
1093
+ const detail = job.error ? `: ${job.error.split("\n")[0]}` : "";
1094
+ throw new Error(`Image generation failed${detail}`);
1095
+ }
1096
+ }
1097
+ throw new Error("Image generation timed out");
1266
1098
  }
1267
- async memoryLearn(args) {
1268
- return this.request("/agent/memory/learn", {
1269
- method: "POST",
1270
- body: JSON.stringify({
1271
- text: args.text,
1272
- source: args.source,
1273
- sourceType: args.sourceType ?? "inline",
1274
- metadata: args.metadata
1275
- })
1276
- });
1099
+ };
1100
+ //#endregion
1101
+ //#region src/domains/integrations.ts
1102
+ var IntegrationsApi = class extends ApiBase {
1103
+ async listIntegrations() {
1104
+ return this.transport.request("/agent/integrations");
1277
1105
  }
1278
- async memoryBootstrapStatus() {
1279
- return this.request("/agent/memory/bootstrap-status");
1106
+ async getIntegrationConfig(integrationId) {
1107
+ try {
1108
+ return await this.transport.request(`/agent/integrations/${encodeURIComponent(integrationId)}/config`);
1109
+ } catch (err) {
1110
+ if (err.status === 404) return {
1111
+ integrationId,
1112
+ config: {},
1113
+ configSchema: [],
1114
+ installed: false
1115
+ };
1116
+ throw err;
1117
+ }
1280
1118
  }
1281
- async memoryBootstrapStatusMark(scope) {
1282
- return this.request("/agent/memory/bootstrap-status", {
1283
- method: "POST",
1284
- ...scope ? { body: JSON.stringify({ scope }) } : {}
1119
+ async updateIntegrationConfig(integrationId, config) {
1120
+ await this.transport.request(`/agent/integrations/${encodeURIComponent(integrationId)}`, {
1121
+ method: "PATCH",
1122
+ body: JSON.stringify({ config })
1285
1123
  });
1286
1124
  }
1287
- async sendSms(args) {
1288
- return this.request("/mobile/sms/send", {
1125
+ async installIntegration(integrationId, options) {
1126
+ return this.transport.request("/agent/integrations", {
1289
1127
  method: "POST",
1290
1128
  body: JSON.stringify({
1291
- to: args.to,
1292
- body: args.body
1129
+ integrationId,
1130
+ version: options?.version,
1131
+ config: options?.config
1293
1132
  })
1294
1133
  });
1295
1134
  }
1296
- async searchWeb(params) {
1297
- return this.request("/agent/search/web", {
1298
- method: "POST",
1299
- body: JSON.stringify(params)
1300
- });
1301
- }
1302
- async searchImages(params) {
1303
- return this.request("/agent/search/images", {
1304
- method: "POST",
1305
- body: JSON.stringify(params)
1306
- });
1135
+ async removeIntegration(integrationId) {
1136
+ return this.transport.request(`/agent/integrations/${encodeURIComponent(integrationId)}`, { method: "DELETE" });
1307
1137
  }
1308
- async searchNews(params) {
1309
- return this.request("/agent/search/news", {
1310
- method: "POST",
1311
- body: JSON.stringify(params)
1312
- });
1138
+ async getOAuthUrl(provider, scopes) {
1139
+ const params = new URLSearchParams({ provider });
1140
+ if (scopes?.length) params.set("scopes", scopes.join(","));
1141
+ return this.transport.request(`/agent/integrations/oauth/url?${params.toString()}`);
1313
1142
  }
1314
- /** Search news across the selected provider's corpus. → POST /agent/news/search */
1315
- async newsSearch(params) {
1316
- return this.request("/agent/news/search", {
1317
- method: "POST",
1318
- body: JSON.stringify(params)
1319
- });
1143
+ async getOAuthStatus(provider) {
1144
+ return this.transport.request(`/agent/integrations/oauth/status?provider=${encodeURIComponent(provider)}`);
1320
1145
  }
1321
- /** Top headlines for the selected provider. → POST /agent/news/headlines */
1322
- async newsHeadlines(params) {
1323
- return this.request("/agent/news/headlines", {
1324
- method: "POST",
1325
- body: JSON.stringify(params ?? {})
1326
- });
1146
+ async getRegistry() {
1147
+ return this.transport.request("/integrations/registry");
1327
1148
  }
1149
+ };
1150
+ //#endregion
1151
+ //#region src/domains/knowledge.ts
1152
+ /**
1153
+ * Knowledge resource methods (org/team/project scoped docs, profiles,
1154
+ * change requests + RAG search) for the Agent API client.
1155
+ */
1156
+ /** Matches services/knowledge's maximum indexed document size. */
1157
+ const MAX_KNOWLEDGE_DOCUMENT_BYTES = 2 * 1024 * 1024;
1158
+ var KnowledgeApi = class extends ApiBase {
1328
1159
  /**
1329
1160
  * Semantic search across the agent's member scopes. Fan-out is gated
1330
1161
  * server-side by `listScopes` set-inclusion (fail-closed). Pass
@@ -1332,7 +1163,7 @@ var AgentApiClient = class {
1332
1163
  * yields empty results (never a cross-scope leak).
1333
1164
  */
1334
1165
  async knowledgeSearch(query, opts) {
1335
- return this.request("/agent/knowledge/search", {
1166
+ return this.transport.request("/agent/knowledge/search", {
1336
1167
  method: "POST",
1337
1168
  body: JSON.stringify({
1338
1169
  query,
@@ -1344,11 +1175,60 @@ var AgentApiClient = class {
1344
1175
  }
1345
1176
  /** Enumerate the scopes (org + teams + projects) this agent belongs to. */
1346
1177
  async listScopes() {
1347
- return this.request("/agent/org/scopes");
1178
+ return this.transport.request("/agent/org/scopes");
1348
1179
  }
1349
1180
  /** Read a scope's structured knowledge profile (after membership check). */
1350
1181
  async getScopeProfile(scopeType, scopeId) {
1351
- return this.request(`/agent/org/profile/${encodeURIComponent(scopeType)}/${encodeURIComponent(scopeId)}`);
1182
+ return this.transport.request(`/agent/org/profile/${encodeURIComponent(scopeType)}/${encodeURIComponent(scopeId)}`);
1183
+ }
1184
+ /**
1185
+ * Open a change request against a scope's knowledge resource. For a doc
1186
+ * create/update, `services/org` returns a presigned staging PUT; this method
1187
+ * uploads the proposed `content` to it (echoing the same Content-Type that
1188
+ * was signed), mirroring `writeScopeDoc`. The staged body is applied to the
1189
+ * canonical doc — attributed to this agent — only when a reviewer approves.
1190
+ */
1191
+ async proposeScopeChange(scopeType, scopeId, input) {
1192
+ const isDocBody = input.resourceType === "doc" && input.operation !== "delete";
1193
+ const contentType = input.contentType ?? "text/markdown";
1194
+ const result = await this.transport.request(`/agent/org/change-requests/${encodeURIComponent(scopeType)}/${encodeURIComponent(scopeId)}`, {
1195
+ method: "POST",
1196
+ body: JSON.stringify({
1197
+ resourceType: input.resourceType,
1198
+ operation: input.operation,
1199
+ rationale: input.rationale,
1200
+ targetPath: input.targetPath,
1201
+ proposedContentType: isDocBody ? contentType : void 0,
1202
+ proposedValue: input.proposedValue
1203
+ })
1204
+ });
1205
+ if (isDocBody && result.uploadUrl) {
1206
+ const putHeaders = new Headers(result.requiredHeaders ?? {});
1207
+ putHeaders.set("Content-Type", contentType);
1208
+ const res = await fetch(result.uploadUrl, {
1209
+ method: "PUT",
1210
+ body: input.content ?? "",
1211
+ headers: putHeaders,
1212
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
1213
+ });
1214
+ if (!res.ok) {
1215
+ await res.text();
1216
+ throw new Error(`Change-request body upload failed (${String(res.status)})`);
1217
+ }
1218
+ }
1219
+ return result.changeRequest;
1220
+ }
1221
+ /**
1222
+ * List the agent's OWN change requests in a scope (filtered server-side to
1223
+ * this agent as proposer). Pass `status` to narrow to open / approved / etc.
1224
+ */
1225
+ async listScopeChangeRequests(scopeType, scopeId, opts) {
1226
+ const qs = new URLSearchParams();
1227
+ if (opts?.status) qs.set("status", opts.status);
1228
+ if (opts?.limit !== void 0) qs.set("limit", String(opts.limit));
1229
+ if (opts?.cursor) qs.set("cursor", opts.cursor);
1230
+ const query = qs.toString();
1231
+ return this.transport.request(`/agent/org/change-requests/${encodeURIComponent(scopeType)}/${encodeURIComponent(scopeId)}${query ? `?${query}` : ""}`);
1352
1232
  }
1353
1233
  /** List a scope's docs (the org-files corpus; mirrored to shared/<scope>/). */
1354
1234
  async listScopeDocs(scopeType, scopeId, opts) {
@@ -1356,23 +1236,25 @@ var AgentApiClient = class {
1356
1236
  if (opts?.limit !== void 0) qs.set("limit", String(opts.limit));
1357
1237
  if (opts?.cursor) qs.set("cursor", opts.cursor);
1358
1238
  const query = qs.toString();
1359
- return this.request(`/agent/org/files/${encodeURIComponent(scopeType)}/${encodeURIComponent(scopeId)}${query ? `?${query}` : ""}`);
1239
+ return this.transport.request(`/agent/org/files/${encodeURIComponent(scopeType)}/${encodeURIComponent(scopeId)}${query ? `?${query}` : ""}`);
1360
1240
  }
1361
1241
  /**
1362
1242
  * Read the full text of a scope doc. Resolves a presigned download URL
1363
1243
  * from `services/org`, then fetches the bytes directly from S3 (the one
1364
1244
  * legitimate raw fetch in a plugin — same pattern as sync).
1365
1245
  */
1366
- async readScopeDoc(scopeType, scopeId, filePath) {
1367
- const { downloadUrl } = await this.request(`/agent/org/files/${encodeURIComponent(scopeType)}/${encodeURIComponent(scopeId)}/download/${encodeFilePath(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)}`);
1249
+ const { downloadUrl } = await this.transport.request(`/agent/org/files/${encodeURIComponent(scopeType)}/${encodeURIComponent(scopeId)}/download/${encodeFilePath(filePath)}`);
1368
1250
  const res = await fetch(downloadUrl, { signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS) });
1369
1251
  if (!res.ok) {
1370
- await res.text();
1252
+ await res.body?.cancel().catch(() => void 0);
1371
1253
  throw new Error(`Doc download failed (${String(res.status)})`);
1372
1254
  }
1373
1255
  return {
1374
1256
  filePath,
1375
- text: await res.text()
1257
+ text: await readBoundedUtf8(res, maxBytes)
1376
1258
  };
1377
1259
  }
1378
1260
  /**
@@ -1384,7 +1266,7 @@ var AgentApiClient = class {
1384
1266
  */
1385
1267
  async writeScopeDoc(scopeType, scopeId, filePath, content, opts) {
1386
1268
  const contentType = opts?.contentType ?? "text/markdown";
1387
- const presign = await this.request(`/agent/org/files/${encodeURIComponent(scopeType)}/${encodeURIComponent(scopeId)}/upload/${encodeFilePath(filePath)}`, {
1269
+ const presign = await this.transport.request(`/agent/org/files/${encodeURIComponent(scopeType)}/${encodeURIComponent(scopeId)}/upload/${encodeFilePath(filePath)}`, {
1388
1270
  method: "POST",
1389
1271
  body: JSON.stringify({
1390
1272
  contentType,
@@ -1405,123 +1287,449 @@ var AgentApiClient = class {
1405
1287
  }
1406
1288
  return { filePath: presign.filePath };
1407
1289
  }
1408
- /**
1409
- * Open a change request against a scope's knowledge resource. For a doc
1410
- * create/update, `services/org` returns a presigned staging PUT; this method
1411
- * uploads the proposed `content` to it (echoing the same Content-Type that
1412
- * was signed), mirroring `writeScopeDoc`. The staged body is applied to the
1413
- * canonical doc — attributed to this agent — only when a reviewer approves.
1414
- */
1415
- async proposeScopeChange(scopeType, scopeId, input) {
1416
- const isDocBody = input.resourceType === "doc" && input.operation !== "delete";
1417
- const contentType = input.contentType ?? "text/markdown";
1418
- const result = await this.request(`/agent/org/change-requests/${encodeURIComponent(scopeType)}/${encodeURIComponent(scopeId)}`, {
1419
- method: "POST",
1420
- body: JSON.stringify({
1421
- resourceType: input.resourceType,
1422
- operation: input.operation,
1423
- rationale: input.rationale,
1424
- targetPath: input.targetPath,
1425
- proposedContentType: isDocBody ? contentType : void 0,
1426
- proposedValue: input.proposedValue
1427
- })
1428
- });
1429
- if (isDocBody && result.uploadUrl) {
1430
- const putHeaders = new Headers(result.requiredHeaders ?? {});
1431
- putHeaders.set("Content-Type", contentType);
1432
- const res = await fetch(result.uploadUrl, {
1433
- method: "PUT",
1434
- body: input.content ?? "",
1435
- headers: putHeaders,
1436
- signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
1437
- });
1438
- if (!res.ok) {
1439
- await res.text();
1440
- throw new Error(`Change-request body upload failed (${String(res.status)})`);
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;
1441
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);
1442
1315
  }
1443
- return result.changeRequest;
1316
+ } finally {
1317
+ reader.releaseLock();
1444
1318
  }
1445
- /**
1446
- * List the agent's OWN change requests in a scope (filtered server-side to
1447
- * this agent as proposer). Pass `status` to narrow to open / approved / etc.
1448
- */
1449
- async listScopeChangeRequests(scopeType, scopeId, opts) {
1450
- const qs = new URLSearchParams();
1451
- if (opts?.status) qs.set("status", opts.status);
1452
- if (opts?.limit !== void 0) qs.set("limit", String(opts.limit));
1453
- if (opts?.cursor) qs.set("cursor", opts.cursor);
1454
- const query = qs.toString();
1455
- return this.request(`/agent/org/change-requests/${encodeURIComponent(scopeType)}/${encodeURIComponent(scopeId)}${query ? `?${query}` : ""}`);
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;
1456
1324
  }
1457
- async registerDatabaseCredentials() {
1458
- return this.request("/agent/database/register", { method: "POST" });
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");
1459
1329
  }
1460
- async reportDatabaseAudit(entry) {
1461
- await this.request("/agent/database/audit", {
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
+ }
1337
+ //#endregion
1338
+ //#region src/domains/memory.ts
1339
+ /**
1340
+ * Cloud memory methods (Turbopuffer vectors + DynamoDB knowledge graph)
1341
+ * for the Agent API client.
1342
+ */
1343
+ var MemoryApi = class extends ApiBase {
1344
+ async memorySearch(query, opts) {
1345
+ return this.transport.request("/agent/memory/search", {
1462
1346
  method: "POST",
1463
- body: JSON.stringify(entry)
1464
- }).catch(() => {});
1465
- }
1347
+ body: JSON.stringify({
1348
+ query,
1349
+ limit: opts?.limit ?? 10,
1350
+ topic: opts?.topic,
1351
+ subtopic: opts?.subtopic,
1352
+ tag: opts?.tag,
1353
+ includeKnowledge: opts?.includeKnowledge ?? true
1354
+ })
1355
+ });
1356
+ }
1357
+ async memoryStore(text, opts) {
1358
+ return this.transport.request("/agent/memory/store", {
1359
+ method: "POST",
1360
+ body: JSON.stringify({
1361
+ text,
1362
+ topic: opts?.topic ?? "general",
1363
+ subtopic: opts?.subtopic ?? "general",
1364
+ tag: opts?.tag ?? "fact",
1365
+ importance: opts?.importance ?? .7
1366
+ })
1367
+ });
1368
+ }
1369
+ async memoryIngest(sessionKey, messages, metadata, ingestEpoch) {
1370
+ return this.transport.request("/agent/memory/ingest", {
1371
+ method: "POST",
1372
+ body: JSON.stringify({
1373
+ sessionKey,
1374
+ lastProcessedIndex: messages.length > 0 ? messages[messages.length - 1].index : -1,
1375
+ ...ingestEpoch !== void 0 ? { ingestEpoch } : {},
1376
+ messages,
1377
+ metadata
1378
+ })
1379
+ });
1380
+ }
1381
+ async memoryLoadContext(tier, topicHint) {
1382
+ const params = new URLSearchParams();
1383
+ if (tier !== void 0) params.set("tier", String(tier));
1384
+ if (topicHint) params.set("topicHint", topicHint);
1385
+ const qs = params.toString();
1386
+ return this.transport.request(`/agent/memory/context${qs ? `?${qs}` : ""}`);
1387
+ }
1388
+ async memoryLookupEntity(subject) {
1389
+ return this.transport.request(`/agent/memory/knowledge/entities?subject=${encodeURIComponent(subject)}`);
1390
+ }
1391
+ async memoryNavigate() {
1392
+ return this.transport.request("/agent/memory/navigate");
1393
+ }
1394
+ async memoryDelete(memoryId) {
1395
+ return this.transport.request(`/agent/memory/${encodeURIComponent(memoryId)}`, { method: "DELETE" });
1396
+ }
1397
+ async memoryStats() {
1398
+ return this.transport.request("/agent/memory/stats");
1399
+ }
1400
+ async memoryLearn(args) {
1401
+ return this.transport.request("/agent/memory/learn", {
1402
+ method: "POST",
1403
+ body: JSON.stringify({
1404
+ text: args.text,
1405
+ source: args.source,
1406
+ sourceType: args.sourceType ?? "inline",
1407
+ metadata: args.metadata
1408
+ })
1409
+ });
1410
+ }
1411
+ async memoryBootstrapStatus() {
1412
+ return this.transport.request("/agent/memory/bootstrap-status");
1413
+ }
1414
+ async memoryBootstrapStatusMark(scope) {
1415
+ return this.transport.request("/agent/memory/bootstrap-status", {
1416
+ method: "POST",
1417
+ ...scope ? { body: JSON.stringify({ scope }) } : {}
1418
+ });
1419
+ }
1420
+ };
1421
+ //#endregion
1422
+ //#region src/domains/mobile.ts
1423
+ /**
1424
+ * Mobile (numbers / SMS / calls) + WhatsApp methods (services/mobile)
1425
+ * for the Agent API client.
1426
+ */
1427
+ var MobileApi = class extends ApiBase {
1428
+ async getMobileNumber() {
1429
+ return this.transport.request("/mobile/numbers");
1430
+ }
1431
+ async searchMobileNumbers(args) {
1432
+ const qs = new URLSearchParams();
1433
+ if (args?.country) qs.set("country", args.country);
1434
+ if (args?.query) qs.set("query", args.query);
1435
+ const query = qs.toString();
1436
+ return this.transport.request(`/mobile/numbers/search${query ? `?${query}` : ""}`);
1437
+ }
1438
+ async assignMobileNumber(args) {
1439
+ return this.transport.request("/mobile/numbers/assign", {
1440
+ method: "POST",
1441
+ body: JSON.stringify(args)
1442
+ });
1443
+ }
1444
+ async releaseMobileNumber() {
1445
+ return this.transport.request("/mobile/numbers/release", {
1446
+ method: "POST",
1447
+ body: JSON.stringify({})
1448
+ });
1449
+ }
1450
+ async sendSms(args) {
1451
+ return this.transport.request("/mobile/sms/send", {
1452
+ method: "POST",
1453
+ body: JSON.stringify(args)
1454
+ });
1455
+ }
1456
+ async startOutboundCall(args) {
1457
+ return this.transport.request("/mobile/calls/outbound", {
1458
+ method: "POST",
1459
+ body: JSON.stringify(args)
1460
+ });
1461
+ }
1462
+ async getWhatsAppSession(to) {
1463
+ return this.transport.request(`/mobile/whatsapp/session?to=${encodeURIComponent(to)}`);
1464
+ }
1465
+ async sendWhatsAppMessage(args) {
1466
+ return this.transport.request("/mobile/whatsapp/send", {
1467
+ method: "POST",
1468
+ body: JSON.stringify(args)
1469
+ });
1470
+ }
1471
+ async sendWhatsAppTemplate(args) {
1472
+ return this.transport.request("/mobile/whatsapp/send-template", {
1473
+ method: "POST",
1474
+ body: JSON.stringify(args)
1475
+ });
1476
+ }
1477
+ async listWhatsAppTemplates() {
1478
+ return this.transport.request("/mobile/whatsapp/templates");
1479
+ }
1480
+ };
1481
+ //#endregion
1482
+ //#region src/domains/remote.ts
1483
+ /**
1484
+ * Remote (interactive relay) methods — browser co-browse / terminal takeover
1485
+ * sessions brokered by the relay service. Ported from main's monolith
1486
+ * additions (b5e3c1e3) into the domain-split layout.
1487
+ */
1488
+ var RemoteApi = class extends ApiBase {
1466
1489
  async requestBrowserTakeover(args) {
1467
- return this.request("/agent/remote/takeover", {
1490
+ return this.transport.request("/agent/remote/takeover", {
1468
1491
  method: "POST",
1469
1492
  body: JSON.stringify(args)
1470
1493
  });
1471
1494
  }
1472
1495
  async getRemoteSession(sessionId) {
1473
- return this.request(`/agent/remote/sessions/${encodeURIComponent(sessionId)}`);
1496
+ return this.transport.request(`/agent/remote/sessions/${encodeURIComponent(sessionId)}`);
1474
1497
  }
1475
1498
  async completeRemoteSession(sessionId) {
1476
- return this.request(`/agent/remote/sessions/${encodeURIComponent(sessionId)}/complete`, {
1499
+ return this.transport.request(`/agent/remote/sessions/${encodeURIComponent(sessionId)}/complete`, {
1477
1500
  method: "POST",
1478
1501
  body: JSON.stringify({})
1479
1502
  });
1480
1503
  }
1504
+ };
1505
+ //#endregion
1506
+ //#region src/domains/search.ts
1507
+ /**
1508
+ * Web/image/news search methods (services/search) for the Agent API client.
1509
+ */
1510
+ var SearchApi = class extends ApiBase {
1511
+ async searchWeb(params, options) {
1512
+ return this.transport.request("/agent/search/web", {
1513
+ method: "POST",
1514
+ body: JSON.stringify(params)
1515
+ }, { signal: options?.signal });
1516
+ }
1517
+ async searchImages(params, options) {
1518
+ return this.transport.request("/agent/search/images", {
1519
+ method: "POST",
1520
+ body: JSON.stringify(params)
1521
+ }, { signal: options?.signal });
1522
+ }
1523
+ async searchNews(params, options) {
1524
+ return this.transport.request("/agent/search/news", {
1525
+ method: "POST",
1526
+ body: JSON.stringify(params)
1527
+ }, { signal: options?.signal });
1528
+ }
1529
+ /** Search news across the selected provider's corpus. → POST /agent/news/search */
1530
+ async newsSearch(params) {
1531
+ return this.transport.request("/agent/news/search", {
1532
+ method: "POST",
1533
+ body: JSON.stringify(params)
1534
+ });
1535
+ }
1536
+ /** Top headlines for the selected provider. → POST /agent/news/headlines */
1537
+ async newsHeadlines(params) {
1538
+ return this.transport.request("/agent/news/headlines", {
1539
+ method: "POST",
1540
+ body: JSON.stringify(params ?? {})
1541
+ });
1542
+ }
1543
+ };
1544
+ //#endregion
1545
+ //#region src/domains/secrets.ts
1546
+ var SecretsApi = class extends ApiBase {
1481
1547
  /**
1482
- * Issue a request that returns the raw `Response` (no JSON parsing, no
1483
- * forced Content-Type). The caller sets `Content-Type`/`Accept` on `headers`
1484
- * and reads the body itself (`arrayBuffer()` / `json()`).
1548
+ * Mint a fresh AES-256 data key for a specific (secret, field) pair. The
1549
+ * encryption context is rebuilt server-side from `auth.tenantId` + the body
1550
+ * fields including `fieldKey`; the agent cannot forge context for a scope
1551
+ * or field it doesn't own. Legacy single-envelope secrets are migrated to
1552
+ * `field#value` rows by the data migration, so call with `fieldKey: "value"`
1553
+ * to reach them.
1554
+ */
1555
+ async generateSecretDataKey(args) {
1556
+ return this.transport.request("/agent/secrets/generate-data-key", {
1557
+ method: "POST",
1558
+ body: JSON.stringify(args)
1559
+ });
1560
+ }
1561
+ /**
1562
+ * Unwrap a wrapped data key so the agent can decrypt the envelope locally.
1563
+ * `fieldKey` MUST match the value supplied when the data key was generated
1564
+ * (it's bound into KMS encryption context); mismatch fails with
1565
+ * `InvalidCiphertextException`.
1566
+ */
1567
+ async decryptSecretDataKey(args) {
1568
+ return this.transport.request("/agent/secrets/decrypt-data-key", {
1569
+ method: "POST",
1570
+ body: JSON.stringify(args)
1571
+ });
1572
+ }
1573
+ /**
1574
+ * Create a new secret with one or more fields. Encrypted fields must arrive
1575
+ * pre-sealed (the agent has already obtained per-field data keys via
1576
+ * `generateSecretDataKey({ ..., fieldKey })` and AES-encrypted locally).
1577
+ * Plaintext fields ship the value inline.
1578
+ */
1579
+ async createSecret(args) {
1580
+ const { scope, scopeId, secretId, ...body } = args;
1581
+ return this.transport.request(`/agent/secrets/${encodeURIComponent(scope)}/${encodeURIComponent(scopeId)}/${encodeURIComponent(secretId)}`, {
1582
+ method: "PUT",
1583
+ body: JSON.stringify(body)
1584
+ });
1585
+ }
1586
+ /** Fetch the secret aggregate plus per-field encrypted envelopes. */
1587
+ async getSecret(args) {
1588
+ return this.transport.request(`/agent/secrets/${encodeURIComponent(args.scope)}/${encodeURIComponent(args.scopeId)}/${encodeURIComponent(args.secretId)}`);
1589
+ }
1590
+ /** Fetch one field. Plaintext: value inline. Encrypted: envelope. */
1591
+ async getSecretField(args) {
1592
+ return this.transport.request(`/agent/secrets/${encodeURIComponent(args.scope)}/${encodeURIComponent(args.scopeId)}/${encodeURIComponent(args.secretId)}/fields/${encodeURIComponent(args.fieldKey)}`);
1593
+ }
1594
+ /** Add OR rotate one field. */
1595
+ async setSecretField(args) {
1596
+ const { scope, scopeId, secretId, fieldKey, ...body } = args;
1597
+ return this.transport.request(`/agent/secrets/${encodeURIComponent(scope)}/${encodeURIComponent(scopeId)}/${encodeURIComponent(secretId)}/fields/${encodeURIComponent(fieldKey)}`, {
1598
+ method: "PUT",
1599
+ body: JSON.stringify(body)
1600
+ });
1601
+ }
1602
+ /** Remove one field. */
1603
+ async removeSecretField(args) {
1604
+ await this.transport.request(`/agent/secrets/${encodeURIComponent(args.scope)}/${encodeURIComponent(args.scopeId)}/${encodeURIComponent(args.secretId)}/fields/${encodeURIComponent(args.fieldKey)}`, { method: "DELETE" });
1605
+ }
1606
+ /** Update secret-level metadata (name/description/tags/category). */
1607
+ async updateSecretMetadata(args) {
1608
+ const { scope, scopeId, secretId, ...body } = args;
1609
+ return this.transport.request(`/agent/secrets/${encodeURIComponent(scope)}/${encodeURIComponent(scopeId)}/${encodeURIComponent(secretId)}`, {
1610
+ method: "PATCH",
1611
+ body: JSON.stringify(body)
1612
+ });
1613
+ }
1614
+ /** List metadata for secrets in a scope. Optional filters route through the byFacet GSI. */
1615
+ async listSecrets(args) {
1616
+ const params = new URLSearchParams();
1617
+ if (args.category) params.set("category", args.category);
1618
+ if (args.tag) params.set("tag", args.tag);
1619
+ if (args.fieldKey) params.set("fieldKey", args.fieldKey);
1620
+ const qs = params.toString();
1621
+ return (await this.transport.request(`/agent/secrets/${encodeURIComponent(args.scope)}/${encodeURIComponent(args.scopeId)}${qs ? `?${qs}` : ""}`)).secrets;
1622
+ }
1623
+ /** Bounded changelog read — metadata-only audit entries. */
1624
+ async getSecretHistory(args) {
1625
+ const params = new URLSearchParams();
1626
+ if (args.limit) params.set("limit", String(args.limit));
1627
+ if (args.cursor) params.set("cursor", args.cursor);
1628
+ const qs = params.toString();
1629
+ return this.transport.request(`/agent/secrets/${encodeURIComponent(args.scope)}/${encodeURIComponent(args.scopeId)}/${encodeURIComponent(args.secretId)}/history${qs ? `?${qs}` : ""}`);
1630
+ }
1631
+ /** Delete a secret (and all its field rows + tag rows + changelog rows). */
1632
+ async deleteSecret(args) {
1633
+ await this.transport.request(`/agent/secrets/${encodeURIComponent(args.scope)}/${encodeURIComponent(args.scopeId)}/${encodeURIComponent(args.secretId)}`, { method: "DELETE" });
1634
+ }
1635
+ /** Enumerate scopes (org/team/project/agent) this agent can access. */
1636
+ async listSecretScopes() {
1637
+ return (await this.transport.request("/agent/secrets/scopes")).scopes;
1638
+ }
1639
+ };
1640
+ //#endregion
1641
+ //#region src/domains/self.ts
1642
+ /**
1643
+ * Self-identity methods — the agent customizing its OWN identity (name,
1644
+ * avatar, voice). agentId + tenantId are resolved from the token, so no id
1645
+ * appears in the request. Ported from main's monolith additions into the
1646
+ * domain-split layout.
1647
+ */
1648
+ const AVATAR_POLL_INTERVAL_MS = 2e3;
1649
+ const AVATAR_JOB_TIMEOUT_MS = 18e4;
1650
+ var SelfApi = class extends ApiBase {
1651
+ /** Update the agent's own name and/or voice config. Returns the updated agent. */
1652
+ async updateSelf(update) {
1653
+ return this.transport.request("/agent/self", {
1654
+ method: "PATCH",
1655
+ body: JSON.stringify(update)
1656
+ });
1657
+ }
1658
+ /**
1659
+ * Generate the agent's own avatar from a text prompt. The image is generated,
1660
+ * stored, and set on the agent server-side; returns the updated agent.
1485
1661
  *
1486
- * A single retry fires only on the same transient statuses `request()`
1487
- * retries (authorizer-timeout 500 + LB 502/503/504) and transient network
1488
- * errors before the route handler runs — so re-issuing a POST does not
1489
- * risk a duplicate side effect. Voice TTS/STT are effectively idempotent
1490
- * (re-synthesize / re-transcribe) and meter server-side keyed on the
1491
- * gateway requestId, so a retried transcription doesn't double-bill.
1662
+ * ASYNC (same reason as `generateImage`): avatar gen runs `gpt-image-1`
1663
+ * (30–60s) which exceeds the API Gateway 30s ceiling, so this enqueues a job
1664
+ * (`POST /agent/avatar/generate` `jobId`) then polls (`GET /agent/avatar/{jobId}`)
1665
+ * until the avatar is set. Signature unchanged the plugin is unaffected.
1492
1666
  */
1493
- async rawFetch(path, init) {
1494
- const url = `${this.apiUrl}${path}`;
1495
- init.headers.set("Authorization", `Bearer ${this.apiKey}`);
1496
- let lastError;
1497
- for (let attempt = 1; attempt <= 2; attempt++) try {
1498
- const res = await fetch(url, {
1499
- method: init.method,
1500
- headers: init.headers,
1501
- body: init.body,
1502
- signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
1503
- });
1504
- if (!res.ok) {
1505
- await res.text();
1506
- const error = /* @__PURE__ */ new Error(`Agent API request failed (${String(res.status)})`);
1507
- if (attempt === 1 && RETRYABLE_STATUS.has(res.status)) {
1508
- lastError = error;
1509
- await sleep(RETRY_DELAY_MS);
1510
- continue;
1511
- }
1667
+ async generateAvatar(args) {
1668
+ const { jobId } = await this.transport.request("/agent/avatar/generate", {
1669
+ method: "POST",
1670
+ body: JSON.stringify(args)
1671
+ }, { retry: false });
1672
+ const deadline = Date.now() + AVATAR_JOB_TIMEOUT_MS;
1673
+ while (Date.now() < deadline) {
1674
+ await sleep(AVATAR_POLL_INTERVAL_MS);
1675
+ let job;
1676
+ try {
1677
+ job = await this.transport.request(`/agent/avatar/${jobId}`);
1678
+ } catch (error) {
1679
+ if (isTransientRequestError(error)) continue;
1512
1680
  throw error;
1513
1681
  }
1514
- return res;
1515
- } catch (err) {
1516
- if (attempt === 1 && isRetryableNetworkError(err)) {
1517
- lastError = err;
1518
- await sleep(RETRY_DELAY_MS);
1519
- continue;
1682
+ if (job.status === "completed") {
1683
+ if (!job.agent) throw new Error("Avatar generation completed without an agent");
1684
+ return job.agent;
1685
+ }
1686
+ if (job.status === "failed") {
1687
+ const detail = job.error ? `: ${job.error.split("\n")[0]}` : "";
1688
+ throw new Error(`Avatar generation failed${detail}`);
1520
1689
  }
1521
- throw err;
1522
1690
  }
1523
- throw lastError instanceof Error ? lastError : new Error(String(lastError));
1691
+ throw new Error("Avatar generation timed out");
1524
1692
  }
1693
+ /**
1694
+ * Get a presigned PUT URL to upload a new avatar image. Upload the bytes to
1695
+ * `uploadUrl`, then call `finalizeAvatar(s3Key)` to set it on the agent.
1696
+ */
1697
+ async presignAvatar(args) {
1698
+ return this.transport.request("/agent/avatar/presign", {
1699
+ method: "POST",
1700
+ body: JSON.stringify(args)
1701
+ });
1702
+ }
1703
+ /**
1704
+ * Finalize an avatar upload — validates ownership + size, then sets the
1705
+ * agent's `avatarUrl` server-side. Returns the updated agent.
1706
+ */
1707
+ async finalizeAvatar(s3Key) {
1708
+ return this.transport.request("/agent/avatar", {
1709
+ method: "POST",
1710
+ body: JSON.stringify({ s3Key })
1711
+ });
1712
+ }
1713
+ /** List the platform voice catalogue (ElevenLabs) so the agent can pick its own voice. */
1714
+ async listVoices() {
1715
+ return this.transport.request("/agent/voices");
1716
+ }
1717
+ };
1718
+ //#endregion
1719
+ //#region src/domains/voice.ts
1720
+ /**
1721
+ * Voice one-shot TTS / STT methods.
1722
+ *
1723
+ * These hit the voice service's agent-authed one-shot endpoints
1724
+ * (`/voice/tts`, `/voice/stt`), which are Lambda routes co-located on the
1725
+ * shared api gateway under the `voice` mapping key. Both are binary flows —
1726
+ * TTS returns raw PCM audio bytes, STT accepts raw PCM audio bytes — so they
1727
+ * bypass the JSON `{ data: T }` transport used by every other method and go
1728
+ * through the transport's `rawRequest` instead. Metering to the tenant credit
1729
+ * pool happens server-side; the caller just gets audio (TTS) or a transcript
1730
+ * (STT). Ported from main's monolith additions into the domain-split layout.
1731
+ */
1732
+ var VoiceApi = class extends ApiBase {
1525
1733
  /**
1526
1734
  * Text-to-speech. Returns raw PCM audio bytes plus their framing — the
1527
1735
  * voice service defaults to 24 kHz / mono / 16-bit. Wrap in a WAV container
@@ -1532,7 +1740,7 @@ var AgentApiClient = class {
1532
1740
  const headers = new Headers();
1533
1741
  headers.set("Content-Type", "application/json");
1534
1742
  headers.set("Accept", "audio/pcm");
1535
- const res = await this.rawFetch("/voice/tts", {
1743
+ const res = await this.transport.rawRequest("/voice/tts", {
1536
1744
  method: "POST",
1537
1745
  headers,
1538
1746
  body: JSON.stringify(args)
@@ -1555,7 +1763,7 @@ var AgentApiClient = class {
1555
1763
  const headers = new Headers();
1556
1764
  headers.set("Content-Type", "application/octet-stream");
1557
1765
  headers.set("x-sample-rate", String(args.sampleRate));
1558
- return (await (await this.rawFetch("/voice/stt", {
1766
+ return (await (await this.transport.rawRequest("/voice/stt", {
1559
1767
  method: "POST",
1560
1768
  headers,
1561
1769
  body: args.audio
@@ -1563,5 +1771,168 @@ var AgentApiClient = class {
1563
1771
  }
1564
1772
  };
1565
1773
  //#endregion
1774
+ //#region src/domains/sync.ts
1775
+ /**
1776
+ * Sync + shared (org/team/project) file methods for the Agent API client.
1777
+ */
1778
+ var SyncApi = class extends ApiBase {
1779
+ async syncRegister(args) {
1780
+ return this.transport.request("/agent/sync/register", {
1781
+ method: "POST",
1782
+ body: JSON.stringify(args ?? {})
1783
+ });
1784
+ }
1785
+ async syncGetManifest() {
1786
+ return this.transport.request("/agent/sync/manifest");
1787
+ }
1788
+ async syncPresign(args) {
1789
+ return this.transport.request("/agent/sync/presign", {
1790
+ method: "POST",
1791
+ body: JSON.stringify(args)
1792
+ });
1793
+ }
1794
+ async syncConfirmUpload(args) {
1795
+ return this.transport.request("/agent/sync/confirm", {
1796
+ method: "POST",
1797
+ body: JSON.stringify(args)
1798
+ });
1799
+ }
1800
+ async syncReconstruct(args) {
1801
+ return this.transport.request("/agent/sync/reconstruct", {
1802
+ method: "POST",
1803
+ body: JSON.stringify(args)
1804
+ });
1805
+ }
1806
+ async syncGetStats() {
1807
+ return this.transport.request("/agent/sync/stats");
1808
+ }
1809
+ async syncListFiles(args) {
1810
+ const qs = new URLSearchParams();
1811
+ if (args?.prefix) qs.set("prefix", args.prefix);
1812
+ const query = qs.toString();
1813
+ return this.transport.request(`/agent/sync/files${query ? `?${query}` : ""}`);
1814
+ }
1815
+ async syncListSessions() {
1816
+ return this.transport.request("/agent/sync/sessions");
1817
+ }
1818
+ async syncGetSession(sessionId) {
1819
+ return this.transport.request(`/agent/sync/sessions/${encodeURIComponent(sessionId)}`);
1820
+ }
1821
+ async syncDeleteFile(filePath) {
1822
+ return this.transport.request(`/agent/sync/files/${encodeFilePath(filePath)}`, { method: "DELETE" });
1823
+ }
1824
+ async sharedListFiles(args) {
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}` : ""}`);
1830
+ }
1831
+ async sharedDownloadUrl(args) {
1832
+ return this.transport.request(`/agent/org/files/${encodeURIComponent(args.scope)}/${encodeURIComponent(args.scopeId)}/download/${encodeFilePath(args.filePath)}`);
1833
+ }
1834
+ };
1835
+ //#endregion
1836
+ //#region src/domains/teams.ts
1837
+ /**
1838
+ * Microsoft Teams adapter methods (services/microsoft bot credentials +
1839
+ * messaging) for the Agent API client.
1840
+ */
1841
+ var TeamsApi = class extends ApiBase {
1842
+ async getTeamsCredentials() {
1843
+ return this.transport.request("/agent/microsoft/credentials");
1844
+ }
1845
+ async sendTeamsMessage(data) {
1846
+ return this.transport.request("/agent/microsoft/send", {
1847
+ method: "POST",
1848
+ body: JSON.stringify(data)
1849
+ });
1850
+ }
1851
+ async listTeamsChannels() {
1852
+ return this.transport.request("/agent/microsoft/channels");
1853
+ }
1854
+ };
1855
+ //#endregion
1856
+ //#region src/domains/workspace.ts
1857
+ /**
1858
+ * Workspace + template file methods for the Agent API client.
1859
+ */
1860
+ var WorkspaceApi = class extends ApiBase {
1861
+ /**
1862
+ * GET /agent/workspace — workspace config for the authenticated agent
1863
+ * (template assignment, default model, org roster).
1864
+ */
1865
+ async getWorkspace() {
1866
+ return this.transport.request("/agent/workspace");
1867
+ }
1868
+ /**
1869
+ * GET /templates/{key}/files — persona/workspace file contents for a
1870
+ * template the agent has access to. Pass `version` to pin to the version
1871
+ * the agent was installed from (omit → the endpoint resolves `latest`).
1872
+ */
1873
+ async getTemplateFiles(templateKey, opts) {
1874
+ const query = opts?.version !== void 0 ? `?version=${String(opts.version)}` : "";
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;
1899
+ }
1900
+ };
1901
+ //#endregion
1902
+ //#region src/index.ts
1903
+ var AgentApiClient = class extends ApiBase {
1904
+ constructor(config) {
1905
+ super(new AgentApiTransport(config));
1906
+ }
1907
+ };
1908
+ /** Copy each domain group's prototype methods onto the client class. */
1909
+ function applyMixins(derived, bases) {
1910
+ for (const base of bases) for (const name of Object.getOwnPropertyNames(base.prototype)) {
1911
+ if (name === "constructor") continue;
1912
+ const descriptor = Object.getOwnPropertyDescriptor(base.prototype, name);
1913
+ if (descriptor) Object.defineProperty(derived.prototype, name, descriptor);
1914
+ }
1915
+ }
1916
+ applyMixins(AgentApiClient, [
1917
+ SyncApi,
1918
+ IntegrationsApi,
1919
+ WorkspaceApi,
1920
+ ConnectCredentialsApi,
1921
+ TeamsApi,
1922
+ ChatApi,
1923
+ SecretsApi,
1924
+ IdentityApi,
1925
+ MemoryApi,
1926
+ SearchApi,
1927
+ KnowledgeApi,
1928
+ DatabaseApi,
1929
+ MobileApi,
1930
+ RemoteApi,
1931
+ SelfApi,
1932
+ VoiceApi,
1933
+ ImagesApi,
1934
+ WebhooksApi
1935
+ ]);
1936
+ //#endregion
1566
1937
  exports.AgentApiClient = AgentApiClient;
1567
1938
  exports.installToolErrorCapture = installToolErrorCapture;