@qoniai/qoni 0.4.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/CHANGELOG.md +232 -0
- package/LICENSE +21 -0
- package/README.md +771 -0
- package/README.zh-CN.md +708 -0
- package/dist/index.cjs +2916 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1362 -0
- package/dist/index.d.ts +1362 -0
- package/dist/index.js +2866 -0
- package/dist/index.js.map +1 -0
- package/docs/open-source-readiness.md +50 -0
- package/package.json +81 -0
- package/skills/qoni-sdk/SKILL.md +324 -0
- package/skills/qoni-sdk/agents/openai.yaml +4 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,2916 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
ActionKinds: () => ActionKinds,
|
|
24
|
+
InteractionHandle: () => InteractionHandle,
|
|
25
|
+
InteractionStatuses: () => InteractionStatuses,
|
|
26
|
+
InteractionTypes: () => InteractionTypes,
|
|
27
|
+
MonitorHandle: () => MonitorHandle,
|
|
28
|
+
Qoni: () => QoniAgentKit,
|
|
29
|
+
QoniAgentKit: () => QoniAgentKit,
|
|
30
|
+
QoniAuthError: () => QoniAuthError,
|
|
31
|
+
QoniDelegationRequiredError: () => QoniDelegationRequiredError,
|
|
32
|
+
QoniError: () => QoniError,
|
|
33
|
+
QoniEventTypes: () => QoniEventTypes,
|
|
34
|
+
QoniPermissionDeniedError: () => QoniPermissionDeniedError,
|
|
35
|
+
QoniProductScopes: () => QoniProductScopes,
|
|
36
|
+
QoniRateLimitError: () => QoniRateLimitError,
|
|
37
|
+
QoniScopeBundles: () => QoniScopeBundles,
|
|
38
|
+
QoniScopes: () => QoniScopes,
|
|
39
|
+
QoniTimeoutError: () => QoniTimeoutError,
|
|
40
|
+
QoniTokenExpiredError: () => QoniTokenExpiredError,
|
|
41
|
+
QoniUpstreamError: () => QoniUpstreamError,
|
|
42
|
+
QoniValidationError: () => QoniValidationError,
|
|
43
|
+
RunHandle: () => RunHandle,
|
|
44
|
+
buildAuthorization: () => buildAuthorization,
|
|
45
|
+
buildSignature: () => buildSignature,
|
|
46
|
+
buildStringToSign: () => buildStringToSign
|
|
47
|
+
});
|
|
48
|
+
module.exports = __toCommonJS(index_exports);
|
|
49
|
+
|
|
50
|
+
// src/signature.ts
|
|
51
|
+
var import_node_crypto = require("crypto");
|
|
52
|
+
function filter(value) {
|
|
53
|
+
return String(value).replace(/[\t\n\r\f]/g, " ");
|
|
54
|
+
}
|
|
55
|
+
function canonicalizedHeaders(headers) {
|
|
56
|
+
return Object.keys(headers).filter((key) => key.startsWith("x-authing-") || key === "date").sort().map((key) => `${key}:${filter(headers[key]).trim()}
|
|
57
|
+
`).join("");
|
|
58
|
+
}
|
|
59
|
+
function canonicalizedResource(pathname, params) {
|
|
60
|
+
const keys = Object.keys(params).sort();
|
|
61
|
+
if (!keys.length) return pathname;
|
|
62
|
+
const query = keys.map((key) => {
|
|
63
|
+
const value = params[key];
|
|
64
|
+
return `${key}=${typeof value === "object" && value !== null ? JSON.stringify(value) : value}`;
|
|
65
|
+
});
|
|
66
|
+
return `${pathname}?${query.join("&")}`;
|
|
67
|
+
}
|
|
68
|
+
function buildStringToSign(method, pathname, headers, params) {
|
|
69
|
+
return `${method.toUpperCase()}
|
|
70
|
+
${canonicalizedHeaders(headers)}${canonicalizedResource(
|
|
71
|
+
pathname,
|
|
72
|
+
params
|
|
73
|
+
)}`;
|
|
74
|
+
}
|
|
75
|
+
function buildSignature(secretKey, stringToSign) {
|
|
76
|
+
return (0, import_node_crypto.createHmac)("sha1", secretKey).update(stringToSign, "utf8").digest("base64");
|
|
77
|
+
}
|
|
78
|
+
function buildAuthorization(accessKey, secretKey, stringToSign) {
|
|
79
|
+
return `authing ${accessKey}:${buildSignature(secretKey, stringToSign)}`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// src/auth.ts
|
|
83
|
+
var lastSigningTimestamp = 0;
|
|
84
|
+
function nextSigningTimestamp() {
|
|
85
|
+
const now = Date.now();
|
|
86
|
+
lastSigningTimestamp = Math.max(now, lastSigningTimestamp + 1);
|
|
87
|
+
return lastSigningTimestamp;
|
|
88
|
+
}
|
|
89
|
+
function buildSignedHeaders(credentials, method, pathname, payload) {
|
|
90
|
+
const headers = {
|
|
91
|
+
"content-type": "application/json",
|
|
92
|
+
// GenAuth rejects an already-consumed signature. A strictly monotonic
|
|
93
|
+
// timestamp keeps otherwise identical requests unique within a process,
|
|
94
|
+
// including retries or concurrent calls that start in the same millisecond.
|
|
95
|
+
"x-authing-date": String(nextSigningTimestamp()),
|
|
96
|
+
"x-authing-signature-method": "HMAC-SHA1",
|
|
97
|
+
"x-authing-signature-version": "1.0"
|
|
98
|
+
};
|
|
99
|
+
const stringToSign = buildStringToSign(method, pathname, headers, payload);
|
|
100
|
+
headers.authorization = buildAuthorization(
|
|
101
|
+
credentials.accessKey,
|
|
102
|
+
credentials.secretKey,
|
|
103
|
+
stringToSign
|
|
104
|
+
);
|
|
105
|
+
return headers;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// src/scopes.ts
|
|
109
|
+
var QoniScopes = {
|
|
110
|
+
GUMEM_SESSION_CREATE: "gumem.session:create",
|
|
111
|
+
GUMEM_MESSAGE_WRITE: "gumem.message:write",
|
|
112
|
+
GUMEM_MEMORY_READ: "gumem.memory:read",
|
|
113
|
+
GUMEM_MEMORY_WRITE: "gumem.memory:write",
|
|
114
|
+
GUMEM_MEMORY_DELETE: "gumem.memory:delete",
|
|
115
|
+
GUMEM_SEARCH_RUN: "gumem.search:run",
|
|
116
|
+
GUMEM_ADMIN_MANAGE: "gumem.admin:manage",
|
|
117
|
+
GUMEM_RESOURCE_WRITE: "gumem.resource:write",
|
|
118
|
+
GUMEM_ACTION_WRITE: "gumem.action:write",
|
|
119
|
+
GUMEM_ACTION_READ: "gumem.action:read",
|
|
120
|
+
GUMEM_PROFILE_READ: "gumem.profile:read",
|
|
121
|
+
// WebAgent products: exactly two verbs per product — `read` (observe) and
|
|
122
|
+
// `manage` (anything that changes execution state). See
|
|
123
|
+
// wa/docs/qoni-sdk-public-surface.md §3.
|
|
124
|
+
DO_ANYTHING_READ: "webagent.do_anything:read",
|
|
125
|
+
DO_ANYTHING_MANAGE: "webagent.do_anything:manage",
|
|
126
|
+
WEB_SEARCH_READ: "webagent.web_search:read",
|
|
127
|
+
WEB_SEARCH_MANAGE: "webagent.web_search:manage",
|
|
128
|
+
DEEP_RESEARCH_READ: "webagent.deep_research:read",
|
|
129
|
+
DEEP_RESEARCH_MANAGE: "webagent.deep_research:manage",
|
|
130
|
+
TRACK_READ: "webagent.track:read",
|
|
131
|
+
TRACK_MANAGE: "webagent.track:manage"
|
|
132
|
+
};
|
|
133
|
+
var QoniProductScopes = {
|
|
134
|
+
doAnything: [
|
|
135
|
+
QoniScopes.DO_ANYTHING_READ,
|
|
136
|
+
QoniScopes.DO_ANYTHING_MANAGE
|
|
137
|
+
],
|
|
138
|
+
webSearch: [
|
|
139
|
+
QoniScopes.WEB_SEARCH_READ,
|
|
140
|
+
QoniScopes.WEB_SEARCH_MANAGE
|
|
141
|
+
],
|
|
142
|
+
deepResearch: [
|
|
143
|
+
QoniScopes.DEEP_RESEARCH_READ,
|
|
144
|
+
QoniScopes.DEEP_RESEARCH_MANAGE
|
|
145
|
+
],
|
|
146
|
+
track: [
|
|
147
|
+
QoniScopes.TRACK_READ,
|
|
148
|
+
QoniScopes.TRACK_MANAGE
|
|
149
|
+
]
|
|
150
|
+
};
|
|
151
|
+
var KNOWN_SCOPES = Object.values(QoniScopes);
|
|
152
|
+
var QoniScopeBundles = {
|
|
153
|
+
GUMEM_READONLY: [QoniScopes.GUMEM_MEMORY_READ, QoniScopes.GUMEM_PROFILE_READ],
|
|
154
|
+
GUMEM_SESSION_RECALL: [
|
|
155
|
+
QoniScopes.GUMEM_MEMORY_READ,
|
|
156
|
+
QoniScopes.GUMEM_MEMORY_WRITE,
|
|
157
|
+
QoniScopes.GUMEM_MESSAGE_WRITE
|
|
158
|
+
],
|
|
159
|
+
GUMEM_WRITE: [
|
|
160
|
+
QoniScopes.GUMEM_SESSION_CREATE,
|
|
161
|
+
QoniScopes.GUMEM_MESSAGE_WRITE,
|
|
162
|
+
QoniScopes.GUMEM_ACTION_WRITE
|
|
163
|
+
],
|
|
164
|
+
// One bundle per WebAgent product = [read, manage].
|
|
165
|
+
DO_ANYTHING: [
|
|
166
|
+
QoniScopes.DO_ANYTHING_READ,
|
|
167
|
+
QoniScopes.DO_ANYTHING_MANAGE
|
|
168
|
+
],
|
|
169
|
+
WEB_SEARCH: [
|
|
170
|
+
QoniScopes.WEB_SEARCH_READ,
|
|
171
|
+
QoniScopes.WEB_SEARCH_MANAGE
|
|
172
|
+
],
|
|
173
|
+
DEEP_RESEARCH: [
|
|
174
|
+
QoniScopes.DEEP_RESEARCH_READ,
|
|
175
|
+
QoniScopes.DEEP_RESEARCH_MANAGE
|
|
176
|
+
],
|
|
177
|
+
TRACK: [
|
|
178
|
+
QoniScopes.TRACK_READ,
|
|
179
|
+
QoniScopes.TRACK_MANAGE
|
|
180
|
+
]
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
// src/types.ts
|
|
184
|
+
function isJsonObject(value) {
|
|
185
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// src/errors.ts
|
|
189
|
+
var QoniError = class extends Error {
|
|
190
|
+
code;
|
|
191
|
+
status;
|
|
192
|
+
requestId;
|
|
193
|
+
traceId;
|
|
194
|
+
auditId;
|
|
195
|
+
retryable;
|
|
196
|
+
body;
|
|
197
|
+
constructor(message, options = {}) {
|
|
198
|
+
super(message);
|
|
199
|
+
this.name = "QoniError";
|
|
200
|
+
this.code = options.code || "qoni_error";
|
|
201
|
+
this.status = options.status;
|
|
202
|
+
this.requestId = options.requestId;
|
|
203
|
+
this.traceId = options.traceId;
|
|
204
|
+
this.auditId = options.auditId;
|
|
205
|
+
this.retryable = options.retryable ?? false;
|
|
206
|
+
this.body = options.body;
|
|
207
|
+
if (options.cause !== void 0) {
|
|
208
|
+
this.cause = options.cause;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
var QoniAuthError = class extends QoniError {
|
|
213
|
+
constructor(message, options = {}) {
|
|
214
|
+
super(message, { code: "auth.failed", ...options });
|
|
215
|
+
this.name = "QoniAuthError";
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
var QoniDelegationRequiredError = class extends QoniError {
|
|
219
|
+
constructor(message, options = {}) {
|
|
220
|
+
super(message, { code: "delegation.required", ...options });
|
|
221
|
+
this.name = "QoniDelegationRequiredError";
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
var QoniPermissionDeniedError = class extends QoniError {
|
|
225
|
+
constructor(message, options = {}) {
|
|
226
|
+
super(message, { code: "permission_denied", ...options });
|
|
227
|
+
this.name = "QoniPermissionDeniedError";
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
var QoniTokenExpiredError = class extends QoniError {
|
|
231
|
+
constructor(message, options = {}) {
|
|
232
|
+
super(message, { code: "token.expired", ...options });
|
|
233
|
+
this.name = "QoniTokenExpiredError";
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
var QoniValidationError = class extends QoniError {
|
|
237
|
+
constructor(message, options = {}) {
|
|
238
|
+
super(message, { code: "validation.failed", ...options });
|
|
239
|
+
this.name = "QoniValidationError";
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
var QoniRateLimitError = class extends QoniError {
|
|
243
|
+
constructor(message, options = {}) {
|
|
244
|
+
super(message, { code: "rate_limit.exceeded", retryable: true, ...options });
|
|
245
|
+
this.name = "QoniRateLimitError";
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
var QoniUpstreamError = class extends QoniError {
|
|
249
|
+
constructor(message, options = {}) {
|
|
250
|
+
super(message, { code: "upstream.failed", retryable: true, ...options });
|
|
251
|
+
this.name = "QoniUpstreamError";
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
var QoniTimeoutError = class extends QoniError {
|
|
255
|
+
constructor(message, options = {}) {
|
|
256
|
+
super(message, { code: "timeout", retryable: true, ...options });
|
|
257
|
+
this.name = "QoniTimeoutError";
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
function errorFromPayload(status, payload, fallbackMessage) {
|
|
261
|
+
const detail = isJsonObject(payload) ? payload.detail : void 0;
|
|
262
|
+
const detailObject = isJsonObject(detail) ? detail : void 0;
|
|
263
|
+
const source = detailObject || (isJsonObject(payload) ? payload : {});
|
|
264
|
+
const code = stringField(source, "code") || stringField(source, "apiCode") || statusCodeToCode(status);
|
|
265
|
+
const rawMessage = stringField(source, "message") || stringField(source, "detail") || fallbackMessage;
|
|
266
|
+
const message = appendOnboardingHint(
|
|
267
|
+
appendDeniedScopes(appendUpstreamDescription(rawMessage, source), source),
|
|
268
|
+
code
|
|
269
|
+
);
|
|
270
|
+
const options = {
|
|
271
|
+
code,
|
|
272
|
+
status,
|
|
273
|
+
requestId: stringField(payload, "requestId") || stringField(source, "requestId"),
|
|
274
|
+
traceId: stringField(payload, "traceId") || stringField(source, "traceId"),
|
|
275
|
+
auditId: stringField(payload, "auditId") || stringField(source, "auditId"),
|
|
276
|
+
retryable: status === 429 || status >= 500,
|
|
277
|
+
body: payload
|
|
278
|
+
};
|
|
279
|
+
if (isTokenExpired(code, message)) return new QoniTokenExpiredError(message, options);
|
|
280
|
+
if (isDelegationRequired(code)) return new QoniDelegationRequiredError(message, options);
|
|
281
|
+
if (status === 401) return new QoniAuthError(message, options);
|
|
282
|
+
if (status === 403 || isPermissionDenied(code)) {
|
|
283
|
+
return new QoniPermissionDeniedError(appendKnownScopes(message), options);
|
|
284
|
+
}
|
|
285
|
+
if (status === 400 || status === 422) return new QoniValidationError(message, options);
|
|
286
|
+
if (status === 429) return new QoniRateLimitError(message, options);
|
|
287
|
+
if (status >= 500) return new QoniUpstreamError(message, options);
|
|
288
|
+
return new QoniError(message, options);
|
|
289
|
+
}
|
|
290
|
+
function timeoutError(cause) {
|
|
291
|
+
return new QoniTimeoutError("Qoni request timed out", { cause });
|
|
292
|
+
}
|
|
293
|
+
function networkError(cause) {
|
|
294
|
+
return new QoniUpstreamError("Qoni network request failed", { cause });
|
|
295
|
+
}
|
|
296
|
+
function stringField(value, key) {
|
|
297
|
+
if (!isJsonObject(value)) return void 0;
|
|
298
|
+
const field = value[key];
|
|
299
|
+
return typeof field === "string" && field ? field : void 0;
|
|
300
|
+
}
|
|
301
|
+
function appendDeniedScopes(message, source) {
|
|
302
|
+
const details = isJsonObject(source) ? source.details : void 0;
|
|
303
|
+
const raw = (isJsonObject(details) ? details.deniedScopes : void 0) ?? (isJsonObject(source) ? source.deniedScopes : void 0);
|
|
304
|
+
if (!Array.isArray(raw) || raw.length === 0) return message;
|
|
305
|
+
const scopes = raw.filter((s) => typeof s === "string");
|
|
306
|
+
if (scopes.length === 0 || message.includes(scopes[0])) return message;
|
|
307
|
+
return `${message} (scopes not granted: ${scopes.join(", ")})`;
|
|
308
|
+
}
|
|
309
|
+
function appendUpstreamDescription(message, source) {
|
|
310
|
+
const details = isJsonObject(source) ? source.details : void 0;
|
|
311
|
+
const upstream = isJsonObject(details) ? details.upstream : void 0;
|
|
312
|
+
const description = stringField(upstream, "error_description") || stringField(upstream, "message") || stringField(upstream, "error");
|
|
313
|
+
if (!description || message.includes(description)) return message;
|
|
314
|
+
return `${message}: ${description}`;
|
|
315
|
+
}
|
|
316
|
+
var ONBOARDING_HINTS = {
|
|
317
|
+
"eak.delegation.user_not_bound": "Hint: the userId is not in the GenAuth userpool bound to this credential. Resolve a real user with qoni.resolveAnyBoundUser() (demo/smoke) or qoni.currentUser({ accessToken }).",
|
|
318
|
+
"eak.genauth.userpool_binding_missing": "Hint: bind the Qoni credential to a GenAuth userpool before calling genauth.users.*."
|
|
319
|
+
};
|
|
320
|
+
function appendOnboardingHint(message, code) {
|
|
321
|
+
const hint = ONBOARDING_HINTS[code];
|
|
322
|
+
if (!hint || message.includes(hint)) return message;
|
|
323
|
+
return `${message} ${hint}`;
|
|
324
|
+
}
|
|
325
|
+
function appendKnownScopes(message) {
|
|
326
|
+
if (message.includes("Known scopes:")) return message;
|
|
327
|
+
return `${message} (Known scopes: ${KNOWN_SCOPES.join(", ")})`;
|
|
328
|
+
}
|
|
329
|
+
function statusCodeToCode(status) {
|
|
330
|
+
if (status === 401) return "auth.failed";
|
|
331
|
+
if (status === 403) return "permission_denied";
|
|
332
|
+
if (status === 404) return "not_found";
|
|
333
|
+
if (status === 409) return "conflict";
|
|
334
|
+
if (status === 422) return "validation.failed";
|
|
335
|
+
if (status === 429) return "rate_limit.exceeded";
|
|
336
|
+
if (status >= 500) return "upstream.failed";
|
|
337
|
+
return "qoni_error";
|
|
338
|
+
}
|
|
339
|
+
function isPermissionDenied(code) {
|
|
340
|
+
return code.includes("permission") || code.includes("insufficient_scope");
|
|
341
|
+
}
|
|
342
|
+
function isDelegationRequired(code) {
|
|
343
|
+
return code.includes("interactive_required") || code.includes("delegation.required");
|
|
344
|
+
}
|
|
345
|
+
function isTokenExpired(code, message) {
|
|
346
|
+
return code.includes("expired") || /token expired/i.test(message);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// src/run-events.ts
|
|
350
|
+
var QoniEventTypes = {
|
|
351
|
+
// --- Core (any product run can emit these) ---
|
|
352
|
+
/** The agent is working — folds ALL internal steps (actions, plan/subtask, sections, telemetry, take-control, …); `event.data` is a best-effort human-readable line (string). */
|
|
353
|
+
Progress: "progress",
|
|
354
|
+
/** The agent produced a user-facing chat message. */
|
|
355
|
+
Message: "message",
|
|
356
|
+
/** HITL — a typed interaction needs the user (login / clarification / confirmation / take-control / wait); `event.data` is the full {@link Interaction} object. Act on it via the action methods (`run.interactionHandle(event.data).answer(…)` etc). */
|
|
357
|
+
Interaction: "interaction",
|
|
358
|
+
/** A live screenshot frame (opt in with `capture: { screenshots: true }`); image on `event.image`. */
|
|
359
|
+
Screenshot: "screenshot",
|
|
360
|
+
/** Terminal — the run finished; outcome + output in `event.data` (cost is on the `RunResult`). */
|
|
361
|
+
Done: "done",
|
|
362
|
+
// --- Web Search ---
|
|
363
|
+
/** Web Search results are ready; `event.data` is the count (the list is in `done.output`). */
|
|
364
|
+
ResultsReady: "resultsReady",
|
|
365
|
+
// --- Deep Research ---
|
|
366
|
+
/** Deep Research coarse phase changed (brief → gather → synthesize …). */
|
|
367
|
+
Phase: "phase",
|
|
368
|
+
/** A Deep Research outline section was completed (stream sections as they finish). */
|
|
369
|
+
SectionReady: "sectionReady",
|
|
370
|
+
// --- Track (monitor) ---
|
|
371
|
+
/** A Track monitor was created (e.g. Do Anything handed a recurring request to Track). */
|
|
372
|
+
MonitorCreated: "monitorCreated",
|
|
373
|
+
/** A monitor detected a change worth acting on (the Track signal you react to). */
|
|
374
|
+
Triggered: "triggered",
|
|
375
|
+
/** A monitor's scheduled check finished (`event.data` is whether it found a change). */
|
|
376
|
+
CheckCompleted: "checkCompleted"
|
|
377
|
+
};
|
|
378
|
+
function isTerminalRunStatus(status) {
|
|
379
|
+
return status === "succeeded" || status === "failed" || status === "canceled";
|
|
380
|
+
}
|
|
381
|
+
function camelKey(key) {
|
|
382
|
+
return key.replace(/_([a-z0-9])/g, (_, ch) => ch.toUpperCase());
|
|
383
|
+
}
|
|
384
|
+
function camelizeKeys(value) {
|
|
385
|
+
if (Array.isArray(value)) return value.map(camelizeKeys);
|
|
386
|
+
if (!isJsonObject(value)) return value;
|
|
387
|
+
const out = {};
|
|
388
|
+
for (const [key, item] of Object.entries(value)) {
|
|
389
|
+
out[camelKey(key)] = camelizeKeys(item);
|
|
390
|
+
}
|
|
391
|
+
return out;
|
|
392
|
+
}
|
|
393
|
+
function camelizeRecord(value) {
|
|
394
|
+
const out = camelizeKeys(value);
|
|
395
|
+
return isJsonObject(out) ? out : {};
|
|
396
|
+
}
|
|
397
|
+
function decodeDataUri(uri) {
|
|
398
|
+
const match = /^data:([^;,]*)?(;base64)?,(.*)$/s.exec(uri);
|
|
399
|
+
if (!match) return void 0;
|
|
400
|
+
const [, mime, isBase64, body] = match;
|
|
401
|
+
const bytes = isBase64 ? new Uint8Array(Buffer.from(body, "base64")) : new TextEncoder().encode(decodeURIComponent(body));
|
|
402
|
+
return { bytes, mime: mime || "application/octet-stream" };
|
|
403
|
+
}
|
|
404
|
+
function asString(value) {
|
|
405
|
+
return typeof value === "string" && value ? value : void 0;
|
|
406
|
+
}
|
|
407
|
+
function asNumber(value) {
|
|
408
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
409
|
+
}
|
|
410
|
+
function decodeScreenshotImage(payload) {
|
|
411
|
+
const url = asString(payload.screenshot_url);
|
|
412
|
+
if (!url) return void 0;
|
|
413
|
+
const decoded = decodeDataUri(url);
|
|
414
|
+
if (!decoded) return void 0;
|
|
415
|
+
return {
|
|
416
|
+
...decoded,
|
|
417
|
+
pageUrl: asString(payload.page_url) ?? asString(payload.url),
|
|
418
|
+
step: asNumber(payload.step) ?? asNumber(payload.step_index)
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
var HEARTBEAT = "stream.heartbeat";
|
|
422
|
+
var SCREENSHOT = "run.screenshot";
|
|
423
|
+
var TERMINAL = "run.completed";
|
|
424
|
+
var INTERACTION = "run.interaction";
|
|
425
|
+
var WIRE_TO_SEMANTIC = {
|
|
426
|
+
// --- Headline events a developer reacts to ---
|
|
427
|
+
"run.message": "message",
|
|
428
|
+
"search.results_ready": "resultsReady",
|
|
429
|
+
"run.phase_changed": "phase",
|
|
430
|
+
// Deep Research coarse phase
|
|
431
|
+
"run.section_completed": "sectionReady",
|
|
432
|
+
// Deep Research section finished
|
|
433
|
+
"run.monitor_created": "monitorCreated",
|
|
434
|
+
// Do Anything → Track handoff
|
|
435
|
+
// NB: `run.interaction` is NOT here — it's an out-of-band case in the
|
|
436
|
+
// normalizer (→ "interaction", carrying the typed Interaction object), like
|
|
437
|
+
// screenshot/done. The old `run.input_request` / `run.take_control_*` family
|
|
438
|
+
// it replaces is being removed backend-side; while the backend still lists
|
|
439
|
+
// them in the catalog (dual-emit window) they fold to progress below — the
|
|
440
|
+
// SDK no longer has an `inputRequired` surface (clean break).
|
|
441
|
+
// --- Internal churn → progress (raw type stays on event.raw.event) ---
|
|
442
|
+
// supervisor lifecycle / planning
|
|
443
|
+
"run.status_changed": "progress",
|
|
444
|
+
"run.input_request": "progress",
|
|
445
|
+
// superseded by run.interaction
|
|
446
|
+
"run.input_request_resolved": "progress",
|
|
447
|
+
"run.cost_update": "progress",
|
|
448
|
+
// cost is surfaced on RunResult, not as an event
|
|
449
|
+
"run.plan_ready": "progress",
|
|
450
|
+
"run.plan_finalized": "progress",
|
|
451
|
+
"run.subtask_started": "progress",
|
|
452
|
+
"run.subtask_graded": "progress",
|
|
453
|
+
"run.subtask_status": "progress",
|
|
454
|
+
"run.todos_updated": "progress",
|
|
455
|
+
// sub-agent execution (actions + browser-agent telemetry)
|
|
456
|
+
"run.action.started": "progress",
|
|
457
|
+
"run.action.completed": "progress",
|
|
458
|
+
"run.action.failed": "progress",
|
|
459
|
+
"run.action.progress": "progress",
|
|
460
|
+
"run.action_result": "progress",
|
|
461
|
+
"run.browser_agent.config_applied": "progress",
|
|
462
|
+
"run.browser_agent.summary": "progress",
|
|
463
|
+
"run.browser_agent.vision_decision": "progress",
|
|
464
|
+
"run.browser_agent.observe": "progress",
|
|
465
|
+
"run.browser_agent.page_gate": "progress",
|
|
466
|
+
// web search / deep research intermediate progress
|
|
467
|
+
"search.progress": "progress",
|
|
468
|
+
"search.summarize_progress": "progress",
|
|
469
|
+
"search.done": "progress",
|
|
470
|
+
// run ends on run.completed → "done"
|
|
471
|
+
"run.section_started": "progress",
|
|
472
|
+
"run.section_failed": "progress",
|
|
473
|
+
"run.replan_started": "progress",
|
|
474
|
+
"run.crosscheck_done": "progress",
|
|
475
|
+
"run.synthesize_progress": "progress",
|
|
476
|
+
// take-control / recording / live-url protocol
|
|
477
|
+
"run.take_control_pending": "progress",
|
|
478
|
+
"run.take_control_expired": "progress",
|
|
479
|
+
"run.user_paused": "progress",
|
|
480
|
+
"run.user_released": "progress",
|
|
481
|
+
"run.recording_started": "progress",
|
|
482
|
+
"run.recording_paused": "progress",
|
|
483
|
+
"run.recording_finalized": "progress",
|
|
484
|
+
"run.browser_live_url_changed": "progress",
|
|
485
|
+
// side-channel
|
|
486
|
+
"run.feedback_submitted": "progress"
|
|
487
|
+
};
|
|
488
|
+
var MONITOR_TRIGGERED = "monitor.triggered";
|
|
489
|
+
var MONITOR_LIFECYCLE = "monitor.lifecycle_changed";
|
|
490
|
+
function pickNote(c) {
|
|
491
|
+
return asString(c.note) ?? asString(c.summary) ?? asString(c.lastStepSummary) ?? asString(c.label) ?? asString(c.text) ?? asString(c.kind) ?? // action events: "click" / "input" / "navigate" / …
|
|
492
|
+
asString(c.toPhase) ?? asString(c.status) ?? asString(c.to) ?? asString(c.reason) ?? "";
|
|
493
|
+
}
|
|
494
|
+
function shapeData(type, c) {
|
|
495
|
+
switch (type) {
|
|
496
|
+
// Simple events: event.data IS the value.
|
|
497
|
+
case "progress":
|
|
498
|
+
return pickNote(c);
|
|
499
|
+
// a human-readable line (string)
|
|
500
|
+
case "resultsReady":
|
|
501
|
+
return asNumber(c.totalUniqueResults) ?? asNumber(c.count) ?? 0;
|
|
502
|
+
// count
|
|
503
|
+
case "phase":
|
|
504
|
+
return asString(c.toPhase) ?? asString(c.phase) ?? "";
|
|
505
|
+
// phase name
|
|
506
|
+
case "sectionReady":
|
|
507
|
+
return asString(c.title) ?? asString(c.sectionTitle) ?? "";
|
|
508
|
+
// section title
|
|
509
|
+
case "monitorCreated":
|
|
510
|
+
return asString(c.monitorId) ?? "";
|
|
511
|
+
// monitor id
|
|
512
|
+
case "triggered":
|
|
513
|
+
return asString(c.summary) ?? asString(c.note) ?? "";
|
|
514
|
+
// change summary
|
|
515
|
+
case "checkCompleted":
|
|
516
|
+
return c.changed === true;
|
|
517
|
+
// whether a change was found
|
|
518
|
+
// Rich events: event.data is a small object.
|
|
519
|
+
case "message":
|
|
520
|
+
return { text: asString(c.text) ?? "", role: asString(c.role) ?? "" };
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
function normalizeRunEvent(wire, options = {}) {
|
|
524
|
+
const envelope = isJsonObject(wire.data) ? wire.data : void 0;
|
|
525
|
+
const wireType = wire.event ?? asString(envelope?.type);
|
|
526
|
+
if (!wireType) return null;
|
|
527
|
+
if (wireType === HEARTBEAT) return null;
|
|
528
|
+
const payload = isJsonObject(envelope?.data) ? envelope.data : {};
|
|
529
|
+
const taskId = asString(envelope?.task_id);
|
|
530
|
+
const runId = taskId ?? options.topRunId ?? "";
|
|
531
|
+
const at = asString(envelope?.occurred_at) ?? asString(payload.occurred_at) ?? asString(payload.timestamp) ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
532
|
+
const c = camelizeRecord(payload);
|
|
533
|
+
const base = { runId, at, raw: wire };
|
|
534
|
+
const isSubRun = Boolean(options.topRunId && taskId && taskId !== options.topRunId);
|
|
535
|
+
if (wireType === SCREENSHOT) {
|
|
536
|
+
const image = decodeScreenshotImage(payload);
|
|
537
|
+
if (image) {
|
|
538
|
+
const sData = {};
|
|
539
|
+
const pageUrl = asString(c.pageUrl) ?? asString(c.url);
|
|
540
|
+
const step = asNumber(c.step) ?? asNumber(c.stepIndex);
|
|
541
|
+
if (pageUrl !== void 0) sData.pageUrl = pageUrl;
|
|
542
|
+
if (step !== void 0) sData.step = step;
|
|
543
|
+
return { ...base, type: "screenshot", data: sData, image, isTerminal: false };
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
if (wireType === INTERACTION) {
|
|
547
|
+
const interaction = normalizeInteraction(payload);
|
|
548
|
+
if (interaction) {
|
|
549
|
+
return { ...base, type: "interaction", data: interaction, isTerminal: false };
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
if (wireType === TERMINAL && !isSubRun) {
|
|
553
|
+
const dData = {
|
|
554
|
+
output: c.output,
|
|
555
|
+
succeeded: typeof c.isTaskSuccessful === "boolean" ? c.isTaskSuccessful : null,
|
|
556
|
+
terminalReason: asString(c.terminalReason) ?? ""
|
|
557
|
+
};
|
|
558
|
+
return { ...base, type: "done", data: dData, isTerminal: true };
|
|
559
|
+
}
|
|
560
|
+
let type;
|
|
561
|
+
if (wireType === MONITOR_TRIGGERED) type = "triggered";
|
|
562
|
+
else if (wireType === MONITOR_LIFECYCLE) type = "checkCompleted";
|
|
563
|
+
else type = WIRE_TO_SEMANTIC[wireType] ?? "progress";
|
|
564
|
+
const data = shapeData(type, c);
|
|
565
|
+
if (type === "progress" && data === "") return null;
|
|
566
|
+
return { ...base, type, data, isTerminal: false };
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// src/generated/interaction-types.ts
|
|
570
|
+
var InteractionTypes = {
|
|
571
|
+
SiteLogin: "site_login",
|
|
572
|
+
Clarification: "clarification",
|
|
573
|
+
Confirmation: "confirmation",
|
|
574
|
+
TakeControl: "take_control",
|
|
575
|
+
Wait: "wait"
|
|
576
|
+
};
|
|
577
|
+
var INTERACTION_TYPE_VALUES = Object.values(InteractionTypes);
|
|
578
|
+
var InteractionStatuses = {
|
|
579
|
+
Pending: "pending",
|
|
580
|
+
Active: "active",
|
|
581
|
+
Resolved: "resolved",
|
|
582
|
+
Expired: "expired",
|
|
583
|
+
Canceled: "canceled"
|
|
584
|
+
};
|
|
585
|
+
var INTERACTION_STATUS_VALUES = Object.values(InteractionStatuses);
|
|
586
|
+
var ActionKinds = {
|
|
587
|
+
answer: "answer",
|
|
588
|
+
skip: "skip",
|
|
589
|
+
confirm: "confirm",
|
|
590
|
+
reject: "reject",
|
|
591
|
+
openLogin: "open_login",
|
|
592
|
+
confirmSignedIn: "confirm_signed_in",
|
|
593
|
+
connectControl: "connect_control",
|
|
594
|
+
refreshControl: "refresh_control",
|
|
595
|
+
releaseControl: "release_control",
|
|
596
|
+
retry: "retry",
|
|
597
|
+
switchProfile: "switch_profile"
|
|
598
|
+
};
|
|
599
|
+
var ACTION_KIND_VALUES = Object.values(ActionKinds);
|
|
600
|
+
|
|
601
|
+
// src/interactions.ts
|
|
602
|
+
function asString2(value) {
|
|
603
|
+
return typeof value === "string" && value ? value : void 0;
|
|
604
|
+
}
|
|
605
|
+
function asBool(value) {
|
|
606
|
+
return typeof value === "boolean" ? value : void 0;
|
|
607
|
+
}
|
|
608
|
+
function normalizeActions(value) {
|
|
609
|
+
if (!Array.isArray(value)) return [];
|
|
610
|
+
const out = [];
|
|
611
|
+
for (const item of value) {
|
|
612
|
+
if (!isJsonObject(item)) continue;
|
|
613
|
+
const kind = asString2(item.kind);
|
|
614
|
+
if (!kind) continue;
|
|
615
|
+
const action = {
|
|
616
|
+
kind,
|
|
617
|
+
label: asString2(item.label) ?? "",
|
|
618
|
+
method: asString2(item.method) ?? "POST",
|
|
619
|
+
endpoint: asString2(item.endpoint) ?? ""
|
|
620
|
+
};
|
|
621
|
+
if (isJsonObject(item.inputSchema)) action.inputSchema = item.inputSchema;
|
|
622
|
+
out.push(action);
|
|
623
|
+
}
|
|
624
|
+
return out;
|
|
625
|
+
}
|
|
626
|
+
function normalizeEvidence(c) {
|
|
627
|
+
const artifactId = asString2(c.evidenceArtifactId) ?? (isJsonObject(c.evidence) ? asString2(c.evidence.artifactId) : void 0);
|
|
628
|
+
return artifactId ? { artifactId } : void 0;
|
|
629
|
+
}
|
|
630
|
+
function shapePayload(type, c) {
|
|
631
|
+
switch (type) {
|
|
632
|
+
case "site_login": {
|
|
633
|
+
const sites = Array.isArray(c.sites) ? c.sites.flatMap((raw) => {
|
|
634
|
+
if (!isJsonObject(raw)) return [];
|
|
635
|
+
return [
|
|
636
|
+
{
|
|
637
|
+
siteId: asString2(raw.siteId) ?? "",
|
|
638
|
+
displayName: asString2(raw.displayName) ?? "",
|
|
639
|
+
loginUrl: asString2(raw.loginUrl) ?? ""
|
|
640
|
+
}
|
|
641
|
+
];
|
|
642
|
+
}) : [];
|
|
643
|
+
const payload = { sites };
|
|
644
|
+
const monitorId = asString2(c.monitorId);
|
|
645
|
+
if (monitorId) payload.monitorId = monitorId;
|
|
646
|
+
return payload;
|
|
647
|
+
}
|
|
648
|
+
case "clarification": {
|
|
649
|
+
const payload = { question: asString2(c.question) ?? "" };
|
|
650
|
+
const inputType = asString2(c.inputType);
|
|
651
|
+
if (inputType) payload.inputType = inputType;
|
|
652
|
+
return payload;
|
|
653
|
+
}
|
|
654
|
+
case "confirmation":
|
|
655
|
+
return { summary: asString2(c.summary) ?? "" };
|
|
656
|
+
case "take_control": {
|
|
657
|
+
const payload = { liveUrl: asString2(c.liveUrl) ?? "" };
|
|
658
|
+
const surface = asString2(c.surface);
|
|
659
|
+
const reason = asString2(c.reason);
|
|
660
|
+
if (surface) payload.surface = surface;
|
|
661
|
+
if (reason) payload.reason = reason;
|
|
662
|
+
return payload;
|
|
663
|
+
}
|
|
664
|
+
case "wait": {
|
|
665
|
+
const payload = {
|
|
666
|
+
waitKind: asString2(c.waitKind) ?? "external"
|
|
667
|
+
};
|
|
668
|
+
const until = asString2(c.until);
|
|
669
|
+
const retryable = asBool(c.retryable);
|
|
670
|
+
if (until) payload.until = until;
|
|
671
|
+
if (retryable !== void 0) payload.retryable = retryable;
|
|
672
|
+
return payload;
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
function normalizeInteraction(payload) {
|
|
677
|
+
const c = camelizeRecord(payload);
|
|
678
|
+
const type = asString2(c.type);
|
|
679
|
+
if (!type) return null;
|
|
680
|
+
const interaction = {
|
|
681
|
+
id: asString2(c.id) ?? "",
|
|
682
|
+
type,
|
|
683
|
+
status: asString2(c.status) ?? "pending",
|
|
684
|
+
createdAt: asString2(c.createdAt) ?? "",
|
|
685
|
+
title: asString2(c.title) ?? "",
|
|
686
|
+
actions: normalizeActions(c.actions),
|
|
687
|
+
payload: shapePayload(type, c)
|
|
688
|
+
};
|
|
689
|
+
const resolvedAt = asString2(c.resolvedAt);
|
|
690
|
+
const expiresAt = asString2(c.expiresAt);
|
|
691
|
+
const prompt = asString2(c.prompt);
|
|
692
|
+
const evidence = normalizeEvidence(c);
|
|
693
|
+
if (resolvedAt) interaction.resolvedAt = resolvedAt;
|
|
694
|
+
if (expiresAt) interaction.expiresAt = expiresAt;
|
|
695
|
+
if (prompt) interaction.prompt = prompt;
|
|
696
|
+
if (evidence) interaction.evidence = evidence;
|
|
697
|
+
return interaction;
|
|
698
|
+
}
|
|
699
|
+
var InteractionHandle = class {
|
|
700
|
+
constructor(interaction, post) {
|
|
701
|
+
this.post = post;
|
|
702
|
+
this.interaction = interaction;
|
|
703
|
+
}
|
|
704
|
+
post;
|
|
705
|
+
interaction;
|
|
706
|
+
get id() {
|
|
707
|
+
return this.interaction.id;
|
|
708
|
+
}
|
|
709
|
+
get type() {
|
|
710
|
+
return this.interaction.type;
|
|
711
|
+
}
|
|
712
|
+
get status() {
|
|
713
|
+
return this.interaction.status;
|
|
714
|
+
}
|
|
715
|
+
get actions() {
|
|
716
|
+
return this.interaction.actions;
|
|
717
|
+
}
|
|
718
|
+
/** Whether the backend declared an action of this kind on the interaction. */
|
|
719
|
+
can(kind) {
|
|
720
|
+
return this.interaction.actions.some((a) => a.kind === kind);
|
|
721
|
+
}
|
|
722
|
+
/** Answer a clarification ask. */
|
|
723
|
+
answer(text) {
|
|
724
|
+
return this.invoke(ActionKinds.answer, { response: text });
|
|
725
|
+
}
|
|
726
|
+
/** Skip / dismiss the interaction (proceed without acting on it). */
|
|
727
|
+
skip() {
|
|
728
|
+
return this.invoke(ActionKinds.skip);
|
|
729
|
+
}
|
|
730
|
+
/** Confirm a pending confirmation (approve the plan / destructive op). */
|
|
731
|
+
confirm() {
|
|
732
|
+
return this.invoke(ActionKinds.confirm);
|
|
733
|
+
}
|
|
734
|
+
/** Reject a pending confirmation. */
|
|
735
|
+
reject() {
|
|
736
|
+
return this.invoke(ActionKinds.reject);
|
|
737
|
+
}
|
|
738
|
+
/** Open the login surface for a site_login interaction. */
|
|
739
|
+
openLogin() {
|
|
740
|
+
return this.invoke(ActionKinds.openLogin);
|
|
741
|
+
}
|
|
742
|
+
/** Tell the agent the user has finished signing in. */
|
|
743
|
+
confirmSignedIn() {
|
|
744
|
+
return this.invoke(ActionKinds.confirmSignedIn);
|
|
745
|
+
}
|
|
746
|
+
/** Connect to the live browser for a take_control interaction. */
|
|
747
|
+
connectControl() {
|
|
748
|
+
return this.invoke(ActionKinds.connectControl);
|
|
749
|
+
}
|
|
750
|
+
/** Refresh the live control surface / live url. */
|
|
751
|
+
refreshControl() {
|
|
752
|
+
return this.invoke(ActionKinds.refreshControl);
|
|
753
|
+
}
|
|
754
|
+
/** Hand control back to the agent. */
|
|
755
|
+
releaseControl() {
|
|
756
|
+
return this.invoke(ActionKinds.releaseControl);
|
|
757
|
+
}
|
|
758
|
+
/** Retry a wait interaction now. */
|
|
759
|
+
retry() {
|
|
760
|
+
return this.invoke(ActionKinds.retry);
|
|
761
|
+
}
|
|
762
|
+
/** Switch the profile a wait/login interaction is bound to. */
|
|
763
|
+
switchProfile() {
|
|
764
|
+
return this.invoke(ActionKinds.switchProfile);
|
|
765
|
+
}
|
|
766
|
+
/**
|
|
767
|
+
* Invoke an action by kind with an optional body. `async` so a missing-action
|
|
768
|
+
* error surfaces as a rejected promise (not a synchronous throw), keeping the
|
|
769
|
+
* method contract uniformly promise-based.
|
|
770
|
+
*/
|
|
771
|
+
async invoke(kind, body) {
|
|
772
|
+
const action = this.interaction.actions.find((a) => a.kind === kind);
|
|
773
|
+
if (!action) {
|
|
774
|
+
const offered = this.interaction.actions.map((a) => a.kind).join(", ") || "none";
|
|
775
|
+
throw new QoniValidationError(
|
|
776
|
+
`interaction "${this.interaction.id}" (${this.interaction.type}) does not offer the "${kind}" action \u2014 the backend declared: ${offered}. Only call methods whose action the interaction currently offers.`
|
|
777
|
+
);
|
|
778
|
+
}
|
|
779
|
+
await this.post(action, body);
|
|
780
|
+
}
|
|
781
|
+
};
|
|
782
|
+
|
|
783
|
+
// src/run-handle.ts
|
|
784
|
+
var CANCEL_IDEMPOTENT_STATUSES = /* @__PURE__ */ new Set([400, 404, 409, 410, 422]);
|
|
785
|
+
var RunHandle = class {
|
|
786
|
+
constructor(ops, init) {
|
|
787
|
+
this.ops = ops;
|
|
788
|
+
this.id = init.id;
|
|
789
|
+
this.sessionRef = init.sessionRef;
|
|
790
|
+
this.capture = init.capture ?? {};
|
|
791
|
+
}
|
|
792
|
+
ops;
|
|
793
|
+
id;
|
|
794
|
+
/** Pass back to `run({ session })` to reuse the same backend session. */
|
|
795
|
+
sessionRef;
|
|
796
|
+
capture;
|
|
797
|
+
/** Refresh and return the run's current state. */
|
|
798
|
+
async status() {
|
|
799
|
+
const detail = await this.ops.getDetail();
|
|
800
|
+
this.#adoptSessionRef(detail);
|
|
801
|
+
return normalizeRunStatus(this.id, detail);
|
|
802
|
+
}
|
|
803
|
+
/**
|
|
804
|
+
* Stream semantic events. Ends automatically at the run's terminal event.
|
|
805
|
+
* Reconnects with `Last-Event-ID` catch-up are handled internally.
|
|
806
|
+
*/
|
|
807
|
+
async *events(opts = {}) {
|
|
808
|
+
for await (const wire of this.ops.streamEvents(opts)) {
|
|
809
|
+
await opts.onWireEvent?.(wire);
|
|
810
|
+
const event = normalizeRunEvent(wire, { topRunId: this.id });
|
|
811
|
+
if (!event) continue;
|
|
812
|
+
if (event.type === "screenshot" && !this.capture.screenshots) continue;
|
|
813
|
+
if (isVideoFrame(wire) && !this.capture.videoFrames) continue;
|
|
814
|
+
yield event;
|
|
815
|
+
if (event.isTerminal) return;
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
/** Drive the run to a terminal state and return the settled result. */
|
|
819
|
+
async wait(opts = {}) {
|
|
820
|
+
const { signal, timedOut } = withTimeout(opts.signal, opts.timeoutMs);
|
|
821
|
+
let lastEventId;
|
|
822
|
+
let eventIdAtLastClose;
|
|
823
|
+
let sawCleanClose = false;
|
|
824
|
+
let terminalPayload;
|
|
825
|
+
let terminalDoneReason;
|
|
826
|
+
let screenshotIndex = 0;
|
|
827
|
+
try {
|
|
828
|
+
settled: for (; ; ) {
|
|
829
|
+
for await (const event of this.events({
|
|
830
|
+
signal,
|
|
831
|
+
lastEventId,
|
|
832
|
+
onWireEvent: opts.onWireEvent,
|
|
833
|
+
onReconnect: opts.onReconnect
|
|
834
|
+
})) {
|
|
835
|
+
if (event.raw.id) lastEventId = event.raw.id;
|
|
836
|
+
await opts.onEvent?.(event);
|
|
837
|
+
if (event.type === "screenshot") {
|
|
838
|
+
await opts.onScreenshot?.(event.image, screenshotIndex++);
|
|
839
|
+
} else if (event.type === "interaction") {
|
|
840
|
+
await opts.onInteraction?.(this.interactionHandle(event.data), event);
|
|
841
|
+
}
|
|
842
|
+
if (event.isTerminal) {
|
|
843
|
+
terminalPayload = isJsonObject(event.raw.data) ? event.raw.data : void 0;
|
|
844
|
+
if (event.type === "done") terminalDoneReason = event.data.terminalReason || void 0;
|
|
845
|
+
break settled;
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
const detail2 = await this.ops.getDetail();
|
|
849
|
+
if (isTerminalRunStatus(detail2.status)) break;
|
|
850
|
+
if (sawCleanClose && lastEventId === eventIdAtLastClose) {
|
|
851
|
+
throw new QoniError(
|
|
852
|
+
`run event stream ended before a terminal event while the run is still "${String(detail2.status)}" \u2014 re-attach or poll status()`,
|
|
853
|
+
{ code: "upstream.failed", retryable: true }
|
|
854
|
+
);
|
|
855
|
+
}
|
|
856
|
+
sawCleanClose = true;
|
|
857
|
+
eventIdAtLastClose = lastEventId;
|
|
858
|
+
}
|
|
859
|
+
} catch (err) {
|
|
860
|
+
if (timedOut() && !opts.signal?.aborted) {
|
|
861
|
+
throw new QoniTimeoutError(
|
|
862
|
+
`run.wait timed out after ${opts.timeoutMs} ms \u2014 the run keeps executing server-side; attach() to it again or cancel() it`,
|
|
863
|
+
{ cause: err }
|
|
864
|
+
);
|
|
865
|
+
}
|
|
866
|
+
throw err;
|
|
867
|
+
}
|
|
868
|
+
let detail;
|
|
869
|
+
try {
|
|
870
|
+
detail = await this.ops.getDetail();
|
|
871
|
+
} catch (err) {
|
|
872
|
+
if (!terminalPayload) throw err;
|
|
873
|
+
detail = terminalPayload;
|
|
874
|
+
}
|
|
875
|
+
this.#adoptSessionRef(detail);
|
|
876
|
+
const artifacts = this.ops.listArtifacts ? await this.ops.listArtifacts() : [];
|
|
877
|
+
return settleRunResult(this.id, detail, artifacts, terminalDoneReason);
|
|
878
|
+
}
|
|
879
|
+
/**
|
|
880
|
+
* Wrap an {@link Interaction} (from `event.data` of an `interaction` event)
|
|
881
|
+
* in a typed {@link InteractionHandle} bound to this run. The handle's action
|
|
882
|
+
* methods (`answer` / `confirmSignedIn` / `releaseControl` / …) post to the
|
|
883
|
+
* endpoint the backend declared for that action.
|
|
884
|
+
*/
|
|
885
|
+
interactionHandle(interaction) {
|
|
886
|
+
return new InteractionHandle(
|
|
887
|
+
interaction,
|
|
888
|
+
(action, body) => this.ops.postAction(action, body)
|
|
889
|
+
);
|
|
890
|
+
}
|
|
891
|
+
/**
|
|
892
|
+
* Cancel the run. Idempotent: cancelling an already-terminal run returns
|
|
893
|
+
* its terminal state instead of throwing.
|
|
894
|
+
*/
|
|
895
|
+
async cancel(reason) {
|
|
896
|
+
try {
|
|
897
|
+
const data = await this.ops.cancel(reason);
|
|
898
|
+
this.#adoptSessionRef(data);
|
|
899
|
+
return normalizeRunStatus(this.id, data);
|
|
900
|
+
} catch (err) {
|
|
901
|
+
if (err instanceof QoniError && CANCEL_IDEMPOTENT_STATUSES.has(err.status ?? 0)) {
|
|
902
|
+
let snapshot;
|
|
903
|
+
try {
|
|
904
|
+
snapshot = await this.status();
|
|
905
|
+
} catch {
|
|
906
|
+
throw err;
|
|
907
|
+
}
|
|
908
|
+
if (isTerminalRunStatus(snapshot.status)) return snapshot;
|
|
909
|
+
}
|
|
910
|
+
throw err;
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
#adoptSessionRef(detail) {
|
|
914
|
+
const sessionId = typeof detail.session_id === "string" ? detail.session_id : void 0;
|
|
915
|
+
if (sessionId && !this.sessionRef) this.sessionRef = { sessionId };
|
|
916
|
+
}
|
|
917
|
+
};
|
|
918
|
+
function normalizeRunStatus(id, detail) {
|
|
919
|
+
return {
|
|
920
|
+
id,
|
|
921
|
+
status: typeof detail.status === "string" ? detail.status : inferStatus(detail),
|
|
922
|
+
sessionId: typeof detail.session_id === "string" ? detail.session_id : void 0,
|
|
923
|
+
output: runOutput(detail),
|
|
924
|
+
raw: detail
|
|
925
|
+
};
|
|
926
|
+
}
|
|
927
|
+
function settleRunResult(runId, detail, artifacts, fallbackTerminalReason) {
|
|
928
|
+
const terminalReason = (typeof detail.terminal_reason === "string" ? detail.terminal_reason : void 0) ?? fallbackTerminalReason;
|
|
929
|
+
const isTaskSuccessful = typeof detail.is_task_successful === "boolean" ? detail.is_task_successful : void 0;
|
|
930
|
+
let status;
|
|
931
|
+
if (isTerminalRunStatus(detail.status)) {
|
|
932
|
+
status = detail.status;
|
|
933
|
+
} else if (terminalReason === "canceled") {
|
|
934
|
+
status = "canceled";
|
|
935
|
+
} else if (terminalReason === "failed" || terminalReason === "expired" || isTaskSuccessful === false) {
|
|
936
|
+
status = "failed";
|
|
937
|
+
} else {
|
|
938
|
+
status = "succeeded";
|
|
939
|
+
}
|
|
940
|
+
return {
|
|
941
|
+
runId,
|
|
942
|
+
status,
|
|
943
|
+
output: runOutput(detail),
|
|
944
|
+
artifacts,
|
|
945
|
+
terminalReason,
|
|
946
|
+
isTaskSuccessful,
|
|
947
|
+
raw: detail
|
|
948
|
+
};
|
|
949
|
+
}
|
|
950
|
+
function runOutput(detail) {
|
|
951
|
+
return detail.output ?? detail.result ?? detail.results;
|
|
952
|
+
}
|
|
953
|
+
function inferStatus(detail) {
|
|
954
|
+
const reason = typeof detail.terminal_reason === "string" ? detail.terminal_reason : void 0;
|
|
955
|
+
if (reason === "canceled") return "canceled";
|
|
956
|
+
if (reason === "failed" || reason === "expired") return "failed";
|
|
957
|
+
if (reason === "done") return "succeeded";
|
|
958
|
+
return "";
|
|
959
|
+
}
|
|
960
|
+
function isVideoFrame(wire) {
|
|
961
|
+
const type = wire.event ?? (isJsonObject(wire.data) ? wire.data.type : void 0);
|
|
962
|
+
return type === "browser_video_frame" || type === "run.browser_video_frame";
|
|
963
|
+
}
|
|
964
|
+
function withTimeout(signal, timeoutMs) {
|
|
965
|
+
if (!timeoutMs) return { signal, timedOut: () => false };
|
|
966
|
+
const controller = new AbortController();
|
|
967
|
+
let fired = false;
|
|
968
|
+
const timer = setTimeout(() => {
|
|
969
|
+
fired = true;
|
|
970
|
+
controller.abort();
|
|
971
|
+
}, timeoutMs);
|
|
972
|
+
controller.signal.addEventListener("abort", () => clearTimeout(timer), { once: true });
|
|
973
|
+
if (signal) {
|
|
974
|
+
if (signal.aborted) controller.abort();
|
|
975
|
+
else signal.addEventListener("abort", () => controller.abort(), { once: true });
|
|
976
|
+
}
|
|
977
|
+
return { signal: controller.signal, timedOut: () => fired };
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
// src/deep-research.ts
|
|
981
|
+
function createDeepResearchNamespace(transport) {
|
|
982
|
+
const api = {
|
|
983
|
+
run: (input) => transport.webAgentJson("POST", "/deep_research/runs", input.token, {
|
|
984
|
+
body: normalizeDeepResearchRunInput(omit(input, "token")),
|
|
985
|
+
requiredScopes: ["webagent.deep_research:manage"]
|
|
986
|
+
}),
|
|
987
|
+
get: (input) => transport.webAgentJson(
|
|
988
|
+
"GET",
|
|
989
|
+
`/deep_research/runs/${encodeURIComponent(input.runId)}`,
|
|
990
|
+
input.token,
|
|
991
|
+
{
|
|
992
|
+
requiredScopes: ["webagent.deep_research:read"]
|
|
993
|
+
}
|
|
994
|
+
),
|
|
995
|
+
events: (input) => transport.webAgentSSE(
|
|
996
|
+
`/deep_research/runs/${encodeURIComponent(input.runId)}/events`,
|
|
997
|
+
input.token,
|
|
998
|
+
{
|
|
999
|
+
lastEventId: input.lastEventId,
|
|
1000
|
+
requiredScopes: ["webagent.deep_research:read"],
|
|
1001
|
+
signal: input.signal,
|
|
1002
|
+
onReconnect: input.onReconnect
|
|
1003
|
+
}
|
|
1004
|
+
),
|
|
1005
|
+
followUp: (input) => transport.webAgentJson(
|
|
1006
|
+
"POST",
|
|
1007
|
+
`/deep_research/runs/${encodeURIComponent(input.runId)}/messages`,
|
|
1008
|
+
input.token,
|
|
1009
|
+
{
|
|
1010
|
+
body: omit(input, "token", "runId"),
|
|
1011
|
+
requiredScopes: ["webagent.deep_research:manage"]
|
|
1012
|
+
}
|
|
1013
|
+
),
|
|
1014
|
+
cancel: (input) => transport.webAgentJson(
|
|
1015
|
+
"POST",
|
|
1016
|
+
`/deep_research/runs/${encodeURIComponent(input.runId)}/cancel`,
|
|
1017
|
+
input.token,
|
|
1018
|
+
{
|
|
1019
|
+
body: {},
|
|
1020
|
+
requiredScopes: ["webagent.deep_research:manage"]
|
|
1021
|
+
}
|
|
1022
|
+
),
|
|
1023
|
+
feedback: (input) => transport.webAgentJson(
|
|
1024
|
+
"POST",
|
|
1025
|
+
`/deep_research/runs/${encodeURIComponent(input.runId)}/feedback`,
|
|
1026
|
+
input.token,
|
|
1027
|
+
{
|
|
1028
|
+
body: renameKeys(omit(input, "token", "runId"), { feedbackText: "feedback_text" }),
|
|
1029
|
+
requiredScopes: ["webagent.deep_research:manage"]
|
|
1030
|
+
}
|
|
1031
|
+
),
|
|
1032
|
+
listArtifacts: (input) => transport.webAgentJson(
|
|
1033
|
+
"GET",
|
|
1034
|
+
`/deep_research/runs/${encodeURIComponent(input.runId)}/artifacts`,
|
|
1035
|
+
input.token,
|
|
1036
|
+
{
|
|
1037
|
+
requiredScopes: ["webagent.deep_research:read"]
|
|
1038
|
+
}
|
|
1039
|
+
),
|
|
1040
|
+
getArtifact: (input) => transport.webAgentJson(
|
|
1041
|
+
"GET",
|
|
1042
|
+
`/deep_research/runs/${encodeURIComponent(input.runId)}/artifacts/${encodeURIComponent(input.artifactId)}`,
|
|
1043
|
+
input.token,
|
|
1044
|
+
{
|
|
1045
|
+
requiredScopes: ["webagent.deep_research:read"]
|
|
1046
|
+
}
|
|
1047
|
+
)
|
|
1048
|
+
};
|
|
1049
|
+
function buildOps(token, runId) {
|
|
1050
|
+
return {
|
|
1051
|
+
getDetail: async () => asRecord((await api.get({ token, runId })).data),
|
|
1052
|
+
streamEvents: (opts) => api.events({
|
|
1053
|
+
token,
|
|
1054
|
+
runId,
|
|
1055
|
+
lastEventId: opts.lastEventId,
|
|
1056
|
+
signal: opts.signal,
|
|
1057
|
+
onReconnect: opts.onReconnect
|
|
1058
|
+
}),
|
|
1059
|
+
postAction: async (action, body) => {
|
|
1060
|
+
await transport.webAgentJson(
|
|
1061
|
+
action.method || "POST",
|
|
1062
|
+
action.endpoint,
|
|
1063
|
+
token,
|
|
1064
|
+
{
|
|
1065
|
+
...body !== void 0 ? { body } : {},
|
|
1066
|
+
requiredScopes: ["webagent.deep_research:manage"]
|
|
1067
|
+
}
|
|
1068
|
+
);
|
|
1069
|
+
},
|
|
1070
|
+
cancel: async () => asRecord((await api.cancel({ token, runId })).data),
|
|
1071
|
+
listArtifacts: async () => {
|
|
1072
|
+
const res = await api.listArtifacts({ token, runId });
|
|
1073
|
+
const items = Array.isArray(res.data?.items) ? res.data.items : [];
|
|
1074
|
+
return items.filter(isJsonObject).map((item) => toArtifact(item, token, runId));
|
|
1075
|
+
}
|
|
1076
|
+
};
|
|
1077
|
+
}
|
|
1078
|
+
function toArtifact(item, token, runId) {
|
|
1079
|
+
const id = typeof item.id === "string" ? item.id : "";
|
|
1080
|
+
return {
|
|
1081
|
+
id,
|
|
1082
|
+
name: typeof item.name === "string" && item.name ? item.name : void 0,
|
|
1083
|
+
mime: typeof item.mime_type === "string" ? item.mime_type : void 0,
|
|
1084
|
+
sizeBytes: typeof item.size_bytes === "number" ? item.size_bytes : void 0,
|
|
1085
|
+
createdAt: typeof item.created_at === "string" ? item.created_at : void 0,
|
|
1086
|
+
content: async () => (await transport.webAgentBytes(
|
|
1087
|
+
`/deep_research/runs/${encodeURIComponent(runId)}/artifacts/${encodeURIComponent(id)}`,
|
|
1088
|
+
token,
|
|
1089
|
+
{ requiredScopes: ["webagent.deep_research:read"] }
|
|
1090
|
+
)).bytes
|
|
1091
|
+
};
|
|
1092
|
+
}
|
|
1093
|
+
return {
|
|
1094
|
+
/**
|
|
1095
|
+
* Start a research run. Pass `session: prior.sessionRef` to start a
|
|
1096
|
+
* follow-up run that inherits the prior run's context.
|
|
1097
|
+
*/
|
|
1098
|
+
run: async (input) => {
|
|
1099
|
+
const { token, prompt, capture, limits, session, ...rest } = input;
|
|
1100
|
+
if (!prompt) {
|
|
1101
|
+
throw new QoniValidationError("deepResearch.run requires a prompt");
|
|
1102
|
+
}
|
|
1103
|
+
const run = await api.run({
|
|
1104
|
+
token,
|
|
1105
|
+
topic: prompt,
|
|
1106
|
+
...rest,
|
|
1107
|
+
...limits?.maxDurationMinutes !== void 0 ? { maxDurationMinutes: limits.maxDurationMinutes } : {},
|
|
1108
|
+
...session ? { session_id: session.sessionId } : {}
|
|
1109
|
+
});
|
|
1110
|
+
const runId = run.data.run_id || run.data.id;
|
|
1111
|
+
if (!runId) {
|
|
1112
|
+
throw new QoniValidationError("deepResearch run create did not return a run id");
|
|
1113
|
+
}
|
|
1114
|
+
return new RunHandle(buildOps(token, runId), {
|
|
1115
|
+
id: runId,
|
|
1116
|
+
sessionRef: session,
|
|
1117
|
+
capture
|
|
1118
|
+
});
|
|
1119
|
+
},
|
|
1120
|
+
/** Reconnect to an existing run. Verifies the run exists before returning. */
|
|
1121
|
+
attach: async (runId, opts) => {
|
|
1122
|
+
const handle = new RunHandle(buildOps(opts.token, runId), {
|
|
1123
|
+
id: runId,
|
|
1124
|
+
capture: opts.capture
|
|
1125
|
+
});
|
|
1126
|
+
await handle.status();
|
|
1127
|
+
return handle;
|
|
1128
|
+
},
|
|
1129
|
+
api
|
|
1130
|
+
};
|
|
1131
|
+
}
|
|
1132
|
+
function normalizeDeepResearchRunInput(input) {
|
|
1133
|
+
return renameKeys(input, {
|
|
1134
|
+
outputFormat: "output_format",
|
|
1135
|
+
targetAudience: "target_audience",
|
|
1136
|
+
maxDurationMinutes: "max_duration_minutes",
|
|
1137
|
+
callbackUrl: "callback_url",
|
|
1138
|
+
domainWhitelist: "domain_whitelist",
|
|
1139
|
+
domainBlacklist: "domain_blacklist"
|
|
1140
|
+
});
|
|
1141
|
+
}
|
|
1142
|
+
function asRecord(value) {
|
|
1143
|
+
return isJsonObject(value) ? value : {};
|
|
1144
|
+
}
|
|
1145
|
+
function omit(value, ...keys) {
|
|
1146
|
+
const skipped = new Set(keys);
|
|
1147
|
+
const out = {};
|
|
1148
|
+
for (const [key, item] of Object.entries(value)) {
|
|
1149
|
+
if (!skipped.has(key) && item !== void 0) out[key] = item;
|
|
1150
|
+
}
|
|
1151
|
+
return out;
|
|
1152
|
+
}
|
|
1153
|
+
function renameKeys(value, mapping) {
|
|
1154
|
+
const out = {};
|
|
1155
|
+
for (const [key, item] of Object.entries(value)) {
|
|
1156
|
+
const target = mapping[key] || key;
|
|
1157
|
+
if (out[target] === void 0) out[target] = item;
|
|
1158
|
+
}
|
|
1159
|
+
return out;
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
// src/do-anything.ts
|
|
1163
|
+
var PLATFORM_DECIDED_RUN_OPTIONS = [
|
|
1164
|
+
"model",
|
|
1165
|
+
"proxyCountryCode",
|
|
1166
|
+
"callbackUrl",
|
|
1167
|
+
"workspaceId",
|
|
1168
|
+
"outputSchema",
|
|
1169
|
+
"cacheScript"
|
|
1170
|
+
];
|
|
1171
|
+
function createDoAnythingNamespace(transport) {
|
|
1172
|
+
const api = {
|
|
1173
|
+
createSession: (input) => transport.webAgentJson("POST", "/do_anything/sessions", input.token, {
|
|
1174
|
+
body: omit2(input, "token"),
|
|
1175
|
+
requiredScopes: ["webagent.do_anything:manage"]
|
|
1176
|
+
}),
|
|
1177
|
+
/**
|
|
1178
|
+
* `POST /do_anything/runs` — one call creates the run. Without
|
|
1179
|
+
* `session_id` a fresh session is created around the run; with it the
|
|
1180
|
+
* run is a follow-up in that session (409 while the prior run is
|
|
1181
|
+
* non-terminal).
|
|
1182
|
+
*/
|
|
1183
|
+
createRun: (input) => transport.webAgentJson("POST", "/do_anything/runs", input.token, {
|
|
1184
|
+
body: normalizeDoAnythingRunInput(omit2(input, "token")),
|
|
1185
|
+
requiredScopes: ["webagent.do_anything:manage"]
|
|
1186
|
+
}),
|
|
1187
|
+
getRun: (input) => transport.webAgentJson(
|
|
1188
|
+
"GET",
|
|
1189
|
+
`/do_anything/runs/${encodeURIComponent(input.runId)}`,
|
|
1190
|
+
input.token,
|
|
1191
|
+
{
|
|
1192
|
+
requiredScopes: ["webagent.do_anything:read"]
|
|
1193
|
+
}
|
|
1194
|
+
),
|
|
1195
|
+
events: async function* (input) {
|
|
1196
|
+
const path = `/do_anything/runs/${encodeURIComponent(input.runId)}/events`;
|
|
1197
|
+
const stream = transport.webAgentSSE(path, input.token, {
|
|
1198
|
+
lastEventId: input.lastEventId,
|
|
1199
|
+
requiredScopes: ["webagent.do_anything:read"],
|
|
1200
|
+
signal: input.signal,
|
|
1201
|
+
onReconnect: input.onReconnect,
|
|
1202
|
+
...input.includeScreenshots === void 0 ? {} : { query: { include_screenshots: input.includeScreenshots } }
|
|
1203
|
+
});
|
|
1204
|
+
for await (const event of stream) {
|
|
1205
|
+
if (input.onlyTopLevel) {
|
|
1206
|
+
const taskId = envelopeTaskId(event);
|
|
1207
|
+
if (taskId && taskId !== input.runId) continue;
|
|
1208
|
+
}
|
|
1209
|
+
yield event;
|
|
1210
|
+
}
|
|
1211
|
+
},
|
|
1212
|
+
intervene: (input) => transport.webAgentJson(
|
|
1213
|
+
"POST",
|
|
1214
|
+
`/do_anything/runs/${encodeURIComponent(input.runId)}/intervene`,
|
|
1215
|
+
input.token,
|
|
1216
|
+
{
|
|
1217
|
+
body: normalizeDoAnythingInterveneInput(omit2(input, "token", "runId")),
|
|
1218
|
+
requiredScopes: ["webagent.do_anything:manage"]
|
|
1219
|
+
}
|
|
1220
|
+
),
|
|
1221
|
+
cancel: (input) => transport.webAgentJson(
|
|
1222
|
+
"POST",
|
|
1223
|
+
`/do_anything/runs/${encodeURIComponent(input.runId)}/cancel`,
|
|
1224
|
+
input.token,
|
|
1225
|
+
{
|
|
1226
|
+
body: omit2(input, "token", "runId"),
|
|
1227
|
+
requiredScopes: ["webagent.do_anything:manage"]
|
|
1228
|
+
}
|
|
1229
|
+
),
|
|
1230
|
+
readArtifacts: (input) => transport.webAgentJson(
|
|
1231
|
+
"GET",
|
|
1232
|
+
`/do_anything/sessions/${encodeURIComponent(input.sessionId)}/artifacts/${encodeURIComponent(input.artifactId)}`,
|
|
1233
|
+
input.token,
|
|
1234
|
+
{
|
|
1235
|
+
requiredScopes: ["webagent.do_anything:read"]
|
|
1236
|
+
}
|
|
1237
|
+
),
|
|
1238
|
+
readRecording: (input) => transport.webAgentJson(
|
|
1239
|
+
"GET",
|
|
1240
|
+
`/do_anything/sessions/${encodeURIComponent(input.sessionId)}/recording`,
|
|
1241
|
+
input.token,
|
|
1242
|
+
{
|
|
1243
|
+
requiredScopes: ["webagent.do_anything:read"]
|
|
1244
|
+
}
|
|
1245
|
+
)
|
|
1246
|
+
};
|
|
1247
|
+
function buildOps(token, runId, capture) {
|
|
1248
|
+
return {
|
|
1249
|
+
getDetail: async () => asRecord2((await api.getRun({ token, runId })).data),
|
|
1250
|
+
streamEvents: (opts) => api.events({
|
|
1251
|
+
token,
|
|
1252
|
+
runId,
|
|
1253
|
+
lastEventId: opts.lastEventId,
|
|
1254
|
+
signal: opts.signal,
|
|
1255
|
+
onReconnect: opts.onReconnect,
|
|
1256
|
+
// Skip screenshot rows in backfill when the caller did not opt in.
|
|
1257
|
+
includeScreenshots: capture?.screenshots ? void 0 : false
|
|
1258
|
+
}),
|
|
1259
|
+
postAction: async (action, body) => {
|
|
1260
|
+
await transport.webAgentJson(
|
|
1261
|
+
action.method || "POST",
|
|
1262
|
+
action.endpoint,
|
|
1263
|
+
token,
|
|
1264
|
+
{
|
|
1265
|
+
...body !== void 0 ? { body } : {},
|
|
1266
|
+
requiredScopes: ["webagent.do_anything:manage"]
|
|
1267
|
+
}
|
|
1268
|
+
);
|
|
1269
|
+
},
|
|
1270
|
+
cancel: async (reason) => asRecord2(
|
|
1271
|
+
(await api.cancel({
|
|
1272
|
+
token,
|
|
1273
|
+
runId,
|
|
1274
|
+
...reason === void 0 ? {} : { reason }
|
|
1275
|
+
})).data
|
|
1276
|
+
)
|
|
1277
|
+
};
|
|
1278
|
+
}
|
|
1279
|
+
return {
|
|
1280
|
+
/** Start a run. Returns a handle — see `RunHandle` for the run lifecycle. */
|
|
1281
|
+
run: async (input) => {
|
|
1282
|
+
const { token, prompt, capture, limits, session, ...rest } = input;
|
|
1283
|
+
if (!prompt || typeof prompt !== "string") {
|
|
1284
|
+
throw new QoniValidationError("doAnything.run requires a prompt string");
|
|
1285
|
+
}
|
|
1286
|
+
const unsupported = PLATFORM_DECIDED_RUN_OPTIONS.filter(
|
|
1287
|
+
(key) => rest[key] !== void 0
|
|
1288
|
+
);
|
|
1289
|
+
if (unsupported.length > 0) {
|
|
1290
|
+
throw new QoniValidationError(
|
|
1291
|
+
`doAnything.run does not support ${unsupported.join(", ")} \u2014 the wire create body has no such field (the platform decides these server-side), so the value would be silently dropped. Remove it from the call.`
|
|
1292
|
+
);
|
|
1293
|
+
}
|
|
1294
|
+
const created = await api.createRun({
|
|
1295
|
+
token,
|
|
1296
|
+
instructions: prompt,
|
|
1297
|
+
...rest,
|
|
1298
|
+
...session?.sessionId ? { sessionId: session.sessionId } : {},
|
|
1299
|
+
...limits?.maxDurationMinutes !== void 0 ? { maxDurationMinutes: limits.maxDurationMinutes } : {}
|
|
1300
|
+
});
|
|
1301
|
+
const runId = created.data.run_id || created.data.id;
|
|
1302
|
+
if (!runId) {
|
|
1303
|
+
throw new QoniValidationError("doAnything run create did not return a run id");
|
|
1304
|
+
}
|
|
1305
|
+
const sessionId = typeof created.data.session_id === "string" && created.data.session_id ? created.data.session_id : session?.sessionId;
|
|
1306
|
+
return new RunHandle(buildOps(token, runId, capture), {
|
|
1307
|
+
id: runId,
|
|
1308
|
+
sessionRef: sessionId ? { sessionId } : void 0,
|
|
1309
|
+
capture
|
|
1310
|
+
});
|
|
1311
|
+
},
|
|
1312
|
+
/**
|
|
1313
|
+
* Reconnect to an existing run by id. Verifies the run exists before
|
|
1314
|
+
* returning; `sessionRef` is adopted from the run detail envelope when
|
|
1315
|
+
* not supplied.
|
|
1316
|
+
*/
|
|
1317
|
+
attach: async (runId, opts) => {
|
|
1318
|
+
const sessionId = opts.session?.sessionId;
|
|
1319
|
+
const handle = new RunHandle(buildOps(opts.token, runId, opts.capture), {
|
|
1320
|
+
id: runId,
|
|
1321
|
+
sessionRef: sessionId ? { sessionId } : void 0,
|
|
1322
|
+
capture: opts.capture
|
|
1323
|
+
});
|
|
1324
|
+
await handle.status();
|
|
1325
|
+
return handle;
|
|
1326
|
+
},
|
|
1327
|
+
api
|
|
1328
|
+
};
|
|
1329
|
+
}
|
|
1330
|
+
function normalizeDoAnythingRunInput(input) {
|
|
1331
|
+
const body = renameKeys2(input, {
|
|
1332
|
+
sessionId: "session_id",
|
|
1333
|
+
profileId: "profile_id",
|
|
1334
|
+
keepAlive: "keep_alive",
|
|
1335
|
+
allowedActions: "allowed_actions",
|
|
1336
|
+
maxDurationMinutes: "max_duration_minutes"
|
|
1337
|
+
});
|
|
1338
|
+
return body;
|
|
1339
|
+
}
|
|
1340
|
+
function normalizeDoAnythingInterveneInput(input) {
|
|
1341
|
+
const body = renameKeys2(input, { requestId: "input_request_id" });
|
|
1342
|
+
if (body.input_request_id !== void 0 && body.kind === void 0) {
|
|
1343
|
+
body.kind = body.response === void 0 ? "skip_input_request" : "answer_input_request";
|
|
1344
|
+
}
|
|
1345
|
+
return body;
|
|
1346
|
+
}
|
|
1347
|
+
function envelopeTaskId(event) {
|
|
1348
|
+
const env = event.data;
|
|
1349
|
+
return env && typeof env === "object" && typeof env.task_id === "string" ? env.task_id : void 0;
|
|
1350
|
+
}
|
|
1351
|
+
function asRecord2(value) {
|
|
1352
|
+
return isJsonObject(value) ? value : {};
|
|
1353
|
+
}
|
|
1354
|
+
function omit2(value, ...keys) {
|
|
1355
|
+
const skipped = new Set(keys);
|
|
1356
|
+
const out = {};
|
|
1357
|
+
for (const [key, item] of Object.entries(value)) {
|
|
1358
|
+
if (!skipped.has(key) && item !== void 0) out[key] = item;
|
|
1359
|
+
}
|
|
1360
|
+
return out;
|
|
1361
|
+
}
|
|
1362
|
+
function renameKeys2(value, mapping) {
|
|
1363
|
+
const out = {};
|
|
1364
|
+
for (const [key, item] of Object.entries(value)) {
|
|
1365
|
+
const target = mapping[key] || key;
|
|
1366
|
+
if (out[target] === void 0) out[target] = item;
|
|
1367
|
+
}
|
|
1368
|
+
return out;
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
// src/qoni.ts
|
|
1372
|
+
function createQoniNamespace(_transport, delegateToken, completeDelegateToken) {
|
|
1373
|
+
return {
|
|
1374
|
+
delegateToken,
|
|
1375
|
+
/** @deprecated Use delegateToken. */
|
|
1376
|
+
delegateAgent: (input) => delegateToken(input),
|
|
1377
|
+
completeDelegateToken,
|
|
1378
|
+
/** @deprecated Use completeDelegateToken. */
|
|
1379
|
+
completeDelegateAgent: (input) => completeDelegateToken(input)
|
|
1380
|
+
};
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
// src/genauth.ts
|
|
1384
|
+
function createGenAuthNamespace(transport, options = {}) {
|
|
1385
|
+
return {
|
|
1386
|
+
userInfo: (input) => transport.genauthJson("GET", "/oidc/me", input.accessToken),
|
|
1387
|
+
jwks: () => transport.genauthJson("GET", "/oidc/.well-known/jwks.json"),
|
|
1388
|
+
discovery: () => transport.genauthJson("GET", "/oidc/.well-known/openid-configuration"),
|
|
1389
|
+
introspectDelegationToken: (input) => transport.genauthJson("POST", "/api/v3/eak/delegations/introspect", void 0, {
|
|
1390
|
+
body: { token: requiredDelegationToken(input) }
|
|
1391
|
+
}),
|
|
1392
|
+
users: {
|
|
1393
|
+
list: async (input = {}) => {
|
|
1394
|
+
const auth = await adminAuthFor(options.adminTokenFor, input);
|
|
1395
|
+
return transport.genauthJson("POST", "/api/v3/list-users", auth.accessToken, {
|
|
1396
|
+
body: listUsersBody(input),
|
|
1397
|
+
headers: userPoolHeaders(auth.userPoolId)
|
|
1398
|
+
});
|
|
1399
|
+
},
|
|
1400
|
+
get: async (input) => {
|
|
1401
|
+
const auth = await adminAuthFor(options.adminTokenFor, input);
|
|
1402
|
+
return transport.genauthJson("GET", "/api/v3/get-user", auth.accessToken, {
|
|
1403
|
+
query: omit3(input, "adminToken", "userPoolId"),
|
|
1404
|
+
headers: userPoolHeaders(auth.userPoolId)
|
|
1405
|
+
});
|
|
1406
|
+
},
|
|
1407
|
+
getBatch: async (input) => {
|
|
1408
|
+
const auth = await adminAuthFor(options.adminTokenFor, input);
|
|
1409
|
+
return transport.genauthJson("GET", "/api/v3/get-user-batch", auth.accessToken, {
|
|
1410
|
+
query: omit3(input, "adminToken", "userPoolId"),
|
|
1411
|
+
headers: userPoolHeaders(auth.userPoolId)
|
|
1412
|
+
});
|
|
1413
|
+
},
|
|
1414
|
+
create: async (input) => {
|
|
1415
|
+
const auth = await adminAuthFor(options.adminTokenFor, input);
|
|
1416
|
+
return transport.genauthJson("POST", "/api/v3/create-user", auth.accessToken, {
|
|
1417
|
+
body: omit3(input, "adminToken", "userPoolId"),
|
|
1418
|
+
headers: userPoolHeaders(auth.userPoolId)
|
|
1419
|
+
});
|
|
1420
|
+
},
|
|
1421
|
+
createBatch: async (input) => {
|
|
1422
|
+
const auth = await adminAuthFor(options.adminTokenFor, input);
|
|
1423
|
+
return transport.genauthJson("POST", "/api/v3/create-users-batch", auth.accessToken, {
|
|
1424
|
+
body: createUsersBatchBody(input),
|
|
1425
|
+
headers: userPoolHeaders(auth.userPoolId)
|
|
1426
|
+
});
|
|
1427
|
+
},
|
|
1428
|
+
update: async (input) => {
|
|
1429
|
+
const auth = await adminAuthFor(options.adminTokenFor, input);
|
|
1430
|
+
return transport.genauthJson("POST", "/api/v3/update-user", auth.accessToken, {
|
|
1431
|
+
body: omit3(input, "adminToken", "userPoolId"),
|
|
1432
|
+
headers: userPoolHeaders(auth.userPoolId)
|
|
1433
|
+
});
|
|
1434
|
+
},
|
|
1435
|
+
deleteBatch: async (input) => {
|
|
1436
|
+
const auth = await adminAuthFor(options.adminTokenFor, input);
|
|
1437
|
+
return transport.genauthJson("POST", "/api/v3/delete-users-batch", auth.accessToken, {
|
|
1438
|
+
body: omit3(input, "adminToken", "userPoolId"),
|
|
1439
|
+
headers: userPoolHeaders(auth.userPoolId)
|
|
1440
|
+
});
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
};
|
|
1444
|
+
}
|
|
1445
|
+
function requiredDelegationToken(input) {
|
|
1446
|
+
const token = input.token ?? input.delegateAgentToken;
|
|
1447
|
+
if (token?.trim()) return token;
|
|
1448
|
+
throw new Error("GenAuth delegation introspection requires token");
|
|
1449
|
+
}
|
|
1450
|
+
async function adminAuthFor(provider, input) {
|
|
1451
|
+
if (input.adminToken?.trim()) {
|
|
1452
|
+
if (!input.userPoolId?.trim()) {
|
|
1453
|
+
throw new Error("GenAuth adminToken override requires userPoolId");
|
|
1454
|
+
}
|
|
1455
|
+
return { accessToken: input.adminToken, userPoolId: input.userPoolId };
|
|
1456
|
+
}
|
|
1457
|
+
if (!provider) throw new Error("GenAuth user management requires Qoni AK/SK admin token exchange");
|
|
1458
|
+
return provider();
|
|
1459
|
+
}
|
|
1460
|
+
function userPoolHeaders(userPoolId) {
|
|
1461
|
+
return { "x-authing-userpool-id": userPoolId };
|
|
1462
|
+
}
|
|
1463
|
+
function omit3(value, ...keys) {
|
|
1464
|
+
const skipped = new Set(keys);
|
|
1465
|
+
const out = {};
|
|
1466
|
+
for (const [key, item] of Object.entries(value)) {
|
|
1467
|
+
if (!skipped.has(key) && item !== void 0) out[key] = item;
|
|
1468
|
+
}
|
|
1469
|
+
return out;
|
|
1470
|
+
}
|
|
1471
|
+
function listUsersBody(input) {
|
|
1472
|
+
const body = omit3(input, "adminToken", "userPoolId", "page", "limit");
|
|
1473
|
+
if (input.page === void 0 && input.limit === void 0) return body;
|
|
1474
|
+
const options = isJsonObject2(body.options) ? { ...body.options } : {};
|
|
1475
|
+
const pagination = isJsonObject2(options.pagination) ? { ...options.pagination } : {};
|
|
1476
|
+
if (input.page !== void 0) pagination.page = input.page;
|
|
1477
|
+
if (input.limit !== void 0) pagination.limit = input.limit;
|
|
1478
|
+
options.pagination = pagination;
|
|
1479
|
+
body.options = options;
|
|
1480
|
+
return body;
|
|
1481
|
+
}
|
|
1482
|
+
function createUsersBatchBody(input) {
|
|
1483
|
+
const body = omit3(input, "adminToken", "userPoolId", "users");
|
|
1484
|
+
if (input.users && !body.list) body.list = input.users;
|
|
1485
|
+
return body;
|
|
1486
|
+
}
|
|
1487
|
+
function isJsonObject2(value) {
|
|
1488
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1491
|
+
// src/gumem.ts
|
|
1492
|
+
function createGumemNamespace(transport) {
|
|
1493
|
+
return {
|
|
1494
|
+
createSession: (input) => transport.gumemJson("POST", "/api/sessions", input.token, {
|
|
1495
|
+
body: gumemBody(omit4(input, "token")),
|
|
1496
|
+
requiredScopes: ["gumem.memory:write"]
|
|
1497
|
+
}),
|
|
1498
|
+
addMessages: (input) => transport.gumemJson(
|
|
1499
|
+
"POST",
|
|
1500
|
+
`/api/sessions/${encodeURIComponent(input.sessionId)}/messages`,
|
|
1501
|
+
input.token,
|
|
1502
|
+
{
|
|
1503
|
+
body: gumemBody(omit4(input, "token", "sessionId")),
|
|
1504
|
+
requiredScopes: ["gumem.memory:write"]
|
|
1505
|
+
}
|
|
1506
|
+
),
|
|
1507
|
+
recall: (input) => transport.gumemJson(
|
|
1508
|
+
"POST",
|
|
1509
|
+
`/api/sessions/${encodeURIComponent(input.sessionId || "default")}/context`,
|
|
1510
|
+
input.token,
|
|
1511
|
+
{
|
|
1512
|
+
query: { query: input.query || "", details: input.details ?? false },
|
|
1513
|
+
body: gumemBody(omit4(input, "token", "sessionId", "query", "details")),
|
|
1514
|
+
requiredScopes: ["gumem.memory:read"]
|
|
1515
|
+
}
|
|
1516
|
+
),
|
|
1517
|
+
uploadResource: (input) => {
|
|
1518
|
+
const form = new FormData();
|
|
1519
|
+
form.set("file", input.file, input.filename);
|
|
1520
|
+
const userId = input.userId ?? input.user_id;
|
|
1521
|
+
const sessionId = input.sessionId ?? input.session_id;
|
|
1522
|
+
if (userId) form.set("user_id", userId);
|
|
1523
|
+
if (sessionId) form.set("session_id", sessionId);
|
|
1524
|
+
if (input.contentType) form.set("content_type", input.contentType);
|
|
1525
|
+
return transport.gumemJson("POST", "/api/resources", input.token, {
|
|
1526
|
+
body: form,
|
|
1527
|
+
requiredScopes: ["gumem.memory:write"]
|
|
1528
|
+
});
|
|
1529
|
+
},
|
|
1530
|
+
actions: {
|
|
1531
|
+
record: (input) => transport.gumemJson("POST", "/api/user/actions", input.token, {
|
|
1532
|
+
body: omit4(input, "token"),
|
|
1533
|
+
requiredScopes: ["gumem.action:write"]
|
|
1534
|
+
}),
|
|
1535
|
+
recall: (input) => transport.gumemJson("GET", "/api/user/actions/query", input.token, {
|
|
1536
|
+
query: omit4(input, "token"),
|
|
1537
|
+
requiredScopes: ["gumem.action:read"]
|
|
1538
|
+
}),
|
|
1539
|
+
stream: (input) => transport.gumemJson("GET", "/api/user/actions/stream", input.token, {
|
|
1540
|
+
query: omit4(input, "token"),
|
|
1541
|
+
requiredScopes: ["gumem.action:read"]
|
|
1542
|
+
})
|
|
1543
|
+
}
|
|
1544
|
+
};
|
|
1545
|
+
}
|
|
1546
|
+
function omit4(value, ...keys) {
|
|
1547
|
+
const skipped = new Set(keys);
|
|
1548
|
+
const out = {};
|
|
1549
|
+
for (const [key, item] of Object.entries(value)) {
|
|
1550
|
+
if (!skipped.has(key) && item !== void 0) out[key] = item;
|
|
1551
|
+
}
|
|
1552
|
+
return out;
|
|
1553
|
+
}
|
|
1554
|
+
function gumemBody(value) {
|
|
1555
|
+
return renameKeys3(value, {
|
|
1556
|
+
userId: "user_id",
|
|
1557
|
+
sessionId: "session_id",
|
|
1558
|
+
recallConfig: "recall_config",
|
|
1559
|
+
metadataFilters: "metadata_filters"
|
|
1560
|
+
});
|
|
1561
|
+
}
|
|
1562
|
+
function renameKeys3(value, mapping) {
|
|
1563
|
+
const out = {};
|
|
1564
|
+
for (const [key, item] of Object.entries(value)) {
|
|
1565
|
+
const target = mapping[key] || key;
|
|
1566
|
+
if (out[target] === void 0) out[target] = item;
|
|
1567
|
+
}
|
|
1568
|
+
return out;
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
// src/track.ts
|
|
1572
|
+
var MonitorHandle = class {
|
|
1573
|
+
constructor(ops, id) {
|
|
1574
|
+
this.ops = ops;
|
|
1575
|
+
this.id = id;
|
|
1576
|
+
}
|
|
1577
|
+
ops;
|
|
1578
|
+
id;
|
|
1579
|
+
/** Refresh and return the monitor (normalized camelCase fields). */
|
|
1580
|
+
async get() {
|
|
1581
|
+
return camelizeRecord(await this.ops.get());
|
|
1582
|
+
}
|
|
1583
|
+
/**
|
|
1584
|
+
* Pause the monitor — stops scheduled ticks until {@link resume}. The
|
|
1585
|
+
* monitor stays addressable; this is the way to "stop monitoring" without
|
|
1586
|
+
* deleting.
|
|
1587
|
+
*/
|
|
1588
|
+
async pause() {
|
|
1589
|
+
return camelizeRecord(await this.ops.update({ action: "pause" }));
|
|
1590
|
+
}
|
|
1591
|
+
/** Resume a paused monitor. */
|
|
1592
|
+
async resume() {
|
|
1593
|
+
return camelizeRecord(await this.ops.update({ action: "resume" }));
|
|
1594
|
+
}
|
|
1595
|
+
/**
|
|
1596
|
+
* Change the monitor definition. Pass any subset of fields (at least one);
|
|
1597
|
+
* `schedule` is `{ kind: "cron", expr }` or `{ kind: "interval", intervalSeconds }`.
|
|
1598
|
+
* The backend re-validates the trigger DSL against the schema, so a bad
|
|
1599
|
+
* change is rejected, not applied. The natural-language intent is not
|
|
1600
|
+
* refinable — create a new monitor to change it.
|
|
1601
|
+
*/
|
|
1602
|
+
async refine(patch) {
|
|
1603
|
+
const { schedule, ...rest } = patch;
|
|
1604
|
+
const body = snakeifyKeys(rest);
|
|
1605
|
+
if (schedule) body.schedule = normalizeSchedule(schedule);
|
|
1606
|
+
if (Object.keys(body).length === 0) {
|
|
1607
|
+
throw new QoniValidationError(
|
|
1608
|
+
"track refine requires at least one field to change (schedule / targetUrls / extractionSchema / triggerDsl / stopConditionDsl / notifyChannel)"
|
|
1609
|
+
);
|
|
1610
|
+
}
|
|
1611
|
+
return camelizeRecord(await this.ops.update({ action: "refine", patch: body }));
|
|
1612
|
+
}
|
|
1613
|
+
/** Trigger an immediate tick outside the schedule. */
|
|
1614
|
+
async runNow() {
|
|
1615
|
+
return camelizeRecord(await this.ops.runNow());
|
|
1616
|
+
}
|
|
1617
|
+
/**
|
|
1618
|
+
* Stream semantic monitor events. Monitors are resident, so the stream has
|
|
1619
|
+
* no terminal event — it ends only when closed by the caller or the server.
|
|
1620
|
+
*/
|
|
1621
|
+
async *events(opts = {}) {
|
|
1622
|
+
for await (const wire of this.ops.streamEvents(opts)) {
|
|
1623
|
+
const event = normalizeRunEvent(wire);
|
|
1624
|
+
if (event) yield event;
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
/**
|
|
1628
|
+
* Wrap an {@link Interaction} (from `event.data` of an `interaction` event on
|
|
1629
|
+
* the monitor stream — e.g. a `site_login` to unlock a `needs_reauth` park)
|
|
1630
|
+
* in a typed {@link InteractionHandle}. The handle's action methods post to
|
|
1631
|
+
* the endpoint the backend declared for that action.
|
|
1632
|
+
*/
|
|
1633
|
+
interactionHandle(interaction) {
|
|
1634
|
+
return new InteractionHandle(
|
|
1635
|
+
interaction,
|
|
1636
|
+
(action, body) => this.ops.postAction(action, body)
|
|
1637
|
+
);
|
|
1638
|
+
}
|
|
1639
|
+
/**
|
|
1640
|
+
* Read-only list of the runs the monitor's ticks produced. Tick runs are
|
|
1641
|
+
* owned by the scheduler — to stop monitoring use `pause()` / `delete()`.
|
|
1642
|
+
*/
|
|
1643
|
+
async runs(opts = {}) {
|
|
1644
|
+
const data = await this.ops.listRuns(opts);
|
|
1645
|
+
const items = Array.isArray(data.items) ? data.items : Array.isArray(data.runs) ? data.runs : [];
|
|
1646
|
+
return items.filter(isJsonObject).map(normalizeMonitorRun);
|
|
1647
|
+
}
|
|
1648
|
+
/** Read-only detail of a single tick run. */
|
|
1649
|
+
async run(runId) {
|
|
1650
|
+
return normalizeMonitorRun(await this.ops.getRun(runId));
|
|
1651
|
+
}
|
|
1652
|
+
/** Delete the monitor. */
|
|
1653
|
+
async delete() {
|
|
1654
|
+
await this.ops.delete();
|
|
1655
|
+
}
|
|
1656
|
+
};
|
|
1657
|
+
function createTrackNamespace(transport) {
|
|
1658
|
+
const api = {
|
|
1659
|
+
createMonitor: (input) => transport.webAgentJson("POST", "/track/monitors", input.token, {
|
|
1660
|
+
body: omit5(input, "token"),
|
|
1661
|
+
requiredScopes: ["webagent.track:manage"]
|
|
1662
|
+
}),
|
|
1663
|
+
getMonitor: (input) => transport.webAgentJson(
|
|
1664
|
+
"GET",
|
|
1665
|
+
`/track/monitors/${encodeURIComponent(input.monitorId)}`,
|
|
1666
|
+
input.token,
|
|
1667
|
+
{
|
|
1668
|
+
requiredScopes: ["webagent.track:read"]
|
|
1669
|
+
}
|
|
1670
|
+
),
|
|
1671
|
+
runNow: (input) => transport.webAgentJson(
|
|
1672
|
+
"POST",
|
|
1673
|
+
`/track/monitors/${encodeURIComponent(input.monitorId)}/run_now`,
|
|
1674
|
+
input.token,
|
|
1675
|
+
{
|
|
1676
|
+
body: {},
|
|
1677
|
+
requiredScopes: ["webagent.track:manage"]
|
|
1678
|
+
}
|
|
1679
|
+
),
|
|
1680
|
+
events: (input) => transport.webAgentSSE(
|
|
1681
|
+
`/track/monitors/${encodeURIComponent(input.monitorId)}/events`,
|
|
1682
|
+
input.token,
|
|
1683
|
+
{
|
|
1684
|
+
lastEventId: input.lastEventId,
|
|
1685
|
+
requiredScopes: ["webagent.track:read"],
|
|
1686
|
+
signal: input.signal
|
|
1687
|
+
}
|
|
1688
|
+
),
|
|
1689
|
+
intervene: (input) => transport.webAgentJson(
|
|
1690
|
+
"POST",
|
|
1691
|
+
`/track/monitors/${encodeURIComponent(input.monitorId)}/intervene`,
|
|
1692
|
+
input.token,
|
|
1693
|
+
{
|
|
1694
|
+
body: normalizeTrackInterveneInput(omit5(input, "token", "monitorId")),
|
|
1695
|
+
requiredScopes: ["webagent.track:manage"]
|
|
1696
|
+
}
|
|
1697
|
+
),
|
|
1698
|
+
listRuns: (input) => transport.webAgentJson(
|
|
1699
|
+
"GET",
|
|
1700
|
+
`/track/monitors/${encodeURIComponent(input.monitorId)}/runs`,
|
|
1701
|
+
input.token,
|
|
1702
|
+
{
|
|
1703
|
+
query: omit5(input, "token", "monitorId"),
|
|
1704
|
+
requiredScopes: ["webagent.track:read"]
|
|
1705
|
+
}
|
|
1706
|
+
),
|
|
1707
|
+
getRun: (input) => transport.webAgentJson(
|
|
1708
|
+
"GET",
|
|
1709
|
+
`/track/monitors/${encodeURIComponent(input.monitorId)}/runs/${encodeURIComponent(input.runId)}`,
|
|
1710
|
+
input.token,
|
|
1711
|
+
{
|
|
1712
|
+
requiredScopes: ["webagent.track:read"]
|
|
1713
|
+
}
|
|
1714
|
+
),
|
|
1715
|
+
updateMonitor: (input) => transport.webAgentJson(
|
|
1716
|
+
"PATCH",
|
|
1717
|
+
`/track/monitors/${encodeURIComponent(input.monitorId)}`,
|
|
1718
|
+
input.token,
|
|
1719
|
+
{
|
|
1720
|
+
body: omit5(input, "token", "monitorId"),
|
|
1721
|
+
requiredScopes: ["webagent.track:manage"]
|
|
1722
|
+
}
|
|
1723
|
+
),
|
|
1724
|
+
deleteMonitor: (input) => transport.webAgentJson(
|
|
1725
|
+
"DELETE",
|
|
1726
|
+
`/track/monitors/${encodeURIComponent(input.monitorId)}`,
|
|
1727
|
+
input.token,
|
|
1728
|
+
{
|
|
1729
|
+
// Backend DELETE /track/monitors/{id} requires track:manage
|
|
1730
|
+
// (see api/v1/track/monitors.py:delete_monitor).
|
|
1731
|
+
requiredScopes: ["webagent.track:manage"]
|
|
1732
|
+
}
|
|
1733
|
+
)
|
|
1734
|
+
};
|
|
1735
|
+
function buildOps(token, monitorId) {
|
|
1736
|
+
return {
|
|
1737
|
+
get: async () => asRecord3((await api.getMonitor({ token, monitorId })).data),
|
|
1738
|
+
update: async (input) => asRecord3((await api.updateMonitor({ token, monitorId, ...input })).data),
|
|
1739
|
+
runNow: async () => asRecord3((await api.runNow({ token, monitorId })).data),
|
|
1740
|
+
streamEvents: (opts) => api.events({ token, monitorId, lastEventId: opts.lastEventId, signal: opts.signal }),
|
|
1741
|
+
postAction: async (action, body) => {
|
|
1742
|
+
await transport.webAgentJson(
|
|
1743
|
+
action.method || "POST",
|
|
1744
|
+
action.endpoint,
|
|
1745
|
+
token,
|
|
1746
|
+
{
|
|
1747
|
+
...body !== void 0 ? { body } : {},
|
|
1748
|
+
requiredScopes: ["webagent.track:manage"]
|
|
1749
|
+
}
|
|
1750
|
+
);
|
|
1751
|
+
},
|
|
1752
|
+
listRuns: async (opts) => asRecord3((await api.listRuns({ token, monitorId, ...opts })).data),
|
|
1753
|
+
getRun: async (runId) => asRecord3((await api.getRun({ token, monitorId, runId })).data),
|
|
1754
|
+
delete: async () => {
|
|
1755
|
+
await api.deleteMonitor({ token, monitorId });
|
|
1756
|
+
}
|
|
1757
|
+
};
|
|
1758
|
+
}
|
|
1759
|
+
return {
|
|
1760
|
+
/** Create a monitor. Returns a handle — see `MonitorHandle`. */
|
|
1761
|
+
create: async (input) => {
|
|
1762
|
+
const { token, prompt, ...rest } = input;
|
|
1763
|
+
if (!prompt || typeof prompt !== "string") {
|
|
1764
|
+
throw new QoniValidationError("track.create requires a prompt string");
|
|
1765
|
+
}
|
|
1766
|
+
const created = await api.createMonitor({
|
|
1767
|
+
token,
|
|
1768
|
+
intent: prompt,
|
|
1769
|
+
...snakeifyKeys(rest)
|
|
1770
|
+
});
|
|
1771
|
+
const monitorId = created.data.id || created.data.monitor_id;
|
|
1772
|
+
if (!monitorId) {
|
|
1773
|
+
throw new QoniValidationError("track monitor create did not return a monitor id");
|
|
1774
|
+
}
|
|
1775
|
+
return new MonitorHandle(buildOps(token, monitorId), monitorId);
|
|
1776
|
+
},
|
|
1777
|
+
/** Reconnect to an existing monitor. Verifies it exists before returning. */
|
|
1778
|
+
attach: async (monitorId, opts) => {
|
|
1779
|
+
const handle = new MonitorHandle(buildOps(opts.token, monitorId), monitorId);
|
|
1780
|
+
await handle.get();
|
|
1781
|
+
return handle;
|
|
1782
|
+
},
|
|
1783
|
+
api
|
|
1784
|
+
};
|
|
1785
|
+
}
|
|
1786
|
+
function normalizeMonitorRun(item) {
|
|
1787
|
+
const normalized = camelizeRecord(item);
|
|
1788
|
+
if (typeof normalized.id !== "string" || !normalized.id) {
|
|
1789
|
+
const id = normalized.runId ?? normalized.snapshotId;
|
|
1790
|
+
if (typeof id === "string" && id) normalized.id = id;
|
|
1791
|
+
}
|
|
1792
|
+
return normalized;
|
|
1793
|
+
}
|
|
1794
|
+
function normalizeTrackInterveneInput(input) {
|
|
1795
|
+
return renameKeys4(input, { requestId: "request_id" });
|
|
1796
|
+
}
|
|
1797
|
+
function normalizeSchedule(schedule) {
|
|
1798
|
+
if (schedule.kind === "interval") {
|
|
1799
|
+
return { kind: "interval", interval_seconds: schedule.intervalSeconds };
|
|
1800
|
+
}
|
|
1801
|
+
return { kind: "cron", expr: schedule.expr };
|
|
1802
|
+
}
|
|
1803
|
+
function snakeifyKeys(value) {
|
|
1804
|
+
const out = {};
|
|
1805
|
+
for (const [key, item] of Object.entries(value)) {
|
|
1806
|
+
if (item === void 0) continue;
|
|
1807
|
+
out[key.replace(/([A-Z])/g, (_, ch) => `_${ch.toLowerCase()}`)] = item;
|
|
1808
|
+
}
|
|
1809
|
+
return out;
|
|
1810
|
+
}
|
|
1811
|
+
function asRecord3(value) {
|
|
1812
|
+
return isJsonObject(value) ? value : {};
|
|
1813
|
+
}
|
|
1814
|
+
function omit5(value, ...keys) {
|
|
1815
|
+
const skipped = new Set(keys);
|
|
1816
|
+
const out = {};
|
|
1817
|
+
for (const [key, item] of Object.entries(value)) {
|
|
1818
|
+
if (!skipped.has(key) && item !== void 0) out[key] = item;
|
|
1819
|
+
}
|
|
1820
|
+
return out;
|
|
1821
|
+
}
|
|
1822
|
+
function renameKeys4(value, mapping) {
|
|
1823
|
+
const out = {};
|
|
1824
|
+
for (const [key, item] of Object.entries(value)) {
|
|
1825
|
+
const target = mapping[key] || key;
|
|
1826
|
+
if (out[target] === void 0) out[target] = item;
|
|
1827
|
+
}
|
|
1828
|
+
return out;
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1831
|
+
// src/web-search.ts
|
|
1832
|
+
function createWebSearchNamespace(transport) {
|
|
1833
|
+
const api = {
|
|
1834
|
+
run: (input) => transport.webAgentJson("POST", "/web_search/runs", input.token, {
|
|
1835
|
+
body: normalizeWebSearchRunInput(omit6(input, "token")),
|
|
1836
|
+
requiredScopes: ["webagent.web_search:manage"]
|
|
1837
|
+
}),
|
|
1838
|
+
get: (input) => transport.webAgentJson(
|
|
1839
|
+
"GET",
|
|
1840
|
+
`/web_search/runs/${encodeURIComponent(input.runId)}`,
|
|
1841
|
+
input.token,
|
|
1842
|
+
{
|
|
1843
|
+
requiredScopes: ["webagent.web_search:read"]
|
|
1844
|
+
}
|
|
1845
|
+
),
|
|
1846
|
+
events: (input) => transport.webAgentSSE(
|
|
1847
|
+
`/web_search/runs/${encodeURIComponent(input.runId)}/events`,
|
|
1848
|
+
input.token,
|
|
1849
|
+
{
|
|
1850
|
+
lastEventId: input.lastEventId,
|
|
1851
|
+
requiredScopes: ["webagent.web_search:read"],
|
|
1852
|
+
signal: input.signal,
|
|
1853
|
+
onReconnect: input.onReconnect
|
|
1854
|
+
}
|
|
1855
|
+
),
|
|
1856
|
+
cancel: (input) => transport.webAgentJson(
|
|
1857
|
+
"POST",
|
|
1858
|
+
`/web_search/runs/${encodeURIComponent(input.runId)}/cancel`,
|
|
1859
|
+
input.token,
|
|
1860
|
+
{
|
|
1861
|
+
body: omit6(input, "token", "runId"),
|
|
1862
|
+
requiredScopes: ["webagent.web_search:manage"]
|
|
1863
|
+
}
|
|
1864
|
+
)
|
|
1865
|
+
};
|
|
1866
|
+
function buildOps(token, runId) {
|
|
1867
|
+
return {
|
|
1868
|
+
getDetail: async () => asRecord4((await api.get({ token, runId })).data),
|
|
1869
|
+
streamEvents: (opts) => api.events({
|
|
1870
|
+
token,
|
|
1871
|
+
runId,
|
|
1872
|
+
lastEventId: opts.lastEventId,
|
|
1873
|
+
signal: opts.signal,
|
|
1874
|
+
onReconnect: opts.onReconnect
|
|
1875
|
+
}),
|
|
1876
|
+
postAction: async () => {
|
|
1877
|
+
throw new QoniError(
|
|
1878
|
+
"webSearch runs never request interaction \u2014 there is no action to invoke",
|
|
1879
|
+
{ code: "validation.failed" }
|
|
1880
|
+
);
|
|
1881
|
+
},
|
|
1882
|
+
cancel: async (reason) => asRecord4(
|
|
1883
|
+
(await api.cancel({
|
|
1884
|
+
token,
|
|
1885
|
+
runId,
|
|
1886
|
+
...reason === void 0 ? {} : { reason }
|
|
1887
|
+
})).data
|
|
1888
|
+
)
|
|
1889
|
+
};
|
|
1890
|
+
}
|
|
1891
|
+
return {
|
|
1892
|
+
/** Start a search run. Returns a handle — see `RunHandle` for the run lifecycle. */
|
|
1893
|
+
run: async (input) => {
|
|
1894
|
+
const { token, prompt, capture, limits, session, ...rest } = input;
|
|
1895
|
+
if (session) {
|
|
1896
|
+
throw new QoniValidationError(
|
|
1897
|
+
"webSearch runs do not execute in reusable sessions \u2014 drop the session option"
|
|
1898
|
+
);
|
|
1899
|
+
}
|
|
1900
|
+
if (limits) {
|
|
1901
|
+
throw new QoniValidationError(
|
|
1902
|
+
"webSearch does not support limits yet \u2014 drop the limits option"
|
|
1903
|
+
);
|
|
1904
|
+
}
|
|
1905
|
+
const resolvedQueries = Array.isArray(prompt) ? prompt : prompt ? [prompt] : void 0;
|
|
1906
|
+
if (!resolvedQueries || resolvedQueries.length === 0) {
|
|
1907
|
+
throw new QoniValidationError("webSearch.run requires a prompt");
|
|
1908
|
+
}
|
|
1909
|
+
const run = await api.run({
|
|
1910
|
+
token,
|
|
1911
|
+
queries: resolvedQueries,
|
|
1912
|
+
...rest
|
|
1913
|
+
});
|
|
1914
|
+
const runId = run.data.run_id || run.data.id;
|
|
1915
|
+
if (!runId) {
|
|
1916
|
+
throw new QoniValidationError("webSearch run create did not return a run id");
|
|
1917
|
+
}
|
|
1918
|
+
return new RunHandle(buildOps(token, runId), { id: runId, capture });
|
|
1919
|
+
},
|
|
1920
|
+
/** Reconnect to an existing run. Verifies the run exists before returning. */
|
|
1921
|
+
attach: async (runId, opts) => {
|
|
1922
|
+
const handle = new RunHandle(buildOps(opts.token, runId), {
|
|
1923
|
+
id: runId,
|
|
1924
|
+
capture: opts.capture
|
|
1925
|
+
});
|
|
1926
|
+
await handle.status();
|
|
1927
|
+
return handle;
|
|
1928
|
+
},
|
|
1929
|
+
api
|
|
1930
|
+
};
|
|
1931
|
+
}
|
|
1932
|
+
function normalizeWebSearchRunInput(input) {
|
|
1933
|
+
const body = renameKeys5(input, {
|
|
1934
|
+
maxResultsPerQuery: "max_results_per_query",
|
|
1935
|
+
siteWhitelist: "site_whitelist",
|
|
1936
|
+
siteBlacklist: "site_blacklist"
|
|
1937
|
+
});
|
|
1938
|
+
return body;
|
|
1939
|
+
}
|
|
1940
|
+
function asRecord4(value) {
|
|
1941
|
+
return isJsonObject(value) ? value : {};
|
|
1942
|
+
}
|
|
1943
|
+
function omit6(value, ...keys) {
|
|
1944
|
+
const skipped = new Set(keys);
|
|
1945
|
+
const out = {};
|
|
1946
|
+
for (const [key, item] of Object.entries(value)) {
|
|
1947
|
+
if (!skipped.has(key) && item !== void 0) out[key] = item;
|
|
1948
|
+
}
|
|
1949
|
+
return out;
|
|
1950
|
+
}
|
|
1951
|
+
function renameKeys5(value, mapping) {
|
|
1952
|
+
const out = {};
|
|
1953
|
+
for (const [key, item] of Object.entries(value)) {
|
|
1954
|
+
const target = mapping[key] || key;
|
|
1955
|
+
if (out[target] === void 0) out[target] = item;
|
|
1956
|
+
}
|
|
1957
|
+
return out;
|
|
1958
|
+
}
|
|
1959
|
+
|
|
1960
|
+
// src/client.ts
|
|
1961
|
+
var DEFAULT_QONI_HOST = "https://anima.eazo.ai/dashboard";
|
|
1962
|
+
var QoniAgentKit = class {
|
|
1963
|
+
constructor(options) {
|
|
1964
|
+
this.options = options;
|
|
1965
|
+
const accessKey = options.accessKey ?? options.accessKeyId;
|
|
1966
|
+
const secretKey = options.secretKey ?? options.accessKeySecret;
|
|
1967
|
+
const host = options.host ?? options.qoniBaseUrl ?? options.genauthBaseUrl ?? DEFAULT_QONI_HOST;
|
|
1968
|
+
if (!accessKey || !secretKey) {
|
|
1969
|
+
throw new Error("Qoni requires accessKey and secretKey");
|
|
1970
|
+
}
|
|
1971
|
+
this.accessKey = accessKey ?? "";
|
|
1972
|
+
this.secretKey = secretKey ?? "";
|
|
1973
|
+
this.host = normalizeBaseUrl(host);
|
|
1974
|
+
this.fetchImpl = options.fetch ?? fetch;
|
|
1975
|
+
this.timeoutMs = options.timeoutMs ?? 3e4;
|
|
1976
|
+
this.sseMaxRetries = options.sseMaxRetries ?? 5;
|
|
1977
|
+
const transport = {
|
|
1978
|
+
qoniJson: (...args) => this.qoniJson(...args),
|
|
1979
|
+
genauthJson: (...args) => this.genauthJson(...args),
|
|
1980
|
+
gumemJson: (...args) => this.gumemJson(...args),
|
|
1981
|
+
gumemSSE: (...args) => this.gumemSSE(...args),
|
|
1982
|
+
webAgentJson: (...args) => this.webAgentJson(...args),
|
|
1983
|
+
webAgentBytes: (...args) => this.webAgentBytes(...args),
|
|
1984
|
+
webAgentSSE: (...args) => this.webAgentSSE(...args)
|
|
1985
|
+
};
|
|
1986
|
+
this.genauth = createGenAuthNamespace(transport, {
|
|
1987
|
+
adminTokenFor: () => this.genauthAdminTokenFor()
|
|
1988
|
+
});
|
|
1989
|
+
this.qoni = createQoniNamespace(
|
|
1990
|
+
transport,
|
|
1991
|
+
this.delegateToken.bind(this),
|
|
1992
|
+
(input) => this.completeDelegateToken(input)
|
|
1993
|
+
);
|
|
1994
|
+
this.gumem = createGumemNamespace(transport);
|
|
1995
|
+
this.webSearch = createWebSearchNamespace(transport);
|
|
1996
|
+
this.doAnything = createDoAnythingNamespace(transport);
|
|
1997
|
+
this.track = createTrackNamespace(transport);
|
|
1998
|
+
this.deepResearch = createDeepResearchNamespace(transport);
|
|
1999
|
+
}
|
|
2000
|
+
options;
|
|
2001
|
+
fetchImpl;
|
|
2002
|
+
timeoutMs;
|
|
2003
|
+
sseMaxRetries;
|
|
2004
|
+
accessKey;
|
|
2005
|
+
secretKey;
|
|
2006
|
+
host;
|
|
2007
|
+
runtimeConfigPromise;
|
|
2008
|
+
productTokenCache = /* @__PURE__ */ new Map();
|
|
2009
|
+
genAuthAdminTokenCache;
|
|
2010
|
+
gumem;
|
|
2011
|
+
genauth;
|
|
2012
|
+
qoni;
|
|
2013
|
+
webSearch;
|
|
2014
|
+
doAnything;
|
|
2015
|
+
track;
|
|
2016
|
+
deepResearch;
|
|
2017
|
+
delegateToken(input) {
|
|
2018
|
+
let body;
|
|
2019
|
+
try {
|
|
2020
|
+
body = normalizeDelegateTokenInput(input);
|
|
2021
|
+
} catch (err) {
|
|
2022
|
+
return Promise.reject(err);
|
|
2023
|
+
}
|
|
2024
|
+
return this.signedPost(
|
|
2025
|
+
"/api/v3/eak/delegations",
|
|
2026
|
+
body
|
|
2027
|
+
).then(normalizeDelegateAgentTokenResponse);
|
|
2028
|
+
}
|
|
2029
|
+
/** @deprecated Use delegateToken. */
|
|
2030
|
+
delegateAgent(input) {
|
|
2031
|
+
return this.delegateToken(input);
|
|
2032
|
+
}
|
|
2033
|
+
completeDelegateToken(input) {
|
|
2034
|
+
return this.signedPost(
|
|
2035
|
+
"/api/v3/eak/delegations/complete",
|
|
2036
|
+
input
|
|
2037
|
+
).then(
|
|
2038
|
+
normalizeDelegateAgentTokenResponse
|
|
2039
|
+
);
|
|
2040
|
+
}
|
|
2041
|
+
/** @deprecated Use completeDelegateToken. */
|
|
2042
|
+
completeDelegateAgent(input) {
|
|
2043
|
+
return this.completeDelegateToken(input);
|
|
2044
|
+
}
|
|
2045
|
+
currentUser(input) {
|
|
2046
|
+
return this.genauth.userInfo(input);
|
|
2047
|
+
}
|
|
2048
|
+
/**
|
|
2049
|
+
* Resolve a real GenAuth user id from the userpool bound to this AK/SK, by
|
|
2050
|
+
* listing users and returning the first one. Convenience for demos / smoke
|
|
2051
|
+
* tests so `delegateToken` has a valid `userId` without hardcoding one. In
|
|
2052
|
+
* application code, resolve the actual logged-in user via `currentUser`
|
|
2053
|
+
* instead of picking an arbitrary userpool member.
|
|
2054
|
+
*/
|
|
2055
|
+
async resolveAnyBoundUser() {
|
|
2056
|
+
const res = await this.genauth.users.list({ page: 1, limit: 1 });
|
|
2057
|
+
const userId = firstBoundUserId(res.data);
|
|
2058
|
+
if (!userId) {
|
|
2059
|
+
throw new QoniError(
|
|
2060
|
+
"resolveAnyBoundUser: the credential-bound GenAuth userpool returned no users. Create a user in the userpool, or pass a real userId to delegateToken.",
|
|
2061
|
+
{ code: "eak.genauth.no_bound_users" }
|
|
2062
|
+
);
|
|
2063
|
+
}
|
|
2064
|
+
return userId;
|
|
2065
|
+
}
|
|
2066
|
+
request(input) {
|
|
2067
|
+
return this.unstableRequest(input);
|
|
2068
|
+
}
|
|
2069
|
+
unstableRequest(input) {
|
|
2070
|
+
if (!input.token) {
|
|
2071
|
+
const pathname = normalizePath(input.path);
|
|
2072
|
+
const signaturePayload = input.method === "GET" ? input.query || {} : input.body || {};
|
|
2073
|
+
const signedHeaders = buildSignedHeaders(
|
|
2074
|
+
{
|
|
2075
|
+
accessKey: this.accessKey,
|
|
2076
|
+
secretKey: this.secretKey
|
|
2077
|
+
},
|
|
2078
|
+
input.method,
|
|
2079
|
+
pathname,
|
|
2080
|
+
signaturePayload
|
|
2081
|
+
);
|
|
2082
|
+
return this.baseUrlFor("qoni").then((baseUrl) => this.jsonRequest(baseUrl, pathname, {
|
|
2083
|
+
method: input.method,
|
|
2084
|
+
query: input.query,
|
|
2085
|
+
body: input.body,
|
|
2086
|
+
headers: {
|
|
2087
|
+
...nonSignatureHeaders(input.headers),
|
|
2088
|
+
...signedHeaders
|
|
2089
|
+
},
|
|
2090
|
+
service: "qoni",
|
|
2091
|
+
signed: true
|
|
2092
|
+
}));
|
|
2093
|
+
}
|
|
2094
|
+
return this.baseUrlFor("qoni").then((baseUrl) => this.jsonRequest(baseUrl, input.path, {
|
|
2095
|
+
method: input.method,
|
|
2096
|
+
token: input.token,
|
|
2097
|
+
query: input.query,
|
|
2098
|
+
body: input.body,
|
|
2099
|
+
headers: input.headers,
|
|
2100
|
+
service: "qoni"
|
|
2101
|
+
}));
|
|
2102
|
+
}
|
|
2103
|
+
qoniJson(method, path, token, payload = {}) {
|
|
2104
|
+
return this.baseUrlFor("qoni").then((baseUrl) => this.jsonRequest(baseUrl, path, {
|
|
2105
|
+
...payload,
|
|
2106
|
+
method,
|
|
2107
|
+
token,
|
|
2108
|
+
service: "qoni"
|
|
2109
|
+
}));
|
|
2110
|
+
}
|
|
2111
|
+
genauthJson(method, path, token, payload = {}) {
|
|
2112
|
+
return this.baseUrlFor("genauth").then((baseUrl) => this.jsonRequest(baseUrl, path, {
|
|
2113
|
+
...payload,
|
|
2114
|
+
method,
|
|
2115
|
+
token,
|
|
2116
|
+
service: "genauth"
|
|
2117
|
+
}));
|
|
2118
|
+
}
|
|
2119
|
+
async gumemJson(method, path, token, payload = {}) {
|
|
2120
|
+
const runtimeToken = await this.productTokenFor("gumem", token, payload.requiredScopes);
|
|
2121
|
+
const baseUrl = await this.baseUrlFor("gumem");
|
|
2122
|
+
return this.jsonRequest(baseUrl, path, {
|
|
2123
|
+
...payload,
|
|
2124
|
+
method,
|
|
2125
|
+
token: runtimeToken,
|
|
2126
|
+
service: "gumem"
|
|
2127
|
+
});
|
|
2128
|
+
}
|
|
2129
|
+
async *gumemSSE(path, token, payload = {}) {
|
|
2130
|
+
const runtimeToken = await this.productTokenFor("gumem", token, payload.requiredScopes);
|
|
2131
|
+
const baseUrl = await this.baseUrlFor("gumem");
|
|
2132
|
+
for await (const event of this.sseRequest(baseUrl, path, {
|
|
2133
|
+
...payload,
|
|
2134
|
+
token: runtimeToken,
|
|
2135
|
+
service: "gumem"
|
|
2136
|
+
})) {
|
|
2137
|
+
yield event;
|
|
2138
|
+
}
|
|
2139
|
+
}
|
|
2140
|
+
async webAgentJson(method, path, token, payload = {}) {
|
|
2141
|
+
const runtimeToken = await this.productTokenFor("webagent", token, payload.requiredScopes);
|
|
2142
|
+
const baseUrl = await this.baseUrlFor("webagent");
|
|
2143
|
+
return this.jsonRequest(
|
|
2144
|
+
baseUrl,
|
|
2145
|
+
this.webAgentPath(runtimeToken, path),
|
|
2146
|
+
{ ...payload, method, token: runtimeToken, service: "webagent" }
|
|
2147
|
+
);
|
|
2148
|
+
}
|
|
2149
|
+
// Binary GET against the WebAgent API — artifact content downloads. JSON
|
|
2150
|
+
// error payloads still map to typed QoniErrors.
|
|
2151
|
+
async webAgentBytes(path, token, payload = {}) {
|
|
2152
|
+
const runtimeToken = await this.productTokenFor("webagent", token, payload.requiredScopes);
|
|
2153
|
+
const baseUrl = await this.baseUrlFor("webagent");
|
|
2154
|
+
const response = await this.fetchWithTimeout(
|
|
2155
|
+
urlWithBasePath(baseUrl, this.webAgentPath(runtimeToken, path), payload.query),
|
|
2156
|
+
{
|
|
2157
|
+
method: "GET",
|
|
2158
|
+
headers: {
|
|
2159
|
+
accept: "*/*",
|
|
2160
|
+
authorization: `Bearer ${runtimeToken}`,
|
|
2161
|
+
...payload.headers || {}
|
|
2162
|
+
}
|
|
2163
|
+
}
|
|
2164
|
+
);
|
|
2165
|
+
if (!response.ok) {
|
|
2166
|
+
throw errorFromPayload(
|
|
2167
|
+
response.status,
|
|
2168
|
+
await readJson(response),
|
|
2169
|
+
`Qoni request failed with HTTP ${response.status}`
|
|
2170
|
+
);
|
|
2171
|
+
}
|
|
2172
|
+
const buffer = await response.arrayBuffer();
|
|
2173
|
+
return {
|
|
2174
|
+
bytes: new Uint8Array(buffer),
|
|
2175
|
+
mime: response.headers.get("content-type") || "application/octet-stream"
|
|
2176
|
+
};
|
|
2177
|
+
}
|
|
2178
|
+
async *webAgentSSE(path, token, payload = {}) {
|
|
2179
|
+
const runtimeToken = await this.productTokenFor("webagent", token, payload.requiredScopes);
|
|
2180
|
+
const baseUrl = await this.baseUrlFor("webagent");
|
|
2181
|
+
for await (const event of this.sseRequest(
|
|
2182
|
+
baseUrl,
|
|
2183
|
+
this.webAgentPath(runtimeToken, path),
|
|
2184
|
+
{
|
|
2185
|
+
...payload,
|
|
2186
|
+
token: runtimeToken,
|
|
2187
|
+
service: "webagent",
|
|
2188
|
+
// Re-resolve the product token before each reconnect (refreshes if the
|
|
2189
|
+
// cached one expired); the tenant — and therefore the path — is stable.
|
|
2190
|
+
refreshToken: () => this.productTokenFor("webagent", token, payload.requiredScopes)
|
|
2191
|
+
}
|
|
2192
|
+
)) {
|
|
2193
|
+
yield normalizeWebAgentEvent(event);
|
|
2194
|
+
}
|
|
2195
|
+
}
|
|
2196
|
+
async signedPost(pathname, body) {
|
|
2197
|
+
const payload = toRecord(body);
|
|
2198
|
+
const headers = buildSignedHeaders(
|
|
2199
|
+
{
|
|
2200
|
+
accessKey: this.accessKey,
|
|
2201
|
+
secretKey: this.secretKey
|
|
2202
|
+
},
|
|
2203
|
+
"POST",
|
|
2204
|
+
pathname,
|
|
2205
|
+
payload
|
|
2206
|
+
);
|
|
2207
|
+
return this.jsonRequest(await this.baseUrlFor("qoni"), pathname, {
|
|
2208
|
+
method: "POST",
|
|
2209
|
+
body: payload,
|
|
2210
|
+
headers,
|
|
2211
|
+
service: "qoni",
|
|
2212
|
+
signed: true
|
|
2213
|
+
});
|
|
2214
|
+
}
|
|
2215
|
+
async jsonRequest(baseUrl, pathname, options) {
|
|
2216
|
+
const response = await this.fetchWithTimeout(
|
|
2217
|
+
urlWithBasePath(baseUrl, pathname, options.query),
|
|
2218
|
+
{
|
|
2219
|
+
method: options.method,
|
|
2220
|
+
headers: requestHeaders(options),
|
|
2221
|
+
body: requestBody(options.body)
|
|
2222
|
+
}
|
|
2223
|
+
);
|
|
2224
|
+
const payload = await readJson(response);
|
|
2225
|
+
if (!response.ok) {
|
|
2226
|
+
throw errorFromPayload(
|
|
2227
|
+
response.status,
|
|
2228
|
+
payload,
|
|
2229
|
+
`Qoni request failed with HTTP ${response.status}`
|
|
2230
|
+
);
|
|
2231
|
+
}
|
|
2232
|
+
const envelopeStatus = errorEnvelopeStatus(payload);
|
|
2233
|
+
if (envelopeStatus !== void 0) {
|
|
2234
|
+
throw errorFromPayload(
|
|
2235
|
+
envelopeStatus,
|
|
2236
|
+
payload,
|
|
2237
|
+
`Qoni request failed with statusCode ${envelopeStatus}`
|
|
2238
|
+
);
|
|
2239
|
+
}
|
|
2240
|
+
return normalizeResponse(payload, options.service);
|
|
2241
|
+
}
|
|
2242
|
+
// Reconnecting wrapper: yields events from a fresh connection, and on a
|
|
2243
|
+
// dropped stream (network/5xx, not a clean close or a user abort) reconnects
|
|
2244
|
+
// with `last-event-id` so the backend replays from where we left off. Each
|
|
2245
|
+
// attempt re-issues the GET with the same Bearer product token (no per-call
|
|
2246
|
+
// AK/SK signature, so no gateway nonce replay). Caps at `sseMaxRetries`.
|
|
2247
|
+
async *sseRequest(baseUrl, pathname, options) {
|
|
2248
|
+
let lastEventId = options.lastEventId;
|
|
2249
|
+
let attempt = 0;
|
|
2250
|
+
for (; ; ) {
|
|
2251
|
+
const token = options.refreshToken ? await options.refreshToken() : options.token;
|
|
2252
|
+
try {
|
|
2253
|
+
for await (const event of this.sseConnectOnce(baseUrl, pathname, {
|
|
2254
|
+
...options,
|
|
2255
|
+
token,
|
|
2256
|
+
lastEventId
|
|
2257
|
+
})) {
|
|
2258
|
+
if (event.id) lastEventId = event.id;
|
|
2259
|
+
attempt = 0;
|
|
2260
|
+
yield event;
|
|
2261
|
+
}
|
|
2262
|
+
return;
|
|
2263
|
+
} catch (err) {
|
|
2264
|
+
if (options.signal?.aborted) throw err;
|
|
2265
|
+
const canRetry = isRetryableStreamError(err) || !!options.refreshToken && isAuthError(err);
|
|
2266
|
+
if (attempt >= this.sseMaxRetries || !canRetry) throw err;
|
|
2267
|
+
attempt++;
|
|
2268
|
+
options.onReconnect?.({ attempt, lastEventId, error: err });
|
|
2269
|
+
await sleepWithAbort(sseBackoffMs(attempt), options.signal);
|
|
2270
|
+
}
|
|
2271
|
+
}
|
|
2272
|
+
}
|
|
2273
|
+
async *sseConnectOnce(baseUrl, pathname, options) {
|
|
2274
|
+
const response = await this.fetchWithTimeout(
|
|
2275
|
+
urlWithBasePath(baseUrl, pathname, options.query),
|
|
2276
|
+
{
|
|
2277
|
+
method: "GET",
|
|
2278
|
+
headers: {
|
|
2279
|
+
accept: "text/event-stream",
|
|
2280
|
+
authorization: `Bearer ${options.token}`,
|
|
2281
|
+
...options.headers || {},
|
|
2282
|
+
...options.lastEventId ? { "last-event-id": options.lastEventId } : {}
|
|
2283
|
+
}
|
|
2284
|
+
},
|
|
2285
|
+
options.signal,
|
|
2286
|
+
{ streamBody: true }
|
|
2287
|
+
);
|
|
2288
|
+
if (!response.ok) {
|
|
2289
|
+
throw errorFromPayload(
|
|
2290
|
+
response.status,
|
|
2291
|
+
await readJson(response),
|
|
2292
|
+
`Qoni stream failed with HTTP ${response.status}`
|
|
2293
|
+
);
|
|
2294
|
+
}
|
|
2295
|
+
if (!response.body) {
|
|
2296
|
+
throw errorFromPayload(502, void 0, "Qoni stream response has no body");
|
|
2297
|
+
}
|
|
2298
|
+
const reader = response.body.getReader();
|
|
2299
|
+
const decoder = new TextDecoder();
|
|
2300
|
+
let buffer = "";
|
|
2301
|
+
try {
|
|
2302
|
+
while (true) {
|
|
2303
|
+
if (options.signal?.aborted) {
|
|
2304
|
+
await reader.cancel().catch(() => void 0);
|
|
2305
|
+
throw options.signal.reason || new DOMException("Aborted", "AbortError");
|
|
2306
|
+
}
|
|
2307
|
+
const { done, value } = await reader.read();
|
|
2308
|
+
if (done) break;
|
|
2309
|
+
buffer += decoder.decode(value, { stream: true });
|
|
2310
|
+
const chunks = buffer.split(/\r?\n\r?\n/);
|
|
2311
|
+
buffer = chunks.pop() || "";
|
|
2312
|
+
for (const chunk of chunks) {
|
|
2313
|
+
const event = parseSSEEvent(chunk);
|
|
2314
|
+
if (event) yield event;
|
|
2315
|
+
}
|
|
2316
|
+
}
|
|
2317
|
+
const finalEvent = parseSSEEvent(buffer);
|
|
2318
|
+
if (finalEvent) yield finalEvent;
|
|
2319
|
+
} finally {
|
|
2320
|
+
reader.releaseLock();
|
|
2321
|
+
}
|
|
2322
|
+
}
|
|
2323
|
+
async fetchWithTimeout(input, init, signal, opts) {
|
|
2324
|
+
if (signal?.aborted) throw signal.reason || new DOMException("Aborted", "AbortError");
|
|
2325
|
+
const controller = new AbortController();
|
|
2326
|
+
const resolved = resolveLocalGenauthRequest(input);
|
|
2327
|
+
const requestInit = resolved.hostHeader ? {
|
|
2328
|
+
...init,
|
|
2329
|
+
headers: withHostHeader(init.headers, resolved.hostHeader)
|
|
2330
|
+
} : init;
|
|
2331
|
+
let didTimeout = false;
|
|
2332
|
+
const abortFromSignal = () => controller.abort(signal?.reason);
|
|
2333
|
+
signal?.addEventListener("abort", abortFromSignal, { once: true });
|
|
2334
|
+
const timeout = setTimeout(() => {
|
|
2335
|
+
didTimeout = true;
|
|
2336
|
+
controller.abort();
|
|
2337
|
+
}, this.timeoutMs);
|
|
2338
|
+
try {
|
|
2339
|
+
return await this.fetchImpl(resolved.url, { ...requestInit, signal: controller.signal });
|
|
2340
|
+
} catch (error) {
|
|
2341
|
+
if (isAbortError(error) && didTimeout || isConnectTimeoutError(error)) {
|
|
2342
|
+
throw timeoutError(error);
|
|
2343
|
+
}
|
|
2344
|
+
if (isFetchNetworkError(error)) throw networkError(error);
|
|
2345
|
+
throw error;
|
|
2346
|
+
} finally {
|
|
2347
|
+
clearTimeout(timeout);
|
|
2348
|
+
if (!opts?.streamBody) signal?.removeEventListener("abort", abortFromSignal);
|
|
2349
|
+
}
|
|
2350
|
+
}
|
|
2351
|
+
async baseUrlFor(service) {
|
|
2352
|
+
const explicit = this.explicitBaseUrlFor(service);
|
|
2353
|
+
if (explicit) return explicit;
|
|
2354
|
+
const runtime = await this.runtimeConfig();
|
|
2355
|
+
if (service === "qoni") return this.host;
|
|
2356
|
+
const hostOrigin = originBaseUrl(this.host);
|
|
2357
|
+
if (service === "genauth") return runtime.genauthBaseUrl ?? hostOrigin;
|
|
2358
|
+
if (service === "gumem") return runtime.gumemBaseUrl ?? hostOrigin;
|
|
2359
|
+
return runtime.webAgentBaseUrl ?? hostOrigin;
|
|
2360
|
+
}
|
|
2361
|
+
explicitBaseUrlFor(service) {
|
|
2362
|
+
if (service === "qoni") {
|
|
2363
|
+
const value = this.options.qoniBaseUrl ?? this.options.genauthBaseUrl;
|
|
2364
|
+
return value ? normalizeBaseUrl(value) : void 0;
|
|
2365
|
+
}
|
|
2366
|
+
if (service === "genauth" && this.options.genauthBaseUrl) {
|
|
2367
|
+
return normalizeBaseUrl(this.options.genauthBaseUrl);
|
|
2368
|
+
}
|
|
2369
|
+
if (service === "genauth") {
|
|
2370
|
+
const value = serviceHostOption(this.options, "genauth");
|
|
2371
|
+
return value ? normalizeBaseUrl(value) : void 0;
|
|
2372
|
+
}
|
|
2373
|
+
if (service === "gumem" && this.options.gumemBaseUrl) {
|
|
2374
|
+
return normalizeBaseUrl(this.options.gumemBaseUrl);
|
|
2375
|
+
}
|
|
2376
|
+
if (service === "gumem") {
|
|
2377
|
+
const value = serviceHostOption(this.options, "gumem");
|
|
2378
|
+
return value ? normalizeBaseUrl(value) : void 0;
|
|
2379
|
+
}
|
|
2380
|
+
if (service === "webagent" && this.options.webAgentBaseUrl) {
|
|
2381
|
+
return normalizeBaseUrl(this.options.webAgentBaseUrl);
|
|
2382
|
+
}
|
|
2383
|
+
if (service === "webagent") {
|
|
2384
|
+
const value = serviceHostOption(this.options, "webagent");
|
|
2385
|
+
return value ? normalizeBaseUrl(value) : void 0;
|
|
2386
|
+
}
|
|
2387
|
+
return void 0;
|
|
2388
|
+
}
|
|
2389
|
+
runtimeConfig() {
|
|
2390
|
+
this.runtimeConfigPromise ??= this.fetchRuntimeConfig();
|
|
2391
|
+
return this.runtimeConfigPromise;
|
|
2392
|
+
}
|
|
2393
|
+
async fetchRuntimeConfig() {
|
|
2394
|
+
const pathname = "/api/v3/eak/runtime-config";
|
|
2395
|
+
const headers = buildSignedHeaders(
|
|
2396
|
+
{
|
|
2397
|
+
accessKey: this.accessKey,
|
|
2398
|
+
secretKey: this.secretKey
|
|
2399
|
+
},
|
|
2400
|
+
"GET",
|
|
2401
|
+
pathname,
|
|
2402
|
+
{}
|
|
2403
|
+
);
|
|
2404
|
+
const response = await this.fetchWithTimeout(
|
|
2405
|
+
urlWithBasePath(this.host, pathname),
|
|
2406
|
+
{
|
|
2407
|
+
method: "GET",
|
|
2408
|
+
headers: {
|
|
2409
|
+
accept: "application/json",
|
|
2410
|
+
...headers
|
|
2411
|
+
}
|
|
2412
|
+
}
|
|
2413
|
+
);
|
|
2414
|
+
const payload = await readJson(response);
|
|
2415
|
+
if (!response.ok) {
|
|
2416
|
+
throw errorFromPayload(
|
|
2417
|
+
response.status,
|
|
2418
|
+
payload,
|
|
2419
|
+
`Qoni runtime config request failed with HTTP ${response.status}`
|
|
2420
|
+
);
|
|
2421
|
+
}
|
|
2422
|
+
return normalizeRuntimeConfig(payload);
|
|
2423
|
+
}
|
|
2424
|
+
webAgentPath(token, path) {
|
|
2425
|
+
const normalized = normalizePath(path);
|
|
2426
|
+
if (normalized.startsWith("/api/v1/projects/")) return normalized;
|
|
2427
|
+
const tenantId = requiredWebAgentTenantId(token);
|
|
2428
|
+
return `/api/v1/projects/${encodeURIComponent(tenantId)}${normalized}`;
|
|
2429
|
+
}
|
|
2430
|
+
async productTokenFor(service, token, requiredScopes) {
|
|
2431
|
+
const runtimeToken = requiredRuntimeToken(token);
|
|
2432
|
+
if (!isQoniDelegationToken(runtimeToken)) return runtimeToken;
|
|
2433
|
+
const scopes = [...requiredScopes || []].sort();
|
|
2434
|
+
const cacheKey = [service, runtimeToken, scopes.join(" ")].join(":");
|
|
2435
|
+
const cached = this.productTokenCache.get(cacheKey);
|
|
2436
|
+
if (cached && cached.expiresAt > Date.now() + 3e4) return cached.token;
|
|
2437
|
+
const response = await this.signedPost("/api/v3/eak/token-exchange", {
|
|
2438
|
+
subjectToken: runtimeToken,
|
|
2439
|
+
resource: service === "gumem" ? "gumem" : "webagent",
|
|
2440
|
+
scopes
|
|
2441
|
+
});
|
|
2442
|
+
const payload = response.data;
|
|
2443
|
+
const accessToken = readAccessToken(payload);
|
|
2444
|
+
if (!accessToken) {
|
|
2445
|
+
throw errorFromPayload(
|
|
2446
|
+
502,
|
|
2447
|
+
payload,
|
|
2448
|
+
"Qoni product token exchange response did not include an access token"
|
|
2449
|
+
);
|
|
2450
|
+
}
|
|
2451
|
+
this.productTokenCache.set(cacheKey, {
|
|
2452
|
+
token: accessToken,
|
|
2453
|
+
expiresAt: Date.now() + Math.max(0, readExpiresIn(payload) ?? 3600) * 1e3
|
|
2454
|
+
});
|
|
2455
|
+
return accessToken;
|
|
2456
|
+
}
|
|
2457
|
+
async genauthAdminTokenFor() {
|
|
2458
|
+
const cached = this.genAuthAdminTokenCache;
|
|
2459
|
+
if (cached && cached.expiresAt > Date.now() + 3e4) {
|
|
2460
|
+
return { accessToken: cached.accessToken, userPoolId: cached.userPoolId };
|
|
2461
|
+
}
|
|
2462
|
+
const response = await this.signedPost("/api/v3/eak/genauth/admin-token", {});
|
|
2463
|
+
const payload = response.data;
|
|
2464
|
+
const accessToken = readAccessToken(payload);
|
|
2465
|
+
const userPoolId = readString(payload, "userPoolId") ?? readString(payload, "userpoolId") ?? readString(payload, "user_pool_id");
|
|
2466
|
+
if (!accessToken || !userPoolId) {
|
|
2467
|
+
throw errorFromPayload(
|
|
2468
|
+
502,
|
|
2469
|
+
payload,
|
|
2470
|
+
"Qoni GenAuth admin-token response did not include accessToken and userPoolId"
|
|
2471
|
+
);
|
|
2472
|
+
}
|
|
2473
|
+
this.genAuthAdminTokenCache = {
|
|
2474
|
+
accessToken,
|
|
2475
|
+
userPoolId,
|
|
2476
|
+
expiresAt: Date.now() + Math.max(0, readExpiresIn(payload) ?? 3600) * 1e3
|
|
2477
|
+
};
|
|
2478
|
+
return { accessToken, userPoolId };
|
|
2479
|
+
}
|
|
2480
|
+
};
|
|
2481
|
+
function requestHeaders(options) {
|
|
2482
|
+
const headers = {
|
|
2483
|
+
accept: "application/json",
|
|
2484
|
+
...options.headers || {}
|
|
2485
|
+
};
|
|
2486
|
+
if (options.token) headers.authorization = `Bearer ${options.token}`;
|
|
2487
|
+
if (options.body !== void 0 && !isBodyInit(options.body) && !headers["content-type"]) {
|
|
2488
|
+
headers["content-type"] = "application/json";
|
|
2489
|
+
}
|
|
2490
|
+
return headers;
|
|
2491
|
+
}
|
|
2492
|
+
function nonSignatureHeaders(headers) {
|
|
2493
|
+
return Object.fromEntries(
|
|
2494
|
+
Object.entries(headers || {}).filter(([key]) => {
|
|
2495
|
+
const normalized = key.toLowerCase();
|
|
2496
|
+
return normalized !== "authorization" && normalized !== "content-type" && normalized !== "date" && !normalized.startsWith("x-authing-");
|
|
2497
|
+
})
|
|
2498
|
+
);
|
|
2499
|
+
}
|
|
2500
|
+
function requestBody(body) {
|
|
2501
|
+
if (body === void 0) return void 0;
|
|
2502
|
+
if (isBodyInit(body)) return body;
|
|
2503
|
+
return JSON.stringify(body);
|
|
2504
|
+
}
|
|
2505
|
+
function isBodyInit(value) {
|
|
2506
|
+
return typeof value === "string" || value instanceof ArrayBuffer || value instanceof Blob || value instanceof FormData || value instanceof URLSearchParams;
|
|
2507
|
+
}
|
|
2508
|
+
async function readJson(response) {
|
|
2509
|
+
const contentType = response.headers.get("content-type")?.toLowerCase() || "";
|
|
2510
|
+
if (contentType.includes("ndjson") || contentType.includes("jsonl")) {
|
|
2511
|
+
const body = await response.text();
|
|
2512
|
+
if (!body.trim()) return [];
|
|
2513
|
+
try {
|
|
2514
|
+
return body.split(/\r?\n/).filter((line) => line.trim().length > 0).map((line) => JSON.parse(line));
|
|
2515
|
+
} catch {
|
|
2516
|
+
return void 0;
|
|
2517
|
+
}
|
|
2518
|
+
}
|
|
2519
|
+
return response.json().catch(() => void 0);
|
|
2520
|
+
}
|
|
2521
|
+
function normalizeResponse(payload, _service) {
|
|
2522
|
+
if (isJsonObject(payload) && ("data" in payload || "meta" in payload)) {
|
|
2523
|
+
return {
|
|
2524
|
+
data: "data" in payload ? payload.data : payload,
|
|
2525
|
+
meta: {
|
|
2526
|
+
...isJsonObject(payload.meta) ? payload.meta : {},
|
|
2527
|
+
...topLevelMeta(payload)
|
|
2528
|
+
}
|
|
2529
|
+
};
|
|
2530
|
+
}
|
|
2531
|
+
return { data: payload, meta: {} };
|
|
2532
|
+
}
|
|
2533
|
+
function topLevelMeta(payload) {
|
|
2534
|
+
const meta = {};
|
|
2535
|
+
for (const key of ["requestId", "traceId", "auditId"]) {
|
|
2536
|
+
if (typeof payload[key] === "string") meta[key] = payload[key];
|
|
2537
|
+
}
|
|
2538
|
+
return meta;
|
|
2539
|
+
}
|
|
2540
|
+
function urlWithQuery(baseUrl, pathname, query) {
|
|
2541
|
+
const url = new URL(normalizePath(pathname), baseUrl);
|
|
2542
|
+
for (const [key, value] of Object.entries(query || {})) {
|
|
2543
|
+
if (value !== void 0 && value !== null) url.searchParams.set(key, String(value));
|
|
2544
|
+
}
|
|
2545
|
+
return url;
|
|
2546
|
+
}
|
|
2547
|
+
function urlWithBasePath(baseUrl, pathname, query) {
|
|
2548
|
+
const base = new URL(baseUrl);
|
|
2549
|
+
const basePath = base.pathname === "/" ? "" : base.pathname.replace(/\/+$/, "");
|
|
2550
|
+
const normalizedPath = normalizePath(pathname);
|
|
2551
|
+
const fullPath = basePath && (normalizedPath === basePath || normalizedPath.startsWith(`${basePath}/`)) ? normalizedPath : `${basePath}${normalizedPath}`;
|
|
2552
|
+
return urlWithQuery(base.origin, fullPath, query);
|
|
2553
|
+
}
|
|
2554
|
+
function resolveLocalGenauthRequest(input) {
|
|
2555
|
+
const url = new URL(input.toString());
|
|
2556
|
+
if (process.env.QONI_LOCAL_GENAUTH_REWRITE === "false" || url.protocol !== "http:" || !url.hostname.endsWith(".genauth.localhost")) {
|
|
2557
|
+
return { url };
|
|
2558
|
+
}
|
|
2559
|
+
const hostHeader = url.host;
|
|
2560
|
+
url.hostname = process.env.QONI_LOCAL_GENAUTH_TARGET_HOST || "127.0.0.1";
|
|
2561
|
+
return { url, hostHeader };
|
|
2562
|
+
}
|
|
2563
|
+
function withHostHeader(headers, hostHeader) {
|
|
2564
|
+
const out = {};
|
|
2565
|
+
new Headers(headers).forEach((value, key) => {
|
|
2566
|
+
out[key] = value;
|
|
2567
|
+
});
|
|
2568
|
+
out.Host = hostHeader;
|
|
2569
|
+
return out;
|
|
2570
|
+
}
|
|
2571
|
+
function normalizePath(path) {
|
|
2572
|
+
return path.startsWith("/") ? path : `/${path}`;
|
|
2573
|
+
}
|
|
2574
|
+
function normalizeBaseUrl(host) {
|
|
2575
|
+
const withProtocol = /^https?:\/\//i.test(host) ? host : `https://${host}`;
|
|
2576
|
+
return withProtocol.replace(/\/+$/, "");
|
|
2577
|
+
}
|
|
2578
|
+
function originBaseUrl(host) {
|
|
2579
|
+
return new URL(host).origin;
|
|
2580
|
+
}
|
|
2581
|
+
function serviceHostOption(options, service) {
|
|
2582
|
+
if (service === "genauth") {
|
|
2583
|
+
return options.genauthHost;
|
|
2584
|
+
}
|
|
2585
|
+
if (service === "gumem") {
|
|
2586
|
+
return options.gumemHost;
|
|
2587
|
+
}
|
|
2588
|
+
return options.webAgentHost;
|
|
2589
|
+
}
|
|
2590
|
+
function normalizeRuntimeConfig(payload) {
|
|
2591
|
+
const data = envelopeData(payload);
|
|
2592
|
+
if (!isJsonObject(data)) return {};
|
|
2593
|
+
const config = data;
|
|
2594
|
+
return {
|
|
2595
|
+
eakBaseUrl: runtimeServiceUrl(config, "eak", "eakBaseUrl"),
|
|
2596
|
+
genauthBaseUrl: runtimeServiceUrl(config, "genauth", "genauthBaseUrl"),
|
|
2597
|
+
gumemBaseUrl: runtimeServiceUrl(config, "gumem", "gumemBaseUrl"),
|
|
2598
|
+
webAgentBaseUrl: runtimeServiceUrl(config, "webAgent", "webAgentBaseUrl") ?? runtimeServiceUrl(config, "webagent", "webAgentBaseUrl")
|
|
2599
|
+
};
|
|
2600
|
+
}
|
|
2601
|
+
function envelopeData(payload) {
|
|
2602
|
+
if (isJsonObject(payload) && "data" in payload) return payload.data;
|
|
2603
|
+
return payload;
|
|
2604
|
+
}
|
|
2605
|
+
function runtimeServiceUrl(config, service, topLevelKey) {
|
|
2606
|
+
const topLevel = config[topLevelKey];
|
|
2607
|
+
if (typeof topLevel === "string" && topLevel.trim()) return normalizeBaseUrl(topLevel);
|
|
2608
|
+
const serviceValue = config.services?.[service];
|
|
2609
|
+
if (typeof serviceValue === "string" && serviceValue.trim()) {
|
|
2610
|
+
return normalizeBaseUrl(serviceValue);
|
|
2611
|
+
}
|
|
2612
|
+
if (isJsonObject(serviceValue)) {
|
|
2613
|
+
for (const key of ["baseUrl", "url", "host"]) {
|
|
2614
|
+
const value = serviceValue[key];
|
|
2615
|
+
if (typeof value === "string" && value.trim()) return normalizeBaseUrl(value);
|
|
2616
|
+
}
|
|
2617
|
+
}
|
|
2618
|
+
return void 0;
|
|
2619
|
+
}
|
|
2620
|
+
function toRecord(value) {
|
|
2621
|
+
const out = {};
|
|
2622
|
+
for (const [key, item] of Object.entries(value)) {
|
|
2623
|
+
if (item !== void 0) out[key] = item;
|
|
2624
|
+
}
|
|
2625
|
+
return out;
|
|
2626
|
+
}
|
|
2627
|
+
function normalizeDelegateTokenInput(input) {
|
|
2628
|
+
const body = toRecord(input);
|
|
2629
|
+
if (!body.userId && isJsonObject(input.user) && typeof input.user.id === "string") {
|
|
2630
|
+
body.userId = input.user.id;
|
|
2631
|
+
}
|
|
2632
|
+
if (input.mode !== "interactive" && !body.userId) {
|
|
2633
|
+
throw new QoniValidationError(
|
|
2634
|
+
'delegateToken: silent mode requires a user \u2014 pass `user: { id: <genauthUserId> }` (or the deprecated top-level `userId`). Interactive mode (`mode: "interactive"`) does not need one.'
|
|
2635
|
+
);
|
|
2636
|
+
}
|
|
2637
|
+
body.agent = input.agent ?? "sdk";
|
|
2638
|
+
body.scopes = resolveDelegationScopes(input.scopes, input.products);
|
|
2639
|
+
delete body.products;
|
|
2640
|
+
return body;
|
|
2641
|
+
}
|
|
2642
|
+
function resolveDelegationScopes(scopes, products) {
|
|
2643
|
+
const resolved = /* @__PURE__ */ new Set();
|
|
2644
|
+
for (const product of products ?? []) {
|
|
2645
|
+
const expansion = QoniProductScopes[product];
|
|
2646
|
+
if (!expansion) {
|
|
2647
|
+
throw new QoniValidationError(
|
|
2648
|
+
`delegateToken: unknown product "${product}" \u2014 expected one of ${Object.keys(QoniProductScopes).join(", ")}`
|
|
2649
|
+
);
|
|
2650
|
+
}
|
|
2651
|
+
for (const scope of expansion) resolved.add(scope);
|
|
2652
|
+
}
|
|
2653
|
+
for (const scope of scopes ?? []) {
|
|
2654
|
+
validateScopeFormat(scope);
|
|
2655
|
+
resolved.add(scope);
|
|
2656
|
+
}
|
|
2657
|
+
if (resolved.size === 0) {
|
|
2658
|
+
throw new QoniValidationError(
|
|
2659
|
+
'delegateToken requires at least one scope \u2014 pass scopes (e.g. ["webagent.do_anything:manage"]) and/or products (e.g. ["doAnything"])'
|
|
2660
|
+
);
|
|
2661
|
+
}
|
|
2662
|
+
return [...resolved];
|
|
2663
|
+
}
|
|
2664
|
+
function validateScopeFormat(scope) {
|
|
2665
|
+
if (KNOWN_SCOPES.includes(scope)) return;
|
|
2666
|
+
const prefixed = KNOWN_SCOPES.find((known) => known.endsWith(`.${scope}`) || known === `webagent.${scope}`);
|
|
2667
|
+
if (prefixed) {
|
|
2668
|
+
throw new QoniValidationError(
|
|
2669
|
+
`delegateToken: scope "${scope}" is missing its service prefix \u2014 write "${prefixed}"`
|
|
2670
|
+
);
|
|
2671
|
+
}
|
|
2672
|
+
if (!/^[a-z0-9_]+(\.[a-z0-9_]+)+:[a-z0-9_]+$/.test(scope)) {
|
|
2673
|
+
throw new QoniValidationError(
|
|
2674
|
+
`delegateToken: malformed scope "${scope}" \u2014 scopes look like "<service>.<resource>:<action>", e.g. "webagent.do_anything:manage"`
|
|
2675
|
+
);
|
|
2676
|
+
}
|
|
2677
|
+
}
|
|
2678
|
+
function errorEnvelopeStatus(payload) {
|
|
2679
|
+
if (!isJsonObject(payload)) return void 0;
|
|
2680
|
+
const statusCode = payload.statusCode;
|
|
2681
|
+
if (typeof statusCode !== "number" || statusCode < 400) return void 0;
|
|
2682
|
+
if ("data" in payload && payload.data !== void 0) return void 0;
|
|
2683
|
+
return statusCode;
|
|
2684
|
+
}
|
|
2685
|
+
function decodeJwtPayload(token) {
|
|
2686
|
+
const [, payload] = token.split(".");
|
|
2687
|
+
if (!payload) throw new Error("token is not a JWT");
|
|
2688
|
+
return JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
|
|
2689
|
+
}
|
|
2690
|
+
function safeDecodeJwtPayload(token) {
|
|
2691
|
+
try {
|
|
2692
|
+
return decodeJwtPayload(token);
|
|
2693
|
+
} catch {
|
|
2694
|
+
return void 0;
|
|
2695
|
+
}
|
|
2696
|
+
}
|
|
2697
|
+
function isQoniDelegationToken(token) {
|
|
2698
|
+
return safeDecodeJwtPayload(token)?.token_type === "eak_delegation_token";
|
|
2699
|
+
}
|
|
2700
|
+
function readAccessToken(payload) {
|
|
2701
|
+
if (!isJsonObject(payload)) return void 0;
|
|
2702
|
+
for (const key of ["accessToken", "access_token", "token"]) {
|
|
2703
|
+
const token = payload[key];
|
|
2704
|
+
if (typeof token === "string" && token.trim()) return token;
|
|
2705
|
+
}
|
|
2706
|
+
return void 0;
|
|
2707
|
+
}
|
|
2708
|
+
function readExpiresIn(payload) {
|
|
2709
|
+
if (!isJsonObject(payload)) return void 0;
|
|
2710
|
+
const expiresIn = payload.expiresIn ?? payload.expires_in;
|
|
2711
|
+
if (typeof expiresIn === "number" && Number.isFinite(expiresIn)) return expiresIn;
|
|
2712
|
+
if (typeof expiresIn === "string" && expiresIn.trim()) {
|
|
2713
|
+
const parsed = Number(expiresIn);
|
|
2714
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
2715
|
+
}
|
|
2716
|
+
return void 0;
|
|
2717
|
+
}
|
|
2718
|
+
function readString(payload, key) {
|
|
2719
|
+
if (!isJsonObject(payload)) return void 0;
|
|
2720
|
+
const value = payload[key];
|
|
2721
|
+
return typeof value === "string" && value.trim() ? value : void 0;
|
|
2722
|
+
}
|
|
2723
|
+
function requiredWebAgentTenantId(token) {
|
|
2724
|
+
const payload = decodeJwtPayload(token);
|
|
2725
|
+
const productResource = payload.product_resource;
|
|
2726
|
+
if (isJsonObject(productResource) && productResource.type === "webagent_tenant") {
|
|
2727
|
+
const id = productResource.id;
|
|
2728
|
+
if (typeof id === "string" && id.trim()) return id;
|
|
2729
|
+
}
|
|
2730
|
+
const resourceBindingTenantId = nestedString(payload, [
|
|
2731
|
+
"resource_bindings",
|
|
2732
|
+
"webagent",
|
|
2733
|
+
"tenant_id"
|
|
2734
|
+
]);
|
|
2735
|
+
if (resourceBindingTenantId) return resourceBindingTenantId;
|
|
2736
|
+
const tenantId = payload.webagent_tenant_id;
|
|
2737
|
+
if (typeof tenantId === "string" && tenantId.trim()) return tenantId;
|
|
2738
|
+
const projectId = payload.webagent_project_id;
|
|
2739
|
+
if (typeof projectId === "string" && projectId.trim()) return projectId;
|
|
2740
|
+
throw new Error("token is missing WebAgent tenant binding");
|
|
2741
|
+
}
|
|
2742
|
+
function nestedString(value, path) {
|
|
2743
|
+
let cursor = value;
|
|
2744
|
+
for (const segment of path) {
|
|
2745
|
+
if (!isJsonObject(cursor)) return void 0;
|
|
2746
|
+
cursor = cursor[segment];
|
|
2747
|
+
}
|
|
2748
|
+
return typeof cursor === "string" && cursor.trim() ? cursor : void 0;
|
|
2749
|
+
}
|
|
2750
|
+
function normalizeWebAgentEvent(event) {
|
|
2751
|
+
if (event.event) return event;
|
|
2752
|
+
const data = event.data;
|
|
2753
|
+
if (data && typeof data === "object" && typeof data.type === "string") {
|
|
2754
|
+
return { ...event, event: data.type };
|
|
2755
|
+
}
|
|
2756
|
+
return event;
|
|
2757
|
+
}
|
|
2758
|
+
function isRetryableStreamError(err) {
|
|
2759
|
+
if (err && typeof err === "object") {
|
|
2760
|
+
const e = err;
|
|
2761
|
+
if (e.name === "AbortError") return false;
|
|
2762
|
+
if (typeof e.retryable === "boolean") return e.retryable;
|
|
2763
|
+
if (typeof e.status === "number") {
|
|
2764
|
+
return e.status >= 500 || e.status === 408 || e.status === 429;
|
|
2765
|
+
}
|
|
2766
|
+
}
|
|
2767
|
+
return true;
|
|
2768
|
+
}
|
|
2769
|
+
function isAuthError(err) {
|
|
2770
|
+
const status = err?.status;
|
|
2771
|
+
return status === 401 || status === 403;
|
|
2772
|
+
}
|
|
2773
|
+
function sseBackoffMs(attempt) {
|
|
2774
|
+
const base = Math.min(500 * 2 ** (attempt - 1), 1e4);
|
|
2775
|
+
return base + Math.floor(Math.random() * 250);
|
|
2776
|
+
}
|
|
2777
|
+
function sleepWithAbort(ms, signal) {
|
|
2778
|
+
return new Promise((resolve, reject) => {
|
|
2779
|
+
if (signal?.aborted) {
|
|
2780
|
+
reject(new DOMException("The operation was aborted", "AbortError"));
|
|
2781
|
+
return;
|
|
2782
|
+
}
|
|
2783
|
+
const timer = setTimeout(() => {
|
|
2784
|
+
signal?.removeEventListener("abort", onAbort);
|
|
2785
|
+
resolve();
|
|
2786
|
+
}, ms);
|
|
2787
|
+
const onAbort = () => {
|
|
2788
|
+
clearTimeout(timer);
|
|
2789
|
+
reject(new DOMException("The operation was aborted", "AbortError"));
|
|
2790
|
+
};
|
|
2791
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
2792
|
+
});
|
|
2793
|
+
}
|
|
2794
|
+
function firstBoundUserId(data) {
|
|
2795
|
+
const arrays = [
|
|
2796
|
+
data,
|
|
2797
|
+
data?.list,
|
|
2798
|
+
data?.users,
|
|
2799
|
+
data?.data,
|
|
2800
|
+
data?.items
|
|
2801
|
+
];
|
|
2802
|
+
for (const candidate of arrays) {
|
|
2803
|
+
if (Array.isArray(candidate) && candidate.length) {
|
|
2804
|
+
const u = candidate[0];
|
|
2805
|
+
for (const key of ["userId", "id", "user_id", "sub", "username"]) {
|
|
2806
|
+
const v = u?.[key];
|
|
2807
|
+
if (typeof v === "string" && v) return v;
|
|
2808
|
+
}
|
|
2809
|
+
}
|
|
2810
|
+
}
|
|
2811
|
+
return void 0;
|
|
2812
|
+
}
|
|
2813
|
+
function parseSSEEvent(chunk) {
|
|
2814
|
+
const lines = chunk.split(/\r?\n/);
|
|
2815
|
+
let id;
|
|
2816
|
+
let event;
|
|
2817
|
+
const data = [];
|
|
2818
|
+
for (const line of lines) {
|
|
2819
|
+
if (!line || line.startsWith(":")) continue;
|
|
2820
|
+
const index = line.indexOf(":");
|
|
2821
|
+
const field = index >= 0 ? line.slice(0, index) : line;
|
|
2822
|
+
const value = index >= 0 ? line.slice(index + 1).replace(/^ /, "") : "";
|
|
2823
|
+
if (field === "id") id = value;
|
|
2824
|
+
if (field === "event") event = value;
|
|
2825
|
+
if (field === "data") data.push(value);
|
|
2826
|
+
}
|
|
2827
|
+
if (!id && !event && !data.length) return null;
|
|
2828
|
+
return { id, event, data: parseEventData(data.join("\n")) };
|
|
2829
|
+
}
|
|
2830
|
+
function parseEventData(raw) {
|
|
2831
|
+
if (!raw) return void 0;
|
|
2832
|
+
try {
|
|
2833
|
+
return JSON.parse(raw);
|
|
2834
|
+
} catch {
|
|
2835
|
+
return raw;
|
|
2836
|
+
}
|
|
2837
|
+
}
|
|
2838
|
+
function requiredRuntimeToken(token) {
|
|
2839
|
+
if (token && token.trim()) return token;
|
|
2840
|
+
throw new QoniDelegationRequiredError(
|
|
2841
|
+
"Product capability calls require an explicit token in the token field"
|
|
2842
|
+
);
|
|
2843
|
+
}
|
|
2844
|
+
function normalizeDelegateAgentTokenResponse(response) {
|
|
2845
|
+
if (!isJsonObject(response.data)) return response;
|
|
2846
|
+
const grantState = typeof response.data.grantState === "string" ? response.data.grantState : typeof response.data.state === "string" ? response.data.state : typeof response.data.grantId === "string" ? response.data.grantId : void 0;
|
|
2847
|
+
if (typeof response.data.authorizationUrl === "string" && grantState) {
|
|
2848
|
+
return {
|
|
2849
|
+
...response,
|
|
2850
|
+
data: {
|
|
2851
|
+
...response.data,
|
|
2852
|
+
grantState,
|
|
2853
|
+
state: grantState
|
|
2854
|
+
}
|
|
2855
|
+
};
|
|
2856
|
+
}
|
|
2857
|
+
const token = typeof response.data.delegateAgentToken === "string" ? response.data.delegateAgentToken : typeof response.data.delegationToken === "string" ? response.data.delegationToken : typeof response.data.token === "string" ? response.data.token : void 0;
|
|
2858
|
+
if (!token) return response;
|
|
2859
|
+
return {
|
|
2860
|
+
...response,
|
|
2861
|
+
data: {
|
|
2862
|
+
...response.data,
|
|
2863
|
+
token,
|
|
2864
|
+
delegateAgentToken: token
|
|
2865
|
+
}
|
|
2866
|
+
};
|
|
2867
|
+
}
|
|
2868
|
+
function isAbortError(error) {
|
|
2869
|
+
return error instanceof DOMException && error.name === "AbortError";
|
|
2870
|
+
}
|
|
2871
|
+
function isConnectTimeoutError(error) {
|
|
2872
|
+
const cause = errorCause(error);
|
|
2873
|
+
return errorCode(cause) === "UND_ERR_CONNECT_TIMEOUT" || errorName(cause) === "ConnectTimeoutError";
|
|
2874
|
+
}
|
|
2875
|
+
function isFetchNetworkError(error) {
|
|
2876
|
+
if (!(error instanceof TypeError)) return false;
|
|
2877
|
+
const message = error.message.toLowerCase();
|
|
2878
|
+
return message.includes("fetch failed") || message.includes("network");
|
|
2879
|
+
}
|
|
2880
|
+
function errorCause(error) {
|
|
2881
|
+
return error && typeof error === "object" ? error.cause : void 0;
|
|
2882
|
+
}
|
|
2883
|
+
function errorCode(error) {
|
|
2884
|
+
return error && typeof error === "object" && typeof error.code === "string" ? error.code : void 0;
|
|
2885
|
+
}
|
|
2886
|
+
function errorName(error) {
|
|
2887
|
+
return error && typeof error === "object" && typeof error.name === "string" ? error.name : void 0;
|
|
2888
|
+
}
|
|
2889
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
2890
|
+
0 && (module.exports = {
|
|
2891
|
+
ActionKinds,
|
|
2892
|
+
InteractionHandle,
|
|
2893
|
+
InteractionStatuses,
|
|
2894
|
+
InteractionTypes,
|
|
2895
|
+
MonitorHandle,
|
|
2896
|
+
Qoni,
|
|
2897
|
+
QoniAgentKit,
|
|
2898
|
+
QoniAuthError,
|
|
2899
|
+
QoniDelegationRequiredError,
|
|
2900
|
+
QoniError,
|
|
2901
|
+
QoniEventTypes,
|
|
2902
|
+
QoniPermissionDeniedError,
|
|
2903
|
+
QoniProductScopes,
|
|
2904
|
+
QoniRateLimitError,
|
|
2905
|
+
QoniScopeBundles,
|
|
2906
|
+
QoniScopes,
|
|
2907
|
+
QoniTimeoutError,
|
|
2908
|
+
QoniTokenExpiredError,
|
|
2909
|
+
QoniUpstreamError,
|
|
2910
|
+
QoniValidationError,
|
|
2911
|
+
RunHandle,
|
|
2912
|
+
buildAuthorization,
|
|
2913
|
+
buildSignature,
|
|
2914
|
+
buildStringToSign
|
|
2915
|
+
});
|
|
2916
|
+
//# sourceMappingURL=index.cjs.map
|