@openbkn/bkn-sdk 0.1.1-alpha.9 → 0.1.2
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/LICENSE +1 -0
- package/NOTICE +21 -0
- package/README.md +33 -26
- package/README.zh.md +11 -12
- package/dist/{chunk-BFE4SU3V.js → chunk-LH3ONZGQ.js} +2345 -220
- package/dist/cli.js +617 -208
- package/dist/index.d.ts +974 -81
- package/dist/index.js +3 -1
- package/package.json +6 -4
- package/dist/chunk-BFE4SU3V.js.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/index.js.map +0 -1
|
@@ -9,12 +9,15 @@ var HttpError = class extends Error {
|
|
|
9
9
|
status;
|
|
10
10
|
statusText;
|
|
11
11
|
body;
|
|
12
|
-
|
|
12
|
+
/** Optional next-step guidance, overriding the status default (e.g. AppKey re-issue). */
|
|
13
|
+
hint;
|
|
14
|
+
constructor(status2, statusText, body, hint) {
|
|
13
15
|
super(`HTTP ${status2} ${statusText}`);
|
|
14
16
|
this.name = "HttpError";
|
|
15
17
|
this.status = status2;
|
|
16
18
|
this.statusText = statusText;
|
|
17
19
|
this.body = body;
|
|
20
|
+
this.hint = hint;
|
|
18
21
|
}
|
|
19
22
|
};
|
|
20
23
|
var InputError = class extends Error {
|
|
@@ -23,37 +26,38 @@ var InputError = class extends Error {
|
|
|
23
26
|
this.name = "InputError";
|
|
24
27
|
}
|
|
25
28
|
};
|
|
26
|
-
function toExitCode(
|
|
27
|
-
if (
|
|
28
|
-
if (
|
|
29
|
-
if (
|
|
29
|
+
function toExitCode(err2) {
|
|
30
|
+
if (err2 instanceof InputError) return 2;
|
|
31
|
+
if (err2 instanceof HttpError) {
|
|
32
|
+
if (err2.status === 401 || err2.status === 403) return 3;
|
|
30
33
|
return 1;
|
|
31
34
|
}
|
|
32
35
|
return 1;
|
|
33
36
|
}
|
|
34
|
-
function formatError(
|
|
35
|
-
if (
|
|
36
|
-
const serverMsg = serverError(
|
|
37
|
-
if (
|
|
38
|
-
|
|
37
|
+
function formatError(err2) {
|
|
38
|
+
if (err2 instanceof HttpError) {
|
|
39
|
+
const serverMsg = serverError(err2.body);
|
|
40
|
+
if (err2.status === 401) {
|
|
41
|
+
const next = err2.hint ?? "Run `openbkn auth login` and retry.";
|
|
42
|
+
return `Not authorized (HTTP 401)${serverMsg ? `: ${serverMsg}` : ""}. ${next}`;
|
|
39
43
|
}
|
|
40
|
-
if (
|
|
44
|
+
if (err2.status === 403) {
|
|
41
45
|
return `Forbidden (HTTP 403)${serverMsg ? `: ${serverMsg}` : " \u2014 admin privileges required"}.`;
|
|
42
46
|
}
|
|
43
|
-
const detail =
|
|
44
|
-
return `Request failed (HTTP ${
|
|
47
|
+
const detail = err2.body ? `: ${truncate(err2.body, 500)}` : "";
|
|
48
|
+
return `Request failed (HTTP ${err2.status} ${err2.statusText})${detail}`;
|
|
45
49
|
}
|
|
46
|
-
if (
|
|
47
|
-
const cause =
|
|
50
|
+
if (err2 instanceof Error) {
|
|
51
|
+
const cause = err2.cause;
|
|
48
52
|
if (cause?.code && isTlsCertError(cause.code)) {
|
|
49
53
|
return `TLS certificate rejected (${cause.code}). The platform is likely self-signed \u2014 retry with \`-k\`/\`--insecure\`.`;
|
|
50
54
|
}
|
|
51
|
-
if (
|
|
55
|
+
if (err2.message === "fetch failed" && cause?.message) {
|
|
52
56
|
return `Request failed: ${cause.message}${cause.code ? ` (${cause.code})` : ""}`;
|
|
53
57
|
}
|
|
54
|
-
return
|
|
58
|
+
return err2.message;
|
|
55
59
|
}
|
|
56
|
-
return String(
|
|
60
|
+
return String(err2);
|
|
57
61
|
}
|
|
58
62
|
function serverError(body) {
|
|
59
63
|
if (!body) return "";
|
|
@@ -74,6 +78,36 @@ function truncate(s, n) {
|
|
|
74
78
|
|
|
75
79
|
// src/auth/oauth.ts
|
|
76
80
|
import { spawn } from "child_process";
|
|
81
|
+
|
|
82
|
+
// src/api/tls.ts
|
|
83
|
+
import { Agent, FormData as UndiciFormData, fetch as undiciFetch } from "undici";
|
|
84
|
+
var insecureAgent;
|
|
85
|
+
function insecureDispatcher() {
|
|
86
|
+
insecureAgent ??= new Agent({ connect: { rejectUnauthorized: false } });
|
|
87
|
+
return insecureAgent;
|
|
88
|
+
}
|
|
89
|
+
function isFormData(body) {
|
|
90
|
+
return typeof body === "object" && body !== null && body[Symbol.toStringTag] === "FormData";
|
|
91
|
+
}
|
|
92
|
+
function toUndiciBody(body) {
|
|
93
|
+
if (body instanceof UndiciFormData || !isFormData(body)) return body;
|
|
94
|
+
const form2 = new UndiciFormData();
|
|
95
|
+
for (const [name, value] of body.entries()) {
|
|
96
|
+
if (typeof value === "string") form2.append(name, value);
|
|
97
|
+
else form2.append(name, value, value.name);
|
|
98
|
+
}
|
|
99
|
+
return form2;
|
|
100
|
+
}
|
|
101
|
+
function tlsFetch(insecure, url, init) {
|
|
102
|
+
if (!insecure) return fetch(url, init);
|
|
103
|
+
return undiciFetch(url, {
|
|
104
|
+
...init,
|
|
105
|
+
...init?.body === void 0 || init?.body === null ? {} : { body: toUndiciBody(init.body) },
|
|
106
|
+
dispatcher: insecureDispatcher()
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// src/auth/oauth.ts
|
|
77
111
|
function normalizeBaseUrl(value) {
|
|
78
112
|
return value.replace(/\/+$/, "");
|
|
79
113
|
}
|
|
@@ -114,22 +148,9 @@ function mergeCookies(existing, res) {
|
|
|
114
148
|
for (const sc of setCookies) add(sc.split(";")[0]?.trim() ?? "");
|
|
115
149
|
return [...map.entries()].map(([k, v]) => `${k}=${v}`).join("; ");
|
|
116
150
|
}
|
|
117
|
-
async function
|
|
118
|
-
try {
|
|
119
|
-
const res = await fetch(`${normalizeBaseUrl(baseUrl)}/install-status.json`, {
|
|
120
|
-
headers: { Accept: "application/json" }
|
|
121
|
-
});
|
|
122
|
-
if (!res.ok) return null;
|
|
123
|
-
const j = await res.json();
|
|
124
|
-
if (!j.auth) return null;
|
|
125
|
-
return { enabled: Boolean(j.auth.enabled), stack: j.auth.stack };
|
|
126
|
-
} catch {
|
|
127
|
-
return null;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
async function refreshAccessToken(baseUrl, refreshToken, clientId = "openbkn-sdk") {
|
|
151
|
+
async function refreshAccessToken(baseUrl, refreshToken, clientId = "openbkn-sdk", insecure) {
|
|
131
152
|
const base = normalizeBaseUrl(baseUrl);
|
|
132
|
-
const res = await
|
|
153
|
+
const res = await tlsFetch(insecure, `${base}/oauth2/token`, {
|
|
133
154
|
method: "POST",
|
|
134
155
|
headers: {
|
|
135
156
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
@@ -159,10 +180,10 @@ var form = (body) => ({
|
|
|
159
180
|
},
|
|
160
181
|
body: new URLSearchParams(body).toString()
|
|
161
182
|
});
|
|
162
|
-
async function requestDeviceCode(base, clientId, scope, audience) {
|
|
183
|
+
async function requestDeviceCode(base, clientId, scope, audience, insecure) {
|
|
163
184
|
const params = { client_id: clientId, scope };
|
|
164
185
|
if (audience) params.audience = audience;
|
|
165
|
-
const res = await
|
|
186
|
+
const res = await tlsFetch(insecure, `${base}/oauth2/device/auth`, form(params));
|
|
166
187
|
if (!res.ok) {
|
|
167
188
|
throw new Error(`Device auth failed (${res.status}): ${await res.text() || res.statusText}`);
|
|
168
189
|
}
|
|
@@ -174,12 +195,13 @@ async function requestDeviceCode(base, clientId, scope, audience) {
|
|
|
174
195
|
}
|
|
175
196
|
return da;
|
|
176
197
|
}
|
|
177
|
-
async function pollDeviceToken(base, deviceCode, clientId, intervalMs, windowMs) {
|
|
198
|
+
async function pollDeviceToken(base, deviceCode, clientId, intervalMs, windowMs, insecure) {
|
|
178
199
|
let interval = intervalMs;
|
|
179
200
|
const deadline = Date.now() + windowMs;
|
|
180
201
|
while (Date.now() < deadline) {
|
|
181
202
|
await new Promise((r) => setTimeout(r, interval));
|
|
182
|
-
const tokRes = await
|
|
203
|
+
const tokRes = await tlsFetch(
|
|
204
|
+
insecure,
|
|
183
205
|
`${base}/oauth2/token`,
|
|
184
206
|
form({ grant_type: DEVICE_GRANT, device_code: deviceCode, client_id: clientId })
|
|
185
207
|
);
|
|
@@ -205,14 +227,27 @@ async function pollDeviceToken(base, deviceCode, clientId, intervalMs, windowMs)
|
|
|
205
227
|
async function deviceLogin(baseUrl, opts = {}) {
|
|
206
228
|
const base = normalizeBaseUrl(baseUrl);
|
|
207
229
|
const clientId = opts.clientId ?? DEFAULT_DEVICE_CLIENT_ID;
|
|
208
|
-
const da = await requestDeviceCode(
|
|
230
|
+
const da = await requestDeviceCode(
|
|
231
|
+
base,
|
|
232
|
+
clientId,
|
|
233
|
+
opts.scope ?? DEVICE_SCOPE,
|
|
234
|
+
opts.audience,
|
|
235
|
+
opts.insecure
|
|
236
|
+
);
|
|
209
237
|
opts.onPrompt?.({
|
|
210
238
|
userCode: da.user_code,
|
|
211
239
|
verificationUri: onBaseHost(base, da.verification_uri),
|
|
212
240
|
verificationUriComplete: da.verification_uri_complete ? onBaseHost(base, da.verification_uri_complete) : void 0
|
|
213
241
|
});
|
|
214
242
|
const windowMs = Math.min(opts.timeoutMs ?? Number.POSITIVE_INFINITY, da.expires_in * 1e3);
|
|
215
|
-
return pollDeviceToken(
|
|
243
|
+
return pollDeviceToken(
|
|
244
|
+
base,
|
|
245
|
+
da.device_code,
|
|
246
|
+
clientId,
|
|
247
|
+
(da.interval ?? 5) * 1e3,
|
|
248
|
+
windowMs,
|
|
249
|
+
opts.insecure
|
|
250
|
+
);
|
|
216
251
|
}
|
|
217
252
|
function onBaseHost(base, uri) {
|
|
218
253
|
try {
|
|
@@ -226,10 +261,16 @@ function onBaseHost(base, uri) {
|
|
|
226
261
|
async function credentialDeviceLogin(baseUrl, username, password, opts = {}) {
|
|
227
262
|
const base = normalizeBaseUrl(baseUrl);
|
|
228
263
|
const clientId = opts.clientId ?? DEFAULT_DEVICE_CLIENT_ID;
|
|
229
|
-
const da = await requestDeviceCode(
|
|
264
|
+
const da = await requestDeviceCode(
|
|
265
|
+
base,
|
|
266
|
+
clientId,
|
|
267
|
+
opts.scope ?? DEVICE_SCOPE,
|
|
268
|
+
opts.audience,
|
|
269
|
+
opts.insecure
|
|
270
|
+
);
|
|
230
271
|
let jar = "";
|
|
231
272
|
const hop = async (url, init) => {
|
|
232
|
-
const r = await
|
|
273
|
+
const r = await tlsFetch(opts.insecure, url, {
|
|
233
274
|
method: init?.method ?? "GET",
|
|
234
275
|
headers: { Cookie: jar, Accept: "text/html,*/*;q=0.8", ...init?.headers ?? {} },
|
|
235
276
|
body: init?.body,
|
|
@@ -267,44 +308,132 @@ async function credentialDeviceLogin(baseUrl, username, password, opts = {}) {
|
|
|
267
308
|
loc = r.headers.get("location");
|
|
268
309
|
}
|
|
269
310
|
const windowMs = Math.min(opts.timeoutMs ?? Number.POSITIVE_INFINITY, da.expires_in * 1e3);
|
|
270
|
-
return pollDeviceToken(
|
|
311
|
+
return pollDeviceToken(
|
|
312
|
+
base,
|
|
313
|
+
da.device_code,
|
|
314
|
+
clientId,
|
|
315
|
+
(da.interval ?? 5) * 1e3,
|
|
316
|
+
windowMs,
|
|
317
|
+
opts.insecure
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// src/trace-context.ts
|
|
322
|
+
import { randomBytes, randomUUID } from "crypto";
|
|
323
|
+
var TRACEPARENT_RE = /^00-([0-9a-f]{32})-([0-9a-f]{16})-[0-9a-f]{2}$/;
|
|
324
|
+
var REQUEST_ID_RE = /^req_[0-9A-Za-z_.-]+$/;
|
|
325
|
+
var CORRELATION_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
|
|
326
|
+
var RFC3339_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?(?:Z|[+-]\d{2}:\d{2})$/;
|
|
327
|
+
var ALLOWED_BAGGAGE = /* @__PURE__ */ new Set(["bkn.account.type", "bkn.runtime.env"]);
|
|
328
|
+
function isValidRequestId(value) {
|
|
329
|
+
return typeof value === "string" && REQUEST_ID_RE.test(value);
|
|
330
|
+
}
|
|
331
|
+
function isValidCorrelationId(value) {
|
|
332
|
+
return typeof value === "string" && CORRELATION_ID_RE.test(value.trim());
|
|
333
|
+
}
|
|
334
|
+
function isValidTraceparent(value) {
|
|
335
|
+
if (typeof value !== "string") return false;
|
|
336
|
+
const match = TRACEPARENT_RE.exec(value);
|
|
337
|
+
if (!match) return false;
|
|
338
|
+
const [, traceId, spanId] = match;
|
|
339
|
+
return traceId !== "0".repeat(32) && spanId !== "0".repeat(16);
|
|
340
|
+
}
|
|
341
|
+
function createTraceContext(opts = {}) {
|
|
342
|
+
const requestId = isValidRequestId(opts.requestId) ? opts.requestId : `req_${randomUUID()}`;
|
|
343
|
+
const traceparent = isValidTraceparent(opts.traceparent) ? opts.traceparent : newTraceparent();
|
|
344
|
+
const baggage = filterBaggage(opts.baggage);
|
|
345
|
+
const conversationId = isValidCorrelationId(opts.conversationId) ? opts.conversationId.trim() : void 0;
|
|
346
|
+
const interactionId = isValidCorrelationId(opts.interactionId) ? opts.interactionId.trim() : void 0;
|
|
347
|
+
const operationId = isValidCorrelationId(opts.operationId) ? opts.operationId.trim() : void 0;
|
|
348
|
+
const attempt = Number.isInteger(opts.attempt) && (opts.attempt ?? 0) >= 1 && (opts.attempt ?? 0) <= 1e3 ? opts.attempt : void 0;
|
|
349
|
+
const observedAt = isValidObservedAt(opts.observedAt) ? opts.observedAt : void 0;
|
|
350
|
+
return {
|
|
351
|
+
requestId,
|
|
352
|
+
traceparent,
|
|
353
|
+
...conversationId ? { conversationId } : {},
|
|
354
|
+
...interactionId ? { interactionId } : {},
|
|
355
|
+
...operationId ? { operationId } : {},
|
|
356
|
+
...attempt ? { attempt } : {},
|
|
357
|
+
...observedAt ? { observedAt } : {},
|
|
358
|
+
...Object.keys(baggage).length > 0 ? { baggage } : {}
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
function createOperationTraceContext(trace2) {
|
|
362
|
+
return {
|
|
363
|
+
...trace2,
|
|
364
|
+
operationId: trace2.operationId ?? `op_${randomUUID()}`,
|
|
365
|
+
attempt: trace2.attempt ?? 1,
|
|
366
|
+
observedAt: isValidObservedAt(trace2.observedAt) ? trace2.observedAt : (/* @__PURE__ */ new Date()).toISOString()
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
function isValidObservedAt(value) {
|
|
370
|
+
return typeof value === "string" && RFC3339_RE.test(value) && !Number.isNaN(Date.parse(value));
|
|
371
|
+
}
|
|
372
|
+
function filterBaggage(baggage) {
|
|
373
|
+
const filtered = {};
|
|
374
|
+
for (const [key, value] of Object.entries(baggage ?? {})) {
|
|
375
|
+
if (ALLOWED_BAGGAGE.has(key) && value !== "") filtered[key] = value;
|
|
376
|
+
}
|
|
377
|
+
return filtered;
|
|
378
|
+
}
|
|
379
|
+
function serializeBaggage(baggage) {
|
|
380
|
+
const filtered = filterBaggage(baggage);
|
|
381
|
+
const entries = Object.entries(filtered);
|
|
382
|
+
if (entries.length === 0) return void 0;
|
|
383
|
+
return entries.map(([key, value]) => `${key}=${encodeURIComponent(value)}`).join(",");
|
|
384
|
+
}
|
|
385
|
+
function newTraceparent() {
|
|
386
|
+
return `00-${randomHex(16)}-${randomHex(8)}-01`;
|
|
387
|
+
}
|
|
388
|
+
function randomHex(bytes) {
|
|
389
|
+
let value = randomBytes(bytes).toString("hex");
|
|
390
|
+
while (/^0+$/.test(value)) value = randomBytes(bytes).toString("hex");
|
|
391
|
+
return value;
|
|
271
392
|
}
|
|
272
393
|
|
|
273
394
|
// src/api/headers.ts
|
|
274
395
|
function buildHeaders(ctx, extra) {
|
|
396
|
+
const baggage = serializeBaggage(ctx.trace?.baggage);
|
|
275
397
|
return {
|
|
276
|
-
|
|
277
|
-
...ctx.token ? { authorization: `Bearer ${ctx.token}`, token: ctx.token } : {},
|
|
398
|
+
authorization: `Bearer ${ctx.token}`,
|
|
278
399
|
"x-business-domain": ctx.businessDomain,
|
|
400
|
+
...ctx.trace ? {
|
|
401
|
+
"bkn-request-id": ctx.trace.requestId,
|
|
402
|
+
"x-request-id": ctx.trace.requestId,
|
|
403
|
+
traceparent: ctx.trace.traceparent,
|
|
404
|
+
...ctx.trace.conversationId ? { "bkn-conversation-id": ctx.trace.conversationId } : {},
|
|
405
|
+
...ctx.trace.interactionId ? { "bkn-interaction-id": ctx.trace.interactionId } : {},
|
|
406
|
+
...ctx.trace.operationId ? { "bkn-operation-id": ctx.trace.operationId } : {},
|
|
407
|
+
...ctx.trace.attempt ? { "bkn-attempt": String(ctx.trace.attempt) } : {},
|
|
408
|
+
...ctx.trace.observedAt ? { "bkn-event-observed-at": ctx.trace.observedAt } : {}
|
|
409
|
+
} : {},
|
|
410
|
+
...baggage ? { baggage } : {},
|
|
279
411
|
...extra
|
|
280
412
|
};
|
|
281
413
|
}
|
|
282
414
|
|
|
283
|
-
// src/api/tls.ts
|
|
284
|
-
function applyTls(ctx) {
|
|
285
|
-
if (ctx.insecure && process.env.NODE_TLS_REJECT_UNAUTHORIZED !== "0") {
|
|
286
|
-
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
|
|
290
415
|
// src/api/http.ts
|
|
291
416
|
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
292
417
|
async function request(ctx, path, init = {}) {
|
|
293
418
|
const url = new URL(path.startsWith("http") ? path : `${ctx.baseUrl}${path}`);
|
|
294
419
|
for (const [k, v] of Object.entries(init.query ?? {})) {
|
|
295
|
-
if (
|
|
420
|
+
if (Array.isArray(v)) {
|
|
421
|
+
for (const item of v) url.searchParams.append(k, String(item));
|
|
422
|
+
} else if (v !== void 0) {
|
|
423
|
+
url.searchParams.set(k, String(v));
|
|
424
|
+
}
|
|
296
425
|
}
|
|
297
|
-
applyTls(ctx);
|
|
298
426
|
const hasBody = init.body !== void 0;
|
|
299
427
|
const controller = new AbortController();
|
|
300
428
|
const timer = setTimeout(() => controller.abort(), init.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
301
|
-
const send = () =>
|
|
429
|
+
const send = () => tlsFetch(ctx.insecure, url, {
|
|
302
430
|
method: init.method ?? (hasBody ? "POST" : "GET"),
|
|
303
431
|
headers: buildHeaders(ctx, {
|
|
304
432
|
...hasBody ? { "content-type": "application/json" } : {},
|
|
305
433
|
...init.headers
|
|
306
434
|
}),
|
|
307
435
|
body: hasBody ? JSON.stringify(init.body) : void 0,
|
|
436
|
+
redirect: init.redirect,
|
|
308
437
|
signal: controller.signal
|
|
309
438
|
});
|
|
310
439
|
try {
|
|
@@ -313,16 +442,27 @@ async function request(ctx, path, init = {}) {
|
|
|
313
442
|
res = await send();
|
|
314
443
|
}
|
|
315
444
|
const text = await res.text();
|
|
316
|
-
if (!res.ok) throw new HttpError(res.status, res.statusText, text);
|
|
445
|
+
if (!res.ok) throw new HttpError(res.status, res.statusText, text, hintFor(ctx, res.status));
|
|
317
446
|
return text ? JSON.parse(text) : void 0;
|
|
318
447
|
} finally {
|
|
319
448
|
clearTimeout(timer);
|
|
320
449
|
}
|
|
321
450
|
}
|
|
451
|
+
function hintFor(ctx, status2) {
|
|
452
|
+
if (status2 === 401 && ctx.token.startsWith("bak_")) {
|
|
453
|
+
return "AppKey invalid / expired / revoked / owner disabled \u2014 re-issue with `openbkn appkey create` (or `appkey regenerate <id>`). Do not auto-retry.";
|
|
454
|
+
}
|
|
455
|
+
return void 0;
|
|
456
|
+
}
|
|
322
457
|
async function tryRefresh(ctx) {
|
|
323
458
|
if (!ctx.refresh) return false;
|
|
324
459
|
try {
|
|
325
|
-
const t = await refreshAccessToken(
|
|
460
|
+
const t = await refreshAccessToken(
|
|
461
|
+
ctx.baseUrl,
|
|
462
|
+
ctx.refresh.refreshToken,
|
|
463
|
+
ctx.refresh.clientId,
|
|
464
|
+
ctx.insecure
|
|
465
|
+
);
|
|
326
466
|
ctx.token = t.accessToken;
|
|
327
467
|
if (t.refreshToken) ctx.refresh.refreshToken = t.refreshToken;
|
|
328
468
|
ctx.refresh.persist(t);
|
|
@@ -367,6 +507,7 @@ function isExpired(claims, nowMs = Date.now()) {
|
|
|
367
507
|
|
|
368
508
|
// src/config/store.ts
|
|
369
509
|
var PROFILE_RE = /^[A-Za-z0-9_-]{1,64}$/;
|
|
510
|
+
var USER_ID_RE = /^[A-Za-z0-9._@-]{1,128}$/;
|
|
370
511
|
var IS_WIN = process.platform === "win32";
|
|
371
512
|
function configDir() {
|
|
372
513
|
return process.env.BKN_CONFIG_DIR ?? join(homedir(), ".bkn");
|
|
@@ -393,7 +534,14 @@ function userDir(baseUrl, userId) {
|
|
|
393
534
|
return join(platformDir(baseUrl), "users", userId);
|
|
394
535
|
}
|
|
395
536
|
function userIdFromToken(token) {
|
|
396
|
-
|
|
537
|
+
const sub = decodeJwt(token.idToken ?? "")?.sub ?? decodeJwt(token.accessToken)?.sub;
|
|
538
|
+
if (sub === void 0) return "default";
|
|
539
|
+
if (typeof sub !== "string" || !USER_ID_RE.test(sub) || sub === "." || sub === "..") {
|
|
540
|
+
throw new Error(
|
|
541
|
+
`Token subject '${String(sub)}' is not a usable user id (expected 1-128 chars from [A-Za-z0-9._@-]). Refusing to store this token.`
|
|
542
|
+
);
|
|
543
|
+
}
|
|
544
|
+
return sub;
|
|
397
545
|
}
|
|
398
546
|
function readState() {
|
|
399
547
|
return readJson(statePath()) ?? {};
|
|
@@ -418,10 +566,10 @@ function readToken(baseUrl, userId = activeUserId(baseUrl)) {
|
|
|
418
566
|
if (!userId) return void 0;
|
|
419
567
|
return readJson(join(userDir(baseUrl, userId), "token.json")) ?? void 0;
|
|
420
568
|
}
|
|
421
|
-
function writeToken(baseUrl, token) {
|
|
569
|
+
function writeToken(baseUrl, token, opts = {}) {
|
|
422
570
|
const userId = userIdFromToken(token);
|
|
423
571
|
writeJson(join(userDir(baseUrl, userId), "token.json"), token, 384);
|
|
424
|
-
setActiveUser(baseUrl, userId);
|
|
572
|
+
if (opts.setActive !== false) setActiveUser(baseUrl, userId);
|
|
425
573
|
return userId;
|
|
426
574
|
}
|
|
427
575
|
function deleteToken(baseUrl, userId = activeUserId(baseUrl)) {
|
|
@@ -460,6 +608,14 @@ function listPlatforms() {
|
|
|
460
608
|
}
|
|
461
609
|
return out;
|
|
462
610
|
}
|
|
611
|
+
function findUserId(baseUrl, userOrName) {
|
|
612
|
+
const users = listPlatforms().find((p) => p.baseUrl === baseUrl)?.users ?? [];
|
|
613
|
+
const match = users.find((u) => u.userId === userOrName) ?? users.find((u) => (u.username ?? u.displayName) === userOrName);
|
|
614
|
+
return match?.userId ?? null;
|
|
615
|
+
}
|
|
616
|
+
function usersOfPlatform(baseUrl) {
|
|
617
|
+
return listPlatforms().find((p) => p.baseUrl === baseUrl)?.users ?? [];
|
|
618
|
+
}
|
|
463
619
|
function decodeKey(key) {
|
|
464
620
|
try {
|
|
465
621
|
const b64 = key.replace(/-/g, "+").replace(/_/g, "/");
|
|
@@ -485,6 +641,14 @@ function writeJson(path, value, mode = 384) {
|
|
|
485
641
|
}
|
|
486
642
|
|
|
487
643
|
// src/config/resolve.ts
|
|
644
|
+
function resolveUserId(baseUrl, userOrName) {
|
|
645
|
+
const id = findUserId(baseUrl, userOrName);
|
|
646
|
+
if (id) return id;
|
|
647
|
+
const known = usersOfPlatform(baseUrl).map((u) => u.username ?? u.userId).join(", ");
|
|
648
|
+
throw new InputError(
|
|
649
|
+
`No saved user '${userOrName}' on ${baseUrl}. Saved: ${known || "(none)"}. See \`openbkn auth users ${baseUrl}\`.`
|
|
650
|
+
);
|
|
651
|
+
}
|
|
488
652
|
function resolveContext(opts = {}) {
|
|
489
653
|
const baseUrl = opts.baseUrl ?? process.env.BKN_BASE_URL ?? activePlatform();
|
|
490
654
|
if (!baseUrl) {
|
|
@@ -493,22 +657,29 @@ function resolveContext(opts = {}) {
|
|
|
493
657
|
);
|
|
494
658
|
}
|
|
495
659
|
const normalized = baseUrl.replace(/\/+$/, "");
|
|
496
|
-
const
|
|
660
|
+
const user = opts.user ?? process.env.BKN_USER;
|
|
661
|
+
const stored = user ? readToken(normalized, resolveUserId(normalized, user)) : readToken(normalized);
|
|
497
662
|
const explicit = opts.token ?? process.env.BKN_TOKEN;
|
|
498
663
|
const token = explicit ?? stored?.accessToken ?? "";
|
|
499
|
-
if (!token
|
|
664
|
+
if (!token) {
|
|
500
665
|
throw new InputError("No access token. Set BKN_TOKEN or run `openbkn auth login`.");
|
|
501
666
|
}
|
|
502
667
|
const insecure = opts.insecure ?? stored?.tlsInsecure ?? false;
|
|
503
668
|
const refresh = !explicit && stored?.refreshToken ? {
|
|
504
669
|
refreshToken: stored.refreshToken,
|
|
505
670
|
persist: (t) => {
|
|
506
|
-
writeToken(
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
671
|
+
writeToken(
|
|
672
|
+
normalized,
|
|
673
|
+
{
|
|
674
|
+
...stored,
|
|
675
|
+
accessToken: t.accessToken,
|
|
676
|
+
refreshToken: t.refreshToken ?? stored.refreshToken,
|
|
677
|
+
idToken: t.idToken ?? stored.idToken
|
|
678
|
+
},
|
|
679
|
+
// `--user` picks an identity for this command only; a refresh
|
|
680
|
+
// must not promote it to the default for the next one.
|
|
681
|
+
{ setActive: !user }
|
|
682
|
+
);
|
|
512
683
|
}
|
|
513
684
|
} : void 0;
|
|
514
685
|
return {
|
|
@@ -516,6 +687,13 @@ function resolveContext(opts = {}) {
|
|
|
516
687
|
token,
|
|
517
688
|
businessDomain: opts.businessDomain ?? readPlatformConfig(normalized).businessDomain ?? DEFAULT_BUSINESS_DOMAIN,
|
|
518
689
|
insecure,
|
|
690
|
+
...opts.evidenceIngestToken ?? process.env.BKN_TRACE_EVIDENCE_INGEST_TOKEN ? {
|
|
691
|
+
evidenceIngestToken: opts.evidenceIngestToken ?? process.env.BKN_TRACE_EVIDENCE_INGEST_TOKEN
|
|
692
|
+
} : {},
|
|
693
|
+
// Correlation ids come from `opts.trace` only. The CLI reads its flags and
|
|
694
|
+
// env vars in `commands/_shared.ts`; a library client must not inherit an
|
|
695
|
+
// ambient interaction id it would then freeze for its whole lifetime.
|
|
696
|
+
trace: createTraceContext(opts.trace),
|
|
519
697
|
...refresh ? { refresh } : {}
|
|
520
698
|
};
|
|
521
699
|
}
|
|
@@ -571,6 +749,13 @@ async function setUserPasswordSafe(ctx, userId, password) {
|
|
|
571
749
|
});
|
|
572
750
|
return { ok: true };
|
|
573
751
|
}
|
|
752
|
+
async function changePasswordSafe(ctx, account, oldPassword, newPassword) {
|
|
753
|
+
await request(ctx, "/api/safe/v1/auth/change-password", {
|
|
754
|
+
method: "POST",
|
|
755
|
+
body: { account, old_password: oldPassword, new_password: newPassword }
|
|
756
|
+
});
|
|
757
|
+
return { ok: true };
|
|
758
|
+
}
|
|
574
759
|
function getUserRolesSafe(ctx, userId) {
|
|
575
760
|
return request(ctx, `${ADMIN}/role-bindings`, { query: { accessor_id: userId } });
|
|
576
761
|
}
|
|
@@ -684,6 +869,43 @@ async function setRolePermissionSafe(ctx, roleId, grant, perm) {
|
|
|
684
869
|
});
|
|
685
870
|
return { ok: true };
|
|
686
871
|
}
|
|
872
|
+
function getLicenseSafe(ctx) {
|
|
873
|
+
return request(ctx, `${ADMIN}/license`);
|
|
874
|
+
}
|
|
875
|
+
async function importLicenseSafe(ctx, licenseText, opts = {}) {
|
|
876
|
+
const text = licenseText.trim();
|
|
877
|
+
if (!text) throw new InputError("license text is empty");
|
|
878
|
+
try {
|
|
879
|
+
return await request(ctx, `${ADMIN}/license/${opts.receipt ? "receipt" : "import"}`, {
|
|
880
|
+
method: "POST",
|
|
881
|
+
body: { license: text }
|
|
882
|
+
});
|
|
883
|
+
} catch (err2) {
|
|
884
|
+
if (err2 instanceof HttpError) {
|
|
885
|
+
const stored = storedImport(err2.body);
|
|
886
|
+
if (stored) return stored;
|
|
887
|
+
}
|
|
888
|
+
throw err2;
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
function storedImport(body) {
|
|
892
|
+
try {
|
|
893
|
+
const parsed = JSON.parse(body);
|
|
894
|
+
if (parsed && parsed.stored === true) return parsed;
|
|
895
|
+
} catch {
|
|
896
|
+
}
|
|
897
|
+
return null;
|
|
898
|
+
}
|
|
899
|
+
function activateLicenseSafe(ctx) {
|
|
900
|
+
return request(ctx, `${ADMIN}/license/activate`, { method: "POST" });
|
|
901
|
+
}
|
|
902
|
+
async function removeLicenseSafe(ctx) {
|
|
903
|
+
await request(ctx, `${ADMIN}/license`, { method: "DELETE" });
|
|
904
|
+
return { ok: true };
|
|
905
|
+
}
|
|
906
|
+
function getLicenseFingerprintSafe(ctx) {
|
|
907
|
+
return request(ctx, `${ADMIN}/license/fingerprint`);
|
|
908
|
+
}
|
|
687
909
|
|
|
688
910
|
// src/resources/admin.ts
|
|
689
911
|
var DEFAULT_NEW_USER_PASSWORD = "openbkn";
|
|
@@ -744,7 +966,13 @@ function admin(ctx) {
|
|
|
744
966
|
roleUpdate: (roleId, input) => updateRoleSafe(ctx, roleId, input),
|
|
745
967
|
roleDelete: (roleId) => deleteRoleSafe(ctx, roleId),
|
|
746
968
|
rolePermission: (roleId, grant, resourceType, resourceId, operations) => setRolePermissionSafe(ctx, roleId, grant, { resourceType, resourceId, operations }),
|
|
747
|
-
auditList: (_opts) => notOnSafe("audit list")
|
|
969
|
+
auditList: (_opts) => notOnSafe("audit list"),
|
|
970
|
+
// ── license (cluster license hub; weak judgements — display/ops only) ──
|
|
971
|
+
licenseGet: () => getLicenseSafe(ctx),
|
|
972
|
+
licenseImport: (licenseText, opts) => importLicenseSafe(ctx, licenseText, opts),
|
|
973
|
+
licenseActivate: () => activateLicenseSafe(ctx),
|
|
974
|
+
licenseRemove: () => removeLicenseSafe(ctx),
|
|
975
|
+
licenseFingerprint: () => getLicenseFingerprintSafe(ctx)
|
|
748
976
|
};
|
|
749
977
|
}
|
|
750
978
|
|
|
@@ -816,7 +1044,6 @@ function extractText(result) {
|
|
|
816
1044
|
return "";
|
|
817
1045
|
}
|
|
818
1046
|
async function sendChat(ctx, info, query, opts = {}) {
|
|
819
|
-
applyTls(ctx);
|
|
820
1047
|
const body = {
|
|
821
1048
|
agent_id: info.id,
|
|
822
1049
|
agent_key: info.key,
|
|
@@ -827,7 +1054,7 @@ async function sendChat(ctx, info, query, opts = {}) {
|
|
|
827
1054
|
if (opts.conversationId) body.conversation_id = opts.conversationId;
|
|
828
1055
|
const res = await authFetch(
|
|
829
1056
|
ctx,
|
|
830
|
-
() =>
|
|
1057
|
+
() => tlsFetch(ctx.insecure, `${ctx.baseUrl}${FACTORY}/v1/app/${info.key}/chat/completion`, {
|
|
831
1058
|
method: "POST",
|
|
832
1059
|
headers: {
|
|
833
1060
|
...buildHeaders(ctx),
|
|
@@ -1029,16 +1256,20 @@ function nextId() {
|
|
|
1029
1256
|
function mcpUrl(ctx) {
|
|
1030
1257
|
return `${ctx.baseUrl}${MCP_PATH}`;
|
|
1031
1258
|
}
|
|
1259
|
+
function operationContext(ctx) {
|
|
1260
|
+
return ctx.trace ? { ...ctx, trace: createOperationTraceContext(ctx.trace) } : ctx;
|
|
1261
|
+
}
|
|
1262
|
+
function mcpInfo(ctx) {
|
|
1263
|
+
return request(ctx, `${MCP_PATH}/info`);
|
|
1264
|
+
}
|
|
1032
1265
|
function headers(ctx, knId, sessionId) {
|
|
1033
|
-
|
|
1266
|
+
return buildHeaders(ctx, {
|
|
1034
1267
|
"content-type": "application/json",
|
|
1035
1268
|
accept: "application/json, text/event-stream",
|
|
1036
1269
|
"x-kn-id": knId,
|
|
1037
1270
|
"mcp-protocol-version": PROTOCOL,
|
|
1038
|
-
|
|
1039
|
-
};
|
|
1040
|
-
if (sessionId) h["mcp-session-id"] = sessionId;
|
|
1041
|
-
return h;
|
|
1271
|
+
...sessionId ? { "mcp-session-id": sessionId } : {}
|
|
1272
|
+
});
|
|
1042
1273
|
}
|
|
1043
1274
|
function parseBody(text) {
|
|
1044
1275
|
try {
|
|
@@ -1050,10 +1281,9 @@ function parseBody(text) {
|
|
|
1050
1281
|
}
|
|
1051
1282
|
}
|
|
1052
1283
|
async function post(ctx, knId, sessionId, body) {
|
|
1053
|
-
applyTls(ctx);
|
|
1054
1284
|
const res = await authFetch(
|
|
1055
1285
|
ctx,
|
|
1056
|
-
() =>
|
|
1286
|
+
() => tlsFetch(ctx.insecure, mcpUrl(ctx), {
|
|
1057
1287
|
method: "POST",
|
|
1058
1288
|
headers: headers(ctx, knId, sessionId),
|
|
1059
1289
|
body: JSON.stringify(body)
|
|
@@ -1100,8 +1330,9 @@ function unwrap(parsed) {
|
|
|
1100
1330
|
return result;
|
|
1101
1331
|
}
|
|
1102
1332
|
async function callTool(ctx, knId, name, args) {
|
|
1103
|
-
const
|
|
1104
|
-
const
|
|
1333
|
+
const operationCtx = operationContext(ctx);
|
|
1334
|
+
const sessionId = await ensureSession(operationCtx, knId);
|
|
1335
|
+
const { text } = await post(operationCtx, knId, sessionId, {
|
|
1105
1336
|
jsonrpc: "2.0",
|
|
1106
1337
|
method: "tools/call",
|
|
1107
1338
|
params: { name, arguments: args },
|
|
@@ -1110,8 +1341,9 @@ async function callTool(ctx, knId, name, args) {
|
|
|
1110
1341
|
return unwrap(parseBody(text));
|
|
1111
1342
|
}
|
|
1112
1343
|
async function callMethod(ctx, knId, method, params = {}) {
|
|
1113
|
-
const
|
|
1114
|
-
const
|
|
1344
|
+
const operationCtx = operationContext(ctx);
|
|
1345
|
+
const sessionId = await ensureSession(operationCtx, knId);
|
|
1346
|
+
const { text } = await post(operationCtx, knId, sessionId, {
|
|
1115
1347
|
jsonrpc: "2.0",
|
|
1116
1348
|
method,
|
|
1117
1349
|
params: Object.keys(params).length > 0 ? params : void 0,
|
|
@@ -1135,6 +1367,17 @@ function findSkills(ctx, knId, objectTypeId, topK) {
|
|
|
1135
1367
|
if (topK !== void 0) args.top_k = topK;
|
|
1136
1368
|
return callTool(ctx, knId, "find_skills", args);
|
|
1137
1369
|
}
|
|
1370
|
+
function getKnDetail(ctx, knId, detailLevel) {
|
|
1371
|
+
const args = { response_format: "json" };
|
|
1372
|
+
if (detailLevel) args.detail_level = detailLevel;
|
|
1373
|
+
return callTool(ctx, knId, "get_kn_detail", args);
|
|
1374
|
+
}
|
|
1375
|
+
function getObjectTypes(ctx, knId, ids) {
|
|
1376
|
+
return callTool(ctx, knId, "get_object_types", { ids, response_format: "json" });
|
|
1377
|
+
}
|
|
1378
|
+
function getRelationTypes(ctx, knId, ids) {
|
|
1379
|
+
return callTool(ctx, knId, "get_relation_types", { ids, response_format: "json" });
|
|
1380
|
+
}
|
|
1138
1381
|
function listTools(ctx, knId) {
|
|
1139
1382
|
return callMethod(ctx, knId, "tools/list");
|
|
1140
1383
|
}
|
|
@@ -1169,8 +1412,16 @@ function context(ctx) {
|
|
|
1169
1412
|
searchSchema: (knId, query, opts) => searchSchema(ctx, knId, query, opts),
|
|
1170
1413
|
queryObjectInstance: (knId, args) => queryObjectInstance(ctx, knId, args),
|
|
1171
1414
|
findSkills: (knId, objectTypeId, topK) => findSkills(ctx, knId, objectTypeId, topK),
|
|
1415
|
+
// Progressive schema disclosure: skeleton first (summary), then drill down.
|
|
1416
|
+
knDetail: (knId, detailLevel) => getKnDetail(ctx, knId, detailLevel),
|
|
1417
|
+
objectTypes: (knId, ids) => getObjectTypes(ctx, knId, ids),
|
|
1418
|
+
relationTypes: (knId, ids) => getRelationTypes(ctx, knId, ids),
|
|
1419
|
+
info: () => mcpInfo(ctx),
|
|
1172
1420
|
tools: (knId) => listTools(ctx, knId),
|
|
1173
1421
|
toolCall: (knId, name, args) => callTool(ctx, knId, name, args),
|
|
1422
|
+
// Generic MCP method passthrough — covers methods not yet wrapped, so the
|
|
1423
|
+
// surface doesn't have to grow every time the server adds one.
|
|
1424
|
+
callMethod: (knId, method, params) => callMethod(ctx, knId, method, params),
|
|
1174
1425
|
queryInstanceSubgraph: (knId, args) => queryInstanceSubgraph(ctx, knId, args),
|
|
1175
1426
|
logicProperties: (knId, args) => getLogicProperties(ctx, knId, args),
|
|
1176
1427
|
actionInfo: (knId, args) => getActionInfo(ctx, knId, args),
|
|
@@ -1227,7 +1478,11 @@ async function executeDataflow(ctx, body, opts = {}) {
|
|
|
1227
1478
|
}
|
|
1228
1479
|
function listDataflowRuns(ctx, dagId, opts = {}) {
|
|
1229
1480
|
return request(ctx, `${BASE2}/dag/${encodeURIComponent(dagId)}/results`, {
|
|
1230
|
-
query: {
|
|
1481
|
+
query: {
|
|
1482
|
+
since: opts.since || void 0,
|
|
1483
|
+
page: opts.page,
|
|
1484
|
+
limit: opts.limit && opts.limit > 0 ? opts.limit : void 0
|
|
1485
|
+
}
|
|
1231
1486
|
});
|
|
1232
1487
|
}
|
|
1233
1488
|
function runDataflowRemote(ctx, dagId, url, name) {
|
|
@@ -1283,9 +1538,11 @@ function deleteKnowledgeNetwork(ctx, knId) {
|
|
|
1283
1538
|
function updateKnowledgeNetwork(ctx, knId, body) {
|
|
1284
1539
|
return request(ctx, `${ONTOLOGY_BASE}/${encodeURIComponent(knId)}`, { method: "PUT", body });
|
|
1285
1540
|
}
|
|
1541
|
+
var QUERY_OVER_POST = { "X-HTTP-Method-Override": "GET" };
|
|
1286
1542
|
function querySubgraph(ctx, knId, body) {
|
|
1287
1543
|
return request(ctx, `${ONTOLOGY_QUERY_BASE}/${encodeURIComponent(knId)}/subgraph`, {
|
|
1288
1544
|
method: "POST",
|
|
1545
|
+
headers: QUERY_OVER_POST,
|
|
1289
1546
|
body
|
|
1290
1547
|
});
|
|
1291
1548
|
}
|
|
@@ -1317,13 +1574,7 @@ function queryObjectTypeInstances(ctx, knId, otId, body) {
|
|
|
1317
1574
|
return request(
|
|
1318
1575
|
ctx,
|
|
1319
1576
|
`${ONTOLOGY_QUERY_BASE}/${encodeURIComponent(knId)}/object-types/${encodeURIComponent(otId)}`,
|
|
1320
|
-
{ method: "POST", body }
|
|
1321
|
-
);
|
|
1322
|
-
}
|
|
1323
|
-
function getObjectTypeProperties(ctx, knId, otId) {
|
|
1324
|
-
return request(
|
|
1325
|
-
ctx,
|
|
1326
|
-
`${ONTOLOGY_QUERY_BASE}/${encodeURIComponent(knId)}/object-types/${encodeURIComponent(otId)}/properties`
|
|
1577
|
+
{ method: "POST", headers: QUERY_OVER_POST, body }
|
|
1327
1578
|
);
|
|
1328
1579
|
}
|
|
1329
1580
|
function queryActionType(ctx, knId, atId, body) {
|
|
@@ -1490,7 +1741,19 @@ function listResources2(ctx, opts = {}) {
|
|
|
1490
1741
|
catalog_id: opts.datasourceId || void 0,
|
|
1491
1742
|
name: opts.name || void 0,
|
|
1492
1743
|
category: opts.category || void 0,
|
|
1493
|
-
|
|
1744
|
+
status: opts.status || void 0,
|
|
1745
|
+
database: opts.database || void 0,
|
|
1746
|
+
// Same `/resources` endpoint as `catalogResources`: limit=-1 (NO_LIMIT)
|
|
1747
|
+
// fetches every row; any other non-positive/invalid value falls back to
|
|
1748
|
+
// the backend default.
|
|
1749
|
+
limit: Number.isFinite(opts.limit) && (opts.limit > 0 || opts.limit === -1) ? opts.limit : void 0,
|
|
1750
|
+
offset: opts.offset,
|
|
1751
|
+
sort: opts.sort,
|
|
1752
|
+
direction: opts.direction,
|
|
1753
|
+
include_extensions: opts.includeExtensions === void 0 ? void 0 : String(opts.includeExtensions),
|
|
1754
|
+
include_extension_keys: opts.includeExtensionKeys || void 0,
|
|
1755
|
+
extension_key: opts.extensionPairs?.map((p) => p.key),
|
|
1756
|
+
extension_value: opts.extensionPairs?.map((p) => p.value)
|
|
1494
1757
|
}
|
|
1495
1758
|
});
|
|
1496
1759
|
}
|
|
@@ -1500,32 +1763,130 @@ function getResource(ctx, id) {
|
|
|
1500
1763
|
function createResourceRaw(ctx, body) {
|
|
1501
1764
|
return request(ctx, BASE3, { method: "POST", body });
|
|
1502
1765
|
}
|
|
1503
|
-
function
|
|
1766
|
+
function updateResourceRaw(ctx, id, body) {
|
|
1767
|
+
return request(ctx, `${BASE3}/${encodeURIComponent(id)}`, { method: "PUT", body });
|
|
1768
|
+
}
|
|
1769
|
+
async function updateResource(ctx, id, patch) {
|
|
1770
|
+
const current = firstResource(await getResource(ctx, id));
|
|
1771
|
+
return updateResourceRaw(ctx, id, resourceUpdateBody(id, current, patch));
|
|
1772
|
+
}
|
|
1773
|
+
async function configureResourceIndex(ctx, id, opts) {
|
|
1774
|
+
const current = firstResource(await getResource(ctx, id));
|
|
1775
|
+
const schema = (current.schema_definition ?? []).map((prop) => ({ ...prop }));
|
|
1776
|
+
const indexConfig = {
|
|
1777
|
+
...current.index_config ?? {},
|
|
1778
|
+
...opts.buildKeyFields?.length ? { build_key_fields: opts.buildKeyFields } : {},
|
|
1779
|
+
...opts.embeddingModel ? { default_embedding_model: opts.embeddingModel } : {},
|
|
1780
|
+
...opts.fulltextAnalyzer ? { default_fulltext_analyzer: opts.fulltextAnalyzer } : {}
|
|
1781
|
+
};
|
|
1782
|
+
for (const field of opts.embeddingFields ?? []) {
|
|
1783
|
+
ensureFeature(
|
|
1784
|
+
schema,
|
|
1785
|
+
field,
|
|
1786
|
+
"vector",
|
|
1787
|
+
opts.embeddingModel ? { embedding_model: opts.embeddingModel } : void 0
|
|
1788
|
+
);
|
|
1789
|
+
}
|
|
1790
|
+
for (const field of opts.fulltextFields ?? []) {
|
|
1791
|
+
ensureFeature(
|
|
1792
|
+
schema,
|
|
1793
|
+
field,
|
|
1794
|
+
"fulltext",
|
|
1795
|
+
opts.fulltextAnalyzer ? { analyzer: opts.fulltextAnalyzer } : void 0
|
|
1796
|
+
);
|
|
1797
|
+
}
|
|
1798
|
+
return updateResourceRaw(
|
|
1799
|
+
ctx,
|
|
1800
|
+
id,
|
|
1801
|
+
resourceUpdateBody(id, current, { schemaDefinition: schema, indexConfig })
|
|
1802
|
+
);
|
|
1803
|
+
}
|
|
1804
|
+
function resourceUpdateBody(id, current, patch) {
|
|
1504
1805
|
const body = {
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1806
|
+
id,
|
|
1807
|
+
name: patch.name ?? current.name,
|
|
1808
|
+
catalog_id: patch.catalogId ?? current.catalog_id,
|
|
1809
|
+
tags: patch.tags ?? current.tags ?? [],
|
|
1810
|
+
description: patch.description ?? current.description ?? "",
|
|
1811
|
+
category: patch.category ?? current.category,
|
|
1812
|
+
status: patch.status ?? current.status,
|
|
1813
|
+
database: patch.database ?? current.database,
|
|
1814
|
+
source_identifier: patch.sourceIdentifier ?? current.source_identifier,
|
|
1815
|
+
source_metadata: patch.sourceMetadata ?? current.source_metadata,
|
|
1816
|
+
schema_definition: patch.schemaDefinition ?? current.schema_definition,
|
|
1817
|
+
index_config: patch.indexConfig === void 0 ? current.index_config : patch.indexConfig,
|
|
1818
|
+
logic_definition: patch.logicDefinition ?? current.logic_definition
|
|
1509
1819
|
};
|
|
1510
|
-
if (
|
|
1511
|
-
|
|
1820
|
+
if (patch.extensions !== void 0 || current.extensions !== void 0) {
|
|
1821
|
+
body.extensions = patch.extensions ?? current.extensions;
|
|
1822
|
+
}
|
|
1823
|
+
return body;
|
|
1824
|
+
}
|
|
1825
|
+
function ensureFeature(schema, field, featureType, config) {
|
|
1826
|
+
const prop = schema.find((p) => p.name === field);
|
|
1827
|
+
if (!prop) throw new Error(`resource field '${field}' not found in schema_definition`);
|
|
1828
|
+
const features = [...prop.features ?? []];
|
|
1829
|
+
const existing = features.find(
|
|
1830
|
+
(f) => f.feature_type === featureType && (f.ref_property || field) === field
|
|
1831
|
+
);
|
|
1832
|
+
if (existing) {
|
|
1833
|
+
existing.ref_property = existing.ref_property || field;
|
|
1834
|
+
existing.config = { ...existing.config ?? {}, ...config ?? {} };
|
|
1835
|
+
} else {
|
|
1836
|
+
features.push({
|
|
1837
|
+
name: `${field}_${featureType}`,
|
|
1838
|
+
feature_type: featureType,
|
|
1839
|
+
ref_property: field,
|
|
1840
|
+
is_default: false,
|
|
1841
|
+
is_native: false,
|
|
1842
|
+
...config ? { config } : {}
|
|
1843
|
+
});
|
|
1844
|
+
}
|
|
1845
|
+
prop.features = features;
|
|
1512
1846
|
}
|
|
1513
|
-
function
|
|
1514
|
-
|
|
1847
|
+
function firstResource(result) {
|
|
1848
|
+
if (result && typeof result === "object") {
|
|
1849
|
+
const o = result;
|
|
1850
|
+
if (Array.isArray(o.entries)) return o.entries[0] ?? {};
|
|
1851
|
+
return o;
|
|
1852
|
+
}
|
|
1853
|
+
return {};
|
|
1854
|
+
}
|
|
1855
|
+
function deleteResource(ctx, id, opts = {}) {
|
|
1856
|
+
const ids = Array.isArray(id) ? id : [id];
|
|
1857
|
+
return request(ctx, `${BASE3}/${ids.map(encodeURIComponent).join(",")}`, {
|
|
1858
|
+
method: "DELETE",
|
|
1859
|
+
query: {
|
|
1860
|
+
ignore_missing: opts.ignoreMissing === void 0 ? void 0 : String(opts.ignoreMissing)
|
|
1861
|
+
}
|
|
1862
|
+
});
|
|
1515
1863
|
}
|
|
1516
1864
|
async function findResource(ctx, name, opts = {}) {
|
|
1517
|
-
const result = await listResources2(ctx, {
|
|
1865
|
+
const result = await listResources2(ctx, {
|
|
1866
|
+
name,
|
|
1867
|
+
datasourceId: opts.datasourceId,
|
|
1868
|
+
limit: opts.limit
|
|
1869
|
+
});
|
|
1518
1870
|
const list = Array.isArray(result) ? result : result.entries ?? [];
|
|
1519
1871
|
return opts.exact ? list.filter((r) => r.name === name) : list;
|
|
1520
1872
|
}
|
|
1521
1873
|
function queryResource(ctx, id, opts = {}) {
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1874
|
+
const body = opts.cursor ? {
|
|
1875
|
+
paging: { cursor: opts.cursor },
|
|
1876
|
+
need_total: opts.needTotal ?? false
|
|
1877
|
+
} : {
|
|
1878
|
+
paging: {
|
|
1879
|
+
mode: opts.pagingMode ?? "single",
|
|
1525
1880
|
limit: opts.limit ?? 50,
|
|
1526
1881
|
offset: opts.offset ?? 0,
|
|
1527
|
-
|
|
1528
|
-
}
|
|
1882
|
+
...opts.keepAliveSec !== void 0 ? { keep_alive_sec: opts.keepAliveSec } : {}
|
|
1883
|
+
},
|
|
1884
|
+
need_total: opts.needTotal ?? false
|
|
1885
|
+
};
|
|
1886
|
+
return request(ctx, `${BASE3}/${encodeURIComponent(id)}/data`, {
|
|
1887
|
+
method: "POST",
|
|
1888
|
+
headers: { "X-HTTP-Method-Override": "GET" },
|
|
1889
|
+
body
|
|
1529
1890
|
});
|
|
1530
1891
|
}
|
|
1531
1892
|
|
|
@@ -2733,7 +3094,6 @@ function knPath(knId, path) {
|
|
|
2733
3094
|
return `${BASE4}/${encodeURIComponent(knId)}/${path}`;
|
|
2734
3095
|
}
|
|
2735
3096
|
async function uploadBkn(ctx, tarBuffer, opts = {}) {
|
|
2736
|
-
applyTls(ctx);
|
|
2737
3097
|
const url = new URL(`${ctx.baseUrl}${BKNS}`);
|
|
2738
3098
|
url.searchParams.set("branch", opts.branch ?? "main");
|
|
2739
3099
|
const form2 = new FormData();
|
|
@@ -2744,17 +3104,19 @@ async function uploadBkn(ctx, tarBuffer, opts = {}) {
|
|
|
2744
3104
|
);
|
|
2745
3105
|
const res = await authFetch(
|
|
2746
3106
|
ctx,
|
|
2747
|
-
() =>
|
|
3107
|
+
() => tlsFetch(ctx.insecure, url, { method: "POST", headers: buildHeaders(ctx), body: form2 })
|
|
2748
3108
|
);
|
|
2749
3109
|
const text = await res.text();
|
|
2750
3110
|
if (!res.ok) throw new HttpError(res.status, res.statusText, text);
|
|
2751
3111
|
return text ? JSON.parse(text) : void 0;
|
|
2752
3112
|
}
|
|
2753
3113
|
async function downloadBkn(ctx, knId, opts = {}) {
|
|
2754
|
-
applyTls(ctx);
|
|
2755
3114
|
const url = new URL(`${ctx.baseUrl}${BKNS}/${encodeURIComponent(knId)}`);
|
|
2756
3115
|
url.searchParams.set("branch", opts.branch ?? "main");
|
|
2757
|
-
const res = await authFetch(
|
|
3116
|
+
const res = await authFetch(
|
|
3117
|
+
ctx,
|
|
3118
|
+
() => tlsFetch(ctx.insecure, url, { method: "GET", headers: buildHeaders(ctx) })
|
|
3119
|
+
);
|
|
2758
3120
|
if (!res.ok) throw new HttpError(res.status, res.statusText, await res.text());
|
|
2759
3121
|
return Buffer.from(await res.arrayBuffer());
|
|
2760
3122
|
}
|
|
@@ -2762,7 +3124,11 @@ function listBknResources(ctx) {
|
|
|
2762
3124
|
return request(ctx, "/api/bkn-backend/v1/resources");
|
|
2763
3125
|
}
|
|
2764
3126
|
function relationTypePaths(ctx, knId, body) {
|
|
2765
|
-
return request(ctx, knPath(knId, "relation-type-paths"), {
|
|
3127
|
+
return request(ctx, knPath(knId, "relation-type-paths"), {
|
|
3128
|
+
method: "POST",
|
|
3129
|
+
headers: { "X-HTTP-Method-Override": "GET" },
|
|
3130
|
+
body
|
|
3131
|
+
});
|
|
2766
3132
|
}
|
|
2767
3133
|
function listConceptGroups(ctx, knId) {
|
|
2768
3134
|
return request(ctx, knPath(knId, "concept-groups"));
|
|
@@ -2823,18 +3189,6 @@ function setActionScheduleStatus(ctx, knId, scheduleId, body) {
|
|
|
2823
3189
|
function deleteActionSchedules(ctx, knId, ids) {
|
|
2824
3190
|
return request(ctx, knPath(knId, `action-schedules/${ids}`), { method: "DELETE" });
|
|
2825
3191
|
}
|
|
2826
|
-
function listJobs(ctx, knId) {
|
|
2827
|
-
return request(ctx, knPath(knId, "jobs"));
|
|
2828
|
-
}
|
|
2829
|
-
function getJob(ctx, knId, jobId) {
|
|
2830
|
-
return request(ctx, knPath(knId, `jobs/${encodeURIComponent(jobId)}`));
|
|
2831
|
-
}
|
|
2832
|
-
function getJobTasks(ctx, knId, jobId) {
|
|
2833
|
-
return request(ctx, knPath(knId, `jobs/${encodeURIComponent(jobId)}/tasks`));
|
|
2834
|
-
}
|
|
2835
|
-
function deleteJobs(ctx, knId, ids) {
|
|
2836
|
-
return request(ctx, knPath(knId, `jobs/${ids}`), { method: "DELETE" });
|
|
2837
|
-
}
|
|
2838
3192
|
|
|
2839
3193
|
// src/api/vega.ts
|
|
2840
3194
|
import { z } from "zod";
|
|
@@ -2843,10 +3197,7 @@ var BuildMode = z.enum(["batch", "streaming"]);
|
|
|
2843
3197
|
var CreateBuildTaskRequest = z.object({
|
|
2844
3198
|
resource_id: z.string().min(1),
|
|
2845
3199
|
mode: BuildMode,
|
|
2846
|
-
|
|
2847
|
-
build_key_fields: z.array(z.string()).optional(),
|
|
2848
|
-
embedding_model: z.string().optional(),
|
|
2849
|
-
model_dimensions: z.number().int().positive().optional()
|
|
3200
|
+
execute_type: z.enum(["incremental", "full"]).optional()
|
|
2850
3201
|
});
|
|
2851
3202
|
var BuildTask = z.object({
|
|
2852
3203
|
id: z.string(),
|
|
@@ -2857,31 +3208,83 @@ var BuildTask = z.object({
|
|
|
2857
3208
|
total_count: z.number().optional(),
|
|
2858
3209
|
synced_count: z.number().optional(),
|
|
2859
3210
|
vectorized_count: z.number().optional(),
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
3211
|
+
index_config: z.unknown().optional(),
|
|
3212
|
+
catalog_id: z.string().optional(),
|
|
3213
|
+
index_health: z.object({
|
|
3214
|
+
embedding: z.string(),
|
|
3215
|
+
fulltext: z.string(),
|
|
3216
|
+
usable: z.boolean()
|
|
3217
|
+
}).passthrough().optional()
|
|
2864
3218
|
}).passthrough();
|
|
2865
3219
|
async function createBuildTask(ctx, req) {
|
|
2866
3220
|
const p = CreateBuildTaskRequest.parse(req);
|
|
2867
3221
|
const body = {
|
|
2868
3222
|
resource_id: p.resource_id,
|
|
2869
3223
|
mode: p.mode,
|
|
2870
|
-
...p.
|
|
2871
|
-
...p.build_key_fields?.length ? { build_key_fields: p.build_key_fields.join(",") } : {},
|
|
2872
|
-
...p.embedding_model ? { embedding_model: p.embedding_model } : {},
|
|
2873
|
-
...p.model_dimensions ? { model_dimensions: p.model_dimensions } : {}
|
|
3224
|
+
...p.execute_type ? { execute_type: p.execute_type } : {}
|
|
2874
3225
|
};
|
|
2875
3226
|
const res = await request(ctx, `${VEGA_BASE}/build-tasks`, { method: "POST", body });
|
|
2876
3227
|
return BuildTask.parse(res);
|
|
2877
3228
|
}
|
|
3229
|
+
function listBuildTasks(ctx, opts = {}) {
|
|
3230
|
+
return request(ctx, `${VEGA_BASE}/build-tasks`, {
|
|
3231
|
+
query: {
|
|
3232
|
+
limit: opts.limit,
|
|
3233
|
+
offset: opts.offset,
|
|
3234
|
+
resource_id: opts.resourceId || void 0,
|
|
3235
|
+
catalog_id: opts.catalogId || void 0,
|
|
3236
|
+
status: Array.isArray(opts.status) ? opts.status.join(",") : opts.status || void 0,
|
|
3237
|
+
active: opts.active === void 0 ? void 0 : String(opts.active),
|
|
3238
|
+
mode: opts.mode,
|
|
3239
|
+
order_by: opts.orderBy,
|
|
3240
|
+
order: opts.order
|
|
3241
|
+
}
|
|
3242
|
+
});
|
|
3243
|
+
}
|
|
2878
3244
|
async function getBuildTask(ctx, taskId) {
|
|
2879
3245
|
const res = await request(ctx, `${VEGA_BASE}/build-tasks/${encodeURIComponent(taskId)}`);
|
|
2880
3246
|
return BuildTask.parse(res);
|
|
2881
3247
|
}
|
|
3248
|
+
function deleteBuildTasks(ctx, ids, opts = {}) {
|
|
3249
|
+
return request(ctx, `${VEGA_BASE}/build-tasks/${ids.map(encodeURIComponent).join(",")}`, {
|
|
3250
|
+
method: "DELETE",
|
|
3251
|
+
query: {
|
|
3252
|
+
ignore_missing: opts.ignoreMissing === void 0 ? void 0 : String(opts.ignoreMissing),
|
|
3253
|
+
delete_active_index: opts.deleteActiveIndex === void 0 ? void 0 : String(opts.deleteActiveIndex)
|
|
3254
|
+
}
|
|
3255
|
+
});
|
|
3256
|
+
}
|
|
3257
|
+
function startBuildTask(ctx, taskId, opts = {}) {
|
|
3258
|
+
return request(ctx, `${VEGA_BASE}/build-tasks/${encodeURIComponent(taskId)}/start`, {
|
|
3259
|
+
method: "POST",
|
|
3260
|
+
body: opts.reset === void 0 ? {} : { reset: opts.reset }
|
|
3261
|
+
});
|
|
3262
|
+
}
|
|
3263
|
+
function stopBuildTask(ctx, taskId) {
|
|
3264
|
+
return request(ctx, `${VEGA_BASE}/build-tasks/${encodeURIComponent(taskId)}/stop`, {
|
|
3265
|
+
method: "POST"
|
|
3266
|
+
});
|
|
3267
|
+
}
|
|
3268
|
+
function runSql(ctx, body) {
|
|
3269
|
+
return request(ctx, `${VEGA_BASE}/resources/query`, { method: "POST", body });
|
|
3270
|
+
}
|
|
2882
3271
|
async function listCatalogs(ctx, opts = {}) {
|
|
2883
3272
|
return request(ctx, `${VEGA_BASE}/catalogs`, {
|
|
2884
|
-
query: {
|
|
3273
|
+
query: {
|
|
3274
|
+
limit: opts.limit,
|
|
3275
|
+
offset: opts.offset,
|
|
3276
|
+
name: opts.name || void 0,
|
|
3277
|
+
tag: opts.tag || void 0,
|
|
3278
|
+
type: opts.type || void 0,
|
|
3279
|
+
enabled: opts.enabled === void 0 ? void 0 : String(opts.enabled),
|
|
3280
|
+
health_check_status: opts.healthCheckStatus || void 0,
|
|
3281
|
+
include_extensions: opts.includeExtensions === void 0 ? void 0 : String(opts.includeExtensions),
|
|
3282
|
+
include_extension_keys: opts.includeExtensionKeys || void 0,
|
|
3283
|
+
extension_key: opts.extensionPairs?.map((p) => p.key),
|
|
3284
|
+
extension_value: opts.extensionPairs?.map((p) => p.value),
|
|
3285
|
+
sort: opts.sort,
|
|
3286
|
+
direction: opts.direction
|
|
3287
|
+
}
|
|
2885
3288
|
});
|
|
2886
3289
|
}
|
|
2887
3290
|
function getCatalog(ctx, id) {
|
|
@@ -2891,18 +3294,49 @@ function createCatalog(ctx, req) {
|
|
|
2891
3294
|
return request(ctx, `${VEGA_BASE}/catalogs`, {
|
|
2892
3295
|
method: "POST",
|
|
2893
3296
|
body: {
|
|
3297
|
+
...req.id ? { id: req.id } : {},
|
|
2894
3298
|
name: req.name,
|
|
2895
3299
|
connector_type: req.connectorType,
|
|
2896
3300
|
connector_config: req.connectorConfig,
|
|
2897
3301
|
...req.tags ? { tags: req.tags } : {},
|
|
2898
3302
|
...req.description ? { description: req.description } : {},
|
|
2899
|
-
...req.enabled !== void 0 ? { enabled: req.enabled } : {}
|
|
3303
|
+
...req.enabled !== void 0 ? { enabled: req.enabled } : {},
|
|
3304
|
+
...req.internal !== void 0 ? { internal: req.internal } : {},
|
|
3305
|
+
...req.extensions ? { extensions: req.extensions } : {}
|
|
3306
|
+
}
|
|
3307
|
+
});
|
|
3308
|
+
}
|
|
3309
|
+
function updateCatalog(ctx, id, req) {
|
|
3310
|
+
return request(ctx, `${VEGA_BASE}/catalogs/${encodeURIComponent(id)}`, {
|
|
3311
|
+
method: "PUT",
|
|
3312
|
+
body: {
|
|
3313
|
+
...req.id ? { id: req.id } : {},
|
|
3314
|
+
...req.name ? { name: req.name } : {},
|
|
3315
|
+
...req.connectorType ? { connector_type: req.connectorType } : {},
|
|
3316
|
+
...req.connectorConfig !== void 0 ? { connector_config: req.connectorConfig } : {},
|
|
3317
|
+
...req.tags ? { tags: req.tags } : {},
|
|
3318
|
+
...req.description !== void 0 ? { description: req.description } : {},
|
|
3319
|
+
...req.enabled !== void 0 ? { enabled: req.enabled } : {},
|
|
3320
|
+
...req.extensions ? { extensions: req.extensions } : {}
|
|
2900
3321
|
}
|
|
2901
3322
|
});
|
|
2902
3323
|
}
|
|
2903
3324
|
function enableCatalog(ctx, id) {
|
|
2904
3325
|
return request(ctx, `${VEGA_BASE}/catalogs/${encodeURIComponent(id)}/enable`, { method: "POST" });
|
|
2905
3326
|
}
|
|
3327
|
+
function disableCatalog(ctx, id) {
|
|
3328
|
+
return request(ctx, `${VEGA_BASE}/catalogs/${encodeURIComponent(id)}/disable`, {
|
|
3329
|
+
method: "POST"
|
|
3330
|
+
});
|
|
3331
|
+
}
|
|
3332
|
+
function deleteCatalog(ctx, id) {
|
|
3333
|
+
return request(ctx, `${VEGA_BASE}/catalogs/${encodeURIComponent(id)}`, { method: "DELETE" });
|
|
3334
|
+
}
|
|
3335
|
+
function testCatalogConnection(ctx, id) {
|
|
3336
|
+
return request(ctx, `${VEGA_BASE}/catalogs/${encodeURIComponent(id)}/test-connection`, {
|
|
3337
|
+
method: "POST"
|
|
3338
|
+
});
|
|
3339
|
+
}
|
|
2906
3340
|
function discoverCatalog(ctx, id, wait = true) {
|
|
2907
3341
|
return request(ctx, `${VEGA_BASE}/catalogs/${encodeURIComponent(id)}/discover`, {
|
|
2908
3342
|
method: "POST",
|
|
@@ -2910,9 +3344,15 @@ function discoverCatalog(ctx, id, wait = true) {
|
|
|
2910
3344
|
timeoutMs: 12e4
|
|
2911
3345
|
});
|
|
2912
3346
|
}
|
|
2913
|
-
function listCatalogResources(ctx, id, category) {
|
|
3347
|
+
function listCatalogResources(ctx, id, category, limit, offset) {
|
|
2914
3348
|
return request(ctx, `${VEGA_BASE}/resources`, {
|
|
2915
|
-
query: {
|
|
3349
|
+
query: {
|
|
3350
|
+
catalog_id: id,
|
|
3351
|
+
category: category || void 0,
|
|
3352
|
+
// limit=-1 (NO_LIMIT) fetches all; NaN / 0 fall back to the backend default.
|
|
3353
|
+
limit: Number.isFinite(limit) && (limit > 0 || limit === -1) ? limit : void 0,
|
|
3354
|
+
offset: offset || void 0
|
|
3355
|
+
}
|
|
2916
3356
|
});
|
|
2917
3357
|
}
|
|
2918
3358
|
function catalogHealthStatus(ctx, ids) {
|
|
@@ -3372,7 +3812,7 @@ async function createFromCatalog(ctx, opts) {
|
|
|
3372
3812
|
}
|
|
3373
3813
|
tablePk[t.name] = res.pk;
|
|
3374
3814
|
}
|
|
3375
|
-
log(`
|
|
3815
|
+
log(`Resolving discovered resources for ${targets.length} table(s)...`);
|
|
3376
3816
|
const viewMap = {};
|
|
3377
3817
|
for (const t of targets) {
|
|
3378
3818
|
const found = asArray(
|
|
@@ -3382,13 +3822,9 @@ async function createFromCatalog(ctx, opts) {
|
|
|
3382
3822
|
if (existingId) {
|
|
3383
3823
|
viewMap[t.name] = existingId;
|
|
3384
3824
|
} else {
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
sourceIdentifier: t.name,
|
|
3389
|
-
fields: t.columns.map((c) => ({ name: c.name, type: c.type }))
|
|
3390
|
-
});
|
|
3391
|
-
viewMap[t.name] = String(created.id ?? "");
|
|
3825
|
+
throw new Error(
|
|
3826
|
+
`Table '${t.name}' has no discovered Vega resource. Run catalog discover and retry.`
|
|
3827
|
+
);
|
|
3392
3828
|
}
|
|
3393
3829
|
}
|
|
3394
3830
|
const knCreated = await createKnowledgeNetwork(ctx, { name: opts.name });
|
|
@@ -3420,12 +3856,14 @@ async function createFromCatalog(ctx, opts) {
|
|
|
3420
3856
|
log("Submitting build tasks...");
|
|
3421
3857
|
for (const t of targets) {
|
|
3422
3858
|
const embedding = opts.embeddingFields?.[t.name];
|
|
3859
|
+
await configureResourceIndex(ctx, viewMap[t.name], {
|
|
3860
|
+
buildKeyFields: [tablePk[t.name]],
|
|
3861
|
+
...embedding && embedding.length > 0 ? { embeddingFields: embedding } : {},
|
|
3862
|
+
...opts.embeddingModel ? { embeddingModel: opts.embeddingModel } : {}
|
|
3863
|
+
});
|
|
3423
3864
|
const task = await createBuildTask(ctx, {
|
|
3424
3865
|
resource_id: viewMap[t.name],
|
|
3425
|
-
mode: "batch"
|
|
3426
|
-
build_key_fields: [tablePk[t.name]],
|
|
3427
|
-
...embedding && embedding.length > 0 ? { embedding_fields: embedding } : {},
|
|
3428
|
-
...opts.embeddingModel ? { embedding_model: opts.embeddingModel } : {}
|
|
3866
|
+
mode: "batch"
|
|
3429
3867
|
});
|
|
3430
3868
|
builds.push({ table: t.name, taskId: String(task.id ?? "") });
|
|
3431
3869
|
}
|
|
@@ -3556,7 +3994,6 @@ function kn(ctx) {
|
|
|
3556
3994
|
metricValidate: (knId, body) => validateMetric(ctx, knId, body),
|
|
3557
3995
|
objectTypes: (knId, opts) => listObjectTypes(ctx, knId, opts),
|
|
3558
3996
|
objectTypeQuery: (knId, otId, body) => queryObjectTypeInstances(ctx, knId, otId, body),
|
|
3559
|
-
objectTypeProperties: (knId, otId) => getObjectTypeProperties(ctx, knId, otId),
|
|
3560
3997
|
objectTypeGet: (knId, id) => getSchemaItem(ctx, knId, "object-types", id),
|
|
3561
3998
|
objectTypeCreate: (knId, body) => createSchemaItem(ctx, knId, "object-types", body),
|
|
3562
3999
|
objectTypeUpdate: (knId, id, body) => updateSchemaItem(ctx, knId, "object-types", id, body),
|
|
@@ -3584,10 +4021,6 @@ function kn(ctx) {
|
|
|
3584
4021
|
actionScheduleUpdate: (knId, scheduleId, body) => updateActionSchedule(ctx, knId, scheduleId, body),
|
|
3585
4022
|
actionScheduleSetStatus: (knId, scheduleId, body) => setActionScheduleStatus(ctx, knId, scheduleId, body),
|
|
3586
4023
|
actionScheduleDelete: (knId, ids) => deleteActionSchedules(ctx, knId, ids),
|
|
3587
|
-
jobs: (knId) => listJobs(ctx, knId),
|
|
3588
|
-
job: (knId, jobId) => getJob(ctx, knId, jobId),
|
|
3589
|
-
jobTasks: (knId, jobId) => getJobTasks(ctx, knId, jobId),
|
|
3590
|
-
jobDelete: (knId, ids) => deleteJobs(ctx, knId, ids),
|
|
3591
4024
|
relationTypePaths: (knId, body) => relationTypePaths(ctx, knId, body),
|
|
3592
4025
|
bknResources: () => listBknResources(ctx),
|
|
3593
4026
|
createFromCatalog: (opts) => createFromCatalog(ctx, opts),
|
|
@@ -3603,12 +4036,19 @@ function kn(ctx) {
|
|
|
3603
4036
|
const targets = collectIndexTargets(dir);
|
|
3604
4037
|
const buildTasks = [];
|
|
3605
4038
|
for (const t of targets) {
|
|
4039
|
+
if (!t.buildKey) {
|
|
4040
|
+
throw new Error(
|
|
4041
|
+
`Object type '${t.objectType}' declares a vector index but no build key; batch Vega builds require resource index_config.build_key_fields.`
|
|
4042
|
+
);
|
|
4043
|
+
}
|
|
4044
|
+
await configureResourceIndex(ctx, t.resourceId, {
|
|
4045
|
+
buildKeyFields: [t.buildKey],
|
|
4046
|
+
embeddingFields: t.embeddingFields,
|
|
4047
|
+
...t.embeddingModel ?? opts.embeddingModel ? { embeddingModel: t.embeddingModel ?? opts.embeddingModel } : {}
|
|
4048
|
+
});
|
|
3606
4049
|
const task = await createBuildTask(ctx, {
|
|
3607
4050
|
resource_id: t.resourceId,
|
|
3608
|
-
mode: "batch"
|
|
3609
|
-
embedding_fields: t.embeddingFields,
|
|
3610
|
-
...t.buildKey ? { build_key_fields: [t.buildKey] } : {},
|
|
3611
|
-
...t.embeddingModel ?? opts.embeddingModel ? { embedding_model: t.embeddingModel ?? opts.embeddingModel } : {}
|
|
4051
|
+
mode: "batch"
|
|
3612
4052
|
});
|
|
3613
4053
|
buildTasks.push({
|
|
3614
4054
|
objectType: t.objectType,
|
|
@@ -3663,10 +4103,9 @@ function deltaContent(chunk) {
|
|
|
3663
4103
|
return typeof c === "string" ? c : "";
|
|
3664
4104
|
}
|
|
3665
4105
|
async function chatCompletionsStream(ctx, model, messages, onDelta) {
|
|
3666
|
-
applyTls(ctx);
|
|
3667
4106
|
const res = await authFetch(
|
|
3668
4107
|
ctx,
|
|
3669
|
-
() =>
|
|
4108
|
+
() => tlsFetch(ctx.insecure, `${ctx.baseUrl}${API}/chat/completions`, {
|
|
3670
4109
|
method: "POST",
|
|
3671
4110
|
headers: {
|
|
3672
4111
|
...buildHeaders(ctx),
|
|
@@ -3725,6 +4164,23 @@ function deleteModels(ctx, kind, modelIds) {
|
|
|
3725
4164
|
function testModel(ctx, kind, body) {
|
|
3726
4165
|
return request(ctx, `${MANAGER}/${kind}/test`, { method: "POST", body });
|
|
3727
4166
|
}
|
|
4167
|
+
function setDefaultLlm(ctx, modelId, isDefault = true) {
|
|
4168
|
+
return request(ctx, `${MANAGER}/llm/default/edit`, {
|
|
4169
|
+
method: "POST",
|
|
4170
|
+
body: { model_id: modelId, default: isDefault }
|
|
4171
|
+
});
|
|
4172
|
+
}
|
|
4173
|
+
function setDefaultSmallModel(ctx, modelId, isDefault = true) {
|
|
4174
|
+
return request(ctx, `${MANAGER}/small-model/set-default`, {
|
|
4175
|
+
method: "POST",
|
|
4176
|
+
body: { model_id: modelId, default: isDefault }
|
|
4177
|
+
});
|
|
4178
|
+
}
|
|
4179
|
+
function getDefaultSmallModel(ctx, modelType = "embedding") {
|
|
4180
|
+
return request(ctx, `${MANAGER}/small-model/get_default`, {
|
|
4181
|
+
query: { model_type: modelType }
|
|
4182
|
+
});
|
|
4183
|
+
}
|
|
3728
4184
|
function rerank(ctx, model, query, documents) {
|
|
3729
4185
|
return request(ctx, `${API}/small-model/reranker`, {
|
|
3730
4186
|
method: "POST",
|
|
@@ -3743,7 +4199,9 @@ function models(ctx) {
|
|
|
3743
4199
|
add: (body) => addModel(ctx, "llm", body),
|
|
3744
4200
|
edit: (body) => editModel(ctx, "llm", body),
|
|
3745
4201
|
delete: (modelIds) => deleteModels(ctx, "llm", modelIds),
|
|
3746
|
-
test: (body) => testModel(ctx, "llm", body)
|
|
4202
|
+
test: (body) => testModel(ctx, "llm", body),
|
|
4203
|
+
/** Set (or clear) the system default LLM. */
|
|
4204
|
+
setDefault: (modelId, isDefault = true) => setDefaultLlm(ctx, modelId, isDefault)
|
|
3747
4205
|
},
|
|
3748
4206
|
small: {
|
|
3749
4207
|
list: (opts) => listSmallModels(ctx, opts),
|
|
@@ -3753,7 +4211,11 @@ function models(ctx) {
|
|
|
3753
4211
|
add: (body) => addModel(ctx, "small-model", body),
|
|
3754
4212
|
edit: (body) => editModel(ctx, "small-model", body),
|
|
3755
4213
|
delete: (modelIds) => deleteModels(ctx, "small-model", modelIds),
|
|
3756
|
-
test: (body) => testModel(ctx, "small-model", body)
|
|
4214
|
+
test: (body) => testModel(ctx, "small-model", body),
|
|
4215
|
+
/** Set (or clear) the system default small model (type inferred from the model). */
|
|
4216
|
+
setDefault: (modelId, isDefault = true) => setDefaultSmallModel(ctx, modelId, isDefault),
|
|
4217
|
+
/** Get the system default small model for a type (default "embedding"). */
|
|
4218
|
+
getDefault: (modelType) => getDefaultSmallModel(ctx, modelType)
|
|
3757
4219
|
}
|
|
3758
4220
|
};
|
|
3759
4221
|
}
|
|
@@ -3764,6 +4226,8 @@ function resources(ctx) {
|
|
|
3764
4226
|
list: (opts) => listResources2(ctx, opts),
|
|
3765
4227
|
get: (id) => getResource(ctx, id),
|
|
3766
4228
|
delete: (id) => deleteResource(ctx, id),
|
|
4229
|
+
update: (id, patch) => updateResource(ctx, id, patch),
|
|
4230
|
+
configureIndex: (id, opts) => configureResourceIndex(ctx, id, opts),
|
|
3767
4231
|
find: (name, opts) => findResource(ctx, name, opts),
|
|
3768
4232
|
query: (id, opts) => queryResource(ctx, id, opts)
|
|
3769
4233
|
};
|
|
@@ -3776,7 +4240,6 @@ import { basename as basename2, dirname as dirname3, resolve as resolve5 } from
|
|
|
3776
4240
|
// src/api/skills.ts
|
|
3777
4241
|
var BASE5 = "/api/agent-operator-integration/v1";
|
|
3778
4242
|
async function registerSkillZip(ctx, bytes, opts = {}) {
|
|
3779
|
-
applyTls(ctx);
|
|
3780
4243
|
const form2 = new FormData();
|
|
3781
4244
|
form2.set("file_type", "zip");
|
|
3782
4245
|
form2.set("file", new Blob([bytes]), opts.filename ?? "skill.zip");
|
|
@@ -3784,7 +4247,7 @@ async function registerSkillZip(ctx, bytes, opts = {}) {
|
|
|
3784
4247
|
if (opts.extendInfo) form2.set("extend_info", JSON.stringify(opts.extendInfo));
|
|
3785
4248
|
const res = await authFetch(
|
|
3786
4249
|
ctx,
|
|
3787
|
-
() =>
|
|
4250
|
+
() => tlsFetch(ctx.insecure, `${ctx.baseUrl}${BASE5}/skills`, {
|
|
3788
4251
|
method: "POST",
|
|
3789
4252
|
headers: buildHeaders(ctx),
|
|
3790
4253
|
body: form2
|
|
@@ -3795,13 +4258,12 @@ async function registerSkillZip(ctx, bytes, opts = {}) {
|
|
|
3795
4258
|
return text ? JSON.parse(text) : void 0;
|
|
3796
4259
|
}
|
|
3797
4260
|
async function updateSkillPackageZip(ctx, skillId, bytes, filename = "skill.zip") {
|
|
3798
|
-
applyTls(ctx);
|
|
3799
4261
|
const form2 = new FormData();
|
|
3800
4262
|
form2.set("file_type", "zip");
|
|
3801
4263
|
form2.set("file", new Blob([bytes]), filename);
|
|
3802
4264
|
const res = await authFetch(
|
|
3803
4265
|
ctx,
|
|
3804
|
-
() =>
|
|
4266
|
+
() => tlsFetch(ctx.insecure, `${ctx.baseUrl}${BASE5}/skills/${encodeURIComponent(skillId)}/package`, {
|
|
3805
4267
|
method: "PUT",
|
|
3806
4268
|
headers: buildHeaders(ctx),
|
|
3807
4269
|
body: form2
|
|
@@ -3812,10 +4274,9 @@ async function updateSkillPackageZip(ctx, skillId, bytes, filename = "skill.zip"
|
|
|
3812
4274
|
return text ? JSON.parse(text) : void 0;
|
|
3813
4275
|
}
|
|
3814
4276
|
async function downloadSkill(ctx, skillId) {
|
|
3815
|
-
applyTls(ctx);
|
|
3816
4277
|
const res = await authFetch(
|
|
3817
4278
|
ctx,
|
|
3818
|
-
() =>
|
|
4279
|
+
() => tlsFetch(ctx.insecure, `${ctx.baseUrl}${BASE5}/skills/${encodeURIComponent(skillId)}/download`, {
|
|
3819
4280
|
headers: buildHeaders(ctx)
|
|
3820
4281
|
})
|
|
3821
4282
|
);
|
|
@@ -3971,25 +4432,27 @@ import { basename as basename3 } from "path";
|
|
|
3971
4432
|
var PATH = "/api/agent-operator-integration/v1/tool-box";
|
|
3972
4433
|
var IMPEX = "/api/agent-operator-integration/v1/impex";
|
|
3973
4434
|
async function exportConfig(ctx, id, type = "toolbox") {
|
|
3974
|
-
applyTls(ctx);
|
|
3975
4435
|
const res = await authFetch(
|
|
3976
4436
|
ctx,
|
|
3977
|
-
() =>
|
|
3978
|
-
|
|
3979
|
-
|
|
4437
|
+
() => tlsFetch(
|
|
4438
|
+
ctx.insecure,
|
|
4439
|
+
`${ctx.baseUrl}${IMPEX}/export/${encodeURIComponent(type)}/${encodeURIComponent(id)}`,
|
|
4440
|
+
{
|
|
4441
|
+
headers: buildHeaders(ctx)
|
|
4442
|
+
}
|
|
4443
|
+
)
|
|
3980
4444
|
);
|
|
3981
4445
|
const buf = new Uint8Array(await res.arrayBuffer());
|
|
3982
4446
|
if (!res.ok) throw new HttpError(res.status, res.statusText, new TextDecoder().decode(buf));
|
|
3983
4447
|
return buf;
|
|
3984
4448
|
}
|
|
3985
4449
|
async function importConfig(ctx, filePath, type = "toolbox") {
|
|
3986
|
-
applyTls(ctx);
|
|
3987
4450
|
const buf = await readFile2(filePath);
|
|
3988
4451
|
const form2 = new FormData();
|
|
3989
4452
|
form2.append("data", new Blob([new Uint8Array(buf)]), basename3(filePath));
|
|
3990
4453
|
const res = await authFetch(
|
|
3991
4454
|
ctx,
|
|
3992
|
-
() =>
|
|
4455
|
+
() => tlsFetch(ctx.insecure, `${ctx.baseUrl}${IMPEX}/import/${encodeURIComponent(type)}`, {
|
|
3993
4456
|
method: "POST",
|
|
3994
4457
|
headers: buildHeaders(ctx),
|
|
3995
4458
|
body: form2
|
|
@@ -4000,14 +4463,13 @@ async function importConfig(ctx, filePath, type = "toolbox") {
|
|
|
4000
4463
|
return text ? JSON.parse(text) : text;
|
|
4001
4464
|
}
|
|
4002
4465
|
async function uploadTool(ctx, boxId, filePath, metadataType = "openapi") {
|
|
4003
|
-
applyTls(ctx);
|
|
4004
4466
|
const buf = await readFile2(filePath);
|
|
4005
4467
|
const form2 = new FormData();
|
|
4006
4468
|
form2.append("metadata_type", metadataType);
|
|
4007
4469
|
form2.append("data", new Blob([new Uint8Array(buf)]), basename3(filePath));
|
|
4008
4470
|
const res = await authFetch(
|
|
4009
4471
|
ctx,
|
|
4010
|
-
() =>
|
|
4472
|
+
() => tlsFetch(ctx.insecure, `${ctx.baseUrl}${PATH}/${encodeURIComponent(boxId)}/tool`, {
|
|
4011
4473
|
method: "POST",
|
|
4012
4474
|
headers: buildHeaders(ctx),
|
|
4013
4475
|
body: form2
|
|
@@ -4022,8 +4484,14 @@ function listToolboxes(ctx, opts = {}) {
|
|
|
4022
4484
|
query: { keyword: opts.keyword || void 0, limit: opts.limit, offset: opts.offset ?? 0 }
|
|
4023
4485
|
});
|
|
4024
4486
|
}
|
|
4025
|
-
function listTools2(ctx, boxId) {
|
|
4026
|
-
return request(ctx, `${PATH}/${encodeURIComponent(boxId)}/tools/list
|
|
4487
|
+
function listTools2(ctx, boxId, opts = {}) {
|
|
4488
|
+
return request(ctx, `${PATH}/${encodeURIComponent(boxId)}/tools/list`, {
|
|
4489
|
+
query: {
|
|
4490
|
+
page: opts.page,
|
|
4491
|
+
page_size: Number.isFinite(opts.pageSize) && opts.pageSize > 0 ? opts.pageSize : void 0,
|
|
4492
|
+
all: opts.all ? "true" : void 0
|
|
4493
|
+
}
|
|
4494
|
+
});
|
|
4027
4495
|
}
|
|
4028
4496
|
function createToolbox(ctx, opts) {
|
|
4029
4497
|
return request(ctx, PATH, {
|
|
@@ -4082,7 +4550,7 @@ function setToolStatuses(ctx, boxId, updates) {
|
|
|
4082
4550
|
function toolboxes(ctx) {
|
|
4083
4551
|
return {
|
|
4084
4552
|
list: (opts) => listToolboxes(ctx, opts),
|
|
4085
|
-
tools: (boxId) => listTools2(ctx, boxId),
|
|
4553
|
+
tools: (boxId, opts) => listTools2(ctx, boxId, opts),
|
|
4086
4554
|
create: (opts) => createToolbox(ctx, opts),
|
|
4087
4555
|
delete: (boxId) => deleteToolbox(ctx, boxId),
|
|
4088
4556
|
publish: (boxId) => setToolboxStatus(ctx, boxId, "published"),
|
|
@@ -4108,8 +4576,728 @@ function toolboxes(ctx) {
|
|
|
4108
4576
|
};
|
|
4109
4577
|
}
|
|
4110
4578
|
|
|
4579
|
+
// src/trace-session.ts
|
|
4580
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
4581
|
+
var PAYLOAD_FIELDS = {
|
|
4582
|
+
"agent.interaction.started": /* @__PURE__ */ new Set([
|
|
4583
|
+
"intent_hash",
|
|
4584
|
+
"mode",
|
|
4585
|
+
"agent_id",
|
|
4586
|
+
"app_ref",
|
|
4587
|
+
"question_artifact_ref"
|
|
4588
|
+
]),
|
|
4589
|
+
"retrieval.completed": /* @__PURE__ */ new Set([
|
|
4590
|
+
"query_hash",
|
|
4591
|
+
"candidate_count",
|
|
4592
|
+
"truncated",
|
|
4593
|
+
"version_status",
|
|
4594
|
+
"source_refs"
|
|
4595
|
+
]),
|
|
4596
|
+
"knowledge.read.observed": /* @__PURE__ */ new Set([
|
|
4597
|
+
"kn_id",
|
|
4598
|
+
"read_kind",
|
|
4599
|
+
"version_status",
|
|
4600
|
+
"schema_version",
|
|
4601
|
+
"business_refs"
|
|
4602
|
+
]),
|
|
4603
|
+
"data.query.observed": /* @__PURE__ */ new Set([
|
|
4604
|
+
"query_hash",
|
|
4605
|
+
"query_type",
|
|
4606
|
+
"row_count",
|
|
4607
|
+
"truncated",
|
|
4608
|
+
"as_of",
|
|
4609
|
+
"version_status",
|
|
4610
|
+
"resource_refs",
|
|
4611
|
+
"field_refs",
|
|
4612
|
+
"query_artifact_ref",
|
|
4613
|
+
"result_artifact_ref"
|
|
4614
|
+
]),
|
|
4615
|
+
"logic.execution.observed": /* @__PURE__ */ new Set([
|
|
4616
|
+
"logic_ref",
|
|
4617
|
+
"input_artifact_ref",
|
|
4618
|
+
"result_artifact_ref",
|
|
4619
|
+
"status"
|
|
4620
|
+
]),
|
|
4621
|
+
"model.call.observed": /* @__PURE__ */ new Set([
|
|
4622
|
+
"model_name",
|
|
4623
|
+
"model_provider",
|
|
4624
|
+
"status",
|
|
4625
|
+
"input_token_count",
|
|
4626
|
+
"output_token_count",
|
|
4627
|
+
"prompt_hash",
|
|
4628
|
+
"output_hash",
|
|
4629
|
+
"error_category",
|
|
4630
|
+
"error_hash"
|
|
4631
|
+
]),
|
|
4632
|
+
"tool.called": /* @__PURE__ */ new Set(["tool_id", "tool_name", "args_hash", "visibility", "version_status"]),
|
|
4633
|
+
"tool.result.observed": /* @__PURE__ */ new Set([
|
|
4634
|
+
"tool_id",
|
|
4635
|
+
"tool_name",
|
|
4636
|
+
"status",
|
|
4637
|
+
"result_hash",
|
|
4638
|
+
"result_length",
|
|
4639
|
+
"result_count",
|
|
4640
|
+
"error_hash",
|
|
4641
|
+
"error_category",
|
|
4642
|
+
"visibility",
|
|
4643
|
+
"version_status"
|
|
4644
|
+
]),
|
|
4645
|
+
"claim.created": /* @__PURE__ */ new Set([
|
|
4646
|
+
"claim_id",
|
|
4647
|
+
"claim_type",
|
|
4648
|
+
"claim_hash",
|
|
4649
|
+
"source_event_ids",
|
|
4650
|
+
"operation_ids",
|
|
4651
|
+
"visibility",
|
|
4652
|
+
"version_status",
|
|
4653
|
+
"result_artifact_ref"
|
|
4654
|
+
]),
|
|
4655
|
+
"evidence.refs.created": /* @__PURE__ */ new Set(["claim_id", "evidence_refs"]),
|
|
4656
|
+
"business.refs.resolved": /* @__PURE__ */ new Set(["claim_id", "resolver_status", "business_refs"]),
|
|
4657
|
+
"action.recommended": /* @__PURE__ */ new Set([
|
|
4658
|
+
"action_instance_id",
|
|
4659
|
+
"action_type",
|
|
4660
|
+
"target_refs",
|
|
4661
|
+
"reason_hash",
|
|
4662
|
+
"status",
|
|
4663
|
+
"reason_artifact_ref",
|
|
4664
|
+
"input_artifact_ref"
|
|
4665
|
+
]),
|
|
4666
|
+
"action.approval_requested": /* @__PURE__ */ new Set(["action_instance_id", "policy_ref", "status"]),
|
|
4667
|
+
"action.approved": /* @__PURE__ */ new Set(["action_instance_id", "actor_ref", "policy_decision_ref", "status"]),
|
|
4668
|
+
"action.rejected": /* @__PURE__ */ new Set(["action_instance_id", "actor_ref", "policy_decision_ref", "status"]),
|
|
4669
|
+
"action.executed": /* @__PURE__ */ new Set([
|
|
4670
|
+
"action_instance_id",
|
|
4671
|
+
"status",
|
|
4672
|
+
"invocation_ref",
|
|
4673
|
+
"error_category",
|
|
4674
|
+
"error_hash"
|
|
4675
|
+
]),
|
|
4676
|
+
"action.result_recorded": /* @__PURE__ */ new Set([
|
|
4677
|
+
"action_instance_id",
|
|
4678
|
+
"status",
|
|
4679
|
+
"result_hash",
|
|
4680
|
+
"task_ref",
|
|
4681
|
+
"artifact_ref",
|
|
4682
|
+
"result_artifact_ref"
|
|
4683
|
+
])
|
|
4684
|
+
};
|
|
4685
|
+
var REQUIRED_PAYLOAD_FIELDS = {
|
|
4686
|
+
"agent.interaction.started": ["intent_hash", "mode"],
|
|
4687
|
+
"retrieval.completed": ["query_hash", "candidate_count", "truncated"],
|
|
4688
|
+
"knowledge.read.observed": ["kn_id", "read_kind", "version_status"],
|
|
4689
|
+
"data.query.observed": ["query_hash", "query_type", "row_count"],
|
|
4690
|
+
"logic.execution.observed": ["logic_ref", "input_artifact_ref", "result_artifact_ref", "status"],
|
|
4691
|
+
"model.call.observed": [
|
|
4692
|
+
"model_name",
|
|
4693
|
+
"model_provider",
|
|
4694
|
+
"status",
|
|
4695
|
+
"input_token_count",
|
|
4696
|
+
"output_token_count",
|
|
4697
|
+
"prompt_hash",
|
|
4698
|
+
"output_hash"
|
|
4699
|
+
],
|
|
4700
|
+
"tool.called": ["tool_id", "tool_name", "args_hash", "visibility", "version_status"],
|
|
4701
|
+
"tool.result.observed": ["tool_id", "tool_name", "status", "visibility", "version_status"],
|
|
4702
|
+
"claim.created": [
|
|
4703
|
+
"claim_id",
|
|
4704
|
+
"claim_type",
|
|
4705
|
+
"claim_hash",
|
|
4706
|
+
"source_event_ids",
|
|
4707
|
+
"operation_ids",
|
|
4708
|
+
"visibility",
|
|
4709
|
+
"version_status"
|
|
4710
|
+
],
|
|
4711
|
+
"evidence.refs.created": ["claim_id", "evidence_refs"],
|
|
4712
|
+
"business.refs.resolved": ["claim_id", "resolver_status", "business_refs"],
|
|
4713
|
+
"action.recommended": [
|
|
4714
|
+
"action_instance_id",
|
|
4715
|
+
"action_type",
|
|
4716
|
+
"target_refs",
|
|
4717
|
+
"reason_hash",
|
|
4718
|
+
"status"
|
|
4719
|
+
],
|
|
4720
|
+
"action.approval_requested": ["action_instance_id", "policy_ref", "status"],
|
|
4721
|
+
"action.approved": ["action_instance_id", "actor_ref", "policy_decision_ref", "status"],
|
|
4722
|
+
"action.rejected": ["action_instance_id", "actor_ref", "policy_decision_ref", "status"],
|
|
4723
|
+
"action.executed": ["action_instance_id", "status", "invocation_ref"],
|
|
4724
|
+
"action.result_recorded": ["action_instance_id", "status", "result_hash"]
|
|
4725
|
+
};
|
|
4726
|
+
var REF_FIELDS = /* @__PURE__ */ new Set([
|
|
4727
|
+
"ref_id",
|
|
4728
|
+
"ref_type",
|
|
4729
|
+
"source_system",
|
|
4730
|
+
"validity",
|
|
4731
|
+
"version_status",
|
|
4732
|
+
"visibility",
|
|
4733
|
+
"summary_hash"
|
|
4734
|
+
]);
|
|
4735
|
+
var RAW_KEYS = /* @__PURE__ */ new Set([
|
|
4736
|
+
"authorization",
|
|
4737
|
+
"cookie",
|
|
4738
|
+
"access_token",
|
|
4739
|
+
"refresh_token",
|
|
4740
|
+
"id_token",
|
|
4741
|
+
"api_key",
|
|
4742
|
+
"password",
|
|
4743
|
+
"private_key",
|
|
4744
|
+
"prompt",
|
|
4745
|
+
"user_question",
|
|
4746
|
+
"approval_comment",
|
|
4747
|
+
"sql",
|
|
4748
|
+
"query_params",
|
|
4749
|
+
"rows"
|
|
4750
|
+
]);
|
|
4751
|
+
var HASH_RE = /^sha256:[0-9a-f]{64}$/;
|
|
4752
|
+
var RAW_VALUE_PATTERNS = [
|
|
4753
|
+
/bearer\s+[A-Za-z0-9._-]+/i,
|
|
4754
|
+
/\bselect\s+.+\s+from\b/is,
|
|
4755
|
+
/\binsert\s+into\b/i,
|
|
4756
|
+
/\bupdate\s+\S+\s+set\b/i,
|
|
4757
|
+
/\bdelete\s+from\b/i,
|
|
4758
|
+
/[\w.+-]+@[\w.-]+\.[A-Za-z]{2,}/,
|
|
4759
|
+
/https?:\/\/[^\s"']+/i
|
|
4760
|
+
];
|
|
4761
|
+
function defaultNow() {
|
|
4762
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
4763
|
+
}
|
|
4764
|
+
function assertSessionOptions(options) {
|
|
4765
|
+
const trace2 = options.trace;
|
|
4766
|
+
if (!/^[0-9a-f]{32}$/.test(trace2.trace_id)) throw new Error("trace_id must be 32 hex characters");
|
|
4767
|
+
const traceparent = /^00-([0-9a-f]{32})-[0-9a-f]{16}-[0-9a-f]{2}$/.exec(trace2.traceparent);
|
|
4768
|
+
if (!traceparent || traceparent[1] !== trace2.trace_id) {
|
|
4769
|
+
throw new Error("traceparent must be valid and match trace_id");
|
|
4770
|
+
}
|
|
4771
|
+
if (!/^req_[0-9A-Za-z_.-]+$/.test(trace2["bkn.request.id"])) {
|
|
4772
|
+
throw new Error("bkn.request.id must start with req_");
|
|
4773
|
+
}
|
|
4774
|
+
const conversationId = options.conversationId ?? trace2["bkn.conversation.id"];
|
|
4775
|
+
if (conversationId && !/^[0-9A-Za-z_.:-]{1,128}$/.test(conversationId)) {
|
|
4776
|
+
throw new Error("conversationId must be an opaque correlation identifier");
|
|
4777
|
+
}
|
|
4778
|
+
if (options.conversationId && trace2["bkn.conversation.id"] && options.conversationId !== trace2["bkn.conversation.id"]) {
|
|
4779
|
+
throw new Error("conversationId conflicts with trace bkn.conversation.id");
|
|
4780
|
+
}
|
|
4781
|
+
if (!trace2["bkn.tenant.id"] && !trace2.business_domain) {
|
|
4782
|
+
throw new Error("trace requires bkn.tenant.id or business_domain");
|
|
4783
|
+
}
|
|
4784
|
+
if (!trace2["bkn.account.id"] || !trace2["bkn.account.type"]) {
|
|
4785
|
+
throw new Error("trace requires account id and type");
|
|
4786
|
+
}
|
|
4787
|
+
if (!/^[0-9a-f]{16}$/.test(options.spanId)) throw new Error("spanId must be 16 hex characters");
|
|
4788
|
+
if (!options.producerModule.trim()) throw new Error("producerModule is required");
|
|
4789
|
+
}
|
|
4790
|
+
function clone(value) {
|
|
4791
|
+
return JSON.parse(JSON.stringify(value));
|
|
4792
|
+
}
|
|
4793
|
+
function assertSafePayload(eventType, payload) {
|
|
4794
|
+
const allowed = PAYLOAD_FIELDS[eventType];
|
|
4795
|
+
for (const key of Object.keys(payload)) {
|
|
4796
|
+
if (!allowed.has(key)) throw new Error(`${eventType} payload field is not registered: ${key}`);
|
|
4797
|
+
}
|
|
4798
|
+
for (const key of REQUIRED_PAYLOAD_FIELDS[eventType] ?? []) {
|
|
4799
|
+
if (payload[key] === void 0 || payload[key] === "") {
|
|
4800
|
+
throw new Error(`${eventType} payload requires ${key}`);
|
|
4801
|
+
}
|
|
4802
|
+
}
|
|
4803
|
+
if (eventType === "agent.interaction.started" && !payload.agent_id && !payload.app_ref) {
|
|
4804
|
+
throw new Error("agent.interaction.started requires agent_id or app_ref");
|
|
4805
|
+
}
|
|
4806
|
+
if (eventType === "agent.interaction.started") {
|
|
4807
|
+
assertEnum(payload, "mode", ["chat", "task", "background"]);
|
|
4808
|
+
}
|
|
4809
|
+
if (eventType === "model.call.observed" || eventType === "action.executed") {
|
|
4810
|
+
assertEnum(payload, "status", ["ok", "error"]);
|
|
4811
|
+
}
|
|
4812
|
+
if (eventType === "model.call.observed" && payload.status === "error") {
|
|
4813
|
+
for (const key of ["error_category", "error_hash"]) {
|
|
4814
|
+
if (!payload[key]) throw new Error(`model.call.observed error requires ${key}`);
|
|
4815
|
+
}
|
|
4816
|
+
}
|
|
4817
|
+
if (eventType === "tool.result.observed") {
|
|
4818
|
+
assertEnum(payload, "status", ["success", "error"]);
|
|
4819
|
+
if (payload.status === "success" && !payload.result_hash) {
|
|
4820
|
+
throw new Error("tool.result.observed success requires result_hash");
|
|
4821
|
+
}
|
|
4822
|
+
if (payload.status === "error" && !payload.error_hash) {
|
|
4823
|
+
throw new Error("tool.result.observed error requires error_hash");
|
|
4824
|
+
}
|
|
4825
|
+
}
|
|
4826
|
+
if (eventType === "business.refs.resolved") {
|
|
4827
|
+
assertEnum(payload, "resolver_status", ["resolved", "partial", "unresolved"]);
|
|
4828
|
+
}
|
|
4829
|
+
for (const key of ["source_event_ids", "operation_ids", "target_refs"]) {
|
|
4830
|
+
if (key in payload && (!Array.isArray(payload[key]) || payload[key].length === 0)) {
|
|
4831
|
+
throw new Error(`${eventType} payload requires non-empty ${key}`);
|
|
4832
|
+
}
|
|
4833
|
+
}
|
|
4834
|
+
if (eventType === "action.recommended") {
|
|
4835
|
+
for (const ref of payload.target_refs) assertQualifiedReference(ref);
|
|
4836
|
+
}
|
|
4837
|
+
if (eventType === "evidence.refs.created") {
|
|
4838
|
+
assertRefs(payload.evidence_refs, false);
|
|
4839
|
+
}
|
|
4840
|
+
if (eventType === "business.refs.resolved") {
|
|
4841
|
+
const unresolved = payload.resolver_status === "unresolved";
|
|
4842
|
+
assertRefs(payload.business_refs, unresolved);
|
|
4843
|
+
}
|
|
4844
|
+
if (eventType === "action.result_recorded" && !payload.task_ref && !payload.artifact_ref && !payload.result_artifact_ref) {
|
|
4845
|
+
throw new Error(
|
|
4846
|
+
"action.result_recorded requires task_ref, artifact_ref, or result_artifact_ref"
|
|
4847
|
+
);
|
|
4848
|
+
}
|
|
4849
|
+
if (eventType === "action.executed" && payload.status === "error") {
|
|
4850
|
+
for (const key of ["error_category", "error_hash"]) {
|
|
4851
|
+
if (!payload[key]) throw new Error(`action.executed error requires ${key}`);
|
|
4852
|
+
}
|
|
4853
|
+
}
|
|
4854
|
+
const fixedStatus = {
|
|
4855
|
+
"action.recommended": "recommended",
|
|
4856
|
+
"action.approval_requested": "approval_requested",
|
|
4857
|
+
"action.approved": "approved",
|
|
4858
|
+
"action.rejected": "rejected"
|
|
4859
|
+
};
|
|
4860
|
+
if (fixedStatus[eventType] && payload.status !== fixedStatus[eventType]) {
|
|
4861
|
+
throw new Error(`${eventType} requires status=${fixedStatus[eventType]}`);
|
|
4862
|
+
}
|
|
4863
|
+
scanSafeValue(payload, "payload");
|
|
4864
|
+
}
|
|
4865
|
+
function assertRefs(value, allowEmpty) {
|
|
4866
|
+
if (!Array.isArray(value) || !allowEmpty && value.length === 0) {
|
|
4867
|
+
throw new Error("reference list must be a non-empty array");
|
|
4868
|
+
}
|
|
4869
|
+
for (const item of value) {
|
|
4870
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) {
|
|
4871
|
+
throw new Error("reference must be an object");
|
|
4872
|
+
}
|
|
4873
|
+
const ref = item;
|
|
4874
|
+
for (const key of Object.keys(ref)) {
|
|
4875
|
+
if (!REF_FIELDS.has(key)) throw new Error(`reference field is not registered: ${key}`);
|
|
4876
|
+
}
|
|
4877
|
+
for (const key of [
|
|
4878
|
+
"ref_id",
|
|
4879
|
+
"ref_type",
|
|
4880
|
+
"source_system",
|
|
4881
|
+
"validity",
|
|
4882
|
+
"version_status",
|
|
4883
|
+
"visibility"
|
|
4884
|
+
]) {
|
|
4885
|
+
if (!ref[key]) throw new Error(`reference requires ${key}`);
|
|
4886
|
+
}
|
|
4887
|
+
assertQualifiedReference(String(ref.ref_id));
|
|
4888
|
+
assertEnum(ref, "validity", ["observed", "available", "unavailable", "expired", "partial"]);
|
|
4889
|
+
assertEnum(ref, "version_status", ["versioned", "unversioned", "not_auditable"]);
|
|
4890
|
+
assertEnum(ref, "visibility", [
|
|
4891
|
+
"visible",
|
|
4892
|
+
"redacted",
|
|
4893
|
+
"hidden",
|
|
4894
|
+
"omitted",
|
|
4895
|
+
"unresolved",
|
|
4896
|
+
"unauthorized"
|
|
4897
|
+
]);
|
|
4898
|
+
}
|
|
4899
|
+
}
|
|
4900
|
+
function assertQualifiedReference(value) {
|
|
4901
|
+
const parts = value.trim().split(":");
|
|
4902
|
+
const namespace = parts[0] ?? "";
|
|
4903
|
+
let valid = parts.every((part) => part.length > 0);
|
|
4904
|
+
if (["kn", "resource"].includes(namespace)) valid = valid && parts.length === 2;
|
|
4905
|
+
if (["object", "relation", "action_type", "metric", "field"].includes(namespace)) {
|
|
4906
|
+
valid = valid && parts.length === 3;
|
|
4907
|
+
}
|
|
4908
|
+
if (namespace === "property") valid = valid && parts.length === 4;
|
|
4909
|
+
if (!valid) {
|
|
4910
|
+
throw new Error("business reference id must include its knowledge-network or resource scope");
|
|
4911
|
+
}
|
|
4912
|
+
}
|
|
4913
|
+
function assertEnum(value, key, allowed) {
|
|
4914
|
+
if (!allowed.includes(String(value[key]))) {
|
|
4915
|
+
throw new Error(`${key} must be one of ${allowed.join(", ")}`);
|
|
4916
|
+
}
|
|
4917
|
+
}
|
|
4918
|
+
function scanSafeValue(value, path) {
|
|
4919
|
+
if (Array.isArray(value)) {
|
|
4920
|
+
value.forEach((child, index) => scanSafeValue(child, `${path}[${index}]`));
|
|
4921
|
+
return;
|
|
4922
|
+
}
|
|
4923
|
+
if (value && typeof value === "object") {
|
|
4924
|
+
for (const [key, child] of Object.entries(value)) {
|
|
4925
|
+
if (RAW_KEYS.has(key.toLowerCase())) throw new Error(`raw sensitive payload field: ${key}`);
|
|
4926
|
+
if (key.endsWith("_hash") && child !== "" && !HASH_RE.test(String(child))) {
|
|
4927
|
+
throw new Error(`${path}.${key} must be a sha256 hash`);
|
|
4928
|
+
}
|
|
4929
|
+
scanSafeValue(child, `${path}.${key}`);
|
|
4930
|
+
}
|
|
4931
|
+
return;
|
|
4932
|
+
}
|
|
4933
|
+
if (typeof value === "string" && RAW_VALUE_PATTERNS.some((pattern) => pattern.test(value))) {
|
|
4934
|
+
throw new Error(`raw sensitive payload value at ${path}`);
|
|
4935
|
+
}
|
|
4936
|
+
}
|
|
4937
|
+
var TraceSession = class {
|
|
4938
|
+
interactionId;
|
|
4939
|
+
trace;
|
|
4940
|
+
producerModule;
|
|
4941
|
+
spanId;
|
|
4942
|
+
emit;
|
|
4943
|
+
contractVersion;
|
|
4944
|
+
idFactory;
|
|
4945
|
+
now;
|
|
4946
|
+
events = [];
|
|
4947
|
+
eventIDs = /* @__PURE__ */ new Set();
|
|
4948
|
+
operationIDs = /* @__PURE__ */ new Set();
|
|
4949
|
+
claimEventIDs = /* @__PURE__ */ new Map();
|
|
4950
|
+
actions = /* @__PURE__ */ new WeakMap();
|
|
4951
|
+
flushTail = Promise.resolve();
|
|
4952
|
+
constructor(options) {
|
|
4953
|
+
assertSessionOptions(options);
|
|
4954
|
+
this.trace = clone(options.trace);
|
|
4955
|
+
if (options.conversationId) {
|
|
4956
|
+
this.trace["bkn.conversation.id"] = options.conversationId;
|
|
4957
|
+
}
|
|
4958
|
+
this.producerModule = options.producerModule;
|
|
4959
|
+
this.spanId = options.spanId;
|
|
4960
|
+
this.emit = options.emit;
|
|
4961
|
+
this.contractVersion = options.contractVersion ?? "2.1.0";
|
|
4962
|
+
this.idFactory = options.idFactory ?? randomUUID2;
|
|
4963
|
+
this.now = options.now ?? defaultNow;
|
|
4964
|
+
this.interactionId = options.interactionId ?? this.idFactory();
|
|
4965
|
+
}
|
|
4966
|
+
startInteraction(input) {
|
|
4967
|
+
return this.append("agent.interaction.started", {
|
|
4968
|
+
operationName: input.operationName,
|
|
4969
|
+
payload: {
|
|
4970
|
+
intent_hash: input.intentHash,
|
|
4971
|
+
mode: input.mode,
|
|
4972
|
+
...input.agentId ? { agent_id: input.agentId } : {},
|
|
4973
|
+
...input.appRef ? { app_ref: input.appRef } : {},
|
|
4974
|
+
...input.questionArtifactRef ? { question_artifact_ref: input.questionArtifactRef } : {}
|
|
4975
|
+
}
|
|
4976
|
+
});
|
|
4977
|
+
}
|
|
4978
|
+
observeOperation(eventType, input) {
|
|
4979
|
+
const operationId = input.operationId ?? this.idFactory();
|
|
4980
|
+
this.operationIDs.add(operationId);
|
|
4981
|
+
return this.append(eventType, { ...input, operationId });
|
|
4982
|
+
}
|
|
4983
|
+
createClaim(input) {
|
|
4984
|
+
if (input.sourceEventIds.length === 0 || input.operationIds.length === 0) {
|
|
4985
|
+
throw new Error("claim requires at least one source event and operation");
|
|
4986
|
+
}
|
|
4987
|
+
this.assertKnownRefs(input.sourceEventIds, this.eventIDs, "event");
|
|
4988
|
+
this.assertKnownRefs(input.operationIds, this.operationIDs, "operation");
|
|
4989
|
+
const event = this.append("claim.created", {
|
|
4990
|
+
operationName: input.operationName,
|
|
4991
|
+
causationEventId: input.causationEventId,
|
|
4992
|
+
claimId: input.claimId,
|
|
4993
|
+
payload: {
|
|
4994
|
+
claim_id: input.claimId,
|
|
4995
|
+
claim_type: input.claimType,
|
|
4996
|
+
claim_hash: input.claimHash,
|
|
4997
|
+
source_event_ids: input.sourceEventIds,
|
|
4998
|
+
operation_ids: input.operationIds,
|
|
4999
|
+
visibility: input.visibility ?? "visible",
|
|
5000
|
+
version_status: input.versionStatus ?? "unversioned",
|
|
5001
|
+
...input.resultArtifactRef ? { result_artifact_ref: input.resultArtifactRef } : {}
|
|
5002
|
+
}
|
|
5003
|
+
});
|
|
5004
|
+
this.claimEventIDs.set(input.claimId, event.event_id);
|
|
5005
|
+
return event;
|
|
5006
|
+
}
|
|
5007
|
+
createEvidenceRefs(input) {
|
|
5008
|
+
const claimEventID = this.requireClaim(input.claimId);
|
|
5009
|
+
if (input.refs.length === 0) throw new Error("evidence refs must not be empty");
|
|
5010
|
+
const operationId = this.idFactory();
|
|
5011
|
+
this.operationIDs.add(operationId);
|
|
5012
|
+
const event = this.append("evidence.refs.created", {
|
|
5013
|
+
operationName: input.operationName,
|
|
5014
|
+
operationId,
|
|
5015
|
+
causationEventId: input.causationEventId ?? claimEventID,
|
|
5016
|
+
claimId: input.claimId,
|
|
5017
|
+
payload: {
|
|
5018
|
+
claim_id: input.claimId,
|
|
5019
|
+
evidence_refs: input.refs.map((ref) => ({
|
|
5020
|
+
ref_id: ref.refId,
|
|
5021
|
+
ref_type: ref.refType,
|
|
5022
|
+
source_system: ref.sourceSystem,
|
|
5023
|
+
validity: ref.validity,
|
|
5024
|
+
version_status: ref.versionStatus,
|
|
5025
|
+
visibility: ref.visibility,
|
|
5026
|
+
...ref.summaryHash ? { summary_hash: ref.summaryHash } : {}
|
|
5027
|
+
}))
|
|
5028
|
+
}
|
|
5029
|
+
});
|
|
5030
|
+
this.claimEventIDs.set(input.claimId, event.event_id);
|
|
5031
|
+
return event;
|
|
5032
|
+
}
|
|
5033
|
+
resolveBusinessRefs(input) {
|
|
5034
|
+
const claimEventID = this.requireClaim(input.claimId);
|
|
5035
|
+
if (input.resolverStatus === "resolved" && input.refs.length === 0) {
|
|
5036
|
+
throw new Error("resolved business refs must not be empty");
|
|
5037
|
+
}
|
|
5038
|
+
const operationId = this.idFactory();
|
|
5039
|
+
this.operationIDs.add(operationId);
|
|
5040
|
+
const event = this.append("business.refs.resolved", {
|
|
5041
|
+
operationName: input.operationName,
|
|
5042
|
+
operationId,
|
|
5043
|
+
causationEventId: input.causationEventId ?? claimEventID,
|
|
5044
|
+
claimId: input.claimId,
|
|
5045
|
+
payload: {
|
|
5046
|
+
claim_id: input.claimId,
|
|
5047
|
+
resolver_status: input.resolverStatus,
|
|
5048
|
+
business_refs: input.refs.map((ref) => ({
|
|
5049
|
+
ref_id: ref.refId,
|
|
5050
|
+
ref_type: ref.refType,
|
|
5051
|
+
source_system: ref.sourceSystem,
|
|
5052
|
+
validity: ref.validity,
|
|
5053
|
+
version_status: ref.versionStatus,
|
|
5054
|
+
visibility: ref.visibility
|
|
5055
|
+
}))
|
|
5056
|
+
}
|
|
5057
|
+
});
|
|
5058
|
+
this.claimEventIDs.set(input.claimId, event.event_id);
|
|
5059
|
+
return event;
|
|
5060
|
+
}
|
|
5061
|
+
recommendAction(input) {
|
|
5062
|
+
const claimEventID = this.requireClaim(input.claimId);
|
|
5063
|
+
if (input.targetRefs.length === 0) throw new Error("action target refs must not be empty");
|
|
5064
|
+
const operationId = this.idFactory();
|
|
5065
|
+
const actionInstanceId = this.idFactory();
|
|
5066
|
+
this.operationIDs.add(operationId);
|
|
5067
|
+
const event = this.append("action.recommended", {
|
|
5068
|
+
operationName: input.operationName,
|
|
5069
|
+
operationId,
|
|
5070
|
+
causationEventId: input.causationEventId ?? claimEventID,
|
|
5071
|
+
claimId: input.claimId,
|
|
5072
|
+
payload: {
|
|
5073
|
+
action_instance_id: actionInstanceId,
|
|
5074
|
+
action_type: input.actionType,
|
|
5075
|
+
target_refs: input.targetRefs,
|
|
5076
|
+
reason_hash: input.reasonHash,
|
|
5077
|
+
...input.reasonArtifactRef ? { reason_artifact_ref: input.reasonArtifactRef } : {},
|
|
5078
|
+
...input.inputArtifactRef ? { input_artifact_ref: input.inputArtifactRef } : {},
|
|
5079
|
+
status: "recommended"
|
|
5080
|
+
}
|
|
5081
|
+
});
|
|
5082
|
+
const internal = {
|
|
5083
|
+
actionInstanceId,
|
|
5084
|
+
claimId: input.claimId,
|
|
5085
|
+
operationId,
|
|
5086
|
+
lastEventId: event.event_id,
|
|
5087
|
+
state: "recommended"
|
|
5088
|
+
};
|
|
5089
|
+
const handle = Object.freeze({
|
|
5090
|
+
get actionInstanceId() {
|
|
5091
|
+
return internal.actionInstanceId;
|
|
5092
|
+
},
|
|
5093
|
+
get claimId() {
|
|
5094
|
+
return internal.claimId;
|
|
5095
|
+
},
|
|
5096
|
+
get operationId() {
|
|
5097
|
+
return internal.operationId;
|
|
5098
|
+
},
|
|
5099
|
+
get lastEventId() {
|
|
5100
|
+
return internal.lastEventId;
|
|
5101
|
+
},
|
|
5102
|
+
get state() {
|
|
5103
|
+
return internal.state;
|
|
5104
|
+
}
|
|
5105
|
+
});
|
|
5106
|
+
this.actions.set(handle, internal);
|
|
5107
|
+
return handle;
|
|
5108
|
+
}
|
|
5109
|
+
requestActionApproval(action, input) {
|
|
5110
|
+
const internal = this.expectActionState(action, "recommended");
|
|
5111
|
+
const event = this.appendAction(internal, "action.approval_requested", {
|
|
5112
|
+
action_instance_id: internal.actionInstanceId,
|
|
5113
|
+
policy_ref: input.policyRef,
|
|
5114
|
+
status: "approval_requested"
|
|
5115
|
+
});
|
|
5116
|
+
internal.state = "approval_requested";
|
|
5117
|
+
internal.lastEventId = event.event_id;
|
|
5118
|
+
return event;
|
|
5119
|
+
}
|
|
5120
|
+
approveAction(action, input) {
|
|
5121
|
+
const internal = this.expectActionState(action, "approval_requested");
|
|
5122
|
+
const event = this.appendAction(internal, "action.approved", {
|
|
5123
|
+
action_instance_id: internal.actionInstanceId,
|
|
5124
|
+
actor_ref: input.actorRef,
|
|
5125
|
+
policy_decision_ref: input.policyDecisionRef,
|
|
5126
|
+
status: "approved"
|
|
5127
|
+
});
|
|
5128
|
+
internal.state = "approved";
|
|
5129
|
+
internal.lastEventId = event.event_id;
|
|
5130
|
+
return event;
|
|
5131
|
+
}
|
|
5132
|
+
rejectAction(action, input) {
|
|
5133
|
+
const internal = this.expectActionState(action, "approval_requested");
|
|
5134
|
+
const event = this.appendAction(internal, "action.rejected", {
|
|
5135
|
+
action_instance_id: internal.actionInstanceId,
|
|
5136
|
+
actor_ref: input.actorRef,
|
|
5137
|
+
policy_decision_ref: input.policyDecisionRef,
|
|
5138
|
+
status: "rejected"
|
|
5139
|
+
});
|
|
5140
|
+
internal.state = "rejected";
|
|
5141
|
+
internal.lastEventId = event.event_id;
|
|
5142
|
+
return event;
|
|
5143
|
+
}
|
|
5144
|
+
executeAction(action, input) {
|
|
5145
|
+
const internal = this.expectActionState(
|
|
5146
|
+
action,
|
|
5147
|
+
"approved",
|
|
5148
|
+
"requires approval before execution"
|
|
5149
|
+
);
|
|
5150
|
+
const event = this.appendAction(internal, "action.executed", {
|
|
5151
|
+
action_instance_id: internal.actionInstanceId,
|
|
5152
|
+
status: input.status,
|
|
5153
|
+
invocation_ref: input.invocationRef,
|
|
5154
|
+
...input.status === "error" ? { error_category: input.errorCategory, error_hash: input.errorHash } : {}
|
|
5155
|
+
});
|
|
5156
|
+
internal.state = "executed";
|
|
5157
|
+
internal.lastEventId = event.event_id;
|
|
5158
|
+
return event;
|
|
5159
|
+
}
|
|
5160
|
+
recordActionResult(action, input) {
|
|
5161
|
+
const internal = this.expectActionState(action, "executed");
|
|
5162
|
+
const event = this.appendAction(internal, "action.result_recorded", {
|
|
5163
|
+
action_instance_id: internal.actionInstanceId,
|
|
5164
|
+
result_hash: input.resultHash,
|
|
5165
|
+
status: input.status,
|
|
5166
|
+
...input.taskRef ? { task_ref: input.taskRef } : {},
|
|
5167
|
+
...input.artifactRef ? { artifact_ref: input.artifactRef } : {},
|
|
5168
|
+
...input.resultArtifactRef ? { result_artifact_ref: input.resultArtifactRef } : {}
|
|
5169
|
+
});
|
|
5170
|
+
internal.state = "result_recorded";
|
|
5171
|
+
internal.lastEventId = event.event_id;
|
|
5172
|
+
return event;
|
|
5173
|
+
}
|
|
5174
|
+
pendingEvents() {
|
|
5175
|
+
return clone(this.events);
|
|
5176
|
+
}
|
|
5177
|
+
flush() {
|
|
5178
|
+
const requestedIDs = new Set(this.events.map((event) => event.event_id));
|
|
5179
|
+
const operation = this.flushTail.then(() => this.flushEvents(requestedIDs));
|
|
5180
|
+
this.flushTail = operation.then(
|
|
5181
|
+
() => void 0,
|
|
5182
|
+
() => void 0
|
|
5183
|
+
);
|
|
5184
|
+
return operation;
|
|
5185
|
+
}
|
|
5186
|
+
appendAction(action, eventType, payload) {
|
|
5187
|
+
return this.append(eventType, {
|
|
5188
|
+
operationName: eventType,
|
|
5189
|
+
operationId: action.operationId,
|
|
5190
|
+
causationEventId: action.lastEventId,
|
|
5191
|
+
claimId: action.claimId,
|
|
5192
|
+
payload
|
|
5193
|
+
});
|
|
5194
|
+
}
|
|
5195
|
+
append(eventType, input) {
|
|
5196
|
+
assertSafePayload(eventType, input.payload);
|
|
5197
|
+
this.assertContractPayload(eventType, input.payload);
|
|
5198
|
+
if (eventType !== "agent.interaction.started") {
|
|
5199
|
+
if (!input.causationEventId) throw new Error(`${eventType} requires causation_event_id`);
|
|
5200
|
+
if (!this.eventIDs.has(input.causationEventId)) {
|
|
5201
|
+
throw new Error(`unknown event reference: ${input.causationEventId}`);
|
|
5202
|
+
}
|
|
5203
|
+
}
|
|
5204
|
+
if (eventType !== "agent.interaction.started" && eventType !== "claim.created" && !input.operationId) {
|
|
5205
|
+
throw new Error(`${eventType} requires operation_id`);
|
|
5206
|
+
}
|
|
5207
|
+
const eventID = this.idFactory();
|
|
5208
|
+
if (this.eventIDs.has(eventID)) throw new Error(`duplicate event id: ${eventID}`);
|
|
5209
|
+
const timestamp = this.now();
|
|
5210
|
+
const event = {
|
|
5211
|
+
event_id: eventID,
|
|
5212
|
+
event_type: eventType,
|
|
5213
|
+
"bkn.trace.schema.version": this.contractVersion,
|
|
5214
|
+
observed_at: timestamp,
|
|
5215
|
+
emitted_at: timestamp,
|
|
5216
|
+
producer_module: this.producerModule,
|
|
5217
|
+
trace_id: this.trace.trace_id,
|
|
5218
|
+
span_id: this.spanId,
|
|
5219
|
+
"bkn.request.id": this.trace["bkn.request.id"],
|
|
5220
|
+
"bkn.operation.name": input.operationName,
|
|
5221
|
+
interaction_id: this.interactionId,
|
|
5222
|
+
...input.operationId ? { operation_id: input.operationId } : {},
|
|
5223
|
+
...input.causationEventId ? { causation_event_id: input.causationEventId } : {},
|
|
5224
|
+
...input.claimId ? { claim_id: input.claimId } : {},
|
|
5225
|
+
...input.attempt ? { attempt: input.attempt } : {},
|
|
5226
|
+
payload: clone(input.payload)
|
|
5227
|
+
};
|
|
5228
|
+
this.events.push(clone(event));
|
|
5229
|
+
this.eventIDs.add(eventID);
|
|
5230
|
+
return clone(event);
|
|
5231
|
+
}
|
|
5232
|
+
async flushEvents(requestedIDs) {
|
|
5233
|
+
const batch = this.events.filter((event) => requestedIDs.has(event.event_id));
|
|
5234
|
+
if (batch.length === 0) return void 0;
|
|
5235
|
+
const response = await this.emit({
|
|
5236
|
+
"bkn.trace.schema.version": this.contractVersion,
|
|
5237
|
+
trace: clone(this.trace),
|
|
5238
|
+
events: clone(batch)
|
|
5239
|
+
});
|
|
5240
|
+
const remaining = this.events.filter((event) => !requestedIDs.has(event.event_id));
|
|
5241
|
+
this.events.splice(0, this.events.length, ...remaining);
|
|
5242
|
+
return response;
|
|
5243
|
+
}
|
|
5244
|
+
assertContractPayload(eventType, payload) {
|
|
5245
|
+
if (this.contractVersion !== "2.2.0") return;
|
|
5246
|
+
const requiredArtifactFields = {
|
|
5247
|
+
"agent.interaction.started": ["question_artifact_ref"],
|
|
5248
|
+
"data.query.observed": ["query_artifact_ref", "result_artifact_ref"],
|
|
5249
|
+
"logic.execution.observed": ["input_artifact_ref", "result_artifact_ref"],
|
|
5250
|
+
"claim.created": ["result_artifact_ref"],
|
|
5251
|
+
"action.recommended": ["input_artifact_ref"],
|
|
5252
|
+
"action.result_recorded": ["result_artifact_ref"]
|
|
5253
|
+
};
|
|
5254
|
+
for (const field of requiredArtifactFields[eventType] ?? []) {
|
|
5255
|
+
const value = payload[field];
|
|
5256
|
+
if (typeof value !== "string" || !/^artifact:[0-9A-Za-z][0-9A-Za-z_.:-]{0,127}$/.test(value)) {
|
|
5257
|
+
throw new Error(`${eventType} payload requires valid ${field}`);
|
|
5258
|
+
}
|
|
5259
|
+
}
|
|
5260
|
+
for (const [field, value] of Object.entries(payload)) {
|
|
5261
|
+
if (field.endsWith("_artifact_ref") && value !== void 0) {
|
|
5262
|
+
if (typeof value !== "string" || !/^artifact:[0-9A-Za-z][0-9A-Za-z_.:-]{0,127}$/.test(value)) {
|
|
5263
|
+
throw new Error(`${eventType} payload requires valid ${field}`);
|
|
5264
|
+
}
|
|
5265
|
+
}
|
|
5266
|
+
}
|
|
5267
|
+
if (eventType === "action.result_recorded" && payload.artifact_ref !== void 0) {
|
|
5268
|
+
throw new Error("action.result_recorded 2.2 does not accept legacy artifact_ref");
|
|
5269
|
+
}
|
|
5270
|
+
}
|
|
5271
|
+
assertKnownRefs(values, known, kind) {
|
|
5272
|
+
for (const value of values) {
|
|
5273
|
+
if (!known.has(value)) throw new Error(`unknown ${kind} reference: ${value}`);
|
|
5274
|
+
}
|
|
5275
|
+
}
|
|
5276
|
+
requireClaim(claimID) {
|
|
5277
|
+
const eventID = this.claimEventIDs.get(claimID);
|
|
5278
|
+
if (!eventID) throw new Error(`unknown claim reference: ${claimID}`);
|
|
5279
|
+
return eventID;
|
|
5280
|
+
}
|
|
5281
|
+
expectActionState(action, expected, message) {
|
|
5282
|
+
const internal = this.actions.get(action);
|
|
5283
|
+
if (!internal) throw new Error("action handle does not belong to this trace session");
|
|
5284
|
+
if (internal.state !== expected) {
|
|
5285
|
+
if (message) throw new Error(`action ${internal.actionInstanceId} ${message}`);
|
|
5286
|
+
throw new Error(
|
|
5287
|
+
`action ${internal.actionInstanceId} must be ${expected}, got ${internal.state}`
|
|
5288
|
+
);
|
|
5289
|
+
}
|
|
5290
|
+
return internal;
|
|
5291
|
+
}
|
|
5292
|
+
};
|
|
5293
|
+
|
|
4111
5294
|
// src/api/trace.ts
|
|
4112
5295
|
var SEARCH = "/api/agent-observability/v1/traces/_search";
|
|
5296
|
+
var EVIDENCE_EVENTS = "/api/agent-observability/v1/evidence/events";
|
|
5297
|
+
var EVIDENCE_ARTIFACTS = "/api/agent-observability/v1/evidence/artifacts";
|
|
5298
|
+
var REQUESTS = "/api/agent-observability/v1/requests";
|
|
5299
|
+
var INTERACTIONS = "/api/agent-observability/v1/interactions";
|
|
5300
|
+
var TRACES = "/api/agent-observability/v1/traces";
|
|
4113
5301
|
function isoToNanos(iso) {
|
|
4114
5302
|
const ms = Date.parse(iso);
|
|
4115
5303
|
if (Number.isNaN(ms)) return void 0;
|
|
@@ -4151,6 +5339,67 @@ async function getRawSpansByConversation(ctx, conversationId, opts = {}) {
|
|
|
4151
5339
|
function traceSearch(ctx, body) {
|
|
4152
5340
|
return request(ctx, SEARCH, { method: "POST", body });
|
|
4153
5341
|
}
|
|
5342
|
+
function emitEvidenceEvents(ctx, body) {
|
|
5343
|
+
return request(ctx, EVIDENCE_EVENTS, {
|
|
5344
|
+
method: "POST",
|
|
5345
|
+
body,
|
|
5346
|
+
headers: evidenceWriteHeaders(ctx),
|
|
5347
|
+
redirect: "manual"
|
|
5348
|
+
});
|
|
5349
|
+
}
|
|
5350
|
+
function emitEvidenceArtifact(ctx, body) {
|
|
5351
|
+
return request(ctx, EVIDENCE_ARTIFACTS, {
|
|
5352
|
+
method: "POST",
|
|
5353
|
+
body,
|
|
5354
|
+
headers: evidenceWriteHeaders(ctx),
|
|
5355
|
+
redirect: "manual"
|
|
5356
|
+
});
|
|
5357
|
+
}
|
|
5358
|
+
function evidenceWriteHeaders(ctx) {
|
|
5359
|
+
return ctx.evidenceIngestToken ? { "x-bkn-trace-ingest-token": ctx.evidenceIngestToken } : void 0;
|
|
5360
|
+
}
|
|
5361
|
+
function getEvidenceArtifact(ctx, artifactId) {
|
|
5362
|
+
return request(ctx, `${EVIDENCE_ARTIFACTS}/${encodeURIComponent(artifactId)}`);
|
|
5363
|
+
}
|
|
5364
|
+
function listRequestSummaries(ctx, query = {}) {
|
|
5365
|
+
return request(ctx, REQUESTS, {
|
|
5366
|
+
query: summaryQuery(query)
|
|
5367
|
+
});
|
|
5368
|
+
}
|
|
5369
|
+
function getRequestSummary(ctx, requestId) {
|
|
5370
|
+
return request(ctx, `${REQUESTS}/${encodeURIComponent(requestId)}`);
|
|
5371
|
+
}
|
|
5372
|
+
function getInteractionSummary(ctx, interactionId) {
|
|
5373
|
+
return request(ctx, `${INTERACTIONS}/${encodeURIComponent(interactionId)}`);
|
|
5374
|
+
}
|
|
5375
|
+
function getRequestTraces(ctx, requestId, query = {}) {
|
|
5376
|
+
return request(
|
|
5377
|
+
ctx,
|
|
5378
|
+
`${REQUESTS}/${encodeURIComponent(requestId)}/traces`,
|
|
5379
|
+
{ query: summaryQuery(query) }
|
|
5380
|
+
);
|
|
5381
|
+
}
|
|
5382
|
+
function getTraceGraph(ctx, traceId) {
|
|
5383
|
+
return request(ctx, `${TRACES}/${encodeURIComponent(traceId)}/trace-graph`);
|
|
5384
|
+
}
|
|
5385
|
+
function getEvidenceChain(ctx, scope, opts = {}) {
|
|
5386
|
+
const target = traceTarget(scope, "evidence-chain");
|
|
5387
|
+
return request(ctx, target.path, {
|
|
5388
|
+
query: queryWithLimit(target.query, opts)
|
|
5389
|
+
});
|
|
5390
|
+
}
|
|
5391
|
+
function getBusinessGraph(ctx, scope, opts = {}) {
|
|
5392
|
+
const target = traceTarget(scope, "business-graph");
|
|
5393
|
+
return request(ctx, target.path, {
|
|
5394
|
+
query: queryWithLimit(target.query, opts)
|
|
5395
|
+
});
|
|
5396
|
+
}
|
|
5397
|
+
function getSnapshotPreview(ctx, scope, opts = {}) {
|
|
5398
|
+
const target = traceTarget(scope, "snapshot-preview");
|
|
5399
|
+
return request(ctx, target.path, {
|
|
5400
|
+
query: queryWithLimit(target.query, opts)
|
|
5401
|
+
});
|
|
5402
|
+
}
|
|
4154
5403
|
async function getSpansByConversation(ctx, conversationId, opts = {}) {
|
|
4155
5404
|
const agg = await request(ctx, SEARCH, {
|
|
4156
5405
|
method: "POST",
|
|
@@ -4175,8 +5424,40 @@ async function getSpansByConversation(ctx, conversationId, opts = {}) {
|
|
|
4175
5424
|
}) ?? {};
|
|
4176
5425
|
return (spans.hits?.hits ?? []).map((h) => h._source ?? {});
|
|
4177
5426
|
}
|
|
5427
|
+
function traceTarget(scope, subresource) {
|
|
5428
|
+
if (typeof scope === "string") {
|
|
5429
|
+
return { path: `${TRACES}/${encodeURIComponent(scope)}/${subresource}` };
|
|
5430
|
+
}
|
|
5431
|
+
if ("traceId" in scope) {
|
|
5432
|
+
return { path: `${TRACES}/${encodeURIComponent(scope.traceId)}/${subresource}` };
|
|
5433
|
+
}
|
|
5434
|
+
const requestPath = subresource === "evidence-chain" ? `${TRACES}/by-request` : `${TRACES}/by-request/${subresource}`;
|
|
5435
|
+
return { path: requestPath, query: { request_id: scope.requestId } };
|
|
5436
|
+
}
|
|
5437
|
+
function queryWithLimit(query, opts) {
|
|
5438
|
+
if (opts.limit === void 0 || !Number.isFinite(opts.limit)) return query;
|
|
5439
|
+
return { ...query ?? {}, limit: opts.limit };
|
|
5440
|
+
}
|
|
5441
|
+
function summaryQuery(query) {
|
|
5442
|
+
const result = {};
|
|
5443
|
+
if (query.limit !== void 0 && Number.isFinite(query.limit)) result.limit = query.limit;
|
|
5444
|
+
if (query.cursor) result.cursor = query.cursor;
|
|
5445
|
+
if (query.from) result.from = query.from;
|
|
5446
|
+
if (query.to) result.to = query.to;
|
|
5447
|
+
if (query.status) result.status = query.status;
|
|
5448
|
+
if (query.agentOrApp) result.agent_or_app = query.agentOrApp;
|
|
5449
|
+
if (query.businessDomain) result.business_domain = query.businessDomain;
|
|
5450
|
+
if (query.conversationId) result.conversation_id = query.conversationId;
|
|
5451
|
+
if (query.interactionId) result.interaction_id = query.interactionId;
|
|
5452
|
+
if (query.knowledgeNetwork) result.knowledge_network = query.knowledgeNetwork;
|
|
5453
|
+
if (query.evidenceCompleteness) {
|
|
5454
|
+
result.evidence_completeness = query.evidenceCompleteness;
|
|
5455
|
+
}
|
|
5456
|
+
if (query.keyword) result.keyword = query.keyword;
|
|
5457
|
+
return Object.keys(result).length ? result : void 0;
|
|
5458
|
+
}
|
|
4178
5459
|
|
|
4179
|
-
// src/trace
|
|
5460
|
+
// src/bkn-trace/claude-judge.ts
|
|
4180
5461
|
import { spawn as spawn2, spawnSync as spawnSync2 } from "child_process";
|
|
4181
5462
|
var ClaudeJudgeError = class extends Error {
|
|
4182
5463
|
constructor(message, reason) {
|
|
@@ -4236,10 +5517,10 @@ async function judgeJson(prompt, opts = {}) {
|
|
|
4236
5517
|
child.stdout.on("data", (d) => {
|
|
4237
5518
|
out += d;
|
|
4238
5519
|
});
|
|
4239
|
-
child.on("error", (
|
|
5520
|
+
child.on("error", (err2) => {
|
|
4240
5521
|
clearTimeout(killer);
|
|
4241
5522
|
reject(
|
|
4242
|
-
|
|
5523
|
+
err2.code === "ENOENT" ? new ClaudeJudgeError("`claude` not found on PATH", "not_available") : err2
|
|
4243
5524
|
);
|
|
4244
5525
|
});
|
|
4245
5526
|
child.on("close", (code) => {
|
|
@@ -4249,8 +5530,8 @@ async function judgeJson(prompt, opts = {}) {
|
|
|
4249
5530
|
if (code !== 0) return reject(new ClaudeJudgeError(`claude exited ${code}`, "exit"));
|
|
4250
5531
|
resolve7(out);
|
|
4251
5532
|
});
|
|
4252
|
-
child.stdin.on("error", (
|
|
4253
|
-
if (
|
|
5533
|
+
child.stdin.on("error", (err2) => {
|
|
5534
|
+
if (err2.code !== "EPIPE") reject(err2);
|
|
4254
5535
|
});
|
|
4255
5536
|
child.stdin.end(prompt);
|
|
4256
5537
|
});
|
|
@@ -4258,7 +5539,7 @@ async function judgeJson(prompt, opts = {}) {
|
|
|
4258
5539
|
return JSON.parse(extractJsonObject(text));
|
|
4259
5540
|
}
|
|
4260
5541
|
|
|
4261
|
-
// src/trace
|
|
5542
|
+
// src/bkn-trace/diagnose.ts
|
|
4262
5543
|
var KIND_MAP = {
|
|
4263
5544
|
chat: "llm",
|
|
4264
5545
|
text_completion: "llm",
|
|
@@ -4652,7 +5933,7 @@ function renderReportMarkdown(r) {
|
|
|
4652
5933
|
return lines.join("\n");
|
|
4653
5934
|
}
|
|
4654
5935
|
|
|
4655
|
-
// src/trace
|
|
5936
|
+
// src/bkn-trace/eval-set.ts
|
|
4656
5937
|
function hashId(s) {
|
|
4657
5938
|
let h = 5381;
|
|
4658
5939
|
for (let i = 0; i < s.length; i++) h = h * 33 ^ s.charCodeAt(i);
|
|
@@ -4800,6 +6081,747 @@ async function runEvalSet(agentId, cases, deps) {
|
|
|
4800
6081
|
};
|
|
4801
6082
|
}
|
|
4802
6083
|
|
|
6084
|
+
// src/bkn-trace/fixture-validate.ts
|
|
6085
|
+
import { readFileSync as readFileSync4, readdirSync as readdirSync5, statSync as statSync4 } from "fs";
|
|
6086
|
+
import { join as join4 } from "path";
|
|
6087
|
+
var CONTRACT_VERSIONS = /* @__PURE__ */ new Set(["1.0.0", "2.0.0", "2.1.0"]);
|
|
6088
|
+
var BUSINESS_CONTRACT_VERSION = "2.1.0";
|
|
6089
|
+
var TRACEPARENT_RE2 = /^00-([0-9a-f]{32})-([0-9a-f]{16})-[0-9a-f]{2}$/;
|
|
6090
|
+
var REQUEST_ID_RE2 = /^req_[0-9A-Za-z_.-]+$/;
|
|
6091
|
+
var RFC3339_NANO_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?Z$/;
|
|
6092
|
+
var ALLOWED_BAGGAGE2 = /* @__PURE__ */ new Set(["bkn.account.type", "bkn.runtime.env"]);
|
|
6093
|
+
var REQUIRED_LOG_FIELDS = [
|
|
6094
|
+
"trace_id",
|
|
6095
|
+
"span_id",
|
|
6096
|
+
"bkn.request.id",
|
|
6097
|
+
"bkn.module.name",
|
|
6098
|
+
"bkn.operation.name",
|
|
6099
|
+
"bkn.status",
|
|
6100
|
+
"bkn.timestamp",
|
|
6101
|
+
"bkn.trace.schema.version"
|
|
6102
|
+
];
|
|
6103
|
+
var REQUIRED_SPAN_FIELDS = [
|
|
6104
|
+
"span_id",
|
|
6105
|
+
"name",
|
|
6106
|
+
"bkn.module.name",
|
|
6107
|
+
"bkn.operation.name",
|
|
6108
|
+
"bkn.status",
|
|
6109
|
+
"bkn.timestamp"
|
|
6110
|
+
];
|
|
6111
|
+
var REQUIRED_EVENT_FIELDS = [
|
|
6112
|
+
"trace_id",
|
|
6113
|
+
"span_id",
|
|
6114
|
+
"bkn.request.id",
|
|
6115
|
+
"bkn.operation.name",
|
|
6116
|
+
"event_id",
|
|
6117
|
+
"event_type",
|
|
6118
|
+
"bkn.trace.schema.version",
|
|
6119
|
+
"observed_at",
|
|
6120
|
+
"emitted_at",
|
|
6121
|
+
"producer_module",
|
|
6122
|
+
"payload"
|
|
6123
|
+
];
|
|
6124
|
+
var SENSITIVE_PATTERNS = [
|
|
6125
|
+
/authorization/i,
|
|
6126
|
+
/bearer\s+[A-Za-z0-9._-]+/i,
|
|
6127
|
+
/access[_-]?token/i,
|
|
6128
|
+
/api[_-]?key/i,
|
|
6129
|
+
/cookie/i,
|
|
6130
|
+
/\bselect\s+.+\s+from\b/is,
|
|
6131
|
+
/prompt\s*[:=]/i,
|
|
6132
|
+
/https?:\/\/[^\s"']+/i,
|
|
6133
|
+
/[\w.+-]+@[\w.-]+\.[A-Za-z]{2,}/
|
|
6134
|
+
];
|
|
6135
|
+
var BUSINESS_EVENT_TYPES = /* @__PURE__ */ new Set([
|
|
6136
|
+
"agent.interaction.started",
|
|
6137
|
+
"retrieval.completed",
|
|
6138
|
+
"knowledge.read.observed",
|
|
6139
|
+
"data.query.observed",
|
|
6140
|
+
"model.call.observed",
|
|
6141
|
+
"tool.called",
|
|
6142
|
+
"tool.result.observed",
|
|
6143
|
+
"claim.created",
|
|
6144
|
+
"evidence.refs.created",
|
|
6145
|
+
"business.refs.resolved",
|
|
6146
|
+
"action.recommended",
|
|
6147
|
+
"action.approval_requested",
|
|
6148
|
+
"action.approved",
|
|
6149
|
+
"action.rejected",
|
|
6150
|
+
"action.executed",
|
|
6151
|
+
"action.result_recorded"
|
|
6152
|
+
]);
|
|
6153
|
+
var CLAIM_EVENT_TYPES = /* @__PURE__ */ new Set([
|
|
6154
|
+
"claim.created",
|
|
6155
|
+
"evidence.refs.created",
|
|
6156
|
+
"business.refs.resolved",
|
|
6157
|
+
"action.recommended",
|
|
6158
|
+
"action.approval_requested",
|
|
6159
|
+
"action.approved",
|
|
6160
|
+
"action.rejected",
|
|
6161
|
+
"action.executed",
|
|
6162
|
+
"action.result_recorded"
|
|
6163
|
+
]);
|
|
6164
|
+
var ACTION_TRANSITIONS = {
|
|
6165
|
+
recommended: /* @__PURE__ */ new Set(["approval_requested"]),
|
|
6166
|
+
approval_requested: /* @__PURE__ */ new Set(["approved", "rejected"]),
|
|
6167
|
+
approved: /* @__PURE__ */ new Set(["executed"]),
|
|
6168
|
+
executed: /* @__PURE__ */ new Set(["result_recorded"]),
|
|
6169
|
+
rejected: /* @__PURE__ */ new Set(),
|
|
6170
|
+
result_recorded: /* @__PURE__ */ new Set()
|
|
6171
|
+
};
|
|
6172
|
+
var ACTION_STATE_BY_EVENT = {
|
|
6173
|
+
"action.recommended": "recommended",
|
|
6174
|
+
"action.approval_requested": "approval_requested",
|
|
6175
|
+
"action.approved": "approved",
|
|
6176
|
+
"action.rejected": "rejected",
|
|
6177
|
+
"action.executed": "executed",
|
|
6178
|
+
"action.result_recorded": "result_recorded"
|
|
6179
|
+
};
|
|
6180
|
+
var EVENT_PAYLOAD_FIELDS = {
|
|
6181
|
+
"agent.interaction.started": /* @__PURE__ */ new Set(["intent_hash", "mode", "agent_id", "app_ref"]),
|
|
6182
|
+
"retrieval.completed": /* @__PURE__ */ new Set([
|
|
6183
|
+
"query_hash",
|
|
6184
|
+
"candidate_count",
|
|
6185
|
+
"truncated",
|
|
6186
|
+
"version_status",
|
|
6187
|
+
"source_refs"
|
|
6188
|
+
]),
|
|
6189
|
+
"knowledge.read.observed": /* @__PURE__ */ new Set([
|
|
6190
|
+
"kn_id",
|
|
6191
|
+
"read_kind",
|
|
6192
|
+
"version_status",
|
|
6193
|
+
"schema_version",
|
|
6194
|
+
"business_refs"
|
|
6195
|
+
]),
|
|
6196
|
+
"data.query.observed": /* @__PURE__ */ new Set([
|
|
6197
|
+
"query_hash",
|
|
6198
|
+
"query_type",
|
|
6199
|
+
"row_count",
|
|
6200
|
+
"truncated",
|
|
6201
|
+
"as_of",
|
|
6202
|
+
"version_status",
|
|
6203
|
+
"resource_refs",
|
|
6204
|
+
"field_refs"
|
|
6205
|
+
]),
|
|
6206
|
+
"model.call.observed": /* @__PURE__ */ new Set([
|
|
6207
|
+
"model_name",
|
|
6208
|
+
"model_provider",
|
|
6209
|
+
"status",
|
|
6210
|
+
"input_token_count",
|
|
6211
|
+
"output_token_count",
|
|
6212
|
+
"prompt_hash",
|
|
6213
|
+
"output_hash",
|
|
6214
|
+
"error_category",
|
|
6215
|
+
"error_hash"
|
|
6216
|
+
]),
|
|
6217
|
+
"tool.called": /* @__PURE__ */ new Set(["tool_id", "tool_name", "args_hash", "visibility", "version_status"]),
|
|
6218
|
+
"tool.result.observed": /* @__PURE__ */ new Set([
|
|
6219
|
+
"tool_id",
|
|
6220
|
+
"tool_name",
|
|
6221
|
+
"status",
|
|
6222
|
+
"result_hash",
|
|
6223
|
+
"result_length",
|
|
6224
|
+
"result_count",
|
|
6225
|
+
"error_hash",
|
|
6226
|
+
"error_category",
|
|
6227
|
+
"visibility",
|
|
6228
|
+
"version_status"
|
|
6229
|
+
]),
|
|
6230
|
+
"claim.created": /* @__PURE__ */ new Set([
|
|
6231
|
+
"claim_id",
|
|
6232
|
+
"claim_type",
|
|
6233
|
+
"claim_hash",
|
|
6234
|
+
"source_event_ids",
|
|
6235
|
+
"operation_ids",
|
|
6236
|
+
"visibility",
|
|
6237
|
+
"version_status"
|
|
6238
|
+
]),
|
|
6239
|
+
"evidence.refs.created": /* @__PURE__ */ new Set(["claim_id", "evidence_refs"]),
|
|
6240
|
+
"business.refs.resolved": /* @__PURE__ */ new Set(["claim_id", "resolver_status", "business_refs"]),
|
|
6241
|
+
"action.recommended": /* @__PURE__ */ new Set([
|
|
6242
|
+
"action_instance_id",
|
|
6243
|
+
"action_type",
|
|
6244
|
+
"target_refs",
|
|
6245
|
+
"reason_hash",
|
|
6246
|
+
"status"
|
|
6247
|
+
]),
|
|
6248
|
+
"action.approval_requested": /* @__PURE__ */ new Set(["action_instance_id", "policy_ref", "status"]),
|
|
6249
|
+
"action.approved": /* @__PURE__ */ new Set(["action_instance_id", "actor_ref", "policy_decision_ref", "status"]),
|
|
6250
|
+
"action.rejected": /* @__PURE__ */ new Set(["action_instance_id", "actor_ref", "policy_decision_ref", "status"]),
|
|
6251
|
+
"action.executed": /* @__PURE__ */ new Set([
|
|
6252
|
+
"action_instance_id",
|
|
6253
|
+
"invocation_ref",
|
|
6254
|
+
"tool_ref",
|
|
6255
|
+
"status",
|
|
6256
|
+
"error_category",
|
|
6257
|
+
"error_hash"
|
|
6258
|
+
]),
|
|
6259
|
+
"action.result_recorded": /* @__PURE__ */ new Set([
|
|
6260
|
+
"action_instance_id",
|
|
6261
|
+
"result_hash",
|
|
6262
|
+
"artifact_ref",
|
|
6263
|
+
"task_ref",
|
|
6264
|
+
"status"
|
|
6265
|
+
])
|
|
6266
|
+
};
|
|
6267
|
+
var REFERENCE_FIELDS = /* @__PURE__ */ new Set([
|
|
6268
|
+
"ref_id",
|
|
6269
|
+
"ref_type",
|
|
6270
|
+
"source_system",
|
|
6271
|
+
"validity",
|
|
6272
|
+
"version_status",
|
|
6273
|
+
"visibility",
|
|
6274
|
+
"summary_hash"
|
|
6275
|
+
]);
|
|
6276
|
+
var FORBIDDEN_RAW_KEYS = /* @__PURE__ */ new Set([
|
|
6277
|
+
"authorization",
|
|
6278
|
+
"cookie",
|
|
6279
|
+
"access_token",
|
|
6280
|
+
"refresh_token",
|
|
6281
|
+
"id_token",
|
|
6282
|
+
"api_key",
|
|
6283
|
+
"password",
|
|
6284
|
+
"private_key",
|
|
6285
|
+
"prompt",
|
|
6286
|
+
"user_question",
|
|
6287
|
+
"approval_comment",
|
|
6288
|
+
"sql",
|
|
6289
|
+
"query_params",
|
|
6290
|
+
"rows"
|
|
6291
|
+
]);
|
|
6292
|
+
function err(code, path, message) {
|
|
6293
|
+
return { code, path, message };
|
|
6294
|
+
}
|
|
6295
|
+
function asRecord(value) {
|
|
6296
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
6297
|
+
}
|
|
6298
|
+
function jsonFiles(path) {
|
|
6299
|
+
const stat = statSync4(path);
|
|
6300
|
+
if (stat.isFile()) return [path];
|
|
6301
|
+
return readdirSync5(path).filter((name) => name.endsWith(".json")).sort().map((name) => join4(path, name));
|
|
6302
|
+
}
|
|
6303
|
+
function validTraceparent(value) {
|
|
6304
|
+
if (typeof value !== "string") return false;
|
|
6305
|
+
const match = TRACEPARENT_RE2.exec(value);
|
|
6306
|
+
if (!match) return false;
|
|
6307
|
+
const [, traceId, spanId] = match;
|
|
6308
|
+
return traceId !== "0".repeat(32) && spanId !== "0".repeat(16);
|
|
6309
|
+
}
|
|
6310
|
+
function checkRequired(item, fields, basePath, errors) {
|
|
6311
|
+
for (const field of fields) {
|
|
6312
|
+
if (item[field] === void 0 || item[field] === "") {
|
|
6313
|
+
errors.push(
|
|
6314
|
+
err(
|
|
6315
|
+
"BKN_TRACE_REQUIRED_FIELD_MISSING",
|
|
6316
|
+
`${basePath}.${field}`,
|
|
6317
|
+
`missing required field ${field}`
|
|
6318
|
+
)
|
|
6319
|
+
);
|
|
6320
|
+
}
|
|
6321
|
+
}
|
|
6322
|
+
}
|
|
6323
|
+
function checkTimestamp(value, path, errors) {
|
|
6324
|
+
if (typeof value !== "string" || !RFC3339_NANO_RE.test(value)) {
|
|
6325
|
+
errors.push(err("BKN_TRACE_INVALID_TIMESTAMP", path, "timestamp must be UTC RFC3339Nano"));
|
|
6326
|
+
}
|
|
6327
|
+
}
|
|
6328
|
+
function checkSensitive(value, path, errors) {
|
|
6329
|
+
if (Array.isArray(value)) {
|
|
6330
|
+
value.forEach((child, index) => checkSensitive(child, `${path}[${index}]`, errors));
|
|
6331
|
+
return;
|
|
6332
|
+
}
|
|
6333
|
+
if (value && typeof value === "object") {
|
|
6334
|
+
for (const [key, child] of Object.entries(value)) {
|
|
6335
|
+
if (FORBIDDEN_RAW_KEYS.has(key.toLowerCase())) {
|
|
6336
|
+
errors.push(
|
|
6337
|
+
err(
|
|
6338
|
+
"BKN_TRACE_SENSITIVE_VALUE_LEAKED",
|
|
6339
|
+
`${path}.${key}`,
|
|
6340
|
+
"raw sensitive field is forbidden"
|
|
6341
|
+
)
|
|
6342
|
+
);
|
|
6343
|
+
}
|
|
6344
|
+
if (key.endsWith("_hash") && child !== "" && (typeof child !== "string" || !/^sha256:[0-9a-f]{64}$/.test(child))) {
|
|
6345
|
+
errors.push(
|
|
6346
|
+
err("BKN_TRACE_REQUIRED_FIELD_MISSING", `${path}.${key}`, `${key} must be a sha256 hash`)
|
|
6347
|
+
);
|
|
6348
|
+
}
|
|
6349
|
+
checkSensitive(child, `${path}.${key}`, errors);
|
|
6350
|
+
}
|
|
6351
|
+
return;
|
|
6352
|
+
}
|
|
6353
|
+
if (typeof value !== "string") return;
|
|
6354
|
+
if (SENSITIVE_PATTERNS.some((pattern) => pattern.test(value))) {
|
|
6355
|
+
errors.push(
|
|
6356
|
+
err(
|
|
6357
|
+
"BKN_TRACE_SENSITIVE_VALUE_LEAKED",
|
|
6358
|
+
path,
|
|
6359
|
+
"sensitive value must be redacted, hashed, or referenced"
|
|
6360
|
+
)
|
|
6361
|
+
);
|
|
6362
|
+
}
|
|
6363
|
+
}
|
|
6364
|
+
function validateFixture(data) {
|
|
6365
|
+
const root = asRecord(data);
|
|
6366
|
+
const errors = [];
|
|
6367
|
+
const fixtureId = typeof root.fixture_id === "string" ? root.fixture_id : "<unknown>";
|
|
6368
|
+
const contractVersion = typeof root["bkn.trace.schema.version"] === "string" ? root["bkn.trace.schema.version"] : null;
|
|
6369
|
+
if (!contractVersion) {
|
|
6370
|
+
errors.push(
|
|
6371
|
+
err(
|
|
6372
|
+
"BKN_TRACE_SCHEMA_VERSION_MISSING",
|
|
6373
|
+
"$.bkn.trace.schema.version",
|
|
6374
|
+
"missing contract version"
|
|
6375
|
+
)
|
|
6376
|
+
);
|
|
6377
|
+
} else if (!CONTRACT_VERSIONS.has(contractVersion)) {
|
|
6378
|
+
errors.push(
|
|
6379
|
+
err(
|
|
6380
|
+
"BKN_TRACE_SCHEMA_VERSION_UNSUPPORTED",
|
|
6381
|
+
"$.bkn.trace.schema.version",
|
|
6382
|
+
`unsupported contract version ${contractVersion}`
|
|
6383
|
+
)
|
|
6384
|
+
);
|
|
6385
|
+
}
|
|
6386
|
+
const trace2 = asRecord(root.trace);
|
|
6387
|
+
const traceId = trace2.trace_id;
|
|
6388
|
+
const requestId = trace2["bkn.request.id"];
|
|
6389
|
+
if (typeof traceId !== "string" || !/^[0-9a-f]{32}$/.test(traceId)) {
|
|
6390
|
+
errors.push(
|
|
6391
|
+
err("BKN_TRACE_REQUIRED_FIELD_MISSING", "$.trace.trace_id", "missing valid trace id")
|
|
6392
|
+
);
|
|
6393
|
+
}
|
|
6394
|
+
if (typeof requestId !== "string" || !REQUEST_ID_RE2.test(requestId)) {
|
|
6395
|
+
errors.push(
|
|
6396
|
+
err(
|
|
6397
|
+
"BKN_TRACE_REQUIRED_FIELD_MISSING",
|
|
6398
|
+
"$.trace.bkn.request.id",
|
|
6399
|
+
"missing valid bkn.request.id"
|
|
6400
|
+
)
|
|
6401
|
+
);
|
|
6402
|
+
}
|
|
6403
|
+
if (!validTraceparent(trace2.traceparent)) {
|
|
6404
|
+
errors.push(err("BKN_TRACE_INVALID_TRACEPARENT", "$.trace.traceparent", "invalid traceparent"));
|
|
6405
|
+
}
|
|
6406
|
+
const spans = Array.isArray(root.spans) ? root.spans : [];
|
|
6407
|
+
const spanIds = /* @__PURE__ */ new Set();
|
|
6408
|
+
spans.forEach((item, index) => {
|
|
6409
|
+
const span = asRecord(item);
|
|
6410
|
+
checkRequired(span, REQUIRED_SPAN_FIELDS, `$.spans[${index}]`, errors);
|
|
6411
|
+
checkTimestamp(span["bkn.timestamp"], `$.spans[${index}].bkn.timestamp`, errors);
|
|
6412
|
+
if (typeof span.span_id === "string") spanIds.add(span.span_id);
|
|
6413
|
+
const parent = span.parent_span_id;
|
|
6414
|
+
if (parent !== null && parent !== void 0 && !spanIds.has(String(parent))) {
|
|
6415
|
+
errors.push(
|
|
6416
|
+
err(
|
|
6417
|
+
"BKN_TRACE_ORPHAN_SPAN",
|
|
6418
|
+
`$.spans[${index}].parent_span_id`,
|
|
6419
|
+
"parent span must appear before child span or be represented as a link"
|
|
6420
|
+
)
|
|
6421
|
+
);
|
|
6422
|
+
}
|
|
6423
|
+
});
|
|
6424
|
+
if (spans.length === 0) {
|
|
6425
|
+
errors.push(err("BKN_TRACE_REQUIRED_FIELD_MISSING", "$.spans", "at least one span required"));
|
|
6426
|
+
}
|
|
6427
|
+
const logs = Array.isArray(root.logs) ? root.logs : [];
|
|
6428
|
+
logs.forEach((item, index) => {
|
|
6429
|
+
const log = asRecord(item);
|
|
6430
|
+
checkRequired(log, REQUIRED_LOG_FIELDS, `$.logs[${index}]`, errors);
|
|
6431
|
+
checkTimestamp(log["bkn.timestamp"], `$.logs[${index}].bkn.timestamp`, errors);
|
|
6432
|
+
if (log.trace_id !== traceId || log["bkn.request.id"] !== requestId) {
|
|
6433
|
+
errors.push(
|
|
6434
|
+
err("BKN_TRACE_JOIN_FAILED", `$.logs[${index}]`, "log cannot join trace/request")
|
|
6435
|
+
);
|
|
6436
|
+
}
|
|
6437
|
+
if (!spanIds.has(String(log.span_id))) {
|
|
6438
|
+
errors.push(
|
|
6439
|
+
err("BKN_TRACE_JOIN_FAILED", `$.logs[${index}].span_id`, "log span_id not found")
|
|
6440
|
+
);
|
|
6441
|
+
}
|
|
6442
|
+
});
|
|
6443
|
+
const events = Array.isArray(root.events) ? root.events : [];
|
|
6444
|
+
const eventIds = /* @__PURE__ */ new Set();
|
|
6445
|
+
const knownEventIds = /* @__PURE__ */ new Set();
|
|
6446
|
+
const knownOperationIds = /* @__PURE__ */ new Set();
|
|
6447
|
+
const knownClaimIds = /* @__PURE__ */ new Set();
|
|
6448
|
+
const actionStates = /* @__PURE__ */ new Map();
|
|
6449
|
+
events.forEach((item, index) => {
|
|
6450
|
+
const event = asRecord(item);
|
|
6451
|
+
const eventPath = `$.events[${index}]`;
|
|
6452
|
+
checkRequired(event, REQUIRED_EVENT_FIELDS, `$.events[${index}]`, errors);
|
|
6453
|
+
checkTimestamp(event.observed_at, `$.events[${index}].observed_at`, errors);
|
|
6454
|
+
checkTimestamp(event.emitted_at, `$.events[${index}].emitted_at`, errors);
|
|
6455
|
+
if (event.trace_id !== traceId || event["bkn.request.id"] !== requestId) {
|
|
6456
|
+
errors.push(
|
|
6457
|
+
err("BKN_TRACE_JOIN_FAILED", `$.events[${index}]`, "event cannot join trace/request")
|
|
6458
|
+
);
|
|
6459
|
+
}
|
|
6460
|
+
if (!spanIds.has(String(event.span_id))) {
|
|
6461
|
+
errors.push(
|
|
6462
|
+
err("BKN_TRACE_JOIN_FAILED", `$.events[${index}].span_id`, "event span_id not found")
|
|
6463
|
+
);
|
|
6464
|
+
}
|
|
6465
|
+
if (typeof event.event_id === "string") {
|
|
6466
|
+
if (eventIds.has(event.event_id)) {
|
|
6467
|
+
errors.push(
|
|
6468
|
+
err("BKN_TRACE_EVENT_ID_CONFLICT", `${eventPath}.event_id`, "duplicate event_id")
|
|
6469
|
+
);
|
|
6470
|
+
}
|
|
6471
|
+
eventIds.add(event.event_id);
|
|
6472
|
+
}
|
|
6473
|
+
if (contractVersion !== BUSINESS_CONTRACT_VERSION) return;
|
|
6474
|
+
if (event["bkn.trace.schema.version"] !== contractVersion) {
|
|
6475
|
+
errors.push(
|
|
6476
|
+
err(
|
|
6477
|
+
"BKN_TRACE_SCHEMA_VERSION_UNSUPPORTED",
|
|
6478
|
+
`${eventPath}.bkn.trace.schema.version`,
|
|
6479
|
+
"event contract version must match the fixture envelope"
|
|
6480
|
+
)
|
|
6481
|
+
);
|
|
6482
|
+
}
|
|
6483
|
+
validateBusinessEvent(
|
|
6484
|
+
event,
|
|
6485
|
+
eventPath,
|
|
6486
|
+
knownEventIds,
|
|
6487
|
+
knownOperationIds,
|
|
6488
|
+
knownClaimIds,
|
|
6489
|
+
actionStates,
|
|
6490
|
+
errors
|
|
6491
|
+
);
|
|
6492
|
+
if (typeof event.event_id === "string") knownEventIds.add(event.event_id);
|
|
6493
|
+
if (typeof event.operation_id === "string") knownOperationIds.add(event.operation_id);
|
|
6494
|
+
if (event.event_type === "claim.created" && typeof event.claim_id === "string") {
|
|
6495
|
+
knownClaimIds.add(event.claim_id);
|
|
6496
|
+
}
|
|
6497
|
+
});
|
|
6498
|
+
if (contractVersion !== "1.0.0" && events.length === 0) {
|
|
6499
|
+
errors.push(err("BKN_TRACE_REQUIRED_FIELD_MISSING", "$.events", "at least one event required"));
|
|
6500
|
+
}
|
|
6501
|
+
const baggage = asRecord(root.baggage);
|
|
6502
|
+
for (const key of Object.keys(baggage)) {
|
|
6503
|
+
if (!ALLOWED_BAGGAGE2.has(key)) {
|
|
6504
|
+
errors.push(
|
|
6505
|
+
err(
|
|
6506
|
+
"BKN_TRACE_BAGGAGE_FORBIDDEN_FIELD",
|
|
6507
|
+
`$.baggage.${key}`,
|
|
6508
|
+
`baggage field ${key} is forbidden`
|
|
6509
|
+
)
|
|
6510
|
+
);
|
|
6511
|
+
}
|
|
6512
|
+
}
|
|
6513
|
+
checkSensitive(root, "$", errors);
|
|
6514
|
+
const result = errors.length > 0 ? "fail" : "pass";
|
|
6515
|
+
const expectedResult = root.expected_result === "pass" || root.expected_result === "fail" ? root.expected_result : null;
|
|
6516
|
+
return {
|
|
6517
|
+
fixtureId,
|
|
6518
|
+
result,
|
|
6519
|
+
contractVersion,
|
|
6520
|
+
errors,
|
|
6521
|
+
warnings: [],
|
|
6522
|
+
expectedResult,
|
|
6523
|
+
expectationMatched: expectedResult === null ? result === "pass" : expectedResult === result
|
|
6524
|
+
};
|
|
6525
|
+
}
|
|
6526
|
+
function validateBusinessEvent(event, path, knownEventIds, knownOperationIds, knownClaimIds, actionStates, errors) {
|
|
6527
|
+
const eventType = typeof event.event_type === "string" ? event.event_type : "";
|
|
6528
|
+
if (!BUSINESS_EVENT_TYPES.has(eventType)) {
|
|
6529
|
+
errors.push(
|
|
6530
|
+
err(
|
|
6531
|
+
"BKN_TRACE_EVENT_TYPE_UNSUPPORTED",
|
|
6532
|
+
`${path}.event_type`,
|
|
6533
|
+
`unsupported event ${eventType}`
|
|
6534
|
+
)
|
|
6535
|
+
);
|
|
6536
|
+
return;
|
|
6537
|
+
}
|
|
6538
|
+
checkRequired(event, ["interaction_id"], path, errors);
|
|
6539
|
+
if (eventType !== "agent.interaction.started" && eventType !== "claim.created") {
|
|
6540
|
+
checkRequired(event, ["operation_id"], path, errors);
|
|
6541
|
+
}
|
|
6542
|
+
if (eventType !== "agent.interaction.started") {
|
|
6543
|
+
checkRequired(event, ["causation_event_id"], path, errors);
|
|
6544
|
+
if (typeof event.causation_event_id === "string" && !knownEventIds.has(event.causation_event_id)) {
|
|
6545
|
+
errors.push(
|
|
6546
|
+
err(
|
|
6547
|
+
"BKN_TRACE_CAUSATION_INVALID",
|
|
6548
|
+
`${path}.causation_event_id`,
|
|
6549
|
+
"causation_event_id must reference an earlier event"
|
|
6550
|
+
)
|
|
6551
|
+
);
|
|
6552
|
+
}
|
|
6553
|
+
}
|
|
6554
|
+
if (CLAIM_EVENT_TYPES.has(eventType)) {
|
|
6555
|
+
checkRequired(event, ["claim_id"], path, errors);
|
|
6556
|
+
if (eventType !== "claim.created" && typeof event.claim_id === "string" && !knownClaimIds.has(event.claim_id)) {
|
|
6557
|
+
errors.push(
|
|
6558
|
+
err(
|
|
6559
|
+
"BKN_TRACE_UNKNOWN_CLAIM_ID",
|
|
6560
|
+
`${path}.claim_id`,
|
|
6561
|
+
"event must reference an earlier claim"
|
|
6562
|
+
)
|
|
6563
|
+
);
|
|
6564
|
+
}
|
|
6565
|
+
}
|
|
6566
|
+
const payload = asRecord(event.payload);
|
|
6567
|
+
checkAllowedKeys(
|
|
6568
|
+
payload,
|
|
6569
|
+
EVENT_PAYLOAD_FIELDS[eventType] ?? /* @__PURE__ */ new Set(),
|
|
6570
|
+
`${path}.payload`,
|
|
6571
|
+
errors
|
|
6572
|
+
);
|
|
6573
|
+
if (eventType === "agent.interaction.started") {
|
|
6574
|
+
checkRequired(payload, ["intent_hash", "mode"], `${path}.payload`, errors);
|
|
6575
|
+
checkOneOf(payload, ["agent_id", "app_ref"], `${path}.payload`, errors);
|
|
6576
|
+
}
|
|
6577
|
+
if (eventType === "retrieval.completed") {
|
|
6578
|
+
checkRequired(
|
|
6579
|
+
payload,
|
|
6580
|
+
["query_hash", "candidate_count", "truncated"],
|
|
6581
|
+
`${path}.payload`,
|
|
6582
|
+
errors
|
|
6583
|
+
);
|
|
6584
|
+
}
|
|
6585
|
+
if (eventType === "knowledge.read.observed") {
|
|
6586
|
+
checkRequired(payload, ["kn_id", "read_kind", "version_status"], `${path}.payload`, errors);
|
|
6587
|
+
}
|
|
6588
|
+
if (eventType === "data.query.observed") {
|
|
6589
|
+
checkRequired(payload, ["query_hash", "query_type", "row_count"], `${path}.payload`, errors);
|
|
6590
|
+
}
|
|
6591
|
+
if (eventType === "model.call.observed") {
|
|
6592
|
+
checkRequired(
|
|
6593
|
+
payload,
|
|
6594
|
+
[
|
|
6595
|
+
"model_name",
|
|
6596
|
+
"model_provider",
|
|
6597
|
+
"status",
|
|
6598
|
+
"input_token_count",
|
|
6599
|
+
"output_token_count",
|
|
6600
|
+
"prompt_hash",
|
|
6601
|
+
"output_hash"
|
|
6602
|
+
],
|
|
6603
|
+
`${path}.payload`,
|
|
6604
|
+
errors
|
|
6605
|
+
);
|
|
6606
|
+
if (payload.status === "error") {
|
|
6607
|
+
checkRequired(payload, ["error_category", "error_hash"], `${path}.payload`, errors);
|
|
6608
|
+
}
|
|
6609
|
+
}
|
|
6610
|
+
if (eventType === "claim.created") {
|
|
6611
|
+
checkRequired(
|
|
6612
|
+
payload,
|
|
6613
|
+
[
|
|
6614
|
+
"claim_id",
|
|
6615
|
+
"claim_type",
|
|
6616
|
+
"claim_hash",
|
|
6617
|
+
"source_event_ids",
|
|
6618
|
+
"operation_ids",
|
|
6619
|
+
"visibility",
|
|
6620
|
+
"version_status"
|
|
6621
|
+
],
|
|
6622
|
+
`${path}.payload`,
|
|
6623
|
+
errors
|
|
6624
|
+
);
|
|
6625
|
+
checkNonEmptyArray(payload, "source_event_ids", `${path}.payload`, errors);
|
|
6626
|
+
checkNonEmptyArray(payload, "operation_ids", `${path}.payload`, errors);
|
|
6627
|
+
checkKnownArray(payload, "source_event_ids", knownEventIds, `${path}.payload`, errors);
|
|
6628
|
+
checkKnownArray(payload, "operation_ids", knownOperationIds, `${path}.payload`, errors);
|
|
6629
|
+
}
|
|
6630
|
+
if (eventType === "evidence.refs.created") {
|
|
6631
|
+
checkReferenceList(payload, "evidence_refs", `${path}.payload`, errors);
|
|
6632
|
+
}
|
|
6633
|
+
if (eventType === "business.refs.resolved") {
|
|
6634
|
+
checkRequired(payload, ["resolver_status"], `${path}.payload`, errors);
|
|
6635
|
+
checkReferenceList(
|
|
6636
|
+
payload,
|
|
6637
|
+
"business_refs",
|
|
6638
|
+
`${path}.payload`,
|
|
6639
|
+
errors,
|
|
6640
|
+
payload.resolver_status === "unresolved"
|
|
6641
|
+
);
|
|
6642
|
+
}
|
|
6643
|
+
const actionState = ACTION_STATE_BY_EVENT[eventType];
|
|
6644
|
+
if (!actionState) return;
|
|
6645
|
+
checkRequired(payload, ["action_instance_id", "status"], `${path}.payload`, errors);
|
|
6646
|
+
const fixedStatus = {
|
|
6647
|
+
"action.recommended": "recommended",
|
|
6648
|
+
"action.approval_requested": "approval_requested",
|
|
6649
|
+
"action.approved": "approved",
|
|
6650
|
+
"action.rejected": "rejected"
|
|
6651
|
+
};
|
|
6652
|
+
if (fixedStatus[eventType] && payload.status !== fixedStatus[eventType]) {
|
|
6653
|
+
errors.push(
|
|
6654
|
+
err(
|
|
6655
|
+
"BKN_TRACE_ACTION_TRANSITION_INVALID",
|
|
6656
|
+
`${path}.payload.status`,
|
|
6657
|
+
`${eventType} requires status=${fixedStatus[eventType]}`
|
|
6658
|
+
)
|
|
6659
|
+
);
|
|
6660
|
+
}
|
|
6661
|
+
if (eventType === "action.recommended") {
|
|
6662
|
+
checkRequired(
|
|
6663
|
+
payload,
|
|
6664
|
+
["action_type", "target_refs", "reason_hash"],
|
|
6665
|
+
`${path}.payload`,
|
|
6666
|
+
errors
|
|
6667
|
+
);
|
|
6668
|
+
checkNonEmptyArray(payload, "target_refs", `${path}.payload`, errors);
|
|
6669
|
+
checkQualifiedStringRefs(payload, "target_refs", `${path}.payload`, errors);
|
|
6670
|
+
}
|
|
6671
|
+
if (eventType === "action.approval_requested") {
|
|
6672
|
+
checkRequired(payload, ["policy_ref"], `${path}.payload`, errors);
|
|
6673
|
+
}
|
|
6674
|
+
if (eventType === "action.approved" || eventType === "action.rejected") {
|
|
6675
|
+
checkRequired(payload, ["actor_ref", "policy_decision_ref"], `${path}.payload`, errors);
|
|
6676
|
+
}
|
|
6677
|
+
if (eventType === "action.executed") {
|
|
6678
|
+
checkOneOf(payload, ["invocation_ref", "tool_ref"], `${path}.payload`, errors);
|
|
6679
|
+
if (payload.status === "error") {
|
|
6680
|
+
checkRequired(payload, ["error_category", "error_hash"], `${path}.payload`, errors);
|
|
6681
|
+
}
|
|
6682
|
+
}
|
|
6683
|
+
if (eventType === "action.result_recorded") {
|
|
6684
|
+
checkRequired(payload, ["result_hash"], `${path}.payload`, errors);
|
|
6685
|
+
checkOneOf(payload, ["artifact_ref", "task_ref"], `${path}.payload`, errors);
|
|
6686
|
+
}
|
|
6687
|
+
const actionID = typeof payload.action_instance_id === "string" ? payload.action_instance_id : "";
|
|
6688
|
+
if (!actionID) return;
|
|
6689
|
+
const claimID = typeof event.claim_id === "string" ? event.claim_id : "";
|
|
6690
|
+
const operationID = typeof event.operation_id === "string" ? event.operation_id : "";
|
|
6691
|
+
const previous = actionStates.get(actionID);
|
|
6692
|
+
if (!previous && actionState !== "recommended" || previous && (!ACTION_TRANSITIONS[previous.state]?.has(actionState) || event.causation_event_id !== previous.lastEventID || claimID !== previous.claimID || operationID !== previous.operationID)) {
|
|
6693
|
+
errors.push(
|
|
6694
|
+
err(
|
|
6695
|
+
"BKN_TRACE_ACTION_TRANSITION_INVALID",
|
|
6696
|
+
`${path}.event_type`,
|
|
6697
|
+
`invalid action transition ${previous?.state ?? "<none>"} -> ${actionState}`
|
|
6698
|
+
)
|
|
6699
|
+
);
|
|
6700
|
+
return;
|
|
6701
|
+
}
|
|
6702
|
+
actionStates.set(actionID, {
|
|
6703
|
+
state: actionState,
|
|
6704
|
+
claimID,
|
|
6705
|
+
operationID,
|
|
6706
|
+
lastEventID: String(event.event_id ?? "")
|
|
6707
|
+
});
|
|
6708
|
+
}
|
|
6709
|
+
function checkOneOf(payload, fields, path, errors) {
|
|
6710
|
+
if (fields.some((field) => typeof payload[field] === "string" && payload[field] !== "")) return;
|
|
6711
|
+
errors.push(
|
|
6712
|
+
err(
|
|
6713
|
+
"BKN_TRACE_REQUIRED_FIELD_MISSING",
|
|
6714
|
+
`${path}.${fields[0]}`,
|
|
6715
|
+
`one of ${fields.join(" or ")} is required`
|
|
6716
|
+
)
|
|
6717
|
+
);
|
|
6718
|
+
}
|
|
6719
|
+
function checkNonEmptyArray(payload, field, path, errors) {
|
|
6720
|
+
if (Array.isArray(payload[field]) && payload[field].length > 0) return;
|
|
6721
|
+
errors.push(
|
|
6722
|
+
err(
|
|
6723
|
+
"BKN_TRACE_REQUIRED_FIELD_MISSING",
|
|
6724
|
+
`${path}.${field}`,
|
|
6725
|
+
`${field} must be a non-empty array`
|
|
6726
|
+
)
|
|
6727
|
+
);
|
|
6728
|
+
}
|
|
6729
|
+
function checkReferenceList(payload, field, path, errors, allowEmpty = false) {
|
|
6730
|
+
if (!allowEmpty) checkNonEmptyArray(payload, field, path, errors);
|
|
6731
|
+
const refs = Array.isArray(payload[field]) ? payload[field] : [];
|
|
6732
|
+
refs.forEach((value, index) => {
|
|
6733
|
+
const ref = asRecord(value);
|
|
6734
|
+
checkRequired(
|
|
6735
|
+
ref,
|
|
6736
|
+
["ref_id", "ref_type", "source_system", "validity", "version_status", "visibility"],
|
|
6737
|
+
`${path}.${field}[${index}]`,
|
|
6738
|
+
errors
|
|
6739
|
+
);
|
|
6740
|
+
checkAllowedKeys(ref, REFERENCE_FIELDS, `${path}.${field}[${index}]`, errors);
|
|
6741
|
+
if (typeof ref.ref_id === "string" && !isQualifiedReference(ref.ref_id)) {
|
|
6742
|
+
errors.push(
|
|
6743
|
+
err(
|
|
6744
|
+
"BKN_TRACE_REFERENCE_ID_INVALID",
|
|
6745
|
+
`${path}.${field}[${index}].ref_id`,
|
|
6746
|
+
"business reference id must include its knowledge-network or resource scope"
|
|
6747
|
+
)
|
|
6748
|
+
);
|
|
6749
|
+
}
|
|
6750
|
+
});
|
|
6751
|
+
}
|
|
6752
|
+
function checkQualifiedStringRefs(payload, field, path, errors) {
|
|
6753
|
+
const refs = Array.isArray(payload[field]) ? payload[field] : [];
|
|
6754
|
+
refs.forEach((value, index) => {
|
|
6755
|
+
if (typeof value !== "string" || isQualifiedReference(value)) return;
|
|
6756
|
+
errors.push(
|
|
6757
|
+
err(
|
|
6758
|
+
"BKN_TRACE_REFERENCE_ID_INVALID",
|
|
6759
|
+
`${path}.${field}[${index}]`,
|
|
6760
|
+
"business reference id must include its knowledge-network or resource scope"
|
|
6761
|
+
)
|
|
6762
|
+
);
|
|
6763
|
+
});
|
|
6764
|
+
}
|
|
6765
|
+
function isQualifiedReference(value) {
|
|
6766
|
+
const parts = value.trim().split(":");
|
|
6767
|
+
if (parts.some((part) => part.length === 0)) return false;
|
|
6768
|
+
if (["kn", "resource"].includes(parts[0] ?? "")) return parts.length === 2;
|
|
6769
|
+
if (["object", "relation", "action_type", "metric", "field"].includes(parts[0] ?? "")) {
|
|
6770
|
+
return parts.length === 3;
|
|
6771
|
+
}
|
|
6772
|
+
if (parts[0] === "property") return parts.length === 4;
|
|
6773
|
+
return true;
|
|
6774
|
+
}
|
|
6775
|
+
function checkKnownArray(payload, field, known, path, errors) {
|
|
6776
|
+
if (!Array.isArray(payload[field])) return;
|
|
6777
|
+
for (const value of payload[field]) {
|
|
6778
|
+
if (typeof value === "string" && known.has(value)) continue;
|
|
6779
|
+
errors.push(
|
|
6780
|
+
err(
|
|
6781
|
+
"BKN_TRACE_CAUSATION_INVALID",
|
|
6782
|
+
`${path}.${field}`,
|
|
6783
|
+
`${field} must reference earlier events or operations`
|
|
6784
|
+
)
|
|
6785
|
+
);
|
|
6786
|
+
}
|
|
6787
|
+
}
|
|
6788
|
+
function checkAllowedKeys(value, allowed, path, errors) {
|
|
6789
|
+
for (const key of Object.keys(value)) {
|
|
6790
|
+
if (allowed.has(key)) continue;
|
|
6791
|
+
errors.push(
|
|
6792
|
+
err(
|
|
6793
|
+
"BKN_TRACE_EVENT_PAYLOAD_FIELD_UNSUPPORTED",
|
|
6794
|
+
`${path}.${key}`,
|
|
6795
|
+
`payload field ${key} is not registered for this event`
|
|
6796
|
+
)
|
|
6797
|
+
);
|
|
6798
|
+
}
|
|
6799
|
+
}
|
|
6800
|
+
function validateFixturePath(path) {
|
|
6801
|
+
const results = jsonFiles(path).map((file) => {
|
|
6802
|
+
try {
|
|
6803
|
+
return validateFixture(JSON.parse(readFileSync4(file, "utf8")));
|
|
6804
|
+
} catch (e) {
|
|
6805
|
+
return {
|
|
6806
|
+
fixtureId: file,
|
|
6807
|
+
result: "fail",
|
|
6808
|
+
contractVersion: null,
|
|
6809
|
+
errors: [
|
|
6810
|
+
err(
|
|
6811
|
+
"BKN_TRACE_FIXTURE_PARSE_FAILED",
|
|
6812
|
+
"$",
|
|
6813
|
+
`failed to parse JSON: ${e instanceof Error ? e.message : String(e)}`
|
|
6814
|
+
)
|
|
6815
|
+
],
|
|
6816
|
+
warnings: [],
|
|
6817
|
+
expectedResult: null,
|
|
6818
|
+
expectationMatched: false
|
|
6819
|
+
};
|
|
6820
|
+
}
|
|
6821
|
+
});
|
|
6822
|
+
return { ok: results.every((r) => r.expectationMatched), results };
|
|
6823
|
+
}
|
|
6824
|
+
|
|
4803
6825
|
// src/resources/trace.ts
|
|
4804
6826
|
async function semanticJudge(question, answer, reference) {
|
|
4805
6827
|
const prompt = [
|
|
@@ -4846,6 +6868,32 @@ function trace(ctx) {
|
|
|
4846
6868
|
return {
|
|
4847
6869
|
/** Raw trace search (OpenSearch-style body). */
|
|
4848
6870
|
search: (body) => traceSearch(ctx, body),
|
|
6871
|
+
/** Submit BKN Trace phase-two claim/evidence/business events. */
|
|
6872
|
+
emitEvidenceEvents: (body) => emitEvidenceEvents(ctx, body),
|
|
6873
|
+
/** Store one authorized BKN Trace 2.2 business-content artifact. */
|
|
6874
|
+
emitArtifact: (body) => emitEvidenceArtifact(ctx, body),
|
|
6875
|
+
/** Read one authorized BKN Trace 2.2 business-content artifact. */
|
|
6876
|
+
artifact: (artifactId) => getEvidenceArtifact(ctx, artifactId),
|
|
6877
|
+
/** Product-facing business request list and request-to-trace drilldown. */
|
|
6878
|
+
requests: {
|
|
6879
|
+
get: (requestId) => getRequestSummary(ctx, requestId),
|
|
6880
|
+
list: (query) => listRequestSummaries(ctx, query),
|
|
6881
|
+
traces: (requestId, query) => getRequestTraces(ctx, requestId, query)
|
|
6882
|
+
},
|
|
6883
|
+
/** Aggregate all OpenBKN requests and traces for one caller-owned interaction. */
|
|
6884
|
+
interactions: {
|
|
6885
|
+
get: (interactionId) => getInteractionSummary(ctx, interactionId)
|
|
6886
|
+
},
|
|
6887
|
+
/** Create a typed BKN Trace 2.1 session for an Agent or AI application. */
|
|
6888
|
+
createSession: (options) => new TraceSession({ ...options, emit: (body) => emitEvidenceEvents(ctx, body) }),
|
|
6889
|
+
/** Normalized trace tree/status graph by trace id. */
|
|
6890
|
+
graph: (traceId) => getTraceGraph(ctx, traceId),
|
|
6891
|
+
/** Claim -> evidence/business refs graph by trace id or BKN request id. */
|
|
6892
|
+
evidenceChain: (scope, opts) => getEvidenceChain(ctx, scope, opts),
|
|
6893
|
+
/** Business semantic graph by trace id or BKN request id. */
|
|
6894
|
+
businessGraph: (scope, opts) => getBusinessGraph(ctx, scope, opts),
|
|
6895
|
+
/** Metadata-only evidence snapshot preview by trace id or BKN request id. */
|
|
6896
|
+
snapshotPreview: (scope, opts) => getSnapshotPreview(ctx, scope, opts),
|
|
4849
6897
|
/** All span source docs for a conversation. */
|
|
4850
6898
|
spans: (conversationId, opts) => getSpansByConversation(ctx, conversationId, opts),
|
|
4851
6899
|
diagnose: diagnoseOne,
|
|
@@ -4883,6 +6931,8 @@ function trace(ctx) {
|
|
|
4883
6931
|
},
|
|
4884
6932
|
/** Build eval cases from a loosely-shaped queries object/array. */
|
|
4885
6933
|
evalSetBuild: (raw) => buildCasesFromQueries(raw),
|
|
6934
|
+
/** Validate BKN Trace phase-one fixture files or directories. */
|
|
6935
|
+
validateFixture: (path) => validateFixturePath(path),
|
|
4886
6936
|
/**
|
|
4887
6937
|
* Run an eval set against an agent: each case's query is sent to the agent,
|
|
4888
6938
|
* the resulting trace is fetched, and assertions are checked. `llm` enables
|
|
@@ -4916,19 +6966,29 @@ function vega(ctx) {
|
|
|
4916
6966
|
catalogs: (opts) => listCatalogs(ctx, opts),
|
|
4917
6967
|
getCatalog: (id) => getCatalog(ctx, id),
|
|
4918
6968
|
createCatalog: (req) => createCatalog(ctx, req),
|
|
6969
|
+
updateCatalog: (id, req) => updateCatalog(ctx, id, req),
|
|
4919
6970
|
enableCatalog: (id) => enableCatalog(ctx, id),
|
|
6971
|
+
disableCatalog: (id) => disableCatalog(ctx, id),
|
|
6972
|
+
deleteCatalog: (id) => deleteCatalog(ctx, id),
|
|
6973
|
+
testCatalogConnection: (id) => testCatalogConnection(ctx, id),
|
|
4920
6974
|
discoverCatalog: (id, wait = false) => discoverCatalog(ctx, id, wait),
|
|
4921
|
-
catalogResources: (id, category) => listCatalogResources(ctx, id, category),
|
|
6975
|
+
catalogResources: (id, category, limit, offset) => listCatalogResources(ctx, id, category, limit, offset),
|
|
4922
6976
|
catalogHealth: (ids) => catalogHealthStatus(ctx, ids),
|
|
4923
6977
|
connectorTypes: () => listConnectorTypes(ctx),
|
|
4924
6978
|
connectorType: (type) => getConnectorType(ctx, type),
|
|
6979
|
+
/** Run SQL / OpenSearch DSL directly against a data source. */
|
|
6980
|
+
sql: (body) => runSql(ctx, body),
|
|
4925
6981
|
/** Build a resource's index. With `wait`, polls until terminal. */
|
|
4926
6982
|
build: async (req, opts = {}) => {
|
|
4927
6983
|
const task = await createBuildTask(ctx, req);
|
|
4928
6984
|
if (!opts.wait) return task;
|
|
4929
6985
|
return pollBuildTask(ctx, task.id, opts.timeoutMs ?? 3e5, opts.intervalMs ?? 2e3);
|
|
4930
6986
|
},
|
|
4931
|
-
buildStatus: (taskId) => getBuildTask(ctx, taskId)
|
|
6987
|
+
buildStatus: (taskId) => getBuildTask(ctx, taskId),
|
|
6988
|
+
buildTasks: (opts) => listBuildTasks(ctx, opts),
|
|
6989
|
+
deleteBuildTasks: (ids, opts) => deleteBuildTasks(ctx, ids, opts),
|
|
6990
|
+
startBuildTask: (taskId, opts) => startBuildTask(ctx, taskId, opts),
|
|
6991
|
+
stopBuildTask: (taskId) => stopBuildTask(ctx, taskId)
|
|
4932
6992
|
};
|
|
4933
6993
|
}
|
|
4934
6994
|
async function pollBuildTask(ctx, taskId, timeoutMs, intervalMs) {
|
|
@@ -4946,7 +7006,7 @@ function sleep(ms) {
|
|
|
4946
7006
|
}
|
|
4947
7007
|
|
|
4948
7008
|
// src/api/call.ts
|
|
4949
|
-
import { readFileSync as
|
|
7009
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
4950
7010
|
function parseHeader(raw) {
|
|
4951
7011
|
const idx = raw.indexOf(":");
|
|
4952
7012
|
if (idx <= 0) return null;
|
|
@@ -4964,7 +7024,6 @@ function resolveUrl(ctx, path) {
|
|
|
4964
7024
|
return path.startsWith("http") ? path : `${ctx.baseUrl}${path.startsWith("/") ? "" : "/"}${path}`;
|
|
4965
7025
|
}
|
|
4966
7026
|
async function rawCall(ctx, path, opts = {}) {
|
|
4967
|
-
applyTls(ctx);
|
|
4968
7027
|
const url = resolveUrl(ctx, path);
|
|
4969
7028
|
const extra = {};
|
|
4970
7029
|
for (const h of opts.header ?? []) {
|
|
@@ -4976,7 +7035,7 @@ async function rawCall(ctx, path, opts = {}) {
|
|
|
4976
7035
|
const fd = new FormData();
|
|
4977
7036
|
for (const field of opts.form) {
|
|
4978
7037
|
const [key, value, isFile] = parseFormField(field);
|
|
4979
|
-
if (isFile) fd.append(key, new Blob([
|
|
7038
|
+
if (isFile) fd.append(key, new Blob([readFileSync5(value)]), value.split("/").pop());
|
|
4980
7039
|
else fd.append(key, value);
|
|
4981
7040
|
}
|
|
4982
7041
|
body = fd;
|
|
@@ -4993,7 +7052,12 @@ async function rawCall(ctx, path, opts = {}) {
|
|
|
4993
7052
|
try {
|
|
4994
7053
|
const res = await authFetch(
|
|
4995
7054
|
ctx,
|
|
4996
|
-
() =>
|
|
7055
|
+
() => tlsFetch(ctx.insecure, url, {
|
|
7056
|
+
method,
|
|
7057
|
+
headers: headersFor(),
|
|
7058
|
+
body,
|
|
7059
|
+
signal: controller.signal
|
|
7060
|
+
})
|
|
4997
7061
|
);
|
|
4998
7062
|
return { status: res.status, statusText: res.statusText, body: await res.text() };
|
|
4999
7063
|
} finally {
|
|
@@ -5001,6 +7065,53 @@ async function rawCall(ctx, path, opts = {}) {
|
|
|
5001
7065
|
}
|
|
5002
7066
|
}
|
|
5003
7067
|
|
|
7068
|
+
// src/api/app-keys.ts
|
|
7069
|
+
var ME = "/api/safe/v1/me/api-keys";
|
|
7070
|
+
var ADMIN2 = "/api/safe/v1/admin/api-keys";
|
|
7071
|
+
function listMyApiKeys(ctx) {
|
|
7072
|
+
return request(ctx, ME);
|
|
7073
|
+
}
|
|
7074
|
+
function createMyApiKey(ctx, input) {
|
|
7075
|
+
return request(ctx, ME, {
|
|
7076
|
+
method: "POST",
|
|
7077
|
+
body: {
|
|
7078
|
+
name: input.name,
|
|
7079
|
+
...input.expiresAt ? { expires_at: input.expiresAt } : {},
|
|
7080
|
+
...input.neverExpire ? { never_expire: true } : {}
|
|
7081
|
+
}
|
|
7082
|
+
});
|
|
7083
|
+
}
|
|
7084
|
+
async function revokeMyApiKey(ctx, id) {
|
|
7085
|
+
await request(ctx, `${ME}/${encodeURIComponent(id)}`, { method: "DELETE" });
|
|
7086
|
+
}
|
|
7087
|
+
function regenerateMyApiKey(ctx, id) {
|
|
7088
|
+
return request(ctx, `${ME}/${encodeURIComponent(id)}/regenerate`, { method: "POST" });
|
|
7089
|
+
}
|
|
7090
|
+
function listApiKeysAdmin(ctx, ownerId) {
|
|
7091
|
+
return request(ctx, ADMIN2, { query: { owner_id: ownerId || void 0 } });
|
|
7092
|
+
}
|
|
7093
|
+
async function revokeApiKeyAdmin(ctx, id) {
|
|
7094
|
+
await request(ctx, `${ADMIN2}/${encodeURIComponent(id)}`, { method: "DELETE" });
|
|
7095
|
+
}
|
|
7096
|
+
|
|
7097
|
+
// src/resources/app-keys.ts
|
|
7098
|
+
function appKeys(ctx) {
|
|
7099
|
+
return {
|
|
7100
|
+
/** List the caller's own keys (no secrets). */
|
|
7101
|
+
list: () => listMyApiKeys(ctx),
|
|
7102
|
+
/** Issue a key — the result's `key` is the plaintext, shown only once. */
|
|
7103
|
+
create: (input) => createMyApiKey(ctx, input),
|
|
7104
|
+
/** Revoke one of the caller's keys (immediate). */
|
|
7105
|
+
revoke: (id) => revokeMyApiKey(ctx, id),
|
|
7106
|
+
/** Rotate a key in place — new plaintext (shown once); old secret dies now. */
|
|
7107
|
+
regenerate: (id) => regenerateMyApiKey(ctx, id),
|
|
7108
|
+
/** Admin: list all keys, or one owner's (adds `owner_user_id`). */
|
|
7109
|
+
adminList: (ownerId) => listApiKeysAdmin(ctx, ownerId),
|
|
7110
|
+
/** Admin: revoke any key. */
|
|
7111
|
+
adminRevoke: (id) => revokeApiKeyAdmin(ctx, id)
|
|
7112
|
+
};
|
|
7113
|
+
}
|
|
7114
|
+
|
|
5004
7115
|
// src/client.ts
|
|
5005
7116
|
function createClient(opts = {}) {
|
|
5006
7117
|
const ctx = resolveContext(opts);
|
|
@@ -5016,6 +7127,7 @@ function createClient(opts = {}) {
|
|
|
5016
7127
|
toolboxes: toolboxes(ctx),
|
|
5017
7128
|
trace: trace(ctx),
|
|
5018
7129
|
admin: admin(ctx),
|
|
7130
|
+
appKeys: appKeys(ctx),
|
|
5019
7131
|
vega: vega(ctx),
|
|
5020
7132
|
call: (path, callOpts) => rawCall(ctx, path, callOpts)
|
|
5021
7133
|
};
|
|
@@ -5024,7 +7136,6 @@ function createClient(opts = {}) {
|
|
|
5024
7136
|
// src/resources/auth.ts
|
|
5025
7137
|
var auth_exports = {};
|
|
5026
7138
|
__export(auth_exports, {
|
|
5027
|
-
attachNoAuth: () => attachNoAuth,
|
|
5028
7139
|
attachToken: () => attachToken,
|
|
5029
7140
|
currentToken: () => currentToken,
|
|
5030
7141
|
currentTokenFresh: () => currentTokenFresh,
|
|
@@ -5062,7 +7173,8 @@ function attachToken(baseUrl, accessToken, opts = {}) {
|
|
|
5062
7173
|
accessToken,
|
|
5063
7174
|
refreshToken: opts.refreshToken,
|
|
5064
7175
|
idToken: opts.idToken,
|
|
5065
|
-
|
|
7176
|
+
// Remember `-k` so a self-signed platform needn't repeat it every command.
|
|
7177
|
+
tlsInsecure: opts.insecure ? true : void 0,
|
|
5066
7178
|
// Prefer the account the user typed (-u); device tokens carry no username.
|
|
5067
7179
|
username: opts.username ?? decodeJwt(opts.idToken ?? accessToken)?.preferred_username
|
|
5068
7180
|
};
|
|
@@ -5070,33 +7182,40 @@ function attachToken(baseUrl, accessToken, opts = {}) {
|
|
|
5070
7182
|
setActivePlatform(url);
|
|
5071
7183
|
return { baseUrl: url, userId, username: usernameOf(token) };
|
|
5072
7184
|
}
|
|
5073
|
-
function
|
|
5074
|
-
|
|
5075
|
-
|
|
5076
|
-
|
|
5077
|
-
|
|
7185
|
+
function targetUser(baseUrl, userOrName) {
|
|
7186
|
+
if (!userOrName) return activeUserId(baseUrl);
|
|
7187
|
+
const id = findUserId(baseUrl, userOrName);
|
|
7188
|
+
if (!id) {
|
|
7189
|
+
const known = usersOfPlatform(baseUrl).map((u) => u.username ?? u.userId).join(", ");
|
|
7190
|
+
throw new InputError(
|
|
7191
|
+
`No saved user '${userOrName}' on ${baseUrl}. Saved: ${known || "(none)"}.`
|
|
7192
|
+
);
|
|
7193
|
+
}
|
|
7194
|
+
return id;
|
|
5078
7195
|
}
|
|
5079
|
-
function status() {
|
|
7196
|
+
function status(opts = {}) {
|
|
5080
7197
|
const baseUrl = activePlatform();
|
|
5081
7198
|
if (!baseUrl) return { hasToken: false };
|
|
5082
|
-
const
|
|
7199
|
+
const userId = targetUser(baseUrl, opts.user);
|
|
7200
|
+
const token = readToken(baseUrl, userId);
|
|
5083
7201
|
return {
|
|
5084
7202
|
baseUrl,
|
|
5085
|
-
userId
|
|
7203
|
+
userId,
|
|
5086
7204
|
hasToken: token !== void 0,
|
|
5087
7205
|
username: usernameOf(token),
|
|
5088
7206
|
expired: token ? isExpired(decodeJwt(token.accessToken)) : void 0
|
|
5089
7207
|
};
|
|
5090
7208
|
}
|
|
5091
|
-
function currentToken() {
|
|
7209
|
+
function currentToken(opts = {}) {
|
|
5092
7210
|
const baseUrl = activePlatform();
|
|
5093
|
-
const token = baseUrl ? readToken(baseUrl) : void 0;
|
|
7211
|
+
const token = baseUrl ? readToken(baseUrl, targetUser(baseUrl, opts.user)) : void 0;
|
|
5094
7212
|
if (!token) throw new InputError("Not logged in. Run `openbkn auth login <url> --token <t>`.");
|
|
5095
7213
|
return token.accessToken;
|
|
5096
7214
|
}
|
|
5097
|
-
async function currentTokenFresh() {
|
|
7215
|
+
async function currentTokenFresh(opts = {}) {
|
|
5098
7216
|
const baseUrl = activePlatform();
|
|
5099
|
-
const
|
|
7217
|
+
const userId = baseUrl ? targetUser(baseUrl, opts.user) : void 0;
|
|
7218
|
+
const token = baseUrl ? readToken(baseUrl, userId) : void 0;
|
|
5100
7219
|
if (!baseUrl || !token) {
|
|
5101
7220
|
throw new InputError("Not logged in. Run `openbkn auth login <url> --token <t>`.");
|
|
5102
7221
|
}
|
|
@@ -5105,22 +7224,27 @@ async function currentTokenFresh() {
|
|
|
5105
7224
|
const needsRefresh = decodable ? isExpired(claims) : true;
|
|
5106
7225
|
if (token.refreshToken && needsRefresh) {
|
|
5107
7226
|
try {
|
|
5108
|
-
const t = await refreshAccessToken(baseUrl, token.refreshToken);
|
|
5109
|
-
writeToken(
|
|
5110
|
-
|
|
5111
|
-
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
|
|
7227
|
+
const t = await refreshAccessToken(baseUrl, token.refreshToken, void 0, opts.insecure);
|
|
7228
|
+
writeToken(
|
|
7229
|
+
baseUrl,
|
|
7230
|
+
{
|
|
7231
|
+
...token,
|
|
7232
|
+
accessToken: t.accessToken,
|
|
7233
|
+
refreshToken: t.refreshToken ?? token.refreshToken,
|
|
7234
|
+
idToken: t.idToken ?? token.idToken
|
|
7235
|
+
},
|
|
7236
|
+
{ setActive: !opts.user }
|
|
7237
|
+
);
|
|
5115
7238
|
return t.accessToken;
|
|
5116
7239
|
} catch {
|
|
5117
7240
|
}
|
|
5118
7241
|
}
|
|
5119
7242
|
return token.accessToken;
|
|
5120
7243
|
}
|
|
5121
|
-
function whoami() {
|
|
7244
|
+
function whoami(opts = {}) {
|
|
5122
7245
|
const baseUrl = activePlatform();
|
|
5123
|
-
const
|
|
7246
|
+
const userId = baseUrl ? targetUser(baseUrl, opts.user) : void 0;
|
|
7247
|
+
const token = baseUrl ? readToken(baseUrl, userId) : void 0;
|
|
5124
7248
|
const claims = decodeJwt(token?.idToken ?? "") ?? decodeJwt(token?.accessToken ?? "");
|
|
5125
7249
|
if (!claims) {
|
|
5126
7250
|
throw new InputError(
|
|
@@ -5130,7 +7254,7 @@ function whoami() {
|
|
|
5130
7254
|
return {
|
|
5131
7255
|
...claims,
|
|
5132
7256
|
baseUrl: baseUrl ?? void 0,
|
|
5133
|
-
userId
|
|
7257
|
+
userId,
|
|
5134
7258
|
username: usernameOf(token)
|
|
5135
7259
|
};
|
|
5136
7260
|
}
|
|
@@ -5194,7 +7318,6 @@ export {
|
|
|
5194
7318
|
formatError,
|
|
5195
7319
|
isHeadless,
|
|
5196
7320
|
openBrowser,
|
|
5197
|
-
fetchAuthStatus,
|
|
5198
7321
|
deviceLogin,
|
|
5199
7322
|
credentialDeviceLogin,
|
|
5200
7323
|
request,
|
|
@@ -5209,6 +7332,7 @@ export {
|
|
|
5209
7332
|
writePlatformConfig,
|
|
5210
7333
|
resolveContext,
|
|
5211
7334
|
getUserSafe,
|
|
7335
|
+
changePasswordSafe,
|
|
5212
7336
|
admin,
|
|
5213
7337
|
agents,
|
|
5214
7338
|
context,
|
|
@@ -5221,11 +7345,12 @@ export {
|
|
|
5221
7345
|
skills,
|
|
5222
7346
|
toolboxes,
|
|
5223
7347
|
renderReportMarkdown,
|
|
7348
|
+
validateFixturePath,
|
|
7349
|
+
TraceSession,
|
|
5224
7350
|
trace,
|
|
5225
7351
|
vega,
|
|
5226
7352
|
createClient,
|
|
5227
7353
|
attachToken,
|
|
5228
|
-
attachNoAuth,
|
|
5229
7354
|
status,
|
|
5230
7355
|
currentToken,
|
|
5231
7356
|
currentTokenFresh,
|
|
@@ -5238,4 +7363,4 @@ export {
|
|
|
5238
7363
|
exportCreds,
|
|
5239
7364
|
auth_exports
|
|
5240
7365
|
};
|
|
5241
|
-
//# sourceMappingURL=chunk-
|
|
7366
|
+
//# sourceMappingURL=chunk-LH3ONZGQ.js.map
|