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