@openbkn/bkn-sdk 0.1.1 → 0.1.3-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/README.zh.md +2 -1
- package/dist/{chunk-NC6DZ2AU.js → chunk-PC2F54XD.js} +2176 -181
- package/dist/cli.js +435 -90
- package/dist/index.d.ts +1471 -62
- package/dist/index.js +9 -1
- package/package.json +5 -4
|
@@ -20,44 +20,53 @@ var HttpError = class extends Error {
|
|
|
20
20
|
this.hint = hint;
|
|
21
21
|
}
|
|
22
22
|
};
|
|
23
|
+
var ToolError = class extends Error {
|
|
24
|
+
code;
|
|
25
|
+
constructor(message, code) {
|
|
26
|
+
super(message);
|
|
27
|
+
this.name = "ToolError";
|
|
28
|
+
if (code) this.code = code;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
23
31
|
var InputError = class extends Error {
|
|
24
32
|
constructor(message) {
|
|
25
33
|
super(message);
|
|
26
34
|
this.name = "InputError";
|
|
27
35
|
}
|
|
28
36
|
};
|
|
29
|
-
function toExitCode(
|
|
30
|
-
if (
|
|
31
|
-
if (
|
|
32
|
-
if (
|
|
37
|
+
function toExitCode(err2) {
|
|
38
|
+
if (err2 instanceof InputError) return 2;
|
|
39
|
+
if (err2 instanceof HttpError) {
|
|
40
|
+
if (err2.status === 401 || err2.status === 403) return 3;
|
|
33
41
|
return 1;
|
|
34
42
|
}
|
|
35
43
|
return 1;
|
|
36
44
|
}
|
|
37
|
-
function formatError(
|
|
38
|
-
if (
|
|
39
|
-
const serverMsg = serverError(
|
|
40
|
-
if (
|
|
41
|
-
const
|
|
42
|
-
return `Not authorized (HTTP 401)${serverMsg ? `: ${serverMsg}` : ""}. ${
|
|
45
|
+
function formatError(err2) {
|
|
46
|
+
if (err2 instanceof HttpError) {
|
|
47
|
+
const serverMsg = serverError(err2.body);
|
|
48
|
+
if (err2.status === 401) {
|
|
49
|
+
const next2 = err2.hint ?? "Run `openbkn auth login` and retry.";
|
|
50
|
+
return `Not authorized (HTTP 401)${serverMsg ? `: ${serverMsg}` : ""}. ${next2}`;
|
|
43
51
|
}
|
|
44
|
-
if (
|
|
52
|
+
if (err2.status === 403) {
|
|
45
53
|
return `Forbidden (HTTP 403)${serverMsg ? `: ${serverMsg}` : " \u2014 admin privileges required"}.`;
|
|
46
54
|
}
|
|
47
|
-
const detail =
|
|
48
|
-
|
|
55
|
+
const detail = err2.body ? `: ${truncate(err2.body, 500)}` : "";
|
|
56
|
+
const next = err2.hint ? ` ${err2.hint}` : "";
|
|
57
|
+
return `Request failed (HTTP ${err2.status} ${err2.statusText})${detail}${next}`;
|
|
49
58
|
}
|
|
50
|
-
if (
|
|
51
|
-
const cause =
|
|
59
|
+
if (err2 instanceof Error) {
|
|
60
|
+
const cause = err2.cause;
|
|
52
61
|
if (cause?.code && isTlsCertError(cause.code)) {
|
|
53
62
|
return `TLS certificate rejected (${cause.code}). The platform is likely self-signed \u2014 retry with \`-k\`/\`--insecure\`.`;
|
|
54
63
|
}
|
|
55
|
-
if (
|
|
64
|
+
if (err2.message === "fetch failed" && cause?.message) {
|
|
56
65
|
return `Request failed: ${cause.message}${cause.code ? ` (${cause.code})` : ""}`;
|
|
57
66
|
}
|
|
58
|
-
return
|
|
67
|
+
return err2.message;
|
|
59
68
|
}
|
|
60
|
-
return String(
|
|
69
|
+
return String(err2);
|
|
61
70
|
}
|
|
62
71
|
function serverError(body) {
|
|
63
72
|
if (!body) return "";
|
|
@@ -80,17 +89,44 @@ function truncate(s, n) {
|
|
|
80
89
|
import { spawn } from "child_process";
|
|
81
90
|
|
|
82
91
|
// src/api/tls.ts
|
|
83
|
-
import { Agent, fetch as undiciFetch } from "undici";
|
|
84
|
-
var
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
92
|
+
import { Agent, FormData as UndiciFormData, fetch as undiciFetch } from "undici";
|
|
93
|
+
var UNDICI_HEADERS_TIMEOUT_MS = 3e5;
|
|
94
|
+
var agents = /* @__PURE__ */ new Map();
|
|
95
|
+
function dispatcherFor(insecure, headersTimeoutMs) {
|
|
96
|
+
const key = `${insecure}|${headersTimeoutMs ?? ""}`;
|
|
97
|
+
let agent = agents.get(key);
|
|
98
|
+
if (!agent) {
|
|
99
|
+
agent = new Agent({
|
|
100
|
+
...insecure ? { connect: { rejectUnauthorized: false } } : {},
|
|
101
|
+
...headersTimeoutMs === void 0 ? {} : (
|
|
102
|
+
// undici also enforces a body deadline; a request that waits this
|
|
103
|
+
// long for headers is not going to stream its body any faster.
|
|
104
|
+
{ headersTimeout: headersTimeoutMs, bodyTimeout: headersTimeoutMs }
|
|
105
|
+
)
|
|
106
|
+
});
|
|
107
|
+
agents.set(key, agent);
|
|
108
|
+
}
|
|
109
|
+
return agent;
|
|
110
|
+
}
|
|
111
|
+
function isFormData(body) {
|
|
112
|
+
return typeof body === "object" && body !== null && body[Symbol.toStringTag] === "FormData";
|
|
113
|
+
}
|
|
114
|
+
function toUndiciBody(body) {
|
|
115
|
+
if (body instanceof UndiciFormData || !isFormData(body)) return body;
|
|
116
|
+
const form2 = new UndiciFormData();
|
|
117
|
+
for (const [name, value] of body.entries()) {
|
|
118
|
+
if (typeof value === "string") form2.append(name, value);
|
|
119
|
+
else form2.append(name, value, value.name);
|
|
120
|
+
}
|
|
121
|
+
return form2;
|
|
122
|
+
}
|
|
123
|
+
function tlsFetch(insecure, url, init, headersTimeoutMs) {
|
|
124
|
+
const needsAgent = headersTimeoutMs !== void 0 && headersTimeoutMs > UNDICI_HEADERS_TIMEOUT_MS;
|
|
125
|
+
if (!insecure && !needsAgent) return fetch(url, init);
|
|
91
126
|
return undiciFetch(url, {
|
|
92
127
|
...init,
|
|
93
|
-
|
|
128
|
+
...init?.body === void 0 || init?.body === null ? {} : { body: toUndiciBody(init.body) },
|
|
129
|
+
dispatcher: dispatcherFor(insecure === true, needsAgent ? headersTimeoutMs : void 0)
|
|
94
130
|
});
|
|
95
131
|
}
|
|
96
132
|
|
|
@@ -305,11 +341,96 @@ async function credentialDeviceLogin(baseUrl, username, password, opts = {}) {
|
|
|
305
341
|
);
|
|
306
342
|
}
|
|
307
343
|
|
|
344
|
+
// src/trace-context.ts
|
|
345
|
+
import { randomBytes, randomUUID } from "crypto";
|
|
346
|
+
var TRACEPARENT_RE = /^00-([0-9a-f]{32})-([0-9a-f]{16})-[0-9a-f]{2}$/;
|
|
347
|
+
var REQUEST_ID_RE = /^req_[0-9A-Za-z_.-]+$/;
|
|
348
|
+
var CORRELATION_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
|
|
349
|
+
var RFC3339_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?(?:Z|[+-]\d{2}:\d{2})$/;
|
|
350
|
+
var ALLOWED_BAGGAGE = /* @__PURE__ */ new Set(["bkn.account.type", "bkn.runtime.env"]);
|
|
351
|
+
function isValidRequestId(value) {
|
|
352
|
+
return typeof value === "string" && REQUEST_ID_RE.test(value);
|
|
353
|
+
}
|
|
354
|
+
function isValidCorrelationId(value) {
|
|
355
|
+
return typeof value === "string" && CORRELATION_ID_RE.test(value.trim());
|
|
356
|
+
}
|
|
357
|
+
function isValidTraceparent(value) {
|
|
358
|
+
if (typeof value !== "string") return false;
|
|
359
|
+
const match = TRACEPARENT_RE.exec(value);
|
|
360
|
+
if (!match) return false;
|
|
361
|
+
const [, traceId, spanId] = match;
|
|
362
|
+
return traceId !== "0".repeat(32) && spanId !== "0".repeat(16);
|
|
363
|
+
}
|
|
364
|
+
function createTraceContext(opts = {}) {
|
|
365
|
+
const requestId = isValidRequestId(opts.requestId) ? opts.requestId : `req_${randomUUID()}`;
|
|
366
|
+
const traceparent = isValidTraceparent(opts.traceparent) ? opts.traceparent : newTraceparent();
|
|
367
|
+
const baggage = filterBaggage(opts.baggage);
|
|
368
|
+
const conversationId = isValidCorrelationId(opts.conversationId) ? opts.conversationId.trim() : void 0;
|
|
369
|
+
const interactionId = isValidCorrelationId(opts.interactionId) ? opts.interactionId.trim() : void 0;
|
|
370
|
+
const operationId = isValidCorrelationId(opts.operationId) ? opts.operationId.trim() : void 0;
|
|
371
|
+
const attempt = Number.isInteger(opts.attempt) && (opts.attempt ?? 0) >= 1 && (opts.attempt ?? 0) <= 1e3 ? opts.attempt : void 0;
|
|
372
|
+
const observedAt = isValidObservedAt(opts.observedAt) ? opts.observedAt : void 0;
|
|
373
|
+
return {
|
|
374
|
+
requestId,
|
|
375
|
+
traceparent,
|
|
376
|
+
...conversationId ? { conversationId } : {},
|
|
377
|
+
...interactionId ? { interactionId } : {},
|
|
378
|
+
...operationId ? { operationId } : {},
|
|
379
|
+
...attempt ? { attempt } : {},
|
|
380
|
+
...observedAt ? { observedAt } : {},
|
|
381
|
+
...Object.keys(baggage).length > 0 ? { baggage } : {}
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
function createOperationTraceContext(trace2) {
|
|
385
|
+
return {
|
|
386
|
+
...trace2,
|
|
387
|
+
operationId: trace2.operationId ?? `op_${randomUUID()}`,
|
|
388
|
+
attempt: trace2.attempt ?? 1,
|
|
389
|
+
observedAt: isValidObservedAt(trace2.observedAt) ? trace2.observedAt : (/* @__PURE__ */ new Date()).toISOString()
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
function isValidObservedAt(value) {
|
|
393
|
+
return typeof value === "string" && RFC3339_RE.test(value) && !Number.isNaN(Date.parse(value));
|
|
394
|
+
}
|
|
395
|
+
function filterBaggage(baggage) {
|
|
396
|
+
const filtered = {};
|
|
397
|
+
for (const [key, value] of Object.entries(baggage ?? {})) {
|
|
398
|
+
if (ALLOWED_BAGGAGE.has(key) && value !== "") filtered[key] = value;
|
|
399
|
+
}
|
|
400
|
+
return filtered;
|
|
401
|
+
}
|
|
402
|
+
function serializeBaggage(baggage) {
|
|
403
|
+
const filtered = filterBaggage(baggage);
|
|
404
|
+
const entries = Object.entries(filtered);
|
|
405
|
+
if (entries.length === 0) return void 0;
|
|
406
|
+
return entries.map(([key, value]) => `${key}=${encodeURIComponent(value)}`).join(",");
|
|
407
|
+
}
|
|
408
|
+
function newTraceparent() {
|
|
409
|
+
return `00-${randomHex(16)}-${randomHex(8)}-01`;
|
|
410
|
+
}
|
|
411
|
+
function randomHex(bytes) {
|
|
412
|
+
let value = randomBytes(bytes).toString("hex");
|
|
413
|
+
while (/^0+$/.test(value)) value = randomBytes(bytes).toString("hex");
|
|
414
|
+
return value;
|
|
415
|
+
}
|
|
416
|
+
|
|
308
417
|
// src/api/headers.ts
|
|
309
418
|
function buildHeaders(ctx, extra) {
|
|
419
|
+
const baggage = serializeBaggage(ctx.trace?.baggage);
|
|
310
420
|
return {
|
|
311
421
|
authorization: `Bearer ${ctx.token}`,
|
|
312
422
|
"x-business-domain": ctx.businessDomain,
|
|
423
|
+
...ctx.trace ? {
|
|
424
|
+
"bkn-request-id": ctx.trace.requestId,
|
|
425
|
+
"x-request-id": ctx.trace.requestId,
|
|
426
|
+
traceparent: ctx.trace.traceparent,
|
|
427
|
+
...ctx.trace.conversationId ? { "bkn-conversation-id": ctx.trace.conversationId } : {},
|
|
428
|
+
...ctx.trace.interactionId ? { "bkn-interaction-id": ctx.trace.interactionId } : {},
|
|
429
|
+
...ctx.trace.operationId ? { "bkn-operation-id": ctx.trace.operationId } : {},
|
|
430
|
+
...ctx.trace.attempt ? { "bkn-attempt": String(ctx.trace.attempt) } : {},
|
|
431
|
+
...ctx.trace.observedAt ? { "bkn-event-observed-at": ctx.trace.observedAt } : {}
|
|
432
|
+
} : {},
|
|
433
|
+
...baggage ? { baggage } : {},
|
|
313
434
|
...extra
|
|
314
435
|
};
|
|
315
436
|
}
|
|
@@ -328,32 +449,59 @@ async function request(ctx, path, init = {}) {
|
|
|
328
449
|
const hasBody = init.body !== void 0;
|
|
329
450
|
const controller = new AbortController();
|
|
330
451
|
const timer = setTimeout(() => controller.abort(), init.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
331
|
-
const send = () => tlsFetch(
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
452
|
+
const send = () => tlsFetch(
|
|
453
|
+
ctx.insecure,
|
|
454
|
+
url,
|
|
455
|
+
{
|
|
456
|
+
method: init.method ?? (hasBody ? "POST" : "GET"),
|
|
457
|
+
headers: buildHeaders(ctx, {
|
|
458
|
+
...hasBody ? { "content-type": "application/json" } : {},
|
|
459
|
+
...init.headers
|
|
460
|
+
}),
|
|
461
|
+
body: hasBody ? JSON.stringify(init.body) : void 0,
|
|
462
|
+
redirect: init.redirect,
|
|
463
|
+
signal: controller.signal
|
|
464
|
+
},
|
|
465
|
+
init.headersTimeoutMs
|
|
466
|
+
);
|
|
340
467
|
try {
|
|
341
468
|
let res = await send();
|
|
342
469
|
if (res.status === 401 && ctx.refresh && await tryRefresh(ctx)) {
|
|
343
470
|
res = await send();
|
|
344
471
|
}
|
|
345
472
|
const text = await res.text();
|
|
346
|
-
if (!res.ok)
|
|
473
|
+
if (!res.ok) {
|
|
474
|
+
throw new HttpError(res.status, res.statusText, text, hintFor(ctx, res.status, text));
|
|
475
|
+
}
|
|
347
476
|
return text ? JSON.parse(text) : void 0;
|
|
348
477
|
} finally {
|
|
349
478
|
clearTimeout(timer);
|
|
350
479
|
}
|
|
351
480
|
}
|
|
352
|
-
function hintFor(ctx, status2) {
|
|
481
|
+
function hintFor(ctx, status2, body) {
|
|
353
482
|
if (status2 === 401 && ctx.token.startsWith("bak_")) {
|
|
354
483
|
return "AppKey invalid / expired / revoked / owner disabled \u2014 re-issue with `openbkn appkey create` (or `appkey regenerate <id>`). Do not auto-retry.";
|
|
355
484
|
}
|
|
356
|
-
return
|
|
485
|
+
return lifecycleHint(body);
|
|
486
|
+
}
|
|
487
|
+
var LIFECYCLE_ACTIONS = /* @__PURE__ */ new Set([
|
|
488
|
+
"create_conversation",
|
|
489
|
+
"start_interaction",
|
|
490
|
+
"ensure_operation",
|
|
491
|
+
"bkn_start_interaction"
|
|
492
|
+
]);
|
|
493
|
+
function lifecycleHint(body) {
|
|
494
|
+
if (!LIFECYCLE_ACTIONS.has(requiredAction(body) ?? "")) return void 0;
|
|
495
|
+
return "This deploy requires a managed lifecycle session: the request needs a `bkn_context` with conversation_id and interaction_id. Easiest fix: use the `openbkn bkn` / `openbkn context` commands, which open and release one for you. To do it by hand, check `openbkn context info` for the deploy's lifecycle tools \u2014 where it lists `bkn_create_conversation`, call that first and pass the conversation_id it returns to `bkn_start_interaction`; where it does not, `bkn_start_interaction` alone returns both ids. Either way: `openbkn context tool-call <kn-id> <tool> --args '{...}'`.";
|
|
496
|
+
}
|
|
497
|
+
function requiredAction(body) {
|
|
498
|
+
try {
|
|
499
|
+
const parsed = JSON.parse(body);
|
|
500
|
+
const action = parsed.error?.required_action;
|
|
501
|
+
return typeof action === "string" ? action : void 0;
|
|
502
|
+
} catch {
|
|
503
|
+
return void 0;
|
|
504
|
+
}
|
|
357
505
|
}
|
|
358
506
|
async function tryRefresh(ctx) {
|
|
359
507
|
if (!ctx.refresh) return false;
|
|
@@ -588,6 +736,13 @@ function resolveContext(opts = {}) {
|
|
|
588
736
|
token,
|
|
589
737
|
businessDomain: opts.businessDomain ?? readPlatformConfig(normalized).businessDomain ?? DEFAULT_BUSINESS_DOMAIN,
|
|
590
738
|
insecure,
|
|
739
|
+
...opts.evidenceIngestToken ?? process.env.BKN_TRACE_EVIDENCE_INGEST_TOKEN ? {
|
|
740
|
+
evidenceIngestToken: opts.evidenceIngestToken ?? process.env.BKN_TRACE_EVIDENCE_INGEST_TOKEN
|
|
741
|
+
} : {},
|
|
742
|
+
// Correlation ids come from `opts.trace` only. The CLI reads its flags and
|
|
743
|
+
// env vars in `commands/_shared.ts`; a library client must not inherit an
|
|
744
|
+
// ambient interaction id it would then freeze for its whole lifetime.
|
|
745
|
+
trace: createTraceContext(opts.trace),
|
|
591
746
|
...refresh ? { refresh } : {}
|
|
592
747
|
};
|
|
593
748
|
}
|
|
@@ -774,12 +929,12 @@ async function importLicenseSafe(ctx, licenseText, opts = {}) {
|
|
|
774
929
|
method: "POST",
|
|
775
930
|
body: { license: text }
|
|
776
931
|
});
|
|
777
|
-
} catch (
|
|
778
|
-
if (
|
|
779
|
-
const stored = storedImport(
|
|
932
|
+
} catch (err2) {
|
|
933
|
+
if (err2 instanceof HttpError) {
|
|
934
|
+
const stored = storedImport(err2.body);
|
|
780
935
|
if (stored) return stored;
|
|
781
936
|
}
|
|
782
|
-
throw
|
|
937
|
+
throw err2;
|
|
783
938
|
}
|
|
784
939
|
}
|
|
785
940
|
function storedImport(body) {
|
|
@@ -1088,7 +1243,7 @@ function setSkillMembers(agent, members) {
|
|
|
1088
1243
|
if (!config.skills || typeof config.skills !== "object") config.skills = {};
|
|
1089
1244
|
config.skills.skills = members;
|
|
1090
1245
|
}
|
|
1091
|
-
function
|
|
1246
|
+
function agents2(ctx) {
|
|
1092
1247
|
return {
|
|
1093
1248
|
list: (opts) => listAgents(ctx, opts),
|
|
1094
1249
|
get: (agentId) => getAgent(ctx, agentId),
|
|
@@ -1137,6 +1292,9 @@ function agents(ctx) {
|
|
|
1137
1292
|
};
|
|
1138
1293
|
}
|
|
1139
1294
|
|
|
1295
|
+
// src/api/lifecycle.ts
|
|
1296
|
+
import { createHash, randomUUID as randomUUID2 } from "crypto";
|
|
1297
|
+
|
|
1140
1298
|
// src/api/context-loader.ts
|
|
1141
1299
|
var MCP_PATH = "/api/agent-retrieval/v1/mcp";
|
|
1142
1300
|
var PROTOCOL = "2024-11-05";
|
|
@@ -1150,19 +1308,20 @@ function nextId() {
|
|
|
1150
1308
|
function mcpUrl(ctx) {
|
|
1151
1309
|
return `${ctx.baseUrl}${MCP_PATH}`;
|
|
1152
1310
|
}
|
|
1311
|
+
function operationContext(ctx) {
|
|
1312
|
+
return ctx.trace ? { ...ctx, trace: createOperationTraceContext(ctx.trace) } : ctx;
|
|
1313
|
+
}
|
|
1153
1314
|
function mcpInfo(ctx) {
|
|
1154
1315
|
return request(ctx, `${MCP_PATH}/info`);
|
|
1155
1316
|
}
|
|
1156
1317
|
function headers(ctx, knId, sessionId) {
|
|
1157
|
-
|
|
1318
|
+
return buildHeaders(ctx, {
|
|
1158
1319
|
"content-type": "application/json",
|
|
1159
1320
|
accept: "application/json, text/event-stream",
|
|
1160
1321
|
"x-kn-id": knId,
|
|
1161
1322
|
"mcp-protocol-version": PROTOCOL,
|
|
1162
|
-
|
|
1163
|
-
};
|
|
1164
|
-
if (sessionId) h["mcp-session-id"] = sessionId;
|
|
1165
|
-
return h;
|
|
1323
|
+
...sessionId ? { "mcp-session-id": sessionId } : {}
|
|
1324
|
+
});
|
|
1166
1325
|
}
|
|
1167
1326
|
function parseBody(text) {
|
|
1168
1327
|
try {
|
|
@@ -1173,18 +1332,25 @@ function parseBody(text) {
|
|
|
1173
1332
|
throw new Error(`Context-loader returned invalid JSON: ${text.slice(0, 200)}`);
|
|
1174
1333
|
}
|
|
1175
1334
|
}
|
|
1176
|
-
async function post(ctx, knId, sessionId, body) {
|
|
1177
|
-
const
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1335
|
+
async function post(ctx, knId, sessionId, body, timeoutMs) {
|
|
1336
|
+
const controller = timeoutMs === void 0 ? void 0 : new AbortController();
|
|
1337
|
+
const timer = controller === void 0 ? void 0 : setTimeout(() => controller.abort(), timeoutMs);
|
|
1338
|
+
try {
|
|
1339
|
+
const res = await authFetch(
|
|
1340
|
+
ctx,
|
|
1341
|
+
() => tlsFetch(ctx.insecure, mcpUrl(ctx), {
|
|
1342
|
+
method: "POST",
|
|
1343
|
+
headers: headers(ctx, knId, sessionId),
|
|
1344
|
+
body: JSON.stringify(body),
|
|
1345
|
+
...controller ? { signal: controller.signal } : {}
|
|
1346
|
+
})
|
|
1347
|
+
);
|
|
1348
|
+
const text = await res.text();
|
|
1349
|
+
if (!res.ok) throw new HttpError(res.status, res.statusText, text);
|
|
1350
|
+
return { res, text };
|
|
1351
|
+
} finally {
|
|
1352
|
+
if (timer) clearTimeout(timer);
|
|
1353
|
+
}
|
|
1188
1354
|
}
|
|
1189
1355
|
async function ensureSession(ctx, knId) {
|
|
1190
1356
|
const key = `${mcpUrl(ctx)}:${knId}`;
|
|
@@ -1207,34 +1373,108 @@ async function ensureSession(ctx, knId) {
|
|
|
1207
1373
|
sessions.set(key, { id: sessionId, at: Date.now() });
|
|
1208
1374
|
return sessionId;
|
|
1209
1375
|
}
|
|
1210
|
-
function
|
|
1376
|
+
function toolErrorCode(structuredContent) {
|
|
1377
|
+
const code = structuredContent?.error?.code;
|
|
1378
|
+
return typeof code === "string" ? code : void 0;
|
|
1379
|
+
}
|
|
1380
|
+
function unwrapToolResult(parsed) {
|
|
1211
1381
|
const rpc = parsed;
|
|
1212
1382
|
if (rpc.error) throw new Error(`Context-loader error: ${rpc.error.message}`);
|
|
1213
1383
|
const result = rpc.result;
|
|
1214
|
-
if (result === void 0) return parsed;
|
|
1384
|
+
if (result === void 0) return { value: parsed };
|
|
1385
|
+
const structuredContent = result.structuredContent;
|
|
1386
|
+
const receipt = structuredContent?.bkn_receipt;
|
|
1215
1387
|
const content = result.content;
|
|
1388
|
+
if (result.isError === true) {
|
|
1389
|
+
const message = Array.isArray(content) && content[0] && typeof content[0].text === "string" ? content[0].text : "tool call failed";
|
|
1390
|
+
throw new ToolError(`Context-loader error: ${message}`, toolErrorCode(structuredContent));
|
|
1391
|
+
}
|
|
1216
1392
|
if (Array.isArray(content) && content[0] && typeof content[0].text === "string") {
|
|
1217
1393
|
try {
|
|
1218
|
-
return JSON.parse(content[0].text);
|
|
1394
|
+
return { value: JSON.parse(content[0].text), receipt };
|
|
1219
1395
|
} catch {
|
|
1220
|
-
|
|
1396
|
+
if (structuredContent !== void 0) {
|
|
1397
|
+
return { value: structuredContent, receipt };
|
|
1398
|
+
}
|
|
1399
|
+
return { value: { raw: content[0].text }, receipt };
|
|
1221
1400
|
}
|
|
1222
1401
|
}
|
|
1223
|
-
return result;
|
|
1402
|
+
return { value: result, receipt };
|
|
1403
|
+
}
|
|
1404
|
+
function toolCallParams(name, args, options) {
|
|
1405
|
+
const meta = {
|
|
1406
|
+
...options?.hostConversationKey ? { "openbkn.ai/host-conversation-key": options.hostConversationKey } : {},
|
|
1407
|
+
...options?.clientInvocationId ? { "openbkn.ai/client-invocation-id": options.clientInvocationId } : {}
|
|
1408
|
+
};
|
|
1409
|
+
return {
|
|
1410
|
+
name,
|
|
1411
|
+
arguments: args,
|
|
1412
|
+
...Object.keys(meta).length > 0 ? { _meta: meta } : {}
|
|
1413
|
+
};
|
|
1224
1414
|
}
|
|
1225
|
-
async function
|
|
1226
|
-
const
|
|
1227
|
-
const
|
|
1415
|
+
async function callToolRaw(ctx, knId, name, args, options, timeoutMs) {
|
|
1416
|
+
const operationCtx = operationContext(ctx);
|
|
1417
|
+
const sessionId = await ensureSession(operationCtx, knId);
|
|
1418
|
+
const { text } = await post(
|
|
1419
|
+
operationCtx,
|
|
1420
|
+
knId,
|
|
1421
|
+
sessionId,
|
|
1422
|
+
{
|
|
1423
|
+
jsonrpc: "2.0",
|
|
1424
|
+
method: "tools/call",
|
|
1425
|
+
params: toolCallParams(name, args, options),
|
|
1426
|
+
id: nextId()
|
|
1427
|
+
},
|
|
1428
|
+
timeoutMs
|
|
1429
|
+
);
|
|
1430
|
+
return unwrapToolResult(parseBody(text)).value;
|
|
1431
|
+
}
|
|
1432
|
+
var LIFECYCLE_TOOLS = /* @__PURE__ */ new Set([
|
|
1433
|
+
"bkn_create_conversation",
|
|
1434
|
+
"bkn_resume_conversation",
|
|
1435
|
+
"bkn_start_interaction",
|
|
1436
|
+
"bkn_complete_interaction",
|
|
1437
|
+
"bkn_finish_interaction",
|
|
1438
|
+
"bkn_fail_interaction",
|
|
1439
|
+
"bkn_cancel_interaction",
|
|
1440
|
+
"bkn_handoff_interaction",
|
|
1441
|
+
"bkn_close_conversation",
|
|
1442
|
+
"bkn_get_operation",
|
|
1443
|
+
"bkn_retry_operation",
|
|
1444
|
+
"bkn_get_receipt"
|
|
1445
|
+
]);
|
|
1446
|
+
function callTool(ctx, knId, name, args, options) {
|
|
1447
|
+
if (LIFECYCLE_TOOLS.has(name)) return callToolRaw(ctx, knId, name, args, options);
|
|
1448
|
+
if (args.bkn_context !== void 0) return callToolRaw(ctx, knId, name, args, options);
|
|
1449
|
+
return withManagedLifecycle(
|
|
1450
|
+
ctx,
|
|
1451
|
+
knId,
|
|
1452
|
+
questionFor(name, args),
|
|
1453
|
+
(bknContext) => callToolRaw(ctx, knId, name, bknContext ? { ...args, bkn_context: bknContext } : args, options)
|
|
1454
|
+
);
|
|
1455
|
+
}
|
|
1456
|
+
function questionFor(name, args) {
|
|
1457
|
+
return typeof args.query === "string" && args.query ? args.query : name;
|
|
1458
|
+
}
|
|
1459
|
+
async function callManagedTool(ctx, knId, name, args, options) {
|
|
1460
|
+
const operationCtx = operationContext(ctx);
|
|
1461
|
+
const sessionId = await ensureSession(operationCtx, knId);
|
|
1462
|
+
const { text } = await post(operationCtx, knId, sessionId, {
|
|
1228
1463
|
jsonrpc: "2.0",
|
|
1229
1464
|
method: "tools/call",
|
|
1230
|
-
params:
|
|
1465
|
+
params: toolCallParams(name, args, options),
|
|
1231
1466
|
id: nextId()
|
|
1232
1467
|
});
|
|
1233
|
-
|
|
1468
|
+
const result = unwrapToolResult(parseBody(text));
|
|
1469
|
+
if (!result.receipt) {
|
|
1470
|
+
throw new Error("Context-loader managed tool response did not include bkn_receipt");
|
|
1471
|
+
}
|
|
1472
|
+
return { value: result.value, receipt: result.receipt };
|
|
1234
1473
|
}
|
|
1235
1474
|
async function callMethod(ctx, knId, method, params = {}) {
|
|
1236
|
-
const
|
|
1237
|
-
const
|
|
1475
|
+
const operationCtx = operationContext(ctx);
|
|
1476
|
+
const sessionId = await ensureSession(operationCtx, knId);
|
|
1477
|
+
const { text } = await post(operationCtx, knId, sessionId, {
|
|
1238
1478
|
jsonrpc: "2.0",
|
|
1239
1479
|
method,
|
|
1240
1480
|
params: Object.keys(params).length > 0 ? params : void 0,
|
|
@@ -1297,6 +1537,225 @@ function getPrompt(ctx, knId, name, args = {}) {
|
|
|
1297
1537
|
return callMethod(ctx, knId, "prompts/get", { name, arguments: args });
|
|
1298
1538
|
}
|
|
1299
1539
|
|
|
1540
|
+
// src/api/lifecycle.ts
|
|
1541
|
+
var V1_MARKER = "bkn_create_conversation";
|
|
1542
|
+
var V2_MARKER = "bkn_start_interaction";
|
|
1543
|
+
var STALE_SESSION_CODES = /* @__PURE__ */ new Set([
|
|
1544
|
+
"conversation_required",
|
|
1545
|
+
"interaction_required",
|
|
1546
|
+
"interaction_terminal",
|
|
1547
|
+
"interaction_in_progress",
|
|
1548
|
+
"lease_expired",
|
|
1549
|
+
"lease_invalid",
|
|
1550
|
+
"lease_superseded"
|
|
1551
|
+
]);
|
|
1552
|
+
var PROCESS_ID = randomUUID2();
|
|
1553
|
+
var generation = 0;
|
|
1554
|
+
var AGENT_NAME = "openbkn-sdk";
|
|
1555
|
+
var RELEASE_TIMEOUT_MS = 3e3;
|
|
1556
|
+
var contracts = /* @__PURE__ */ new Map();
|
|
1557
|
+
var PROBE_FAILURE_TTL_MS = 3e4;
|
|
1558
|
+
var probeFailures = /* @__PURE__ */ new Map();
|
|
1559
|
+
var sessions2 = /* @__PURE__ */ new Map();
|
|
1560
|
+
function lifecycleContract(ctx) {
|
|
1561
|
+
const failureKey = `${ctx.baseUrl}\0${identityOf(ctx)}`;
|
|
1562
|
+
const failedAt = probeFailures.get(failureKey);
|
|
1563
|
+
if (failedAt !== void 0) {
|
|
1564
|
+
if (Date.now() - failedAt < PROBE_FAILURE_TTL_MS) return Promise.resolve("none");
|
|
1565
|
+
probeFailures.delete(failureKey);
|
|
1566
|
+
}
|
|
1567
|
+
let pending = contracts.get(ctx.baseUrl);
|
|
1568
|
+
if (!pending) {
|
|
1569
|
+
pending = mcpInfo(ctx).then((info) => {
|
|
1570
|
+
const names = toolNames(info);
|
|
1571
|
+
if (names.includes(V1_MARKER)) return "managed-v1";
|
|
1572
|
+
return names.includes(V2_MARKER) ? "managed-v2" : "none";
|
|
1573
|
+
});
|
|
1574
|
+
contracts.set(ctx.baseUrl, pending);
|
|
1575
|
+
pending.catch((err2) => {
|
|
1576
|
+
contracts.delete(ctx.baseUrl);
|
|
1577
|
+
if (!isAuthFailure(err2)) probeFailures.set(failureKey, Date.now());
|
|
1578
|
+
});
|
|
1579
|
+
}
|
|
1580
|
+
return pending.catch(() => "none");
|
|
1581
|
+
}
|
|
1582
|
+
function isAuthFailure(err2) {
|
|
1583
|
+
return err2 instanceof HttpError && (err2.status === 401 || err2.status === 403);
|
|
1584
|
+
}
|
|
1585
|
+
function toolNames(info) {
|
|
1586
|
+
const tools = info?.tools;
|
|
1587
|
+
if (!Array.isArray(tools)) return [];
|
|
1588
|
+
return tools.flatMap((tool) => typeof tool?.name === "string" ? [tool.name] : []);
|
|
1589
|
+
}
|
|
1590
|
+
function callerOwnedSession(ctx) {
|
|
1591
|
+
const conversationId = ctx.trace?.conversationId;
|
|
1592
|
+
const interactionId = ctx.trace?.interactionId;
|
|
1593
|
+
return conversationId && interactionId ? { conversationId, interactionId } : void 0;
|
|
1594
|
+
}
|
|
1595
|
+
function readId(result, field, tool) {
|
|
1596
|
+
const value = result?.[field];
|
|
1597
|
+
if (typeof value !== "string" || !value) {
|
|
1598
|
+
throw new Error(`Managed lifecycle: ${tool} returned no ${field}.`);
|
|
1599
|
+
}
|
|
1600
|
+
return value;
|
|
1601
|
+
}
|
|
1602
|
+
function callerNamedConversation(ctx) {
|
|
1603
|
+
return ctx.trace?.interactionId ? void 0 : ctx.trace?.conversationId;
|
|
1604
|
+
}
|
|
1605
|
+
async function openSession(ctx, knId, contract, question) {
|
|
1606
|
+
generation += 1;
|
|
1607
|
+
const named = callerNamedConversation(ctx);
|
|
1608
|
+
if (contract === "managed-v2") {
|
|
1609
|
+
const started2 = await callToolRaw(ctx, knId, V2_MARKER, {
|
|
1610
|
+
question,
|
|
1611
|
+
// The name is fixed when the conversation is created, so it belongs only
|
|
1612
|
+
// on the call that creates one. Joining a conversation the caller named
|
|
1613
|
+
// and relabelling it `openbkn-sdk` would rewrite their attribution — the
|
|
1614
|
+
// v1 join below has always omitted it, and this is the same decision.
|
|
1615
|
+
...named ? { conversation_id: named } : (
|
|
1616
|
+
// Display-only, but the only thing separating a session the SDK
|
|
1617
|
+
// opened from a real agent's in a Trace listing.
|
|
1618
|
+
{ agent_name: AGENT_NAME }
|
|
1619
|
+
)
|
|
1620
|
+
});
|
|
1621
|
+
return {
|
|
1622
|
+
contract,
|
|
1623
|
+
ctx,
|
|
1624
|
+
knId,
|
|
1625
|
+
conversationId: readId(started2, "conversation_id", V2_MARKER),
|
|
1626
|
+
interactionId: readId(started2, "interaction_id", V2_MARKER)
|
|
1627
|
+
};
|
|
1628
|
+
}
|
|
1629
|
+
if (named) {
|
|
1630
|
+
const started2 = await callToolRaw(ctx, knId, V2_MARKER, {
|
|
1631
|
+
conversation_id: named,
|
|
1632
|
+
idempotency_key: `start:${PROCESS_ID}:${generation}`,
|
|
1633
|
+
question
|
|
1634
|
+
});
|
|
1635
|
+
return {
|
|
1636
|
+
contract,
|
|
1637
|
+
ctx,
|
|
1638
|
+
knId,
|
|
1639
|
+
conversationId: named,
|
|
1640
|
+
interactionId: readId(started2, "interaction_id", V2_MARKER)
|
|
1641
|
+
};
|
|
1642
|
+
}
|
|
1643
|
+
const conversation = await callToolRaw(ctx, knId, V1_MARKER, {
|
|
1644
|
+
external_conversation_key: `cli:${PROCESS_ID}:${generation}`,
|
|
1645
|
+
// Nothing closes this conversation: a CLI invocation has no answer to close
|
|
1646
|
+
// over, and v1's closure manifest must enumerate every operation it
|
|
1647
|
+
// produced. one_shot hands it to the server's idle sweeper instead.
|
|
1648
|
+
one_shot: true
|
|
1649
|
+
});
|
|
1650
|
+
const conversationId = readId(conversation, "conversation_id", V1_MARKER);
|
|
1651
|
+
const started = await callToolRaw(ctx, knId, V2_MARKER, {
|
|
1652
|
+
conversation_id: conversationId,
|
|
1653
|
+
idempotency_key: `start:${PROCESS_ID}:${generation}`,
|
|
1654
|
+
question
|
|
1655
|
+
});
|
|
1656
|
+
return {
|
|
1657
|
+
contract,
|
|
1658
|
+
ctx,
|
|
1659
|
+
knId,
|
|
1660
|
+
conversationId,
|
|
1661
|
+
interactionId: readId(started, "interaction_id", V2_MARKER)
|
|
1662
|
+
};
|
|
1663
|
+
}
|
|
1664
|
+
function identityOf(ctx) {
|
|
1665
|
+
return createHash("sha256").update(`${ctx.token}\0${ctx.businessDomain}`).digest("hex").slice(0, 16);
|
|
1666
|
+
}
|
|
1667
|
+
function sessionKey(ctx, knId) {
|
|
1668
|
+
return `${ctx.baseUrl}\0${knId}\0${identityOf(ctx)}\0${ctx.trace?.conversationId ?? ""}`;
|
|
1669
|
+
}
|
|
1670
|
+
function ensureSession2(ctx, knId, contract, question) {
|
|
1671
|
+
const key = sessionKey(ctx, knId);
|
|
1672
|
+
const cached = sessions2.get(key);
|
|
1673
|
+
if (cached) return cached;
|
|
1674
|
+
const opening = openSession(ctx, knId, contract, question);
|
|
1675
|
+
sessions2.set(key, opening);
|
|
1676
|
+
opening.catch(() => sessions2.delete(key));
|
|
1677
|
+
return opening;
|
|
1678
|
+
}
|
|
1679
|
+
function newOperationKey() {
|
|
1680
|
+
return `op:${randomUUID2()}`;
|
|
1681
|
+
}
|
|
1682
|
+
function contextFor(session, contract) {
|
|
1683
|
+
return {
|
|
1684
|
+
conversation_id: session.conversationId,
|
|
1685
|
+
interaction_id: session.interactionId,
|
|
1686
|
+
// v2 validates bkn_context strictly and rejects the field.
|
|
1687
|
+
...contract === "managed-v1" ? { operation_key: newOperationKey() } : {}
|
|
1688
|
+
};
|
|
1689
|
+
}
|
|
1690
|
+
async function bknContextFor(ctx, knId, question) {
|
|
1691
|
+
const contract = await lifecycleContract(ctx);
|
|
1692
|
+
if (contract === "none") return void 0;
|
|
1693
|
+
const owned = callerOwnedSession(ctx);
|
|
1694
|
+
if (owned) return contextFor(owned, contract);
|
|
1695
|
+
try {
|
|
1696
|
+
return contextFor(await ensureSession2(ctx, knId, contract, question), contract);
|
|
1697
|
+
} catch {
|
|
1698
|
+
return void 0;
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
function serverErrorCode(err2) {
|
|
1702
|
+
if (err2 instanceof ToolError) return err2.code;
|
|
1703
|
+
if (!(err2 instanceof HttpError)) return void 0;
|
|
1704
|
+
try {
|
|
1705
|
+
const parsed = JSON.parse(err2.body);
|
|
1706
|
+
return typeof parsed.error?.code === "string" ? parsed.error.code : void 0;
|
|
1707
|
+
} catch {
|
|
1708
|
+
return void 0;
|
|
1709
|
+
}
|
|
1710
|
+
}
|
|
1711
|
+
async function withManagedLifecycle(ctx, knId, question, send) {
|
|
1712
|
+
const first = await bknContextFor(ctx, knId, question);
|
|
1713
|
+
try {
|
|
1714
|
+
return await send(first);
|
|
1715
|
+
} catch (err2) {
|
|
1716
|
+
const code = serverErrorCode(err2);
|
|
1717
|
+
if (!first || callerOwnedSession(ctx) || !code || !STALE_SESSION_CODES.has(code)) throw err2;
|
|
1718
|
+
sessions2.delete(sessionKey(ctx, knId));
|
|
1719
|
+
const reopened = await bknContextFor(ctx, knId, question);
|
|
1720
|
+
if (!reopened) throw err2;
|
|
1721
|
+
return await send(reopened);
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1724
|
+
async function releaseLifecycleSessions() {
|
|
1725
|
+
const pending = [...sessions2.values()];
|
|
1726
|
+
sessions2.clear();
|
|
1727
|
+
await Promise.all(pending.map((opening) => withDeadline(releaseOne(opening))));
|
|
1728
|
+
}
|
|
1729
|
+
function withDeadline(work) {
|
|
1730
|
+
return new Promise((resolve7) => {
|
|
1731
|
+
const timer = setTimeout(resolve7, RELEASE_TIMEOUT_MS);
|
|
1732
|
+
timer.unref?.();
|
|
1733
|
+
work.finally(() => {
|
|
1734
|
+
clearTimeout(timer);
|
|
1735
|
+
resolve7();
|
|
1736
|
+
});
|
|
1737
|
+
});
|
|
1738
|
+
}
|
|
1739
|
+
async function releaseOne(opening) {
|
|
1740
|
+
try {
|
|
1741
|
+
const session = await opening;
|
|
1742
|
+
if (session.contract !== "managed-v2") return;
|
|
1743
|
+
await callToolRaw(
|
|
1744
|
+
session.ctx,
|
|
1745
|
+
session.knId,
|
|
1746
|
+
"bkn_finish_interaction",
|
|
1747
|
+
{
|
|
1748
|
+
interaction_id: session.interactionId,
|
|
1749
|
+
outcome: "cancelled",
|
|
1750
|
+
reason: "client session ended"
|
|
1751
|
+
},
|
|
1752
|
+
void 0,
|
|
1753
|
+
RELEASE_TIMEOUT_MS
|
|
1754
|
+
);
|
|
1755
|
+
} catch {
|
|
1756
|
+
}
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1300
1759
|
// src/resources/context-loader.ts
|
|
1301
1760
|
function context(ctx) {
|
|
1302
1761
|
return {
|
|
@@ -1309,7 +1768,8 @@ function context(ctx) {
|
|
|
1309
1768
|
relationTypes: (knId, ids) => getRelationTypes(ctx, knId, ids),
|
|
1310
1769
|
info: () => mcpInfo(ctx),
|
|
1311
1770
|
tools: (knId) => listTools(ctx, knId),
|
|
1312
|
-
toolCall: (knId, name, args) => callTool(ctx, knId, name, args),
|
|
1771
|
+
toolCall: (knId, name, args, options) => callTool(ctx, knId, name, args, options),
|
|
1772
|
+
managedToolCall: (knId, name, args, options) => callManagedTool(ctx, knId, name, args, options),
|
|
1313
1773
|
// Generic MCP method passthrough — covers methods not yet wrapped, so the
|
|
1314
1774
|
// surface doesn't have to grow every time the server adds one.
|
|
1315
1775
|
callMethod: (knId, method, params) => callMethod(ctx, knId, method, params),
|
|
@@ -1369,7 +1829,11 @@ async function executeDataflow(ctx, body, opts = {}) {
|
|
|
1369
1829
|
}
|
|
1370
1830
|
function listDataflowRuns(ctx, dagId, opts = {}) {
|
|
1371
1831
|
return request(ctx, `${BASE2}/dag/${encodeURIComponent(dagId)}/results`, {
|
|
1372
|
-
query: {
|
|
1832
|
+
query: {
|
|
1833
|
+
since: opts.since || void 0,
|
|
1834
|
+
page: opts.page,
|
|
1835
|
+
limit: opts.limit && opts.limit > 0 ? opts.limit : void 0
|
|
1836
|
+
}
|
|
1373
1837
|
});
|
|
1374
1838
|
}
|
|
1375
1839
|
function runDataflowRemote(ctx, dagId, url, name) {
|
|
@@ -1425,9 +1889,11 @@ function deleteKnowledgeNetwork(ctx, knId) {
|
|
|
1425
1889
|
function updateKnowledgeNetwork(ctx, knId, body) {
|
|
1426
1890
|
return request(ctx, `${ONTOLOGY_BASE}/${encodeURIComponent(knId)}`, { method: "PUT", body });
|
|
1427
1891
|
}
|
|
1892
|
+
var QUERY_OVER_POST = { "X-HTTP-Method-Override": "GET" };
|
|
1428
1893
|
function querySubgraph(ctx, knId, body) {
|
|
1429
1894
|
return request(ctx, `${ONTOLOGY_QUERY_BASE}/${encodeURIComponent(knId)}/subgraph`, {
|
|
1430
1895
|
method: "POST",
|
|
1896
|
+
headers: QUERY_OVER_POST,
|
|
1431
1897
|
body
|
|
1432
1898
|
});
|
|
1433
1899
|
}
|
|
@@ -1459,13 +1925,7 @@ function queryObjectTypeInstances(ctx, knId, otId, body) {
|
|
|
1459
1925
|
return request(
|
|
1460
1926
|
ctx,
|
|
1461
1927
|
`${ONTOLOGY_QUERY_BASE}/${encodeURIComponent(knId)}/object-types/${encodeURIComponent(otId)}`,
|
|
1462
|
-
{ method: "POST", body }
|
|
1463
|
-
);
|
|
1464
|
-
}
|
|
1465
|
-
function getObjectTypeProperties(ctx, knId, otId) {
|
|
1466
|
-
return request(
|
|
1467
|
-
ctx,
|
|
1468
|
-
`${ONTOLOGY_QUERY_BASE}/${encodeURIComponent(knId)}/object-types/${encodeURIComponent(otId)}/properties`
|
|
1928
|
+
{ method: "POST", headers: QUERY_OVER_POST, body }
|
|
1469
1929
|
);
|
|
1470
1930
|
}
|
|
1471
1931
|
function queryActionType(ctx, knId, atId, body) {
|
|
@@ -1606,22 +2066,39 @@ function searchMetrics(ctx, knId, body) {
|
|
|
1606
2066
|
});
|
|
1607
2067
|
}
|
|
1608
2068
|
function validateMetric(ctx, knId, body) {
|
|
1609
|
-
return request(ctx, `${ONTOLOGY_BASE}/${encodeURIComponent(knId)}/metrics/
|
|
2069
|
+
return request(ctx, `${ONTOLOGY_BASE}/${encodeURIComponent(knId)}/metrics/validation`, {
|
|
1610
2070
|
method: "POST",
|
|
1611
2071
|
body
|
|
1612
2072
|
});
|
|
1613
2073
|
}
|
|
2074
|
+
function searchBody(knId, query, opts) {
|
|
2075
|
+
return {
|
|
2076
|
+
kn_id: knId,
|
|
2077
|
+
query,
|
|
2078
|
+
mode: opts.mode ?? "keyword_vector_retrieval",
|
|
2079
|
+
max_concepts: opts.maxConcepts ?? 10,
|
|
2080
|
+
return_query_understanding: opts.returnQueryUnderstanding ?? false
|
|
2081
|
+
};
|
|
2082
|
+
}
|
|
1614
2083
|
function semanticSearch(ctx, knId, query, opts = {}) {
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
2084
|
+
if (opts.bknContext) {
|
|
2085
|
+
return request(ctx, `${RETRIEVAL_BASE}/semantic-search`, {
|
|
2086
|
+
method: "POST",
|
|
2087
|
+
body: { ...searchBody(knId, query, opts), bkn_context: opts.bknContext }
|
|
2088
|
+
});
|
|
2089
|
+
}
|
|
2090
|
+
return withManagedLifecycle(
|
|
2091
|
+
ctx,
|
|
2092
|
+
knId,
|
|
2093
|
+
query,
|
|
2094
|
+
(bknContext) => request(ctx, `${RETRIEVAL_BASE}/semantic-search`, {
|
|
2095
|
+
method: "POST",
|
|
2096
|
+
body: {
|
|
2097
|
+
...searchBody(knId, query, opts),
|
|
2098
|
+
...bknContext ? { bkn_context: bknContext } : {}
|
|
2099
|
+
}
|
|
2100
|
+
})
|
|
2101
|
+
);
|
|
1625
2102
|
}
|
|
1626
2103
|
|
|
1627
2104
|
// src/api/resources.ts
|
|
@@ -1634,7 +2111,10 @@ function listResources2(ctx, opts = {}) {
|
|
|
1634
2111
|
category: opts.category || void 0,
|
|
1635
2112
|
status: opts.status || void 0,
|
|
1636
2113
|
database: opts.database || void 0,
|
|
1637
|
-
|
|
2114
|
+
// Same `/resources` endpoint as `catalogResources`: limit=-1 (NO_LIMIT)
|
|
2115
|
+
// fetches every row; any other non-positive/invalid value falls back to
|
|
2116
|
+
// the backend default.
|
|
2117
|
+
limit: Number.isFinite(opts.limit) && (opts.limit > 0 || opts.limit === -1) ? opts.limit : void 0,
|
|
1638
2118
|
offset: opts.offset,
|
|
1639
2119
|
sort: opts.sort,
|
|
1640
2120
|
direction: opts.direction,
|
|
@@ -1750,18 +2230,31 @@ function deleteResource(ctx, id, opts = {}) {
|
|
|
1750
2230
|
});
|
|
1751
2231
|
}
|
|
1752
2232
|
async function findResource(ctx, name, opts = {}) {
|
|
1753
|
-
const result = await listResources2(ctx, {
|
|
2233
|
+
const result = await listResources2(ctx, {
|
|
2234
|
+
name,
|
|
2235
|
+
datasourceId: opts.datasourceId,
|
|
2236
|
+
limit: opts.limit
|
|
2237
|
+
});
|
|
1754
2238
|
const list = Array.isArray(result) ? result : result.entries ?? [];
|
|
1755
2239
|
return opts.exact ? list.filter((r) => r.name === name) : list;
|
|
1756
2240
|
}
|
|
1757
2241
|
function queryResource(ctx, id, opts = {}) {
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
2242
|
+
const body = opts.cursor ? {
|
|
2243
|
+
paging: { cursor: opts.cursor },
|
|
2244
|
+
need_total: opts.needTotal ?? false
|
|
2245
|
+
} : {
|
|
2246
|
+
paging: {
|
|
2247
|
+
mode: opts.pagingMode ?? "single",
|
|
1761
2248
|
limit: opts.limit ?? 50,
|
|
1762
2249
|
offset: opts.offset ?? 0,
|
|
1763
|
-
|
|
1764
|
-
}
|
|
2250
|
+
...opts.keepAliveSec !== void 0 ? { keep_alive_sec: opts.keepAliveSec } : {}
|
|
2251
|
+
},
|
|
2252
|
+
need_total: opts.needTotal ?? false
|
|
2253
|
+
};
|
|
2254
|
+
return request(ctx, `${BASE3}/${encodeURIComponent(id)}/data`, {
|
|
2255
|
+
method: "POST",
|
|
2256
|
+
headers: { "X-HTTP-Method-Override": "GET" },
|
|
2257
|
+
body
|
|
1765
2258
|
});
|
|
1766
2259
|
}
|
|
1767
2260
|
|
|
@@ -2999,7 +3492,11 @@ function listBknResources(ctx) {
|
|
|
2999
3492
|
return request(ctx, "/api/bkn-backend/v1/resources");
|
|
3000
3493
|
}
|
|
3001
3494
|
function relationTypePaths(ctx, knId, body) {
|
|
3002
|
-
return request(ctx, knPath(knId, "relation-type-paths"), {
|
|
3495
|
+
return request(ctx, knPath(knId, "relation-type-paths"), {
|
|
3496
|
+
method: "POST",
|
|
3497
|
+
headers: { "X-HTTP-Method-Override": "GET" },
|
|
3498
|
+
body
|
|
3499
|
+
});
|
|
3003
3500
|
}
|
|
3004
3501
|
function listConceptGroups(ctx, knId) {
|
|
3005
3502
|
return request(ctx, knPath(knId, "concept-groups"));
|
|
@@ -3060,45 +3557,107 @@ function setActionScheduleStatus(ctx, knId, scheduleId, body) {
|
|
|
3060
3557
|
function deleteActionSchedules(ctx, knId, ids) {
|
|
3061
3558
|
return request(ctx, knPath(knId, `action-schedules/${ids}`), { method: "DELETE" });
|
|
3062
3559
|
}
|
|
3063
|
-
function listJobs(ctx, knId) {
|
|
3064
|
-
return request(ctx, knPath(knId, "jobs"));
|
|
3065
|
-
}
|
|
3066
|
-
function getJob(ctx, knId, jobId) {
|
|
3067
|
-
return request(ctx, knPath(knId, `jobs/${encodeURIComponent(jobId)}`));
|
|
3068
|
-
}
|
|
3069
|
-
function getJobTasks(ctx, knId, jobId) {
|
|
3070
|
-
return request(ctx, knPath(knId, `jobs/${encodeURIComponent(jobId)}/tasks`));
|
|
3071
|
-
}
|
|
3072
|
-
function deleteJobs(ctx, knId, ids) {
|
|
3073
|
-
return request(ctx, knPath(knId, `jobs/${ids}`), { method: "DELETE" });
|
|
3074
|
-
}
|
|
3075
3560
|
|
|
3076
3561
|
// src/api/vega.ts
|
|
3077
3562
|
import { z } from "zod";
|
|
3078
3563
|
var VEGA_BASE = "/api/vega-backend/v1";
|
|
3079
3564
|
var BuildMode = z.enum(["batch", "streaming"]);
|
|
3080
|
-
var
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3565
|
+
var BuildTaskExecuteType = z.enum(["incremental", "full"]);
|
|
3566
|
+
var BuildTaskStatus = z.enum([
|
|
3567
|
+
"init",
|
|
3568
|
+
"running",
|
|
3569
|
+
"stopping",
|
|
3570
|
+
"stopped",
|
|
3571
|
+
"completed",
|
|
3572
|
+
"failed"
|
|
3573
|
+
]);
|
|
3574
|
+
var CatalogHealthCheckScheduleMode = z.enum(["inherit", "enabled", "disabled"]);
|
|
3575
|
+
var CatalogHealthCheckStatus = z.enum([
|
|
3576
|
+
"healthy",
|
|
3577
|
+
"degraded",
|
|
3578
|
+
"unhealthy",
|
|
3579
|
+
"offline",
|
|
3580
|
+
"unchecked"
|
|
3581
|
+
]);
|
|
3582
|
+
var CatalogHealthStatus = z.object({
|
|
3583
|
+
id: z.string(),
|
|
3584
|
+
health_check_status: CatalogHealthCheckStatus,
|
|
3585
|
+
last_check_time: z.number().optional(),
|
|
3586
|
+
health_check_result: z.string().optional()
|
|
3587
|
+
}).passthrough();
|
|
3588
|
+
var CatalogHealthCheckSchedule = z.object({
|
|
3589
|
+
catalog_id: z.string(),
|
|
3590
|
+
mode: CatalogHealthCheckScheduleMode,
|
|
3591
|
+
cron_expr: z.string().optional(),
|
|
3592
|
+
last_run: z.number(),
|
|
3593
|
+
next_run: z.number()
|
|
3594
|
+
}).passthrough();
|
|
3595
|
+
var CatalogConnectionTestResult = z.object({
|
|
3596
|
+
success: z.boolean(),
|
|
3597
|
+
message: z.string().optional()
|
|
3598
|
+
}).passthrough();
|
|
3599
|
+
var CreateBuildTaskRequest = z.discriminatedUnion("mode", [
|
|
3600
|
+
z.object({
|
|
3601
|
+
resource_id: z.string().min(1),
|
|
3602
|
+
mode: z.literal("batch"),
|
|
3603
|
+
execute_type: BuildTaskExecuteType.optional()
|
|
3604
|
+
}),
|
|
3605
|
+
z.object({
|
|
3606
|
+
resource_id: z.string().min(1),
|
|
3607
|
+
mode: z.literal("streaming"),
|
|
3608
|
+
// Streaming tasks do not have an execution type.
|
|
3609
|
+
execute_type: z.never().optional()
|
|
3610
|
+
})
|
|
3611
|
+
]);
|
|
3085
3612
|
var BuildTask = z.object({
|
|
3086
3613
|
id: z.string(),
|
|
3087
3614
|
resource_id: z.string().optional(),
|
|
3088
3615
|
mode: BuildMode.optional(),
|
|
3089
|
-
status:
|
|
3616
|
+
status: BuildTaskStatus.optional(),
|
|
3090
3617
|
state: z.string().optional(),
|
|
3091
3618
|
total_count: z.number().optional(),
|
|
3092
3619
|
synced_count: z.number().optional(),
|
|
3093
3620
|
vectorized_count: z.number().optional(),
|
|
3094
3621
|
index_config: z.unknown().optional(),
|
|
3095
3622
|
catalog_id: z.string().optional(),
|
|
3623
|
+
execute_type: BuildTaskExecuteType.optional(),
|
|
3096
3624
|
index_health: z.object({
|
|
3097
3625
|
embedding: z.string(),
|
|
3098
3626
|
fulltext: z.string(),
|
|
3099
3627
|
usable: z.boolean()
|
|
3100
3628
|
}).passthrough().optional()
|
|
3101
3629
|
}).passthrough();
|
|
3630
|
+
var BuildTaskSummary = z.object({
|
|
3631
|
+
id: z.string(),
|
|
3632
|
+
resource_id: z.string(),
|
|
3633
|
+
resource_name: z.string().optional(),
|
|
3634
|
+
catalog_id: z.string(),
|
|
3635
|
+
catalog_name: z.string().optional(),
|
|
3636
|
+
status: BuildTaskStatus,
|
|
3637
|
+
mode: BuildMode,
|
|
3638
|
+
execute_type: BuildTaskExecuteType.optional(),
|
|
3639
|
+
total_count: z.number(),
|
|
3640
|
+
synced_count: z.number(),
|
|
3641
|
+
vectorized_count: z.number(),
|
|
3642
|
+
synced_mark: z.string(),
|
|
3643
|
+
error_msg: z.string().optional(),
|
|
3644
|
+
creator: z.object({
|
|
3645
|
+
id: z.string(),
|
|
3646
|
+
name: z.string().optional(),
|
|
3647
|
+
type: z.string()
|
|
3648
|
+
}),
|
|
3649
|
+
create_time: z.number(),
|
|
3650
|
+
update_time: z.number(),
|
|
3651
|
+
index_health: z.object({
|
|
3652
|
+
embedding: z.string(),
|
|
3653
|
+
fulltext: z.string(),
|
|
3654
|
+
usable: z.boolean()
|
|
3655
|
+
}).passthrough().optional()
|
|
3656
|
+
}).passthrough();
|
|
3657
|
+
var ListBuildTasksResponse = z.object({
|
|
3658
|
+
entries: z.array(BuildTaskSummary),
|
|
3659
|
+
total_count: z.number()
|
|
3660
|
+
}).passthrough();
|
|
3102
3661
|
async function createBuildTask(ctx, req) {
|
|
3103
3662
|
const p = CreateBuildTaskRequest.parse(req);
|
|
3104
3663
|
const body = {
|
|
@@ -3109,8 +3668,13 @@ async function createBuildTask(ctx, req) {
|
|
|
3109
3668
|
const res = await request(ctx, `${VEGA_BASE}/build-tasks`, { method: "POST", body });
|
|
3110
3669
|
return BuildTask.parse(res);
|
|
3111
3670
|
}
|
|
3112
|
-
function listBuildTasks(ctx, opts = {}) {
|
|
3113
|
-
|
|
3671
|
+
async function listBuildTasks(ctx, opts = {}) {
|
|
3672
|
+
if (opts.orderBy === "default") {
|
|
3673
|
+
throw new InputError(
|
|
3674
|
+
'orderBy "default" is no longer supported; use "created_at" or "updated_at"'
|
|
3675
|
+
);
|
|
3676
|
+
}
|
|
3677
|
+
const res = await request(ctx, `${VEGA_BASE}/build-tasks`, {
|
|
3114
3678
|
query: {
|
|
3115
3679
|
limit: opts.limit,
|
|
3116
3680
|
offset: opts.offset,
|
|
@@ -3123,6 +3687,7 @@ function listBuildTasks(ctx, opts = {}) {
|
|
|
3123
3687
|
order: opts.order
|
|
3124
3688
|
}
|
|
3125
3689
|
});
|
|
3690
|
+
return ListBuildTasksResponse.parse(res);
|
|
3126
3691
|
}
|
|
3127
3692
|
async function getBuildTask(ctx, taskId) {
|
|
3128
3693
|
const res = await request(ctx, `${VEGA_BASE}/build-tasks/${encodeURIComponent(taskId)}`);
|
|
@@ -3173,35 +3738,46 @@ async function listCatalogs(ctx, opts = {}) {
|
|
|
3173
3738
|
function getCatalog(ctx, id) {
|
|
3174
3739
|
return request(ctx, `${VEGA_BASE}/catalogs/${encodeURIComponent(id)}`);
|
|
3175
3740
|
}
|
|
3176
|
-
function createCatalog(ctx, req) {
|
|
3741
|
+
function createCatalog(ctx, req, opts = {}) {
|
|
3177
3742
|
return request(ctx, `${VEGA_BASE}/catalogs`, {
|
|
3178
3743
|
method: "POST",
|
|
3744
|
+
query: {
|
|
3745
|
+
allow_unhealthy: opts.allowUnhealthy === void 0 ? void 0 : String(opts.allowUnhealthy)
|
|
3746
|
+
},
|
|
3179
3747
|
body: {
|
|
3180
3748
|
...req.id ? { id: req.id } : {},
|
|
3181
3749
|
name: req.name,
|
|
3182
3750
|
connector_type: req.connectorType,
|
|
3183
3751
|
connector_config: req.connectorConfig,
|
|
3184
|
-
...req.tags ? { tags: req.tags } : {},
|
|
3185
|
-
...req.description ? { description: req.description } : {},
|
|
3752
|
+
...req.tags !== void 0 ? { tags: req.tags } : {},
|
|
3753
|
+
...req.description !== void 0 ? { description: req.description } : {},
|
|
3186
3754
|
...req.enabled !== void 0 ? { enabled: req.enabled } : {},
|
|
3187
3755
|
...req.internal !== void 0 ? { internal: req.internal } : {},
|
|
3188
|
-
...req.extensions ? { extensions: req.extensions } : {}
|
|
3189
|
-
|
|
3756
|
+
...req.extensions !== void 0 ? { extensions: req.extensions } : {},
|
|
3757
|
+
...req.healthCheckSchedule !== void 0 ? {
|
|
3758
|
+
health_check_schedule: req.healthCheckSchedule === null ? null : mapCatalogHealthCheckScheduleRequest(req.healthCheckSchedule)
|
|
3759
|
+
} : {}
|
|
3760
|
+
},
|
|
3761
|
+
timeoutMs: 6e4
|
|
3190
3762
|
});
|
|
3191
3763
|
}
|
|
3192
|
-
function updateCatalog(ctx, id, req) {
|
|
3764
|
+
function updateCatalog(ctx, id, req, opts = {}) {
|
|
3193
3765
|
return request(ctx, `${VEGA_BASE}/catalogs/${encodeURIComponent(id)}`, {
|
|
3194
3766
|
method: "PUT",
|
|
3767
|
+
query: {
|
|
3768
|
+
allow_unhealthy: opts.allowUnhealthy === void 0 ? void 0 : String(opts.allowUnhealthy)
|
|
3769
|
+
},
|
|
3195
3770
|
body: {
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3771
|
+
id,
|
|
3772
|
+
name: req.name,
|
|
3773
|
+
connector_type: req.connectorType,
|
|
3774
|
+
enabled: req.enabled,
|
|
3199
3775
|
...req.connectorConfig !== void 0 ? { connector_config: req.connectorConfig } : {},
|
|
3200
|
-
...req.tags ? { tags: req.tags } : {},
|
|
3776
|
+
...req.tags !== void 0 ? { tags: req.tags } : {},
|
|
3201
3777
|
...req.description !== void 0 ? { description: req.description } : {},
|
|
3202
|
-
...req.
|
|
3203
|
-
|
|
3204
|
-
|
|
3778
|
+
...req.extensions !== void 0 ? { extensions: req.extensions } : {}
|
|
3779
|
+
},
|
|
3780
|
+
timeoutMs: 6e4
|
|
3205
3781
|
});
|
|
3206
3782
|
}
|
|
3207
3783
|
function enableCatalog(ctx, id) {
|
|
@@ -3215,10 +3791,51 @@ function disableCatalog(ctx, id) {
|
|
|
3215
3791
|
function deleteCatalog(ctx, id) {
|
|
3216
3792
|
return request(ctx, `${VEGA_BASE}/catalogs/${encodeURIComponent(id)}`, { method: "DELETE" });
|
|
3217
3793
|
}
|
|
3218
|
-
function
|
|
3219
|
-
|
|
3220
|
-
method: "POST"
|
|
3794
|
+
async function testCatalogConnectionConfig(ctx, req) {
|
|
3795
|
+
const result = await request(ctx, `${VEGA_BASE}/catalogs/test-connection`, {
|
|
3796
|
+
method: "POST",
|
|
3797
|
+
body: {
|
|
3798
|
+
connector_type: req.connectorType,
|
|
3799
|
+
connector_config: req.connectorConfig
|
|
3800
|
+
},
|
|
3801
|
+
timeoutMs: 6e4
|
|
3221
3802
|
});
|
|
3803
|
+
return CatalogConnectionTestResult.parse(result);
|
|
3804
|
+
}
|
|
3805
|
+
async function testCatalogConnection(ctx, id) {
|
|
3806
|
+
const result = await request(
|
|
3807
|
+
ctx,
|
|
3808
|
+
`${VEGA_BASE}/catalogs/${encodeURIComponent(id)}/test-connection`,
|
|
3809
|
+
{
|
|
3810
|
+
method: "POST",
|
|
3811
|
+
timeoutMs: 6e4
|
|
3812
|
+
}
|
|
3813
|
+
);
|
|
3814
|
+
return CatalogConnectionTestResult.parse(result);
|
|
3815
|
+
}
|
|
3816
|
+
async function getCatalogHealthCheckSchedule(ctx, id) {
|
|
3817
|
+
const result = await request(
|
|
3818
|
+
ctx,
|
|
3819
|
+
`${VEGA_BASE}/catalogs/${encodeURIComponent(id)}/health-check-schedule`
|
|
3820
|
+
);
|
|
3821
|
+
return CatalogHealthCheckSchedule.parse(result);
|
|
3822
|
+
}
|
|
3823
|
+
async function updateCatalogHealthCheckSchedule(ctx, id, req) {
|
|
3824
|
+
const result = await request(
|
|
3825
|
+
ctx,
|
|
3826
|
+
`${VEGA_BASE}/catalogs/${encodeURIComponent(id)}/health-check-schedule`,
|
|
3827
|
+
{
|
|
3828
|
+
method: "PUT",
|
|
3829
|
+
body: mapCatalogHealthCheckScheduleRequest(req)
|
|
3830
|
+
}
|
|
3831
|
+
);
|
|
3832
|
+
return CatalogHealthCheckSchedule.parse(result);
|
|
3833
|
+
}
|
|
3834
|
+
function mapCatalogHealthCheckScheduleRequest(req) {
|
|
3835
|
+
return {
|
|
3836
|
+
mode: req.mode,
|
|
3837
|
+
...req.mode === "enabled" ? { cron_expr: req.cronExpr } : {}
|
|
3838
|
+
};
|
|
3222
3839
|
}
|
|
3223
3840
|
function discoverCatalog(ctx, id, wait = true) {
|
|
3224
3841
|
return request(ctx, `${VEGA_BASE}/catalogs/${encodeURIComponent(id)}/discover`, {
|
|
@@ -3227,16 +3844,23 @@ function discoverCatalog(ctx, id, wait = true) {
|
|
|
3227
3844
|
timeoutMs: 12e4
|
|
3228
3845
|
});
|
|
3229
3846
|
}
|
|
3230
|
-
function listCatalogResources(ctx, id, category) {
|
|
3847
|
+
function listCatalogResources(ctx, id, category, limit, offset) {
|
|
3231
3848
|
return request(ctx, `${VEGA_BASE}/resources`, {
|
|
3232
|
-
query: {
|
|
3849
|
+
query: {
|
|
3850
|
+
catalog_id: id,
|
|
3851
|
+
category: category || void 0,
|
|
3852
|
+
// limit=-1 (NO_LIMIT) fetches all; NaN / 0 fall back to the backend default.
|
|
3853
|
+
limit: Number.isFinite(limit) && (limit > 0 || limit === -1) ? limit : void 0,
|
|
3854
|
+
offset: offset || void 0
|
|
3855
|
+
}
|
|
3233
3856
|
});
|
|
3234
3857
|
}
|
|
3235
|
-
function catalogHealthStatus(ctx,
|
|
3236
|
-
|
|
3858
|
+
async function catalogHealthStatus(ctx, id) {
|
|
3859
|
+
const result = await request(
|
|
3237
3860
|
ctx,
|
|
3238
|
-
`${VEGA_BASE}/catalogs/${
|
|
3861
|
+
`${VEGA_BASE}/catalogs/${encodeURIComponent(id)}/health-status`
|
|
3239
3862
|
);
|
|
3863
|
+
return CatalogHealthStatus.parse(result);
|
|
3240
3864
|
}
|
|
3241
3865
|
function listConnectorTypes(ctx) {
|
|
3242
3866
|
return request(ctx, `${VEGA_BASE}/connector-types`, { query: { sort: "name", order: "asc" } });
|
|
@@ -3871,7 +4495,6 @@ function kn(ctx) {
|
|
|
3871
4495
|
metricValidate: (knId, body) => validateMetric(ctx, knId, body),
|
|
3872
4496
|
objectTypes: (knId, opts) => listObjectTypes(ctx, knId, opts),
|
|
3873
4497
|
objectTypeQuery: (knId, otId, body) => queryObjectTypeInstances(ctx, knId, otId, body),
|
|
3874
|
-
objectTypeProperties: (knId, otId) => getObjectTypeProperties(ctx, knId, otId),
|
|
3875
4498
|
objectTypeGet: (knId, id) => getSchemaItem(ctx, knId, "object-types", id),
|
|
3876
4499
|
objectTypeCreate: (knId, body) => createSchemaItem(ctx, knId, "object-types", body),
|
|
3877
4500
|
objectTypeUpdate: (knId, id, body) => updateSchemaItem(ctx, knId, "object-types", id, body),
|
|
@@ -3899,10 +4522,6 @@ function kn(ctx) {
|
|
|
3899
4522
|
actionScheduleUpdate: (knId, scheduleId, body) => updateActionSchedule(ctx, knId, scheduleId, body),
|
|
3900
4523
|
actionScheduleSetStatus: (knId, scheduleId, body) => setActionScheduleStatus(ctx, knId, scheduleId, body),
|
|
3901
4524
|
actionScheduleDelete: (knId, ids) => deleteActionSchedules(ctx, knId, ids),
|
|
3902
|
-
jobs: (knId) => listJobs(ctx, knId),
|
|
3903
|
-
job: (knId, jobId) => getJob(ctx, knId, jobId),
|
|
3904
|
-
jobTasks: (knId, jobId) => getJobTasks(ctx, knId, jobId),
|
|
3905
|
-
jobDelete: (knId, ids) => deleteJobs(ctx, knId, ids),
|
|
3906
4525
|
relationTypePaths: (knId, body) => relationTypePaths(ctx, knId, body),
|
|
3907
4526
|
bknResources: () => listBknResources(ctx),
|
|
3908
4527
|
createFromCatalog: (opts) => createFromCatalog(ctx, opts),
|
|
@@ -4155,16 +4774,45 @@ async function updateSkillPackageZip(ctx, skillId, bytes, filename = "skill.zip"
|
|
|
4155
4774
|
if (!res.ok) throw new HttpError(res.status, res.statusText, text);
|
|
4156
4775
|
return text ? JSON.parse(text) : void 0;
|
|
4157
4776
|
}
|
|
4158
|
-
|
|
4777
|
+
function skillPath(skillId, view, path) {
|
|
4778
|
+
const seg = view === "draft" ? "management/" : "";
|
|
4779
|
+
return `${BASE5}/skills/${encodeURIComponent(skillId)}/${seg}${path}`;
|
|
4780
|
+
}
|
|
4781
|
+
async function downloadSkill(ctx, skillId, view = "published") {
|
|
4159
4782
|
const res = await authFetch(
|
|
4160
4783
|
ctx,
|
|
4161
|
-
() => tlsFetch(ctx.insecure, `${ctx.baseUrl}${
|
|
4784
|
+
() => tlsFetch(ctx.insecure, `${ctx.baseUrl}${skillPath(skillId, view, "download")}`, {
|
|
4162
4785
|
headers: buildHeaders(ctx)
|
|
4163
4786
|
})
|
|
4164
4787
|
);
|
|
4165
4788
|
if (!res.ok) throw new HttpError(res.status, res.statusText, await res.text());
|
|
4166
4789
|
return new Uint8Array(await res.arrayBuffer());
|
|
4167
4790
|
}
|
|
4791
|
+
function executeSkill(ctx, skillId, opts) {
|
|
4792
|
+
return request(ctx, `${BASE5}/skills/${encodeURIComponent(skillId)}/execute`, {
|
|
4793
|
+
method: "POST",
|
|
4794
|
+
body: {
|
|
4795
|
+
entry_shell: opts.entryShell,
|
|
4796
|
+
...opts.timeout === void 0 ? {} : { timeout: opts.timeout }
|
|
4797
|
+
},
|
|
4798
|
+
// Outlast the sandbox: the default client timeout is shorter than the run
|
|
4799
|
+
// budget, so without this a long run aborts locally mid-execution and the
|
|
4800
|
+
// caller never learns the exit code. With no stated limit the sandbox
|
|
4801
|
+
// applies its own — 300s by default, 3600s at most — so the budget has to
|
|
4802
|
+
// cover that rather than a number of ours.
|
|
4803
|
+
timeoutMs: executeBudgetMs(opts.timeout),
|
|
4804
|
+
// The abort deadline alone tops out at undici's 300s header deadline,
|
|
4805
|
+
// because `execute-sync` blocks and sends no headers until the run is over.
|
|
4806
|
+
headersTimeoutMs: executeBudgetMs(opts.timeout)
|
|
4807
|
+
});
|
|
4808
|
+
}
|
|
4809
|
+
var SANDBOX_MAX_TIMEOUT_SEC = 3600;
|
|
4810
|
+
function executeBudgetMs(timeoutSec) {
|
|
4811
|
+
return (timeoutSec ?? SANDBOX_MAX_TIMEOUT_SEC) * 1e3 + 15e3;
|
|
4812
|
+
}
|
|
4813
|
+
function getSkillNames(ctx, ids) {
|
|
4814
|
+
return request(ctx, `${BASE5}/skills/names`, { method: "POST", body: { ids } });
|
|
4815
|
+
}
|
|
4168
4816
|
function updateSkillMetadata(ctx, skillId, body) {
|
|
4169
4817
|
return request(ctx, `${BASE5}/skills/${encodeURIComponent(skillId)}`, { method: "PUT", body });
|
|
4170
4818
|
}
|
|
@@ -4205,12 +4853,15 @@ function getSkillMarket(ctx, skillId) {
|
|
|
4205
4853
|
function deleteSkill(ctx, skillId) {
|
|
4206
4854
|
return request(ctx, `${BASE5}/skills/${encodeURIComponent(skillId)}`, { method: "DELETE" });
|
|
4207
4855
|
}
|
|
4208
|
-
function getSkillContent(ctx, skillId) {
|
|
4209
|
-
return request(ctx,
|
|
4856
|
+
function getSkillContent(ctx, skillId, opts = {}) {
|
|
4857
|
+
return request(ctx, skillPath(skillId, opts.view ?? "published", "content"), {
|
|
4858
|
+
query: { response_mode: opts.responseMode }
|
|
4859
|
+
});
|
|
4210
4860
|
}
|
|
4211
|
-
function readSkillFile(ctx, skillId, relPath) {
|
|
4212
|
-
return request(ctx,
|
|
4861
|
+
function readSkillFile(ctx, skillId, relPath, opts = {}) {
|
|
4862
|
+
return request(ctx, skillPath(skillId, opts.view ?? "published", "files/read"), {
|
|
4213
4863
|
method: "POST",
|
|
4864
|
+
query: { response_mode: opts.responseMode },
|
|
4214
4865
|
body: { rel_path: relPath }
|
|
4215
4866
|
});
|
|
4216
4867
|
}
|
|
@@ -4248,6 +4899,15 @@ async function zipDirectory(dir) {
|
|
|
4248
4899
|
}
|
|
4249
4900
|
return new Uint8Array(await zip.generateAsync({ type: "uint8array", compression: "DEFLATE" }));
|
|
4250
4901
|
}
|
|
4902
|
+
async function unzipToMap(bytes) {
|
|
4903
|
+
const zip = await JSZip.loadAsync(bytes);
|
|
4904
|
+
const out = /* @__PURE__ */ new Map();
|
|
4905
|
+
for (const entry of Object.values(zip.files)) {
|
|
4906
|
+
if (entry.dir) continue;
|
|
4907
|
+
out.set(entry.name, new Uint8Array(await entry.async("uint8array")));
|
|
4908
|
+
}
|
|
4909
|
+
return out;
|
|
4910
|
+
}
|
|
4251
4911
|
async function unzipToDirectory(bytes, dir) {
|
|
4252
4912
|
const abs = resolve4(dir);
|
|
4253
4913
|
mkdirSync3(abs, { recursive: true });
|
|
@@ -4264,16 +4924,202 @@ async function unzipToDirectory(bytes, dir) {
|
|
|
4264
4924
|
return written;
|
|
4265
4925
|
}
|
|
4266
4926
|
|
|
4927
|
+
// src/utils/skill-tree.ts
|
|
4928
|
+
function normalize(path) {
|
|
4929
|
+
return (path ?? "").replace(/^\/+|\/+$/g, "");
|
|
4930
|
+
}
|
|
4931
|
+
function segments(relPath) {
|
|
4932
|
+
return relPath.split("/").filter(Boolean);
|
|
4933
|
+
}
|
|
4934
|
+
function classifyPath(files, path) {
|
|
4935
|
+
const target = normalize(path);
|
|
4936
|
+
if (!target) return "root";
|
|
4937
|
+
if (files.some((f) => normalize(f.rel_path) === target)) return "file";
|
|
4938
|
+
const prefix = `${target}/`;
|
|
4939
|
+
return files.some((f) => normalize(f.rel_path).startsWith(prefix)) ? "dir" : "missing";
|
|
4940
|
+
}
|
|
4941
|
+
function listChildren(files, path) {
|
|
4942
|
+
const target = normalize(path);
|
|
4943
|
+
const prefix = target ? `${target}/` : "";
|
|
4944
|
+
const dirs = /* @__PURE__ */ new Map();
|
|
4945
|
+
const leaves = [];
|
|
4946
|
+
for (const file of files) {
|
|
4947
|
+
const rel = normalize(file.rel_path);
|
|
4948
|
+
if (prefix && !rel.startsWith(prefix)) continue;
|
|
4949
|
+
const rest = segments(rel.slice(prefix.length));
|
|
4950
|
+
if (rest.length === 0) continue;
|
|
4951
|
+
const [head] = rest;
|
|
4952
|
+
if (!head) continue;
|
|
4953
|
+
if (rest.length === 1) {
|
|
4954
|
+
leaves.push({
|
|
4955
|
+
name: head,
|
|
4956
|
+
type: "file",
|
|
4957
|
+
relPath: rel,
|
|
4958
|
+
fileType: file.file_type,
|
|
4959
|
+
size: file.size,
|
|
4960
|
+
mime: file.mime_type
|
|
4961
|
+
});
|
|
4962
|
+
continue;
|
|
4963
|
+
}
|
|
4964
|
+
const dir = dirs.get(head) ?? { name: head, type: "dir", files: 0, size: 0 };
|
|
4965
|
+
dir.files += 1;
|
|
4966
|
+
dir.size += file.size ?? 0;
|
|
4967
|
+
dirs.set(head, dir);
|
|
4968
|
+
}
|
|
4969
|
+
const byName = (a, b) => a.name.localeCompare(b.name);
|
|
4970
|
+
return [...[...dirs.values()].sort(byName), ...leaves.sort(byName)];
|
|
4971
|
+
}
|
|
4972
|
+
function filesUnder(files, path) {
|
|
4973
|
+
const target = normalize(path);
|
|
4974
|
+
if (!target) return files;
|
|
4975
|
+
const prefix = `${target}/`;
|
|
4976
|
+
return files.filter((f) => normalize(f.rel_path).startsWith(prefix));
|
|
4977
|
+
}
|
|
4978
|
+
function emptyNode() {
|
|
4979
|
+
return { dirs: /* @__PURE__ */ new Map(), files: [] };
|
|
4980
|
+
}
|
|
4981
|
+
function buildTree(files) {
|
|
4982
|
+
const root = emptyNode();
|
|
4983
|
+
for (const file of files) {
|
|
4984
|
+
const parts = segments(normalize(file.rel_path));
|
|
4985
|
+
const name = parts.pop();
|
|
4986
|
+
if (!name) continue;
|
|
4987
|
+
let node = root;
|
|
4988
|
+
for (const part of parts) {
|
|
4989
|
+
const next = node.dirs.get(part) ?? emptyNode();
|
|
4990
|
+
node.dirs.set(part, next);
|
|
4991
|
+
node = next;
|
|
4992
|
+
}
|
|
4993
|
+
node.files.push({
|
|
4994
|
+
name,
|
|
4995
|
+
type: "file",
|
|
4996
|
+
relPath: normalize(file.rel_path),
|
|
4997
|
+
fileType: file.file_type,
|
|
4998
|
+
size: file.size,
|
|
4999
|
+
mime: file.mime_type
|
|
5000
|
+
});
|
|
5001
|
+
}
|
|
5002
|
+
return root;
|
|
5003
|
+
}
|
|
5004
|
+
function renderNode(node, indent, out) {
|
|
5005
|
+
const dirs = [...node.dirs.entries()].sort(([a], [b]) => a.localeCompare(b));
|
|
5006
|
+
const files = [...node.files].sort((a, b) => a.name.localeCompare(b.name));
|
|
5007
|
+
const total = dirs.length + files.length;
|
|
5008
|
+
let index = 0;
|
|
5009
|
+
for (const [name, child] of dirs) {
|
|
5010
|
+
index += 1;
|
|
5011
|
+
const last = index === total;
|
|
5012
|
+
out.push(`${indent}${last ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 "}${name}/`);
|
|
5013
|
+
renderNode(child, `${indent}${last ? " " : "\u2502 "}`, out);
|
|
5014
|
+
}
|
|
5015
|
+
for (const file of files) {
|
|
5016
|
+
index += 1;
|
|
5017
|
+
const last = index === total;
|
|
5018
|
+
const meta = [file.fileType, file.size === void 0 ? void 0 : `${file.size} B`].filter(Boolean).join(", ");
|
|
5019
|
+
out.push(`${indent}${last ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 "}${file.name}${meta ? ` (${meta})` : ""}`);
|
|
5020
|
+
}
|
|
5021
|
+
}
|
|
5022
|
+
function renderTree(files) {
|
|
5023
|
+
const out = [];
|
|
5024
|
+
renderNode(buildTree(files), "", out);
|
|
5025
|
+
return out.join("\n");
|
|
5026
|
+
}
|
|
5027
|
+
|
|
4267
5028
|
// src/resources/skills.ts
|
|
5029
|
+
function viewOf(opts) {
|
|
5030
|
+
return opts?.draft ? "draft" : "published";
|
|
5031
|
+
}
|
|
5032
|
+
function cachedArchive(cache, ctx, skillId, view) {
|
|
5033
|
+
const key = `${skillId}|${view}`;
|
|
5034
|
+
const hit = cache.get(key);
|
|
5035
|
+
if (hit) return hit;
|
|
5036
|
+
const pending = downloadSkill(ctx, skillId, view).then(unzipToMap);
|
|
5037
|
+
cache.set(key, pending);
|
|
5038
|
+
pending.catch(() => cache.delete(key));
|
|
5039
|
+
return pending;
|
|
5040
|
+
}
|
|
5041
|
+
function binaryError(relPath) {
|
|
5042
|
+
return new InputError(
|
|
5043
|
+
`'${relPath}' is a binary file \u2014 use \`openbkn skill install\` or \`skill download\` to fetch it.`
|
|
5044
|
+
);
|
|
5045
|
+
}
|
|
5046
|
+
function decodeStrict(bytes, relPath) {
|
|
5047
|
+
let text;
|
|
5048
|
+
try {
|
|
5049
|
+
text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
5050
|
+
} catch {
|
|
5051
|
+
throw binaryError(relPath);
|
|
5052
|
+
}
|
|
5053
|
+
if (text.includes("\0")) throw binaryError(relPath);
|
|
5054
|
+
return text;
|
|
5055
|
+
}
|
|
5056
|
+
function looksLossy(text) {
|
|
5057
|
+
return text.includes("\0") || text.includes("\uFFFD");
|
|
5058
|
+
}
|
|
5059
|
+
async function readFileText(cache, ctx, skillId, relPath, view) {
|
|
5060
|
+
const res = await readSkillFile(ctx, skillId, normalize(relPath), {
|
|
5061
|
+
view,
|
|
5062
|
+
responseMode: "content"
|
|
5063
|
+
});
|
|
5064
|
+
if (typeof res?.content === "string" && res.content.length > 0) {
|
|
5065
|
+
if (looksLossy(res.content)) throw binaryError(relPath);
|
|
5066
|
+
return res.content;
|
|
5067
|
+
}
|
|
5068
|
+
const archive = await cachedArchive(cache, ctx, skillId, view);
|
|
5069
|
+
const bytes = archive.get(normalize(relPath));
|
|
5070
|
+
if (!bytes) {
|
|
5071
|
+
throw new InputError(`'${relPath}' not found in skill ${skillId}.`);
|
|
5072
|
+
}
|
|
5073
|
+
return decodeStrict(bytes, relPath);
|
|
5074
|
+
}
|
|
4268
5075
|
function skills(ctx) {
|
|
5076
|
+
const archives = /* @__PURE__ */ new Map();
|
|
5077
|
+
const manifest = async (skillId, opts) => {
|
|
5078
|
+
const res = await getSkillContent(ctx, skillId, { view: viewOf(opts) });
|
|
5079
|
+
return res?.files ?? [];
|
|
5080
|
+
};
|
|
4269
5081
|
return {
|
|
4270
5082
|
list: (opts) => listSkills(ctx, opts),
|
|
4271
5083
|
get: (skillId) => getSkill(ctx, skillId),
|
|
4272
5084
|
market: (opts) => listSkillMarket(ctx, opts),
|
|
4273
5085
|
marketGet: (skillId) => getSkillMarket(ctx, skillId),
|
|
4274
5086
|
delete: (skillId) => deleteSkill(ctx, skillId),
|
|
4275
|
-
content: (skillId) => getSkillContent(ctx, skillId),
|
|
4276
|
-
|
|
5087
|
+
content: (skillId, opts) => getSkillContent(ctx, skillId, { view: viewOf(opts) }),
|
|
5088
|
+
// Normalized like every other path entry point, so `/a/b.md` addresses the
|
|
5089
|
+
// same file here as it does under `--raw` and in `skill files`.
|
|
5090
|
+
readFile: (skillId, relPath, opts) => readSkillFile(ctx, skillId, normalize(relPath), { view: viewOf(opts) }),
|
|
5091
|
+
/** SKILL.md's own text, for callers that want the document rather than a link. */
|
|
5092
|
+
contentRaw: (skillId, opts) => readFileText(archives, ctx, skillId, "SKILL.md", viewOf(opts)),
|
|
5093
|
+
/** A bundled file's text. */
|
|
5094
|
+
readFileRaw: (skillId, relPath, opts) => readFileText(archives, ctx, skillId, relPath, viewOf(opts)),
|
|
5095
|
+
/** Run the skill in the platform sandbox. */
|
|
5096
|
+
execute: (skillId, opts) => executeSkill(ctx, skillId, opts),
|
|
5097
|
+
/** Resolve skill ids to names; unknown ids are simply absent from the result. */
|
|
5098
|
+
names: (ids) => getSkillNames(ctx, ids),
|
|
5099
|
+
/**
|
|
5100
|
+
* One level of the skill's file tree. Directories are inferred from the
|
|
5101
|
+
* manifest's paths — see utils/skill-tree.
|
|
5102
|
+
*/
|
|
5103
|
+
files: async (skillId, path, opts) => {
|
|
5104
|
+
const files = await manifest(skillId, opts);
|
|
5105
|
+
const kind = classifyPath(files, path);
|
|
5106
|
+
if (kind === "file") {
|
|
5107
|
+
throw new InputError(`'${path}' is a file, not a directory \u2014 use \`skill read-file\`.`);
|
|
5108
|
+
}
|
|
5109
|
+
if (kind === "missing") {
|
|
5110
|
+
throw new InputError(`'${path}' not found in skill ${skillId}.`);
|
|
5111
|
+
}
|
|
5112
|
+
const subtree = filesUnder(files, path);
|
|
5113
|
+
return {
|
|
5114
|
+
skillId,
|
|
5115
|
+
path: path ?? "",
|
|
5116
|
+
entries: listChildren(files, path),
|
|
5117
|
+
totalFiles: subtree.length,
|
|
5118
|
+
totalSize: subtree.reduce((sum, f) => sum + (f.size ?? 0), 0)
|
|
5119
|
+
};
|
|
5120
|
+
},
|
|
5121
|
+
/** The manifest itself, for callers that want to render the whole tree. */
|
|
5122
|
+
fileManifest: manifest,
|
|
4277
5123
|
history: (skillId) => getSkillHistory(ctx, skillId),
|
|
4278
5124
|
setStatus: (skillId, status2) => setSkillStatus(ctx, skillId, status2),
|
|
4279
5125
|
updateMetadata: (skillId, body) => updateSkillMetadata(ctx, skillId, body),
|
|
@@ -4287,16 +5133,16 @@ function skills(ctx) {
|
|
|
4287
5133
|
/** Replace a skill's package from a local directory. */
|
|
4288
5134
|
updatePackage: async (skillId, dir) => updateSkillPackageZip(ctx, skillId, await zipDirectory(dir), `${basename2(resolve5(dir))}.zip`),
|
|
4289
5135
|
/** Download a skill archive to a local .zip file. */
|
|
4290
|
-
download: async (skillId, outPath) => {
|
|
4291
|
-
const bytes = await downloadSkill(ctx, skillId);
|
|
5136
|
+
download: async (skillId, outPath, opts) => {
|
|
5137
|
+
const bytes = await downloadSkill(ctx, skillId, viewOf(opts));
|
|
4292
5138
|
const dest = resolve5(outPath ?? `${skillId}.zip`);
|
|
4293
5139
|
mkdirSync4(dirname3(dest), { recursive: true });
|
|
4294
5140
|
writeFileSync3(dest, bytes);
|
|
4295
5141
|
return { skillId, path: dest, bytes: bytes.length };
|
|
4296
5142
|
},
|
|
4297
5143
|
/** Download a skill archive and extract it into a directory. */
|
|
4298
|
-
install: async (skillId, dir) => {
|
|
4299
|
-
const bytes = await downloadSkill(ctx, skillId);
|
|
5144
|
+
install: async (skillId, dir, opts) => {
|
|
5145
|
+
const bytes = await downloadSkill(ctx, skillId, viewOf(opts));
|
|
4300
5146
|
const target = resolve5(dir ?? skillId);
|
|
4301
5147
|
const files = await unzipToDirectory(bytes, target);
|
|
4302
5148
|
return { skillId, dir: target, files: files.length };
|
|
@@ -4366,8 +5212,14 @@ function listToolboxes(ctx, opts = {}) {
|
|
|
4366
5212
|
query: { keyword: opts.keyword || void 0, limit: opts.limit, offset: opts.offset ?? 0 }
|
|
4367
5213
|
});
|
|
4368
5214
|
}
|
|
4369
|
-
function listTools2(ctx, boxId) {
|
|
4370
|
-
return request(ctx, `${PATH}/${encodeURIComponent(boxId)}/tools/list
|
|
5215
|
+
function listTools2(ctx, boxId, opts = {}) {
|
|
5216
|
+
return request(ctx, `${PATH}/${encodeURIComponent(boxId)}/tools/list`, {
|
|
5217
|
+
query: {
|
|
5218
|
+
page: opts.page,
|
|
5219
|
+
page_size: Number.isFinite(opts.pageSize) && opts.pageSize > 0 ? opts.pageSize : void 0,
|
|
5220
|
+
all: opts.all ? "true" : void 0
|
|
5221
|
+
}
|
|
5222
|
+
});
|
|
4371
5223
|
}
|
|
4372
5224
|
function createToolbox(ctx, opts) {
|
|
4373
5225
|
return request(ctx, PATH, {
|
|
@@ -4426,7 +5278,7 @@ function setToolStatuses(ctx, boxId, updates) {
|
|
|
4426
5278
|
function toolboxes(ctx) {
|
|
4427
5279
|
return {
|
|
4428
5280
|
list: (opts) => listToolboxes(ctx, opts),
|
|
4429
|
-
tools: (boxId) => listTools2(ctx, boxId),
|
|
5281
|
+
tools: (boxId, opts) => listTools2(ctx, boxId, opts),
|
|
4430
5282
|
create: (opts) => createToolbox(ctx, opts),
|
|
4431
5283
|
delete: (boxId) => deleteToolbox(ctx, boxId),
|
|
4432
5284
|
publish: (boxId) => setToolboxStatus(ctx, boxId, "published"),
|
|
@@ -4452,8 +5304,385 @@ function toolboxes(ctx) {
|
|
|
4452
5304
|
};
|
|
4453
5305
|
}
|
|
4454
5306
|
|
|
5307
|
+
// src/api/trace-lifecycle.ts
|
|
5308
|
+
var LIFECYCLE = "/api/agent-observability/v1";
|
|
5309
|
+
var FORBIDDEN_INPUT_FIELDS = /* @__PURE__ */ new Set([
|
|
5310
|
+
"generation",
|
|
5311
|
+
"on_behalf_of",
|
|
5312
|
+
"onBehalfOf",
|
|
5313
|
+
"owner",
|
|
5314
|
+
"tenant_id",
|
|
5315
|
+
"application_principal_id",
|
|
5316
|
+
"actor_subject",
|
|
5317
|
+
"actor_subject_type",
|
|
5318
|
+
"actor_subject_id",
|
|
5319
|
+
"effective_subject",
|
|
5320
|
+
"effective_subject_type",
|
|
5321
|
+
"effective_subject_id",
|
|
5322
|
+
"delegation_id"
|
|
5323
|
+
]);
|
|
5324
|
+
function traceLifecycleApi(ctx) {
|
|
5325
|
+
const post2 = async (path, input) => {
|
|
5326
|
+
assertNoForbiddenInputFields(input);
|
|
5327
|
+
return await request(ctx, `${LIFECYCLE}${path}`, { method: "POST", body: input });
|
|
5328
|
+
};
|
|
5329
|
+
const get = (path) => request(ctx, `${LIFECYCLE}${path}`, { method: "GET" });
|
|
5330
|
+
const interactionTerminal = (interactionId, action, input) => post2(`/interactions/${encodeURIComponent(interactionId)}/${action}`, input);
|
|
5331
|
+
const finishAttempt = (operationId, attempt, action, input) => post2(
|
|
5332
|
+
`/operations/${encodeURIComponent(operationId)}/attempts/${encodeURIComponent(String(attempt))}:${action}`,
|
|
5333
|
+
input
|
|
5334
|
+
);
|
|
5335
|
+
return {
|
|
5336
|
+
listConversations: (query = {}) => {
|
|
5337
|
+
const params = new URLSearchParams();
|
|
5338
|
+
if (query.limit !== void 0 && Number.isFinite(query.limit)) {
|
|
5339
|
+
params.set("limit", String(query.limit));
|
|
5340
|
+
}
|
|
5341
|
+
const suffix = params.size > 0 ? `?${params.toString()}` : "";
|
|
5342
|
+
return get(`/conversations${suffix}`);
|
|
5343
|
+
},
|
|
5344
|
+
ensureConversation: (input) => post2("/conversations:ensure-current", input),
|
|
5345
|
+
createNewConversationGeneration: (input) => post2("/conversations:create-new-generation", input),
|
|
5346
|
+
resumeConversation: (input) => post2("/conversations:resume-by-id", input),
|
|
5347
|
+
getConversation: (conversationId) => get(`/conversations/${encodeURIComponent(conversationId)}`),
|
|
5348
|
+
closeConversation: (conversationId, input) => post2(`/conversations/${encodeURIComponent(conversationId)}/close`, input),
|
|
5349
|
+
startInteraction: (conversationId, input) => post2(`/conversations/${encodeURIComponent(conversationId)}/interactions`, input),
|
|
5350
|
+
getInteraction: (interactionId) => get(`/interactions/${encodeURIComponent(interactionId)}`),
|
|
5351
|
+
completeInteraction: (interactionId, input) => interactionTerminal(interactionId, "complete", input),
|
|
5352
|
+
failInteraction: (interactionId, input) => interactionTerminal(interactionId, "fail", input),
|
|
5353
|
+
cancelInteraction: (interactionId, input) => interactionTerminal(interactionId, "cancel", input),
|
|
5354
|
+
handoffInteraction: (interactionId, input) => interactionTerminal(interactionId, "handoff", input),
|
|
5355
|
+
ensureOperation: (conversationId, interactionId, input) => post2(
|
|
5356
|
+
`/conversations/${encodeURIComponent(conversationId)}/interactions/${encodeURIComponent(interactionId)}/operations:ensure`,
|
|
5357
|
+
input
|
|
5358
|
+
),
|
|
5359
|
+
getOperation: (operationId) => get(`/operations/${encodeURIComponent(operationId)}`),
|
|
5360
|
+
retryOperationAttempt: (operationId, input) => post2(`/operations/${encodeURIComponent(operationId)}/attempts`, input),
|
|
5361
|
+
completeOperationAttempt: (operationId, attempt, input) => finishAttempt(operationId, attempt, "complete", input),
|
|
5362
|
+
failOperationAttempt: (operationId, attempt, input) => finishAttempt(operationId, attempt, "fail", input),
|
|
5363
|
+
getReceipt: (receiptId) => get(`/receipts/${encodeURIComponent(receiptId)}`)
|
|
5364
|
+
};
|
|
5365
|
+
}
|
|
5366
|
+
function assertNoForbiddenInputFields(input) {
|
|
5367
|
+
const pending = [input];
|
|
5368
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5369
|
+
while (pending.length > 0) {
|
|
5370
|
+
const value = pending.pop();
|
|
5371
|
+
if (typeof value !== "object" || value === null || seen.has(value)) continue;
|
|
5372
|
+
seen.add(value);
|
|
5373
|
+
for (const [field, nested] of Object.entries(value)) {
|
|
5374
|
+
if (FORBIDDEN_INPUT_FIELDS.has(field)) {
|
|
5375
|
+
throw new InputError(`Lifecycle input field "${field}" is not allowed`);
|
|
5376
|
+
}
|
|
5377
|
+
pending.push(nested);
|
|
5378
|
+
}
|
|
5379
|
+
}
|
|
5380
|
+
}
|
|
5381
|
+
|
|
5382
|
+
// src/managed-trace.ts
|
|
5383
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
5384
|
+
var FORBIDDEN_INPUT_FIELDS2 = ["generation", "on_behalf_of", "onBehalfOf"];
|
|
5385
|
+
var CompletionMissingError = class extends InputError {
|
|
5386
|
+
};
|
|
5387
|
+
var OperationFailedError = class extends InputError {
|
|
5388
|
+
};
|
|
5389
|
+
var ManagedTrace = class {
|
|
5390
|
+
constructor(api, options = {}) {
|
|
5391
|
+
this.api = api;
|
|
5392
|
+
this.idFactory = options.idFactory ?? randomUUID3;
|
|
5393
|
+
}
|
|
5394
|
+
api;
|
|
5395
|
+
idFactory;
|
|
5396
|
+
pendingConversations = /* @__PURE__ */ new Map();
|
|
5397
|
+
activeConversationIds = /* @__PURE__ */ new Set();
|
|
5398
|
+
async withInteraction(strategy, callback) {
|
|
5399
|
+
assertSafeStrategy(strategy);
|
|
5400
|
+
const conversation = await this.resolveConversation(strategy);
|
|
5401
|
+
if (this.activeConversationIds.has(conversation.conversation_id)) {
|
|
5402
|
+
throw new InputError(
|
|
5403
|
+
`Conversation "${conversation.conversation_id}" already has an active interaction`
|
|
5404
|
+
);
|
|
5405
|
+
}
|
|
5406
|
+
this.activeConversationIds.add(conversation.conversation_id);
|
|
5407
|
+
try {
|
|
5408
|
+
const interaction = await this.api.startInteraction(conversation.conversation_id, {
|
|
5409
|
+
idempotency_key: this.idFactory()
|
|
5410
|
+
});
|
|
5411
|
+
const receipts = /* @__PURE__ */ new Map();
|
|
5412
|
+
let terminal;
|
|
5413
|
+
let terminalAction;
|
|
5414
|
+
const terminalInput = (reason) => ({
|
|
5415
|
+
terminal_idempotency_key: this.idFactory(),
|
|
5416
|
+
lease_token: interaction.lease_token,
|
|
5417
|
+
lease_epoch: interaction.lease_epoch,
|
|
5418
|
+
completion_manifest_version: "3.0.0",
|
|
5419
|
+
completion_reason: reason,
|
|
5420
|
+
claims: [],
|
|
5421
|
+
expected_operations: expectedOperations(receipts.values()),
|
|
5422
|
+
expected_receipts: expectedReceipts(receipts.values())
|
|
5423
|
+
});
|
|
5424
|
+
const scope = {
|
|
5425
|
+
conversation,
|
|
5426
|
+
interaction,
|
|
5427
|
+
bknContext: (operationKey, parentOperationId, causationEventIds) => ({
|
|
5428
|
+
bkn_context: {
|
|
5429
|
+
conversation_id: conversation.conversation_id,
|
|
5430
|
+
interaction_id: interaction.interaction_id,
|
|
5431
|
+
operation_key: operationKey,
|
|
5432
|
+
...parentOperationId ? { parent_operation_id: parentOperationId } : {},
|
|
5433
|
+
...causationEventIds?.length ? { causation_event_ids: causationEventIds } : {}
|
|
5434
|
+
}
|
|
5435
|
+
}),
|
|
5436
|
+
recordReceipt: (receipt) => {
|
|
5437
|
+
if (receipt.conversation_id !== conversation.conversation_id || receipt.interaction_id !== interaction.interaction_id) {
|
|
5438
|
+
throw new InputError("Receipt does not belong to the active managed interaction");
|
|
5439
|
+
}
|
|
5440
|
+
receipts.set(receipt.receipt_id, receipt);
|
|
5441
|
+
},
|
|
5442
|
+
supportCandidates: () => [...receipts.values()].flatMap(
|
|
5443
|
+
(receipt) => receipt.observed_evidence_refs.map((ref) => ({
|
|
5444
|
+
ref,
|
|
5445
|
+
state: "observed",
|
|
5446
|
+
adopted: false
|
|
5447
|
+
}))
|
|
5448
|
+
),
|
|
5449
|
+
runOperation: async (input, execute) => this.runOperation(
|
|
5450
|
+
conversation,
|
|
5451
|
+
interaction,
|
|
5452
|
+
input,
|
|
5453
|
+
execute,
|
|
5454
|
+
scope.bknContext,
|
|
5455
|
+
scope.recordReceipt
|
|
5456
|
+
),
|
|
5457
|
+
cancel: async (reason) => {
|
|
5458
|
+
terminalAction ??= this.api.cancelInteraction(
|
|
5459
|
+
interaction.interaction_id,
|
|
5460
|
+
terminalInput(reason)
|
|
5461
|
+
);
|
|
5462
|
+
terminal = await terminalAction;
|
|
5463
|
+
return terminal;
|
|
5464
|
+
},
|
|
5465
|
+
handoff: async (reason) => {
|
|
5466
|
+
terminalAction ??= this.api.handoffInteraction(
|
|
5467
|
+
interaction.interaction_id,
|
|
5468
|
+
terminalInput(reason)
|
|
5469
|
+
);
|
|
5470
|
+
terminal = await terminalAction;
|
|
5471
|
+
return terminal;
|
|
5472
|
+
}
|
|
5473
|
+
};
|
|
5474
|
+
let completion;
|
|
5475
|
+
try {
|
|
5476
|
+
completion = await callback(scope);
|
|
5477
|
+
if (terminalAction) return await terminalAction;
|
|
5478
|
+
if (terminal) return terminal;
|
|
5479
|
+
if (!completion || typeof completion !== "object") {
|
|
5480
|
+
throw new CompletionMissingError(
|
|
5481
|
+
"Interaction callback must return a completion manifest"
|
|
5482
|
+
);
|
|
5483
|
+
}
|
|
5484
|
+
} catch (error) {
|
|
5485
|
+
if (!terminalAction && !terminal) {
|
|
5486
|
+
try {
|
|
5487
|
+
terminal = await this.api.failInteraction(
|
|
5488
|
+
interaction.interaction_id,
|
|
5489
|
+
terminalInput(
|
|
5490
|
+
error instanceof CompletionMissingError ? "completion_missing" : "callback_failed"
|
|
5491
|
+
)
|
|
5492
|
+
);
|
|
5493
|
+
} catch {
|
|
5494
|
+
}
|
|
5495
|
+
}
|
|
5496
|
+
throw error;
|
|
5497
|
+
}
|
|
5498
|
+
const completionInput = {
|
|
5499
|
+
...completion,
|
|
5500
|
+
terminal_idempotency_key: this.idFactory(),
|
|
5501
|
+
lease_token: interaction.lease_token,
|
|
5502
|
+
lease_epoch: interaction.lease_epoch,
|
|
5503
|
+
expected_operations: completion.expected_operations ?? expectedOperations(receipts.values()),
|
|
5504
|
+
expected_receipts: completion.expected_receipts ?? expectedReceipts(receipts.values())
|
|
5505
|
+
};
|
|
5506
|
+
try {
|
|
5507
|
+
return await this.api.completeInteraction(interaction.interaction_id, completionInput);
|
|
5508
|
+
} catch (completeError) {
|
|
5509
|
+
let current;
|
|
5510
|
+
try {
|
|
5511
|
+
current = await this.api.getInteraction(interaction.interaction_id);
|
|
5512
|
+
} catch {
|
|
5513
|
+
throw completeError;
|
|
5514
|
+
}
|
|
5515
|
+
if (current.execution_status === "completed") return current;
|
|
5516
|
+
if (current.execution_status !== "active") {
|
|
5517
|
+
throw new InputError(
|
|
5518
|
+
`Interaction terminal state "${current.execution_status}" conflicts with complete`
|
|
5519
|
+
);
|
|
5520
|
+
}
|
|
5521
|
+
return await this.api.completeInteraction(interaction.interaction_id, completionInput);
|
|
5522
|
+
}
|
|
5523
|
+
} finally {
|
|
5524
|
+
this.activeConversationIds.delete(conversation.conversation_id);
|
|
5525
|
+
}
|
|
5526
|
+
}
|
|
5527
|
+
async resolveConversation(strategy) {
|
|
5528
|
+
switch (strategy.mode) {
|
|
5529
|
+
case "resume_by_id":
|
|
5530
|
+
return await this.api.resumeConversation({ conversation_id: strategy.conversationId });
|
|
5531
|
+
case "create_new_generation":
|
|
5532
|
+
return await this.api.createNewConversationGeneration({
|
|
5533
|
+
external_conversation_key: strategy.externalConversationKey,
|
|
5534
|
+
idempotency_key: this.idFactory()
|
|
5535
|
+
});
|
|
5536
|
+
case "one_shot":
|
|
5537
|
+
return await this.api.ensureConversation({
|
|
5538
|
+
external_conversation_key: strategy.externalConversationKey ?? `one-shot-${this.idFactory()}`,
|
|
5539
|
+
idempotency_key: this.idFactory(),
|
|
5540
|
+
one_shot: true
|
|
5541
|
+
});
|
|
5542
|
+
case "ensure_current":
|
|
5543
|
+
return await this.ensureCurrent(strategy.externalConversationKey);
|
|
5544
|
+
}
|
|
5545
|
+
}
|
|
5546
|
+
async ensureCurrent(externalConversationKey) {
|
|
5547
|
+
const pending = this.pendingConversations.get(externalConversationKey);
|
|
5548
|
+
if (pending) return await pending;
|
|
5549
|
+
const request2 = this.api.ensureConversation({
|
|
5550
|
+
external_conversation_key: externalConversationKey,
|
|
5551
|
+
idempotency_key: this.idFactory()
|
|
5552
|
+
});
|
|
5553
|
+
this.pendingConversations.set(externalConversationKey, request2);
|
|
5554
|
+
try {
|
|
5555
|
+
return await request2;
|
|
5556
|
+
} finally {
|
|
5557
|
+
if (this.pendingConversations.get(externalConversationKey) === request2) {
|
|
5558
|
+
this.pendingConversations.delete(externalConversationKey);
|
|
5559
|
+
}
|
|
5560
|
+
}
|
|
5561
|
+
}
|
|
5562
|
+
async runOperation(conversation, interaction, input, execute, bknContext, recordReceipt) {
|
|
5563
|
+
const operationKey = input.operationKey ?? this.idFactory();
|
|
5564
|
+
let current = await this.api.ensureOperation(
|
|
5565
|
+
conversation.conversation_id,
|
|
5566
|
+
interaction.interaction_id,
|
|
5567
|
+
{
|
|
5568
|
+
operation_key: operationKey,
|
|
5569
|
+
tool_name: input.toolName,
|
|
5570
|
+
normalized_input_hash: input.normalizedInputHash,
|
|
5571
|
+
parent_operation_id: input.parentOperationId,
|
|
5572
|
+
causation_event_ids: input.causationEventIds,
|
|
5573
|
+
required: input.required ?? true,
|
|
5574
|
+
lease_token: interaction.lease_token,
|
|
5575
|
+
lease_epoch: interaction.lease_epoch
|
|
5576
|
+
}
|
|
5577
|
+
);
|
|
5578
|
+
const maxAttempts = input.maxAttempts ?? 2;
|
|
5579
|
+
if (!Number.isInteger(maxAttempts) || maxAttempts < 1 || maxAttempts > 10) {
|
|
5580
|
+
throw new InputError("maxAttempts must be an integer between 1 and 10");
|
|
5581
|
+
}
|
|
5582
|
+
while (true) {
|
|
5583
|
+
if (isCompletedReceipt(current.receipt)) {
|
|
5584
|
+
recordReceipt(current.receipt);
|
|
5585
|
+
return { value: void 0, receipt: current.receipt, recovered: true };
|
|
5586
|
+
}
|
|
5587
|
+
if (isFailedReceipt(current.receipt)) {
|
|
5588
|
+
recordReceipt(current.receipt);
|
|
5589
|
+
if (!current.operation.retryable) {
|
|
5590
|
+
throw new OperationFailedError(
|
|
5591
|
+
`Operation "${current.operation.operation_id}" failed and is not retryable`
|
|
5592
|
+
);
|
|
5593
|
+
}
|
|
5594
|
+
if (current.operation.attempt >= maxAttempts) {
|
|
5595
|
+
throw new OperationFailedError(
|
|
5596
|
+
`Operation "${current.operation.operation_id}" reached maximum attempt count ${maxAttempts}`
|
|
5597
|
+
);
|
|
5598
|
+
}
|
|
5599
|
+
current = await this.api.retryOperationAttempt(current.operation.operation_id, {
|
|
5600
|
+
lease_token: interaction.lease_token,
|
|
5601
|
+
lease_epoch: interaction.lease_epoch
|
|
5602
|
+
});
|
|
5603
|
+
continue;
|
|
5604
|
+
}
|
|
5605
|
+
let result;
|
|
5606
|
+
try {
|
|
5607
|
+
result = await execute({
|
|
5608
|
+
context: bknContext(operationKey, input.parentOperationId, input.causationEventIds),
|
|
5609
|
+
operation: current.operation,
|
|
5610
|
+
receipt: current.receipt
|
|
5611
|
+
});
|
|
5612
|
+
} catch (executeError) {
|
|
5613
|
+
const recovered = await this.api.getReceipt(current.receipt.receipt_id);
|
|
5614
|
+
if (!isTerminalReceipt(recovered)) throw executeError;
|
|
5615
|
+
assertSameReceipt(current.receipt, recovered);
|
|
5616
|
+
current = {
|
|
5617
|
+
...current,
|
|
5618
|
+
operation: isFailedReceipt(recovered) ? await this.api.getOperation(current.operation.operation_id) : current.operation,
|
|
5619
|
+
receipt: recovered
|
|
5620
|
+
};
|
|
5621
|
+
continue;
|
|
5622
|
+
}
|
|
5623
|
+
assertSameReceipt(current.receipt, result.receipt);
|
|
5624
|
+
recordReceipt(result.receipt);
|
|
5625
|
+
if (isCompletedReceipt(result.receipt)) {
|
|
5626
|
+
return { ...result, recovered: false };
|
|
5627
|
+
}
|
|
5628
|
+
current = {
|
|
5629
|
+
...current,
|
|
5630
|
+
operation: isFailedReceipt(result.receipt) ? await this.api.getOperation(current.operation.operation_id) : current.operation,
|
|
5631
|
+
receipt: result.receipt
|
|
5632
|
+
};
|
|
5633
|
+
if (!isTerminalReceipt(result.receipt)) {
|
|
5634
|
+
throw new InputError(
|
|
5635
|
+
`Operation "${current.operation.operation_id}" returned a pending receipt`
|
|
5636
|
+
);
|
|
5637
|
+
}
|
|
5638
|
+
}
|
|
5639
|
+
}
|
|
5640
|
+
};
|
|
5641
|
+
function assertSafeStrategy(strategy) {
|
|
5642
|
+
if (!strategy || typeof strategy !== "object") {
|
|
5643
|
+
throw new InputError("A conversation strategy is required");
|
|
5644
|
+
}
|
|
5645
|
+
for (const field of FORBIDDEN_INPUT_FIELDS2) {
|
|
5646
|
+
if (field in strategy) throw new InputError(`Lifecycle input field "${field}" is not allowed`);
|
|
5647
|
+
}
|
|
5648
|
+
}
|
|
5649
|
+
function expectedOperations(receipts) {
|
|
5650
|
+
const operations = /* @__PURE__ */ new Map();
|
|
5651
|
+
for (const receipt of receipts) {
|
|
5652
|
+
operations.set(
|
|
5653
|
+
receipt.operation_id,
|
|
5654
|
+
(operations.get(receipt.operation_id) ?? false) || receipt.required
|
|
5655
|
+
);
|
|
5656
|
+
}
|
|
5657
|
+
return [...operations].map(([operation_id, required]) => ({ operation_id, required }));
|
|
5658
|
+
}
|
|
5659
|
+
function expectedReceipts(receipts) {
|
|
5660
|
+
return [...receipts].map((receipt) => ({
|
|
5661
|
+
receipt_id: receipt.receipt_id,
|
|
5662
|
+
required: receipt.required
|
|
5663
|
+
}));
|
|
5664
|
+
}
|
|
5665
|
+
function isTerminalReceipt(receipt) {
|
|
5666
|
+
return receipt.receipt_status !== "pending";
|
|
5667
|
+
}
|
|
5668
|
+
function isCompletedReceipt(receipt) {
|
|
5669
|
+
return receipt.receipt_status === "completed";
|
|
5670
|
+
}
|
|
5671
|
+
function isFailedReceipt(receipt) {
|
|
5672
|
+
return receipt.receipt_status === "failed";
|
|
5673
|
+
}
|
|
5674
|
+
function assertSameReceipt(expected, actual) {
|
|
5675
|
+
if (actual.receipt_id !== expected.receipt_id || actual.operation_id !== expected.operation_id || actual.attempt !== expected.attempt || actual.operation_key !== expected.operation_key || actual.normalized_input_hash !== expected.normalized_input_hash) {
|
|
5676
|
+
throw new InputError("Recovered receipt does not match the registered operation attempt");
|
|
5677
|
+
}
|
|
5678
|
+
}
|
|
5679
|
+
|
|
4455
5680
|
// src/api/trace.ts
|
|
4456
5681
|
var SEARCH = "/api/agent-observability/v1/traces/_search";
|
|
5682
|
+
var BUSINESS_PROVENANCE = "/api/agent-observability/v1/business-provenance";
|
|
5683
|
+
var REQUESTS = `${BUSINESS_PROVENANCE}/requests`;
|
|
5684
|
+
var INTERACTIONS = `${BUSINESS_PROVENANCE}/interactions`;
|
|
5685
|
+
var TRACES = "/api/agent-observability/v1/traces";
|
|
4457
5686
|
function isoToNanos(iso) {
|
|
4458
5687
|
const ms = Date.parse(iso);
|
|
4459
5688
|
if (Number.isNaN(ms)) return void 0;
|
|
@@ -4495,6 +5724,9 @@ async function getRawSpansByConversation(ctx, conversationId, opts = {}) {
|
|
|
4495
5724
|
function traceSearch(ctx, body) {
|
|
4496
5725
|
return request(ctx, SEARCH, { method: "POST", body });
|
|
4497
5726
|
}
|
|
5727
|
+
function getTraceGraph(ctx, traceId) {
|
|
5728
|
+
return request(ctx, `${TRACES}/${encodeURIComponent(traceId)}/trace-graph`);
|
|
5729
|
+
}
|
|
4498
5730
|
async function getSpansByConversation(ctx, conversationId, opts = {}) {
|
|
4499
5731
|
const agg = await request(ctx, SEARCH, {
|
|
4500
5732
|
method: "POST",
|
|
@@ -4580,10 +5812,10 @@ async function judgeJson(prompt, opts = {}) {
|
|
|
4580
5812
|
child.stdout.on("data", (d) => {
|
|
4581
5813
|
out += d;
|
|
4582
5814
|
});
|
|
4583
|
-
child.on("error", (
|
|
5815
|
+
child.on("error", (err2) => {
|
|
4584
5816
|
clearTimeout(killer);
|
|
4585
5817
|
reject(
|
|
4586
|
-
|
|
5818
|
+
err2.code === "ENOENT" ? new ClaudeJudgeError("`claude` not found on PATH", "not_available") : err2
|
|
4587
5819
|
);
|
|
4588
5820
|
});
|
|
4589
5821
|
child.on("close", (code) => {
|
|
@@ -4593,8 +5825,8 @@ async function judgeJson(prompt, opts = {}) {
|
|
|
4593
5825
|
if (code !== 0) return reject(new ClaudeJudgeError(`claude exited ${code}`, "exit"));
|
|
4594
5826
|
resolve7(out);
|
|
4595
5827
|
});
|
|
4596
|
-
child.stdin.on("error", (
|
|
4597
|
-
if (
|
|
5828
|
+
child.stdin.on("error", (err2) => {
|
|
5829
|
+
if (err2.code !== "EPIPE") reject(err2);
|
|
4598
5830
|
});
|
|
4599
5831
|
child.stdin.end(prompt);
|
|
4600
5832
|
});
|
|
@@ -5144,6 +6376,747 @@ async function runEvalSet(agentId, cases, deps) {
|
|
|
5144
6376
|
};
|
|
5145
6377
|
}
|
|
5146
6378
|
|
|
6379
|
+
// src/bkn-trace/fixture-validate.ts
|
|
6380
|
+
import { readFileSync as readFileSync4, readdirSync as readdirSync5, statSync as statSync4 } from "fs";
|
|
6381
|
+
import { join as join4 } from "path";
|
|
6382
|
+
var CONTRACT_VERSIONS = /* @__PURE__ */ new Set(["1.0.0", "2.0.0", "2.1.0"]);
|
|
6383
|
+
var BUSINESS_CONTRACT_VERSION = "2.1.0";
|
|
6384
|
+
var TRACEPARENT_RE2 = /^00-([0-9a-f]{32})-([0-9a-f]{16})-[0-9a-f]{2}$/;
|
|
6385
|
+
var REQUEST_ID_RE2 = /^req_[0-9A-Za-z_.-]+$/;
|
|
6386
|
+
var RFC3339_NANO_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?Z$/;
|
|
6387
|
+
var ALLOWED_BAGGAGE2 = /* @__PURE__ */ new Set(["bkn.account.type", "bkn.runtime.env"]);
|
|
6388
|
+
var REQUIRED_LOG_FIELDS = [
|
|
6389
|
+
"trace_id",
|
|
6390
|
+
"span_id",
|
|
6391
|
+
"bkn.request.id",
|
|
6392
|
+
"bkn.module.name",
|
|
6393
|
+
"bkn.operation.name",
|
|
6394
|
+
"bkn.status",
|
|
6395
|
+
"bkn.timestamp",
|
|
6396
|
+
"bkn.trace.schema.version"
|
|
6397
|
+
];
|
|
6398
|
+
var REQUIRED_SPAN_FIELDS = [
|
|
6399
|
+
"span_id",
|
|
6400
|
+
"name",
|
|
6401
|
+
"bkn.module.name",
|
|
6402
|
+
"bkn.operation.name",
|
|
6403
|
+
"bkn.status",
|
|
6404
|
+
"bkn.timestamp"
|
|
6405
|
+
];
|
|
6406
|
+
var REQUIRED_EVENT_FIELDS = [
|
|
6407
|
+
"trace_id",
|
|
6408
|
+
"span_id",
|
|
6409
|
+
"bkn.request.id",
|
|
6410
|
+
"bkn.operation.name",
|
|
6411
|
+
"event_id",
|
|
6412
|
+
"event_type",
|
|
6413
|
+
"bkn.trace.schema.version",
|
|
6414
|
+
"observed_at",
|
|
6415
|
+
"emitted_at",
|
|
6416
|
+
"producer_module",
|
|
6417
|
+
"payload"
|
|
6418
|
+
];
|
|
6419
|
+
var SENSITIVE_PATTERNS = [
|
|
6420
|
+
/authorization/i,
|
|
6421
|
+
/bearer\s+[A-Za-z0-9._-]+/i,
|
|
6422
|
+
/access[_-]?token/i,
|
|
6423
|
+
/api[_-]?key/i,
|
|
6424
|
+
/cookie/i,
|
|
6425
|
+
/\bselect\s+.+\s+from\b/is,
|
|
6426
|
+
/prompt\s*[:=]/i,
|
|
6427
|
+
/https?:\/\/[^\s"']+/i,
|
|
6428
|
+
/[\w.+-]+@[\w.-]+\.[A-Za-z]{2,}/
|
|
6429
|
+
];
|
|
6430
|
+
var BUSINESS_EVENT_TYPES = /* @__PURE__ */ new Set([
|
|
6431
|
+
"agent.interaction.started",
|
|
6432
|
+
"retrieval.completed",
|
|
6433
|
+
"knowledge.read.observed",
|
|
6434
|
+
"data.query.observed",
|
|
6435
|
+
"model.call.observed",
|
|
6436
|
+
"tool.called",
|
|
6437
|
+
"tool.result.observed",
|
|
6438
|
+
"claim.created",
|
|
6439
|
+
"evidence.refs.created",
|
|
6440
|
+
"business.refs.resolved",
|
|
6441
|
+
"action.recommended",
|
|
6442
|
+
"action.approval_requested",
|
|
6443
|
+
"action.approved",
|
|
6444
|
+
"action.rejected",
|
|
6445
|
+
"action.executed",
|
|
6446
|
+
"action.result_recorded"
|
|
6447
|
+
]);
|
|
6448
|
+
var CLAIM_EVENT_TYPES = /* @__PURE__ */ new Set([
|
|
6449
|
+
"claim.created",
|
|
6450
|
+
"evidence.refs.created",
|
|
6451
|
+
"business.refs.resolved",
|
|
6452
|
+
"action.recommended",
|
|
6453
|
+
"action.approval_requested",
|
|
6454
|
+
"action.approved",
|
|
6455
|
+
"action.rejected",
|
|
6456
|
+
"action.executed",
|
|
6457
|
+
"action.result_recorded"
|
|
6458
|
+
]);
|
|
6459
|
+
var ACTION_TRANSITIONS = {
|
|
6460
|
+
recommended: /* @__PURE__ */ new Set(["approval_requested"]),
|
|
6461
|
+
approval_requested: /* @__PURE__ */ new Set(["approved", "rejected"]),
|
|
6462
|
+
approved: /* @__PURE__ */ new Set(["executed"]),
|
|
6463
|
+
executed: /* @__PURE__ */ new Set(["result_recorded"]),
|
|
6464
|
+
rejected: /* @__PURE__ */ new Set(),
|
|
6465
|
+
result_recorded: /* @__PURE__ */ new Set()
|
|
6466
|
+
};
|
|
6467
|
+
var ACTION_STATE_BY_EVENT = {
|
|
6468
|
+
"action.recommended": "recommended",
|
|
6469
|
+
"action.approval_requested": "approval_requested",
|
|
6470
|
+
"action.approved": "approved",
|
|
6471
|
+
"action.rejected": "rejected",
|
|
6472
|
+
"action.executed": "executed",
|
|
6473
|
+
"action.result_recorded": "result_recorded"
|
|
6474
|
+
};
|
|
6475
|
+
var EVENT_PAYLOAD_FIELDS = {
|
|
6476
|
+
"agent.interaction.started": /* @__PURE__ */ new Set(["intent_hash", "mode", "agent_id", "app_ref"]),
|
|
6477
|
+
"retrieval.completed": /* @__PURE__ */ new Set([
|
|
6478
|
+
"query_hash",
|
|
6479
|
+
"candidate_count",
|
|
6480
|
+
"truncated",
|
|
6481
|
+
"version_status",
|
|
6482
|
+
"source_refs"
|
|
6483
|
+
]),
|
|
6484
|
+
"knowledge.read.observed": /* @__PURE__ */ new Set([
|
|
6485
|
+
"kn_id",
|
|
6486
|
+
"read_kind",
|
|
6487
|
+
"version_status",
|
|
6488
|
+
"schema_version",
|
|
6489
|
+
"business_refs"
|
|
6490
|
+
]),
|
|
6491
|
+
"data.query.observed": /* @__PURE__ */ new Set([
|
|
6492
|
+
"query_hash",
|
|
6493
|
+
"query_type",
|
|
6494
|
+
"row_count",
|
|
6495
|
+
"truncated",
|
|
6496
|
+
"as_of",
|
|
6497
|
+
"version_status",
|
|
6498
|
+
"resource_refs",
|
|
6499
|
+
"field_refs"
|
|
6500
|
+
]),
|
|
6501
|
+
"model.call.observed": /* @__PURE__ */ new Set([
|
|
6502
|
+
"model_name",
|
|
6503
|
+
"model_provider",
|
|
6504
|
+
"status",
|
|
6505
|
+
"input_token_count",
|
|
6506
|
+
"output_token_count",
|
|
6507
|
+
"prompt_hash",
|
|
6508
|
+
"output_hash",
|
|
6509
|
+
"error_category",
|
|
6510
|
+
"error_hash"
|
|
6511
|
+
]),
|
|
6512
|
+
"tool.called": /* @__PURE__ */ new Set(["tool_id", "tool_name", "args_hash", "visibility", "version_status"]),
|
|
6513
|
+
"tool.result.observed": /* @__PURE__ */ new Set([
|
|
6514
|
+
"tool_id",
|
|
6515
|
+
"tool_name",
|
|
6516
|
+
"status",
|
|
6517
|
+
"result_hash",
|
|
6518
|
+
"result_length",
|
|
6519
|
+
"result_count",
|
|
6520
|
+
"error_hash",
|
|
6521
|
+
"error_category",
|
|
6522
|
+
"visibility",
|
|
6523
|
+
"version_status"
|
|
6524
|
+
]),
|
|
6525
|
+
"claim.created": /* @__PURE__ */ new Set([
|
|
6526
|
+
"claim_id",
|
|
6527
|
+
"claim_type",
|
|
6528
|
+
"claim_hash",
|
|
6529
|
+
"source_event_ids",
|
|
6530
|
+
"operation_ids",
|
|
6531
|
+
"visibility",
|
|
6532
|
+
"version_status"
|
|
6533
|
+
]),
|
|
6534
|
+
"evidence.refs.created": /* @__PURE__ */ new Set(["claim_id", "evidence_refs"]),
|
|
6535
|
+
"business.refs.resolved": /* @__PURE__ */ new Set(["claim_id", "resolver_status", "business_refs"]),
|
|
6536
|
+
"action.recommended": /* @__PURE__ */ new Set([
|
|
6537
|
+
"action_instance_id",
|
|
6538
|
+
"action_type",
|
|
6539
|
+
"target_refs",
|
|
6540
|
+
"reason_hash",
|
|
6541
|
+
"status"
|
|
6542
|
+
]),
|
|
6543
|
+
"action.approval_requested": /* @__PURE__ */ new Set(["action_instance_id", "policy_ref", "status"]),
|
|
6544
|
+
"action.approved": /* @__PURE__ */ new Set(["action_instance_id", "actor_ref", "policy_decision_ref", "status"]),
|
|
6545
|
+
"action.rejected": /* @__PURE__ */ new Set(["action_instance_id", "actor_ref", "policy_decision_ref", "status"]),
|
|
6546
|
+
"action.executed": /* @__PURE__ */ new Set([
|
|
6547
|
+
"action_instance_id",
|
|
6548
|
+
"invocation_ref",
|
|
6549
|
+
"tool_ref",
|
|
6550
|
+
"status",
|
|
6551
|
+
"error_category",
|
|
6552
|
+
"error_hash"
|
|
6553
|
+
]),
|
|
6554
|
+
"action.result_recorded": /* @__PURE__ */ new Set([
|
|
6555
|
+
"action_instance_id",
|
|
6556
|
+
"result_hash",
|
|
6557
|
+
"artifact_ref",
|
|
6558
|
+
"task_ref",
|
|
6559
|
+
"status"
|
|
6560
|
+
])
|
|
6561
|
+
};
|
|
6562
|
+
var REFERENCE_FIELDS = /* @__PURE__ */ new Set([
|
|
6563
|
+
"ref_id",
|
|
6564
|
+
"ref_type",
|
|
6565
|
+
"source_system",
|
|
6566
|
+
"validity",
|
|
6567
|
+
"version_status",
|
|
6568
|
+
"visibility",
|
|
6569
|
+
"summary_hash"
|
|
6570
|
+
]);
|
|
6571
|
+
var FORBIDDEN_RAW_KEYS = /* @__PURE__ */ new Set([
|
|
6572
|
+
"authorization",
|
|
6573
|
+
"cookie",
|
|
6574
|
+
"access_token",
|
|
6575
|
+
"refresh_token",
|
|
6576
|
+
"id_token",
|
|
6577
|
+
"api_key",
|
|
6578
|
+
"password",
|
|
6579
|
+
"private_key",
|
|
6580
|
+
"prompt",
|
|
6581
|
+
"user_question",
|
|
6582
|
+
"approval_comment",
|
|
6583
|
+
"sql",
|
|
6584
|
+
"query_params",
|
|
6585
|
+
"rows"
|
|
6586
|
+
]);
|
|
6587
|
+
function err(code, path, message) {
|
|
6588
|
+
return { code, path, message };
|
|
6589
|
+
}
|
|
6590
|
+
function asRecord(value) {
|
|
6591
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
6592
|
+
}
|
|
6593
|
+
function jsonFiles(path) {
|
|
6594
|
+
const stat = statSync4(path);
|
|
6595
|
+
if (stat.isFile()) return [path];
|
|
6596
|
+
return readdirSync5(path).filter((name) => name.endsWith(".json")).sort().map((name) => join4(path, name));
|
|
6597
|
+
}
|
|
6598
|
+
function validTraceparent(value) {
|
|
6599
|
+
if (typeof value !== "string") return false;
|
|
6600
|
+
const match = TRACEPARENT_RE2.exec(value);
|
|
6601
|
+
if (!match) return false;
|
|
6602
|
+
const [, traceId, spanId] = match;
|
|
6603
|
+
return traceId !== "0".repeat(32) && spanId !== "0".repeat(16);
|
|
6604
|
+
}
|
|
6605
|
+
function checkRequired(item, fields, basePath, errors) {
|
|
6606
|
+
for (const field of fields) {
|
|
6607
|
+
if (item[field] === void 0 || item[field] === "") {
|
|
6608
|
+
errors.push(
|
|
6609
|
+
err(
|
|
6610
|
+
"BKN_TRACE_REQUIRED_FIELD_MISSING",
|
|
6611
|
+
`${basePath}.${field}`,
|
|
6612
|
+
`missing required field ${field}`
|
|
6613
|
+
)
|
|
6614
|
+
);
|
|
6615
|
+
}
|
|
6616
|
+
}
|
|
6617
|
+
}
|
|
6618
|
+
function checkTimestamp(value, path, errors) {
|
|
6619
|
+
if (typeof value !== "string" || !RFC3339_NANO_RE.test(value)) {
|
|
6620
|
+
errors.push(err("BKN_TRACE_INVALID_TIMESTAMP", path, "timestamp must be UTC RFC3339Nano"));
|
|
6621
|
+
}
|
|
6622
|
+
}
|
|
6623
|
+
function checkSensitive(value, path, errors) {
|
|
6624
|
+
if (Array.isArray(value)) {
|
|
6625
|
+
value.forEach((child, index) => checkSensitive(child, `${path}[${index}]`, errors));
|
|
6626
|
+
return;
|
|
6627
|
+
}
|
|
6628
|
+
if (value && typeof value === "object") {
|
|
6629
|
+
for (const [key, child] of Object.entries(value)) {
|
|
6630
|
+
if (FORBIDDEN_RAW_KEYS.has(key.toLowerCase())) {
|
|
6631
|
+
errors.push(
|
|
6632
|
+
err(
|
|
6633
|
+
"BKN_TRACE_SENSITIVE_VALUE_LEAKED",
|
|
6634
|
+
`${path}.${key}`,
|
|
6635
|
+
"raw sensitive field is forbidden"
|
|
6636
|
+
)
|
|
6637
|
+
);
|
|
6638
|
+
}
|
|
6639
|
+
if (key.endsWith("_hash") && child !== "" && (typeof child !== "string" || !/^sha256:[0-9a-f]{64}$/.test(child))) {
|
|
6640
|
+
errors.push(
|
|
6641
|
+
err("BKN_TRACE_REQUIRED_FIELD_MISSING", `${path}.${key}`, `${key} must be a sha256 hash`)
|
|
6642
|
+
);
|
|
6643
|
+
}
|
|
6644
|
+
checkSensitive(child, `${path}.${key}`, errors);
|
|
6645
|
+
}
|
|
6646
|
+
return;
|
|
6647
|
+
}
|
|
6648
|
+
if (typeof value !== "string") return;
|
|
6649
|
+
if (SENSITIVE_PATTERNS.some((pattern) => pattern.test(value))) {
|
|
6650
|
+
errors.push(
|
|
6651
|
+
err(
|
|
6652
|
+
"BKN_TRACE_SENSITIVE_VALUE_LEAKED",
|
|
6653
|
+
path,
|
|
6654
|
+
"sensitive value must be redacted, hashed, or referenced"
|
|
6655
|
+
)
|
|
6656
|
+
);
|
|
6657
|
+
}
|
|
6658
|
+
}
|
|
6659
|
+
function validateFixture(data) {
|
|
6660
|
+
const root = asRecord(data);
|
|
6661
|
+
const errors = [];
|
|
6662
|
+
const fixtureId = typeof root.fixture_id === "string" ? root.fixture_id : "<unknown>";
|
|
6663
|
+
const contractVersion = typeof root["bkn.trace.schema.version"] === "string" ? root["bkn.trace.schema.version"] : null;
|
|
6664
|
+
if (!contractVersion) {
|
|
6665
|
+
errors.push(
|
|
6666
|
+
err(
|
|
6667
|
+
"BKN_TRACE_SCHEMA_VERSION_MISSING",
|
|
6668
|
+
"$.bkn.trace.schema.version",
|
|
6669
|
+
"missing contract version"
|
|
6670
|
+
)
|
|
6671
|
+
);
|
|
6672
|
+
} else if (!CONTRACT_VERSIONS.has(contractVersion)) {
|
|
6673
|
+
errors.push(
|
|
6674
|
+
err(
|
|
6675
|
+
"BKN_TRACE_SCHEMA_VERSION_UNSUPPORTED",
|
|
6676
|
+
"$.bkn.trace.schema.version",
|
|
6677
|
+
`unsupported contract version ${contractVersion}`
|
|
6678
|
+
)
|
|
6679
|
+
);
|
|
6680
|
+
}
|
|
6681
|
+
const trace2 = asRecord(root.trace);
|
|
6682
|
+
const traceId = trace2.trace_id;
|
|
6683
|
+
const requestId = trace2["bkn.request.id"];
|
|
6684
|
+
if (typeof traceId !== "string" || !/^[0-9a-f]{32}$/.test(traceId)) {
|
|
6685
|
+
errors.push(
|
|
6686
|
+
err("BKN_TRACE_REQUIRED_FIELD_MISSING", "$.trace.trace_id", "missing valid trace id")
|
|
6687
|
+
);
|
|
6688
|
+
}
|
|
6689
|
+
if (typeof requestId !== "string" || !REQUEST_ID_RE2.test(requestId)) {
|
|
6690
|
+
errors.push(
|
|
6691
|
+
err(
|
|
6692
|
+
"BKN_TRACE_REQUIRED_FIELD_MISSING",
|
|
6693
|
+
"$.trace.bkn.request.id",
|
|
6694
|
+
"missing valid bkn.request.id"
|
|
6695
|
+
)
|
|
6696
|
+
);
|
|
6697
|
+
}
|
|
6698
|
+
if (!validTraceparent(trace2.traceparent)) {
|
|
6699
|
+
errors.push(err("BKN_TRACE_INVALID_TRACEPARENT", "$.trace.traceparent", "invalid traceparent"));
|
|
6700
|
+
}
|
|
6701
|
+
const spans = Array.isArray(root.spans) ? root.spans : [];
|
|
6702
|
+
const spanIds = /* @__PURE__ */ new Set();
|
|
6703
|
+
spans.forEach((item, index) => {
|
|
6704
|
+
const span = asRecord(item);
|
|
6705
|
+
checkRequired(span, REQUIRED_SPAN_FIELDS, `$.spans[${index}]`, errors);
|
|
6706
|
+
checkTimestamp(span["bkn.timestamp"], `$.spans[${index}].bkn.timestamp`, errors);
|
|
6707
|
+
if (typeof span.span_id === "string") spanIds.add(span.span_id);
|
|
6708
|
+
const parent = span.parent_span_id;
|
|
6709
|
+
if (parent !== null && parent !== void 0 && !spanIds.has(String(parent))) {
|
|
6710
|
+
errors.push(
|
|
6711
|
+
err(
|
|
6712
|
+
"BKN_TRACE_ORPHAN_SPAN",
|
|
6713
|
+
`$.spans[${index}].parent_span_id`,
|
|
6714
|
+
"parent span must appear before child span or be represented as a link"
|
|
6715
|
+
)
|
|
6716
|
+
);
|
|
6717
|
+
}
|
|
6718
|
+
});
|
|
6719
|
+
if (spans.length === 0) {
|
|
6720
|
+
errors.push(err("BKN_TRACE_REQUIRED_FIELD_MISSING", "$.spans", "at least one span required"));
|
|
6721
|
+
}
|
|
6722
|
+
const logs = Array.isArray(root.logs) ? root.logs : [];
|
|
6723
|
+
logs.forEach((item, index) => {
|
|
6724
|
+
const log = asRecord(item);
|
|
6725
|
+
checkRequired(log, REQUIRED_LOG_FIELDS, `$.logs[${index}]`, errors);
|
|
6726
|
+
checkTimestamp(log["bkn.timestamp"], `$.logs[${index}].bkn.timestamp`, errors);
|
|
6727
|
+
if (log.trace_id !== traceId || log["bkn.request.id"] !== requestId) {
|
|
6728
|
+
errors.push(
|
|
6729
|
+
err("BKN_TRACE_JOIN_FAILED", `$.logs[${index}]`, "log cannot join trace/request")
|
|
6730
|
+
);
|
|
6731
|
+
}
|
|
6732
|
+
if (!spanIds.has(String(log.span_id))) {
|
|
6733
|
+
errors.push(
|
|
6734
|
+
err("BKN_TRACE_JOIN_FAILED", `$.logs[${index}].span_id`, "log span_id not found")
|
|
6735
|
+
);
|
|
6736
|
+
}
|
|
6737
|
+
});
|
|
6738
|
+
const events = Array.isArray(root.events) ? root.events : [];
|
|
6739
|
+
const eventIds = /* @__PURE__ */ new Set();
|
|
6740
|
+
const knownEventIds = /* @__PURE__ */ new Set();
|
|
6741
|
+
const knownOperationIds = /* @__PURE__ */ new Set();
|
|
6742
|
+
const knownClaimIds = /* @__PURE__ */ new Set();
|
|
6743
|
+
const actionStates = /* @__PURE__ */ new Map();
|
|
6744
|
+
events.forEach((item, index) => {
|
|
6745
|
+
const event = asRecord(item);
|
|
6746
|
+
const eventPath = `$.events[${index}]`;
|
|
6747
|
+
checkRequired(event, REQUIRED_EVENT_FIELDS, `$.events[${index}]`, errors);
|
|
6748
|
+
checkTimestamp(event.observed_at, `$.events[${index}].observed_at`, errors);
|
|
6749
|
+
checkTimestamp(event.emitted_at, `$.events[${index}].emitted_at`, errors);
|
|
6750
|
+
if (event.trace_id !== traceId || event["bkn.request.id"] !== requestId) {
|
|
6751
|
+
errors.push(
|
|
6752
|
+
err("BKN_TRACE_JOIN_FAILED", `$.events[${index}]`, "event cannot join trace/request")
|
|
6753
|
+
);
|
|
6754
|
+
}
|
|
6755
|
+
if (!spanIds.has(String(event.span_id))) {
|
|
6756
|
+
errors.push(
|
|
6757
|
+
err("BKN_TRACE_JOIN_FAILED", `$.events[${index}].span_id`, "event span_id not found")
|
|
6758
|
+
);
|
|
6759
|
+
}
|
|
6760
|
+
if (typeof event.event_id === "string") {
|
|
6761
|
+
if (eventIds.has(event.event_id)) {
|
|
6762
|
+
errors.push(
|
|
6763
|
+
err("BKN_TRACE_EVENT_ID_CONFLICT", `${eventPath}.event_id`, "duplicate event_id")
|
|
6764
|
+
);
|
|
6765
|
+
}
|
|
6766
|
+
eventIds.add(event.event_id);
|
|
6767
|
+
}
|
|
6768
|
+
if (contractVersion !== BUSINESS_CONTRACT_VERSION) return;
|
|
6769
|
+
if (event["bkn.trace.schema.version"] !== contractVersion) {
|
|
6770
|
+
errors.push(
|
|
6771
|
+
err(
|
|
6772
|
+
"BKN_TRACE_SCHEMA_VERSION_UNSUPPORTED",
|
|
6773
|
+
`${eventPath}.bkn.trace.schema.version`,
|
|
6774
|
+
"event contract version must match the fixture envelope"
|
|
6775
|
+
)
|
|
6776
|
+
);
|
|
6777
|
+
}
|
|
6778
|
+
validateBusinessEvent(
|
|
6779
|
+
event,
|
|
6780
|
+
eventPath,
|
|
6781
|
+
knownEventIds,
|
|
6782
|
+
knownOperationIds,
|
|
6783
|
+
knownClaimIds,
|
|
6784
|
+
actionStates,
|
|
6785
|
+
errors
|
|
6786
|
+
);
|
|
6787
|
+
if (typeof event.event_id === "string") knownEventIds.add(event.event_id);
|
|
6788
|
+
if (typeof event.operation_id === "string") knownOperationIds.add(event.operation_id);
|
|
6789
|
+
if (event.event_type === "claim.created" && typeof event.claim_id === "string") {
|
|
6790
|
+
knownClaimIds.add(event.claim_id);
|
|
6791
|
+
}
|
|
6792
|
+
});
|
|
6793
|
+
if (contractVersion !== "1.0.0" && events.length === 0) {
|
|
6794
|
+
errors.push(err("BKN_TRACE_REQUIRED_FIELD_MISSING", "$.events", "at least one event required"));
|
|
6795
|
+
}
|
|
6796
|
+
const baggage = asRecord(root.baggage);
|
|
6797
|
+
for (const key of Object.keys(baggage)) {
|
|
6798
|
+
if (!ALLOWED_BAGGAGE2.has(key)) {
|
|
6799
|
+
errors.push(
|
|
6800
|
+
err(
|
|
6801
|
+
"BKN_TRACE_BAGGAGE_FORBIDDEN_FIELD",
|
|
6802
|
+
`$.baggage.${key}`,
|
|
6803
|
+
`baggage field ${key} is forbidden`
|
|
6804
|
+
)
|
|
6805
|
+
);
|
|
6806
|
+
}
|
|
6807
|
+
}
|
|
6808
|
+
checkSensitive(root, "$", errors);
|
|
6809
|
+
const result = errors.length > 0 ? "fail" : "pass";
|
|
6810
|
+
const expectedResult = root.expected_result === "pass" || root.expected_result === "fail" ? root.expected_result : null;
|
|
6811
|
+
return {
|
|
6812
|
+
fixtureId,
|
|
6813
|
+
result,
|
|
6814
|
+
contractVersion,
|
|
6815
|
+
errors,
|
|
6816
|
+
warnings: [],
|
|
6817
|
+
expectedResult,
|
|
6818
|
+
expectationMatched: expectedResult === null ? result === "pass" : expectedResult === result
|
|
6819
|
+
};
|
|
6820
|
+
}
|
|
6821
|
+
function validateBusinessEvent(event, path, knownEventIds, knownOperationIds, knownClaimIds, actionStates, errors) {
|
|
6822
|
+
const eventType = typeof event.event_type === "string" ? event.event_type : "";
|
|
6823
|
+
if (!BUSINESS_EVENT_TYPES.has(eventType)) {
|
|
6824
|
+
errors.push(
|
|
6825
|
+
err(
|
|
6826
|
+
"BKN_TRACE_EVENT_TYPE_UNSUPPORTED",
|
|
6827
|
+
`${path}.event_type`,
|
|
6828
|
+
`unsupported event ${eventType}`
|
|
6829
|
+
)
|
|
6830
|
+
);
|
|
6831
|
+
return;
|
|
6832
|
+
}
|
|
6833
|
+
checkRequired(event, ["interaction_id"], path, errors);
|
|
6834
|
+
if (eventType !== "agent.interaction.started" && eventType !== "claim.created") {
|
|
6835
|
+
checkRequired(event, ["operation_id"], path, errors);
|
|
6836
|
+
}
|
|
6837
|
+
if (eventType !== "agent.interaction.started") {
|
|
6838
|
+
checkRequired(event, ["causation_event_id"], path, errors);
|
|
6839
|
+
if (typeof event.causation_event_id === "string" && !knownEventIds.has(event.causation_event_id)) {
|
|
6840
|
+
errors.push(
|
|
6841
|
+
err(
|
|
6842
|
+
"BKN_TRACE_CAUSATION_INVALID",
|
|
6843
|
+
`${path}.causation_event_id`,
|
|
6844
|
+
"causation_event_id must reference an earlier event"
|
|
6845
|
+
)
|
|
6846
|
+
);
|
|
6847
|
+
}
|
|
6848
|
+
}
|
|
6849
|
+
if (CLAIM_EVENT_TYPES.has(eventType)) {
|
|
6850
|
+
checkRequired(event, ["claim_id"], path, errors);
|
|
6851
|
+
if (eventType !== "claim.created" && typeof event.claim_id === "string" && !knownClaimIds.has(event.claim_id)) {
|
|
6852
|
+
errors.push(
|
|
6853
|
+
err(
|
|
6854
|
+
"BKN_TRACE_UNKNOWN_CLAIM_ID",
|
|
6855
|
+
`${path}.claim_id`,
|
|
6856
|
+
"event must reference an earlier claim"
|
|
6857
|
+
)
|
|
6858
|
+
);
|
|
6859
|
+
}
|
|
6860
|
+
}
|
|
6861
|
+
const payload = asRecord(event.payload);
|
|
6862
|
+
checkAllowedKeys(
|
|
6863
|
+
payload,
|
|
6864
|
+
EVENT_PAYLOAD_FIELDS[eventType] ?? /* @__PURE__ */ new Set(),
|
|
6865
|
+
`${path}.payload`,
|
|
6866
|
+
errors
|
|
6867
|
+
);
|
|
6868
|
+
if (eventType === "agent.interaction.started") {
|
|
6869
|
+
checkRequired(payload, ["intent_hash", "mode"], `${path}.payload`, errors);
|
|
6870
|
+
checkOneOf(payload, ["agent_id", "app_ref"], `${path}.payload`, errors);
|
|
6871
|
+
}
|
|
6872
|
+
if (eventType === "retrieval.completed") {
|
|
6873
|
+
checkRequired(
|
|
6874
|
+
payload,
|
|
6875
|
+
["query_hash", "candidate_count", "truncated"],
|
|
6876
|
+
`${path}.payload`,
|
|
6877
|
+
errors
|
|
6878
|
+
);
|
|
6879
|
+
}
|
|
6880
|
+
if (eventType === "knowledge.read.observed") {
|
|
6881
|
+
checkRequired(payload, ["kn_id", "read_kind", "version_status"], `${path}.payload`, errors);
|
|
6882
|
+
}
|
|
6883
|
+
if (eventType === "data.query.observed") {
|
|
6884
|
+
checkRequired(payload, ["query_hash", "query_type", "row_count"], `${path}.payload`, errors);
|
|
6885
|
+
}
|
|
6886
|
+
if (eventType === "model.call.observed") {
|
|
6887
|
+
checkRequired(
|
|
6888
|
+
payload,
|
|
6889
|
+
[
|
|
6890
|
+
"model_name",
|
|
6891
|
+
"model_provider",
|
|
6892
|
+
"status",
|
|
6893
|
+
"input_token_count",
|
|
6894
|
+
"output_token_count",
|
|
6895
|
+
"prompt_hash",
|
|
6896
|
+
"output_hash"
|
|
6897
|
+
],
|
|
6898
|
+
`${path}.payload`,
|
|
6899
|
+
errors
|
|
6900
|
+
);
|
|
6901
|
+
if (payload.status === "error") {
|
|
6902
|
+
checkRequired(payload, ["error_category", "error_hash"], `${path}.payload`, errors);
|
|
6903
|
+
}
|
|
6904
|
+
}
|
|
6905
|
+
if (eventType === "claim.created") {
|
|
6906
|
+
checkRequired(
|
|
6907
|
+
payload,
|
|
6908
|
+
[
|
|
6909
|
+
"claim_id",
|
|
6910
|
+
"claim_type",
|
|
6911
|
+
"claim_hash",
|
|
6912
|
+
"source_event_ids",
|
|
6913
|
+
"operation_ids",
|
|
6914
|
+
"visibility",
|
|
6915
|
+
"version_status"
|
|
6916
|
+
],
|
|
6917
|
+
`${path}.payload`,
|
|
6918
|
+
errors
|
|
6919
|
+
);
|
|
6920
|
+
checkNonEmptyArray(payload, "source_event_ids", `${path}.payload`, errors);
|
|
6921
|
+
checkNonEmptyArray(payload, "operation_ids", `${path}.payload`, errors);
|
|
6922
|
+
checkKnownArray(payload, "source_event_ids", knownEventIds, `${path}.payload`, errors);
|
|
6923
|
+
checkKnownArray(payload, "operation_ids", knownOperationIds, `${path}.payload`, errors);
|
|
6924
|
+
}
|
|
6925
|
+
if (eventType === "evidence.refs.created") {
|
|
6926
|
+
checkReferenceList(payload, "evidence_refs", `${path}.payload`, errors);
|
|
6927
|
+
}
|
|
6928
|
+
if (eventType === "business.refs.resolved") {
|
|
6929
|
+
checkRequired(payload, ["resolver_status"], `${path}.payload`, errors);
|
|
6930
|
+
checkReferenceList(
|
|
6931
|
+
payload,
|
|
6932
|
+
"business_refs",
|
|
6933
|
+
`${path}.payload`,
|
|
6934
|
+
errors,
|
|
6935
|
+
payload.resolver_status === "unresolved"
|
|
6936
|
+
);
|
|
6937
|
+
}
|
|
6938
|
+
const actionState = ACTION_STATE_BY_EVENT[eventType];
|
|
6939
|
+
if (!actionState) return;
|
|
6940
|
+
checkRequired(payload, ["action_instance_id", "status"], `${path}.payload`, errors);
|
|
6941
|
+
const fixedStatus = {
|
|
6942
|
+
"action.recommended": "recommended",
|
|
6943
|
+
"action.approval_requested": "approval_requested",
|
|
6944
|
+
"action.approved": "approved",
|
|
6945
|
+
"action.rejected": "rejected"
|
|
6946
|
+
};
|
|
6947
|
+
if (fixedStatus[eventType] && payload.status !== fixedStatus[eventType]) {
|
|
6948
|
+
errors.push(
|
|
6949
|
+
err(
|
|
6950
|
+
"BKN_TRACE_ACTION_TRANSITION_INVALID",
|
|
6951
|
+
`${path}.payload.status`,
|
|
6952
|
+
`${eventType} requires status=${fixedStatus[eventType]}`
|
|
6953
|
+
)
|
|
6954
|
+
);
|
|
6955
|
+
}
|
|
6956
|
+
if (eventType === "action.recommended") {
|
|
6957
|
+
checkRequired(
|
|
6958
|
+
payload,
|
|
6959
|
+
["action_type", "target_refs", "reason_hash"],
|
|
6960
|
+
`${path}.payload`,
|
|
6961
|
+
errors
|
|
6962
|
+
);
|
|
6963
|
+
checkNonEmptyArray(payload, "target_refs", `${path}.payload`, errors);
|
|
6964
|
+
checkQualifiedStringRefs(payload, "target_refs", `${path}.payload`, errors);
|
|
6965
|
+
}
|
|
6966
|
+
if (eventType === "action.approval_requested") {
|
|
6967
|
+
checkRequired(payload, ["policy_ref"], `${path}.payload`, errors);
|
|
6968
|
+
}
|
|
6969
|
+
if (eventType === "action.approved" || eventType === "action.rejected") {
|
|
6970
|
+
checkRequired(payload, ["actor_ref", "policy_decision_ref"], `${path}.payload`, errors);
|
|
6971
|
+
}
|
|
6972
|
+
if (eventType === "action.executed") {
|
|
6973
|
+
checkOneOf(payload, ["invocation_ref", "tool_ref"], `${path}.payload`, errors);
|
|
6974
|
+
if (payload.status === "error") {
|
|
6975
|
+
checkRequired(payload, ["error_category", "error_hash"], `${path}.payload`, errors);
|
|
6976
|
+
}
|
|
6977
|
+
}
|
|
6978
|
+
if (eventType === "action.result_recorded") {
|
|
6979
|
+
checkRequired(payload, ["result_hash"], `${path}.payload`, errors);
|
|
6980
|
+
checkOneOf(payload, ["artifact_ref", "task_ref"], `${path}.payload`, errors);
|
|
6981
|
+
}
|
|
6982
|
+
const actionID = typeof payload.action_instance_id === "string" ? payload.action_instance_id : "";
|
|
6983
|
+
if (!actionID) return;
|
|
6984
|
+
const claimID = typeof event.claim_id === "string" ? event.claim_id : "";
|
|
6985
|
+
const operationID = typeof event.operation_id === "string" ? event.operation_id : "";
|
|
6986
|
+
const previous = actionStates.get(actionID);
|
|
6987
|
+
if (!previous && actionState !== "recommended" || previous && (!ACTION_TRANSITIONS[previous.state]?.has(actionState) || event.causation_event_id !== previous.lastEventID || claimID !== previous.claimID || operationID !== previous.operationID)) {
|
|
6988
|
+
errors.push(
|
|
6989
|
+
err(
|
|
6990
|
+
"BKN_TRACE_ACTION_TRANSITION_INVALID",
|
|
6991
|
+
`${path}.event_type`,
|
|
6992
|
+
`invalid action transition ${previous?.state ?? "<none>"} -> ${actionState}`
|
|
6993
|
+
)
|
|
6994
|
+
);
|
|
6995
|
+
return;
|
|
6996
|
+
}
|
|
6997
|
+
actionStates.set(actionID, {
|
|
6998
|
+
state: actionState,
|
|
6999
|
+
claimID,
|
|
7000
|
+
operationID,
|
|
7001
|
+
lastEventID: String(event.event_id ?? "")
|
|
7002
|
+
});
|
|
7003
|
+
}
|
|
7004
|
+
function checkOneOf(payload, fields, path, errors) {
|
|
7005
|
+
if (fields.some((field) => typeof payload[field] === "string" && payload[field] !== "")) return;
|
|
7006
|
+
errors.push(
|
|
7007
|
+
err(
|
|
7008
|
+
"BKN_TRACE_REQUIRED_FIELD_MISSING",
|
|
7009
|
+
`${path}.${fields[0]}`,
|
|
7010
|
+
`one of ${fields.join(" or ")} is required`
|
|
7011
|
+
)
|
|
7012
|
+
);
|
|
7013
|
+
}
|
|
7014
|
+
function checkNonEmptyArray(payload, field, path, errors) {
|
|
7015
|
+
if (Array.isArray(payload[field]) && payload[field].length > 0) return;
|
|
7016
|
+
errors.push(
|
|
7017
|
+
err(
|
|
7018
|
+
"BKN_TRACE_REQUIRED_FIELD_MISSING",
|
|
7019
|
+
`${path}.${field}`,
|
|
7020
|
+
`${field} must be a non-empty array`
|
|
7021
|
+
)
|
|
7022
|
+
);
|
|
7023
|
+
}
|
|
7024
|
+
function checkReferenceList(payload, field, path, errors, allowEmpty = false) {
|
|
7025
|
+
if (!allowEmpty) checkNonEmptyArray(payload, field, path, errors);
|
|
7026
|
+
const refs = Array.isArray(payload[field]) ? payload[field] : [];
|
|
7027
|
+
refs.forEach((value, index) => {
|
|
7028
|
+
const ref = asRecord(value);
|
|
7029
|
+
checkRequired(
|
|
7030
|
+
ref,
|
|
7031
|
+
["ref_id", "ref_type", "source_system", "validity", "version_status", "visibility"],
|
|
7032
|
+
`${path}.${field}[${index}]`,
|
|
7033
|
+
errors
|
|
7034
|
+
);
|
|
7035
|
+
checkAllowedKeys(ref, REFERENCE_FIELDS, `${path}.${field}[${index}]`, errors);
|
|
7036
|
+
if (typeof ref.ref_id === "string" && !isQualifiedReference(ref.ref_id)) {
|
|
7037
|
+
errors.push(
|
|
7038
|
+
err(
|
|
7039
|
+
"BKN_TRACE_REFERENCE_ID_INVALID",
|
|
7040
|
+
`${path}.${field}[${index}].ref_id`,
|
|
7041
|
+
"business reference id must include its knowledge-network or resource scope"
|
|
7042
|
+
)
|
|
7043
|
+
);
|
|
7044
|
+
}
|
|
7045
|
+
});
|
|
7046
|
+
}
|
|
7047
|
+
function checkQualifiedStringRefs(payload, field, path, errors) {
|
|
7048
|
+
const refs = Array.isArray(payload[field]) ? payload[field] : [];
|
|
7049
|
+
refs.forEach((value, index) => {
|
|
7050
|
+
if (typeof value !== "string" || isQualifiedReference(value)) return;
|
|
7051
|
+
errors.push(
|
|
7052
|
+
err(
|
|
7053
|
+
"BKN_TRACE_REFERENCE_ID_INVALID",
|
|
7054
|
+
`${path}.${field}[${index}]`,
|
|
7055
|
+
"business reference id must include its knowledge-network or resource scope"
|
|
7056
|
+
)
|
|
7057
|
+
);
|
|
7058
|
+
});
|
|
7059
|
+
}
|
|
7060
|
+
function isQualifiedReference(value) {
|
|
7061
|
+
const parts = value.trim().split(":");
|
|
7062
|
+
if (parts.some((part) => part.length === 0)) return false;
|
|
7063
|
+
if (["kn", "resource"].includes(parts[0] ?? "")) return parts.length === 2;
|
|
7064
|
+
if (["object", "relation", "action_type", "metric", "field"].includes(parts[0] ?? "")) {
|
|
7065
|
+
return parts.length === 3;
|
|
7066
|
+
}
|
|
7067
|
+
if (parts[0] === "property") return parts.length === 4;
|
|
7068
|
+
return true;
|
|
7069
|
+
}
|
|
7070
|
+
function checkKnownArray(payload, field, known, path, errors) {
|
|
7071
|
+
if (!Array.isArray(payload[field])) return;
|
|
7072
|
+
for (const value of payload[field]) {
|
|
7073
|
+
if (typeof value === "string" && known.has(value)) continue;
|
|
7074
|
+
errors.push(
|
|
7075
|
+
err(
|
|
7076
|
+
"BKN_TRACE_CAUSATION_INVALID",
|
|
7077
|
+
`${path}.${field}`,
|
|
7078
|
+
`${field} must reference earlier events or operations`
|
|
7079
|
+
)
|
|
7080
|
+
);
|
|
7081
|
+
}
|
|
7082
|
+
}
|
|
7083
|
+
function checkAllowedKeys(value, allowed, path, errors) {
|
|
7084
|
+
for (const key of Object.keys(value)) {
|
|
7085
|
+
if (allowed.has(key)) continue;
|
|
7086
|
+
errors.push(
|
|
7087
|
+
err(
|
|
7088
|
+
"BKN_TRACE_EVENT_PAYLOAD_FIELD_UNSUPPORTED",
|
|
7089
|
+
`${path}.${key}`,
|
|
7090
|
+
`payload field ${key} is not registered for this event`
|
|
7091
|
+
)
|
|
7092
|
+
);
|
|
7093
|
+
}
|
|
7094
|
+
}
|
|
7095
|
+
function validateFixturePath(path) {
|
|
7096
|
+
const results = jsonFiles(path).map((file) => {
|
|
7097
|
+
try {
|
|
7098
|
+
return validateFixture(JSON.parse(readFileSync4(file, "utf8")));
|
|
7099
|
+
} catch (e) {
|
|
7100
|
+
return {
|
|
7101
|
+
fixtureId: file,
|
|
7102
|
+
result: "fail",
|
|
7103
|
+
contractVersion: null,
|
|
7104
|
+
errors: [
|
|
7105
|
+
err(
|
|
7106
|
+
"BKN_TRACE_FIXTURE_PARSE_FAILED",
|
|
7107
|
+
"$",
|
|
7108
|
+
`failed to parse JSON: ${e instanceof Error ? e.message : String(e)}`
|
|
7109
|
+
)
|
|
7110
|
+
],
|
|
7111
|
+
warnings: [],
|
|
7112
|
+
expectedResult: null,
|
|
7113
|
+
expectationMatched: false
|
|
7114
|
+
};
|
|
7115
|
+
}
|
|
7116
|
+
});
|
|
7117
|
+
return { ok: results.every((r) => r.expectationMatched), results };
|
|
7118
|
+
}
|
|
7119
|
+
|
|
5147
7120
|
// src/resources/trace.ts
|
|
5148
7121
|
async function semanticJudge(question, answer, reference) {
|
|
5149
7122
|
const prompt = [
|
|
@@ -5160,6 +7133,8 @@ async function semanticJudge(question, answer, reference) {
|
|
|
5160
7133
|
};
|
|
5161
7134
|
}
|
|
5162
7135
|
function trace(ctx) {
|
|
7136
|
+
const lifecycle = traceLifecycleApi(ctx);
|
|
7137
|
+
const managed = new ManagedTrace(lifecycle);
|
|
5163
7138
|
const diagnoseOne = async (conversationId, opts = {}) => {
|
|
5164
7139
|
const { spans, traceIds } = await getRawSpansByConversation(ctx, conversationId);
|
|
5165
7140
|
if (spans.length === 0) throw new Error(`No spans found for conversation: ${conversationId}`);
|
|
@@ -5188,8 +7163,14 @@ function trace(ctx) {
|
|
|
5188
7163
|
};
|
|
5189
7164
|
};
|
|
5190
7165
|
return {
|
|
7166
|
+
/** Low-level BKN Trace 3.0 lifecycle and durable receipt API. */
|
|
7167
|
+
lifecycle,
|
|
7168
|
+
/** Own one complete interaction lifecycle around an application callback. */
|
|
7169
|
+
withInteraction: managed.withInteraction.bind(managed),
|
|
5191
7170
|
/** Raw trace search (OpenSearch-style body). */
|
|
5192
7171
|
search: (body) => traceSearch(ctx, body),
|
|
7172
|
+
/** Normalized trace tree/status graph by trace id. */
|
|
7173
|
+
graph: (traceId) => getTraceGraph(ctx, traceId),
|
|
5193
7174
|
/** All span source docs for a conversation. */
|
|
5194
7175
|
spans: (conversationId, opts) => getSpansByConversation(ctx, conversationId, opts),
|
|
5195
7176
|
diagnose: diagnoseOne,
|
|
@@ -5227,6 +7208,8 @@ function trace(ctx) {
|
|
|
5227
7208
|
},
|
|
5228
7209
|
/** Build eval cases from a loosely-shaped queries object/array. */
|
|
5229
7210
|
evalSetBuild: (raw) => buildCasesFromQueries(raw),
|
|
7211
|
+
/** Validate BKN Trace phase-one fixture files or directories. */
|
|
7212
|
+
validateFixture: (path) => validateFixturePath(path),
|
|
5230
7213
|
/**
|
|
5231
7214
|
* Run an eval set against an agent: each case's query is sent to the agent,
|
|
5232
7215
|
* the resulting trace is fetched, and assertions are checked. `llm` enables
|
|
@@ -5259,15 +7242,18 @@ function vega(ctx) {
|
|
|
5259
7242
|
return {
|
|
5260
7243
|
catalogs: (opts) => listCatalogs(ctx, opts),
|
|
5261
7244
|
getCatalog: (id) => getCatalog(ctx, id),
|
|
5262
|
-
createCatalog: (req) => createCatalog(ctx, req),
|
|
5263
|
-
updateCatalog: (id, req) => updateCatalog(ctx, id, req),
|
|
7245
|
+
createCatalog: (req, opts) => createCatalog(ctx, req, opts),
|
|
7246
|
+
updateCatalog: (id, req, opts) => updateCatalog(ctx, id, req, opts),
|
|
5264
7247
|
enableCatalog: (id) => enableCatalog(ctx, id),
|
|
5265
7248
|
disableCatalog: (id) => disableCatalog(ctx, id),
|
|
5266
7249
|
deleteCatalog: (id) => deleteCatalog(ctx, id),
|
|
7250
|
+
testCatalogConnectionConfig: (req) => testCatalogConnectionConfig(ctx, req),
|
|
5267
7251
|
testCatalogConnection: (id) => testCatalogConnection(ctx, id),
|
|
7252
|
+
catalogHealthCheckSchedule: (id) => getCatalogHealthCheckSchedule(ctx, id),
|
|
7253
|
+
updateCatalogHealthCheckSchedule: (id, req) => updateCatalogHealthCheckSchedule(ctx, id, req),
|
|
5268
7254
|
discoverCatalog: (id, wait = false) => discoverCatalog(ctx, id, wait),
|
|
5269
|
-
catalogResources: (id, category) => listCatalogResources(ctx, id, category),
|
|
5270
|
-
catalogHealth: (
|
|
7255
|
+
catalogResources: (id, category, limit, offset) => listCatalogResources(ctx, id, category, limit, offset),
|
|
7256
|
+
catalogHealth: (id) => catalogHealthStatus(ctx, id),
|
|
5271
7257
|
connectorTypes: () => listConnectorTypes(ctx),
|
|
5272
7258
|
connectorType: (type) => getConnectorType(ctx, type),
|
|
5273
7259
|
/** Run SQL / OpenSearch DSL directly against a data source. */
|
|
@@ -5300,7 +7286,7 @@ function sleep(ms) {
|
|
|
5300
7286
|
}
|
|
5301
7287
|
|
|
5302
7288
|
// src/api/call.ts
|
|
5303
|
-
import { readFileSync as
|
|
7289
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
5304
7290
|
function parseHeader(raw) {
|
|
5305
7291
|
const idx = raw.indexOf(":");
|
|
5306
7292
|
if (idx <= 0) return null;
|
|
@@ -5329,7 +7315,7 @@ async function rawCall(ctx, path, opts = {}) {
|
|
|
5329
7315
|
const fd = new FormData();
|
|
5330
7316
|
for (const field of opts.form) {
|
|
5331
7317
|
const [key, value, isFile] = parseFormField(field);
|
|
5332
|
-
if (isFile) fd.append(key, new Blob([
|
|
7318
|
+
if (isFile) fd.append(key, new Blob([readFileSync5(value)]), value.split("/").pop());
|
|
5333
7319
|
else fd.append(key, value);
|
|
5334
7320
|
}
|
|
5335
7321
|
body = fd;
|
|
@@ -5414,7 +7400,7 @@ function createClient(opts = {}) {
|
|
|
5414
7400
|
kn: kn(ctx),
|
|
5415
7401
|
resource: resources(ctx),
|
|
5416
7402
|
dataflows: dataflows(ctx),
|
|
5417
|
-
agents:
|
|
7403
|
+
agents: agents2(ctx),
|
|
5418
7404
|
context: context(ctx),
|
|
5419
7405
|
models: models(ctx),
|
|
5420
7406
|
skills: skills(ctx),
|
|
@@ -5452,7 +7438,7 @@ function hostOf(baseUrl) {
|
|
|
5452
7438
|
return baseUrl;
|
|
5453
7439
|
}
|
|
5454
7440
|
}
|
|
5455
|
-
function
|
|
7441
|
+
function normalize2(baseUrl) {
|
|
5456
7442
|
return baseUrl.replace(/\/+$/, "");
|
|
5457
7443
|
}
|
|
5458
7444
|
function usernameOf(token) {
|
|
@@ -5461,7 +7447,7 @@ function usernameOf(token) {
|
|
|
5461
7447
|
return token.username ?? token.displayName ?? claims?.preferred_username ?? claims?.name ?? claims?.sub;
|
|
5462
7448
|
}
|
|
5463
7449
|
function attachToken(baseUrl, accessToken, opts = {}) {
|
|
5464
|
-
const url =
|
|
7450
|
+
const url = normalize2(baseUrl);
|
|
5465
7451
|
const token = {
|
|
5466
7452
|
baseUrl: url,
|
|
5467
7453
|
accessToken,
|
|
@@ -5564,7 +7550,7 @@ function listPlatforms2() {
|
|
|
5564
7550
|
);
|
|
5565
7551
|
}
|
|
5566
7552
|
function use(baseUrl) {
|
|
5567
|
-
const url =
|
|
7553
|
+
const url = normalize2(baseUrl);
|
|
5568
7554
|
if (!readToken(url)) {
|
|
5569
7555
|
throw new InputError(`No saved credentials for ${url}. Run \`openbkn auth login\` first.`);
|
|
5570
7556
|
}
|
|
@@ -5575,10 +7561,10 @@ function logout() {
|
|
|
5575
7561
|
return baseUrl ? deleteToken(baseUrl) : false;
|
|
5576
7562
|
}
|
|
5577
7563
|
function deletePlatform(baseUrl, userId) {
|
|
5578
|
-
return deleteToken(
|
|
7564
|
+
return deleteToken(normalize2(baseUrl), userId);
|
|
5579
7565
|
}
|
|
5580
7566
|
function switchUser(baseUrl, userOrName) {
|
|
5581
|
-
const url =
|
|
7567
|
+
const url = normalize2(baseUrl);
|
|
5582
7568
|
const users = usersOf(url);
|
|
5583
7569
|
const match = users.find((u) => u.userId === userOrName) ?? users.find((u) => (u.username ?? u.displayName) === userOrName);
|
|
5584
7570
|
if (!match) {
|
|
@@ -5589,7 +7575,7 @@ function switchUser(baseUrl, userOrName) {
|
|
|
5589
7575
|
return { baseUrl: url, userId: match.userId, username: match.username ?? match.displayName };
|
|
5590
7576
|
}
|
|
5591
7577
|
function usersOf(baseUrl) {
|
|
5592
|
-
const url =
|
|
7578
|
+
const url = normalize2(baseUrl);
|
|
5593
7579
|
return listPlatforms().find((p) => p.baseUrl === url)?.users ?? [];
|
|
5594
7580
|
}
|
|
5595
7581
|
function exportCreds() {
|
|
@@ -5607,6 +7593,7 @@ function exportCreds() {
|
|
|
5607
7593
|
|
|
5608
7594
|
export {
|
|
5609
7595
|
HttpError,
|
|
7596
|
+
ToolError,
|
|
5610
7597
|
InputError,
|
|
5611
7598
|
toExitCode,
|
|
5612
7599
|
formatError,
|
|
@@ -5615,6 +7602,7 @@ export {
|
|
|
5615
7602
|
deviceLogin,
|
|
5616
7603
|
credentialDeviceLogin,
|
|
5617
7604
|
request,
|
|
7605
|
+
lifecycleHint,
|
|
5618
7606
|
rawCall,
|
|
5619
7607
|
DEFAULT_BUSINESS_DOMAIN,
|
|
5620
7608
|
DEFAULT_LIST_LIMIT,
|
|
@@ -5628,7 +7616,8 @@ export {
|
|
|
5628
7616
|
getUserSafe,
|
|
5629
7617
|
changePasswordSafe,
|
|
5630
7618
|
admin,
|
|
5631
|
-
agents,
|
|
7619
|
+
agents2 as agents,
|
|
7620
|
+
releaseLifecycleSessions,
|
|
5632
7621
|
context,
|
|
5633
7622
|
dataflows,
|
|
5634
7623
|
parsePkMap,
|
|
@@ -5636,9 +7625,15 @@ export {
|
|
|
5636
7625
|
kn,
|
|
5637
7626
|
models,
|
|
5638
7627
|
resources,
|
|
7628
|
+
classifyPath,
|
|
7629
|
+
filesUnder,
|
|
7630
|
+
renderTree,
|
|
5639
7631
|
skills,
|
|
5640
7632
|
toolboxes,
|
|
7633
|
+
traceLifecycleApi,
|
|
5641
7634
|
renderReportMarkdown,
|
|
7635
|
+
validateFixturePath,
|
|
7636
|
+
ManagedTrace,
|
|
5642
7637
|
trace,
|
|
5643
7638
|
vega,
|
|
5644
7639
|
createClient,
|
|
@@ -5655,4 +7650,4 @@ export {
|
|
|
5655
7650
|
exportCreds,
|
|
5656
7651
|
auth_exports
|
|
5657
7652
|
};
|
|
5658
|
-
//# sourceMappingURL=chunk-
|
|
7653
|
+
//# sourceMappingURL=chunk-PC2F54XD.js.map
|