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