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