@alfe.ai/agent-api-client 0.12.0 → 0.14.0

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