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