@zanii/blackbox 0.21.2 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/actions/index.d.ts +29 -0
- package/dist/actions/index.js +114 -0
- package/dist/admission/index.d.ts +30 -0
- package/dist/admission/index.js +106 -0
- package/dist/analysis/accepted.d.ts +26 -0
- package/dist/analysis/accepted.js +61 -0
- package/dist/analysis/detectors.js +12 -9
- package/dist/analysis/faults.js +71 -0
- package/dist/analysis/grounding.js +5 -9
- package/dist/analysis/guard.d.ts +72 -0
- package/dist/analysis/guard.js +240 -0
- package/dist/analysis/hallucination.js +2 -2
- package/dist/analysis/index.d.ts +21 -1
- package/dist/analysis/index.js +101 -19
- package/dist/analysis/injection.d.ts +2 -1
- package/dist/analysis/injection.js +16 -16
- package/dist/analysis/landing.js +60 -14
- package/dist/analysis/plan-checks.d.ts +55 -0
- package/dist/analysis/plan-checks.js +748 -0
- package/dist/analysis/taxonomy.js +1 -0
- package/dist/analysis/waste.js +2 -3
- package/dist/approvals/index.d.ts +12 -3
- package/dist/approvals/index.js +36 -10
- package/dist/authority/index.js +3 -3
- package/dist/aviation/index.d.ts +422 -0
- package/dist/aviation/index.js +1014 -0
- package/dist/aviation/qrh.d.ts +4 -0
- package/dist/aviation/qrh.js +171 -0
- package/dist/budgets/index.d.ts +63 -0
- package/dist/budgets/index.js +161 -0
- package/dist/cli.js +199 -24
- package/dist/compliance/index.d.ts +2 -0
- package/dist/compliance/index.js +26 -2
- package/dist/cost/batch.d.ts +26 -0
- package/dist/cost/batch.js +173 -0
- package/dist/cost/index.d.ts +21 -0
- package/dist/cost/index.js +115 -30
- package/dist/cv/index.js +1 -1
- package/dist/duty/index.js +2 -1
- package/dist/evals/index.d.ts +233 -0
- package/dist/evals/index.js +488 -0
- package/dist/evidence/index.d.ts +9 -0
- package/dist/evidence/index.js +50 -0
- package/dist/federation/index.d.ts +9 -0
- package/dist/federation/index.js +8 -1
- package/dist/finance/index.js +3 -4
- package/dist/fleet/index.d.ts +25 -0
- package/dist/fleet/index.js +57 -17
- package/dist/fleet/insights.js +4 -1
- package/dist/health/index.js +2 -2
- package/dist/hooks/ai-sdk.d.ts +17 -19
- package/dist/hooks/ai-sdk.js +13 -7
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/mastra.d.ts +26 -0
- package/dist/hooks/mastra.js +63 -0
- package/dist/index.d.ts +24 -9
- package/dist/index.js +25 -9
- package/dist/mcp-requests/index.d.ts +26 -0
- package/dist/mcp-requests/index.js +154 -0
- package/dist/mcp-server/index.d.ts +12 -0
- package/dist/mcp-server/index.js +84 -2
- package/dist/mcp-wrap/index.js +8 -4
- package/dist/otlp/index.d.ts +155 -0
- package/dist/otlp/index.js +468 -0
- package/dist/otlp/protobuf.d.ts +7 -0
- package/dist/otlp/protobuf.js +314 -0
- package/dist/packs/index.d.ts +2 -0
- package/dist/packs/index.js +5 -1
- package/dist/pdf/arabic.d.ts +45 -0
- package/dist/pdf/arabic.js +373 -0
- package/dist/pdf/index.d.ts +77 -0
- package/dist/pdf/index.js +673 -0
- package/dist/policy/index.d.ts +0 -4
- package/dist/policy/index.js +13 -5
- package/dist/realtime/index.d.ts +33 -0
- package/dist/realtime/index.js +204 -0
- package/dist/reconcile/record.d.ts +14 -0
- package/dist/reconcile/record.js +161 -66
- package/dist/session/index.d.ts +43 -0
- package/dist/session/index.js +83 -0
- package/dist/undo/index.js +3 -3
- package/dist/verify/anchor.d.ts +5 -1
- package/dist/verify/anchor.js +13 -4
- package/dist/verify/chain.d.ts +4 -0
- package/dist/verify/chain.js +4 -3
- package/dist/verify/envelope.d.ts +1 -1
- package/dist/verify/envelope.js +5 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/walls/index.js +2 -2
- package/dist/warehouse/index.d.ts +62 -0
- package/dist/warehouse/index.js +142 -0
- package/package.json +6 -3
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Json } from "../verify/envelope.ts";
|
|
2
|
+
type Meta = {
|
|
3
|
+
[key: string]: Json;
|
|
4
|
+
};
|
|
5
|
+
/** The server's request methods and the event kind each is recorded as. */
|
|
6
|
+
export declare const SERVER_REQUESTS: Readonly<Record<string, ServerRequestKind>>;
|
|
7
|
+
export type ServerRequestKind = "tool.sampling" | "tool.elicitation" | "tool.roots";
|
|
8
|
+
/** A JSON-RPC id as recorded: a string of at most 256 characters, or a safe integer. */
|
|
9
|
+
export declare function rpcIdOf(id: unknown): string | number | undefined;
|
|
10
|
+
/** The field names that ask for a credential: the name, lowercased with everything but letters
|
|
11
|
+
* and digits removed, contains one of the credential words, or is `pin`. In the order given. */
|
|
12
|
+
export declare function secretFields(names: readonly string[]): string[];
|
|
13
|
+
/**
|
|
14
|
+
* A server → client message that is one of the server's requests: its kind and `meta`, or null
|
|
15
|
+
* for anything else (a notification, a response, another method, a batch, a request without a
|
|
16
|
+
* usable id).
|
|
17
|
+
*/
|
|
18
|
+
export declare function serverRequestMeta(message: unknown): {
|
|
19
|
+
kind: ServerRequestKind;
|
|
20
|
+
meta: Meta;
|
|
21
|
+
} | null;
|
|
22
|
+
/** The client's reply to a server request of `method`: its `meta` (spec/mcp.md §6). */
|
|
23
|
+
export declare function serverReplyMeta(method: string, message: unknown): Meta;
|
|
24
|
+
/** Whether a client → server message is a reply (a JSON-RPC response) rather than a request. */
|
|
25
|
+
export declare function isRpcResponse(message: unknown): boolean;
|
|
26
|
+
export {};
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
// MCP server-to-client requests (spec/mcp.md §6): `sampling/createMessage`, `elicitation/create` and
|
|
2
|
+
// `roots/list`, and the client's replies, as their own event kinds. Pure functions, pinned by
|
|
3
|
+
// spec/vectors/mcp-requests.json. Mirrors sdks/python/src/zanii_blackbox/mcp_requests.py.
|
|
4
|
+
/** The server's request methods and the event kind each is recorded as. */
|
|
5
|
+
export const SERVER_REQUESTS = {
|
|
6
|
+
"sampling/createMessage": "tool.sampling",
|
|
7
|
+
"elicitation/create": "tool.elicitation",
|
|
8
|
+
"roots/list": "tool.roots",
|
|
9
|
+
};
|
|
10
|
+
const obj = (v) => typeof v === "object" && v !== null && !Array.isArray(v) ? v : {};
|
|
11
|
+
const str = (v, max) => (typeof v === "string" && v.length <= max ? v : undefined);
|
|
12
|
+
const count = (v) => (Array.isArray(v) ? v.length : undefined);
|
|
13
|
+
const nonNegative = (v) => typeof v === "number" && Number.isSafeInteger(v) && v >= 0 ? v : undefined;
|
|
14
|
+
const put = (meta, key, value) => {
|
|
15
|
+
if (value !== undefined)
|
|
16
|
+
meta[key] = value;
|
|
17
|
+
};
|
|
18
|
+
const keys = (v) => {
|
|
19
|
+
const o = obj(v);
|
|
20
|
+
return Object.keys(o)
|
|
21
|
+
.slice(0, 50)
|
|
22
|
+
.map((k) => k.slice(0, 64));
|
|
23
|
+
};
|
|
24
|
+
/** A JSON-RPC id as recorded: a string of at most 256 characters, or a safe integer. */
|
|
25
|
+
export function rpcIdOf(id) {
|
|
26
|
+
if (typeof id === "string" && id.length <= 256)
|
|
27
|
+
return id;
|
|
28
|
+
if (typeof id === "number" && Number.isSafeInteger(id))
|
|
29
|
+
return id;
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
// ponytail: a name list, not a classifier; a field named for a credential is what a phishing
|
|
33
|
+
// elicitation asks for. Extend the list when a real one slips through.
|
|
34
|
+
const SECRET_NAMES = [
|
|
35
|
+
"password",
|
|
36
|
+
"passwd",
|
|
37
|
+
"passphrase",
|
|
38
|
+
"secret",
|
|
39
|
+
"token",
|
|
40
|
+
"apikey",
|
|
41
|
+
"accesskey",
|
|
42
|
+
"privatekey",
|
|
43
|
+
"credential",
|
|
44
|
+
"otp",
|
|
45
|
+
"totp",
|
|
46
|
+
"mfacode",
|
|
47
|
+
"pincode",
|
|
48
|
+
"cvv",
|
|
49
|
+
"cvc",
|
|
50
|
+
"cardnumber",
|
|
51
|
+
"iban",
|
|
52
|
+
"ssn",
|
|
53
|
+
"seedphrase",
|
|
54
|
+
"mnemonic",
|
|
55
|
+
];
|
|
56
|
+
/** The field names that ask for a credential: the name, lowercased with everything but letters
|
|
57
|
+
* and digits removed, contains one of the credential words, or is `pin`. In the order given. */
|
|
58
|
+
export function secretFields(names) {
|
|
59
|
+
return names.filter((n) => {
|
|
60
|
+
const plain = n.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
61
|
+
return plain === "pin" || SECRET_NAMES.some((w) => plain.includes(w));
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* A server → client message that is one of the server's requests: its kind and `meta`, or null
|
|
66
|
+
* for anything else (a notification, a response, another method, a batch, a request without a
|
|
67
|
+
* usable id).
|
|
68
|
+
*/
|
|
69
|
+
export function serverRequestMeta(message) {
|
|
70
|
+
const m = obj(message);
|
|
71
|
+
const method = typeof m.method === "string" ? m.method : "";
|
|
72
|
+
const kind = Object.hasOwn(SERVER_REQUESTS, method) ? SERVER_REQUESTS[method] : undefined;
|
|
73
|
+
const id = rpcIdOf(m.id);
|
|
74
|
+
if (!kind || id === undefined)
|
|
75
|
+
return null;
|
|
76
|
+
const p = obj(m.params);
|
|
77
|
+
const meta = { phase: "request", method, rpc_id: id };
|
|
78
|
+
if (kind === "tool.sampling") {
|
|
79
|
+
put(meta, "max_tokens", nonNegative(p.maxTokens));
|
|
80
|
+
put(meta, "messages", count(p.messages));
|
|
81
|
+
const include = str(p.includeContext, 32);
|
|
82
|
+
if (include === "none" || include === "thisServer" || include === "allServers")
|
|
83
|
+
meta.include_context = include;
|
|
84
|
+
const hints = obj(p.modelPreferences).hints;
|
|
85
|
+
if (Array.isArray(hints)) {
|
|
86
|
+
const names = hints
|
|
87
|
+
.map((h) => str(obj(h).name, 128))
|
|
88
|
+
.filter((n) => n !== undefined)
|
|
89
|
+
.slice(0, 8);
|
|
90
|
+
if (names.length)
|
|
91
|
+
meta.model_hints = names;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
else if (kind === "tool.elicitation") {
|
|
95
|
+
put(meta, "mode", str(p.mode, 32));
|
|
96
|
+
const fields = keys(obj(p.requestedSchema).properties);
|
|
97
|
+
if (fields.length)
|
|
98
|
+
meta.fields = fields;
|
|
99
|
+
const secret = secretFields(fields);
|
|
100
|
+
if (secret.length)
|
|
101
|
+
meta.secret_fields = secret;
|
|
102
|
+
}
|
|
103
|
+
return { kind, meta };
|
|
104
|
+
}
|
|
105
|
+
/** Flattens a usage object into integer fields (`{a: {b: 1}}` → `{a_b: 1}`), at most 64. */
|
|
106
|
+
function flatUsage(usage) {
|
|
107
|
+
const out = {};
|
|
108
|
+
const walk = (o, prefix, depth) => {
|
|
109
|
+
for (const [k, v] of Object.entries(o)) {
|
|
110
|
+
if (Object.keys(out).length >= 64)
|
|
111
|
+
return;
|
|
112
|
+
const key = (prefix + k).slice(0, 128);
|
|
113
|
+
if (typeof v === "number" && Number.isSafeInteger(v) && v >= 0)
|
|
114
|
+
out[key] = v;
|
|
115
|
+
else if (depth < 3 && typeof v === "object" && v !== null && !Array.isArray(v))
|
|
116
|
+
walk(v, `${key}_`, depth + 1);
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
walk(obj(usage), "", 0);
|
|
120
|
+
return Object.keys(out).length ? out : undefined;
|
|
121
|
+
}
|
|
122
|
+
/** The client's reply to a server request of `method`: its `meta` (spec/mcp.md §6). */
|
|
123
|
+
export function serverReplyMeta(method, message) {
|
|
124
|
+
const m = obj(message);
|
|
125
|
+
const meta = { phase: "reply", method };
|
|
126
|
+
put(meta, "rpc_id", rpcIdOf(m.id));
|
|
127
|
+
const code = obj(m.error).code;
|
|
128
|
+
if (typeof code === "number" && Number.isSafeInteger(code))
|
|
129
|
+
meta.rpc_error = code;
|
|
130
|
+
if (!("result" in m))
|
|
131
|
+
return meta;
|
|
132
|
+
const r = obj(m.result);
|
|
133
|
+
if (method === "sampling/createMessage") {
|
|
134
|
+
put(meta, "model", str(r.model, 256));
|
|
135
|
+
put(meta, "stop_reason", str(r.stopReason, 64));
|
|
136
|
+
put(meta, "usage", flatUsage(r.usage));
|
|
137
|
+
}
|
|
138
|
+
else if (method === "elicitation/create") {
|
|
139
|
+
const action = str(r.action, 16);
|
|
140
|
+
if (action === "accept" || action === "decline" || action === "cancel")
|
|
141
|
+
meta.action = action;
|
|
142
|
+
const fields = keys(r.content);
|
|
143
|
+
if (fields.length)
|
|
144
|
+
meta.fields = fields;
|
|
145
|
+
}
|
|
146
|
+
else if (method === "roots/list")
|
|
147
|
+
put(meta, "roots", count(r.roots));
|
|
148
|
+
return meta;
|
|
149
|
+
}
|
|
150
|
+
/** Whether a client → server message is a reply (a JSON-RPC response) rather than a request. */
|
|
151
|
+
export function isRpcResponse(message) {
|
|
152
|
+
const m = obj(message);
|
|
153
|
+
return "id" in m && !("method" in m) && ("result" in m || "error" in m);
|
|
154
|
+
}
|
|
@@ -80,6 +80,18 @@ type Result = {
|
|
|
80
80
|
export declare function callTool(get: Get, name: unknown, args: unknown): Promise<Result>;
|
|
81
81
|
/** Answers one JSON-RPC line; undefined for a notification. */
|
|
82
82
|
export declare function handleLine(get: Get, line: string): Promise<object | undefined>;
|
|
83
|
+
/** Answers one parsed JSON-RPC message; undefined for a notification. */
|
|
84
|
+
export declare function handleMessage(get: Get, parsed: unknown): Promise<object | undefined>;
|
|
83
85
|
/** Serves stdin → stdout until stdin closes; one request at a time, in order. */
|
|
84
86
|
export declare function serveMcp(get: Get): Promise<number>;
|
|
87
|
+
/**
|
|
88
|
+
* spec/mcp-server.md §HTTP: the same server over MCP Streamable HTTP, at `/mcp`. Each POST is
|
|
89
|
+
* answered on its own, so requests run concurrently. The caller's bearer key (an admin or tenant
|
|
90
|
+
* key of the Blackbox server) is the key every tool call reads with: the server holds none.
|
|
91
|
+
*/
|
|
92
|
+
export declare function serveMcpHttp(getFor: (key: string) => Get, options: {
|
|
93
|
+
host: string;
|
|
94
|
+
port: number;
|
|
95
|
+
onListening?: (url: string) => void;
|
|
96
|
+
}): Promise<number>;
|
|
85
97
|
export {};
|
package/dist/mcp-server/index.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
// The Blackbox MCP server (spec/mcp-server.md): read-only tools over the gateway's API, on stdio
|
|
1
|
+
// The Blackbox MCP server (spec/mcp-server.md): read-only tools over the gateway's API, on stdio or
|
|
2
|
+
// Streamable HTTP.
|
|
2
3
|
// Mirrors sdks/python/src/zanii_blackbox/mcp_server.py.
|
|
4
|
+
import { createServer } from "node:http";
|
|
3
5
|
import { createInterface } from "node:readline";
|
|
4
6
|
const SESSION_ID = /^ses_[A-Za-z0-9]{16,64}$/;
|
|
5
7
|
const sid = {
|
|
@@ -172,6 +174,7 @@ export async function callTool(get, name, args) {
|
|
|
172
174
|
: doc;
|
|
173
175
|
return text(JSON.stringify(out, null, 2));
|
|
174
176
|
}
|
|
177
|
+
const PARSE_ERROR = { jsonrpc: "2.0", id: null, error: { code: -32700, message: "parse error" } };
|
|
175
178
|
/** Answers one JSON-RPC line; undefined for a notification. */
|
|
176
179
|
export async function handleLine(get, line) {
|
|
177
180
|
let msg;
|
|
@@ -179,8 +182,13 @@ export async function handleLine(get, line) {
|
|
|
179
182
|
msg = JSON.parse(line);
|
|
180
183
|
}
|
|
181
184
|
catch {
|
|
182
|
-
return
|
|
185
|
+
return PARSE_ERROR;
|
|
183
186
|
}
|
|
187
|
+
return handleMessage(get, msg);
|
|
188
|
+
}
|
|
189
|
+
/** Answers one parsed JSON-RPC message; undefined for a notification. */
|
|
190
|
+
export async function handleMessage(get, parsed) {
|
|
191
|
+
const msg = parsed;
|
|
184
192
|
if (typeof msg !== "object" || msg === null || !("id" in msg))
|
|
185
193
|
return undefined;
|
|
186
194
|
const reply = (result) => ({ jsonrpc: "2.0", id: msg.id, result });
|
|
@@ -214,3 +222,77 @@ export async function serveMcp(get) {
|
|
|
214
222
|
}
|
|
215
223
|
return 0;
|
|
216
224
|
}
|
|
225
|
+
/**
|
|
226
|
+
* spec/mcp-server.md §HTTP: the same server over MCP Streamable HTTP, at `/mcp`. Each POST is
|
|
227
|
+
* answered on its own, so requests run concurrently. The caller's bearer key (an admin or tenant
|
|
228
|
+
* key of the Blackbox server) is the key every tool call reads with: the server holds none.
|
|
229
|
+
*/
|
|
230
|
+
export function serveMcpHttp(getFor, options) {
|
|
231
|
+
const server = createServer((req, res) => void answerHttp(getFor, req, res));
|
|
232
|
+
return new Promise((resolve, reject) => {
|
|
233
|
+
server.once("error", reject);
|
|
234
|
+
server.listen(options.port, options.host, () => {
|
|
235
|
+
const { port } = server.address();
|
|
236
|
+
const host = options.host.includes(":") ? `[${options.host}]` : options.host;
|
|
237
|
+
options.onListening?.(`http://${host}:${port}/mcp`);
|
|
238
|
+
});
|
|
239
|
+
const stop = () => {
|
|
240
|
+
server.close(() => resolve(0));
|
|
241
|
+
server.closeAllConnections();
|
|
242
|
+
};
|
|
243
|
+
process.once("SIGINT", stop);
|
|
244
|
+
process.once("SIGTERM", stop);
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
const MAX_BODY = 1024 * 1024;
|
|
248
|
+
const BEARER = /^Bearer ([\x21-\x7e]{1,512})$/;
|
|
249
|
+
async function answerHttp(getFor, req, res) {
|
|
250
|
+
req.on("error", () => { });
|
|
251
|
+
res.on("error", () => { });
|
|
252
|
+
const send = (status, body, headers = {}) => void res
|
|
253
|
+
.writeHead(status, body ? { "content-type": "application/json", ...headers } : headers)
|
|
254
|
+
.end(body ? JSON.stringify(body) : undefined);
|
|
255
|
+
const fail = (status, message, headers = {}) => send(status, { jsonrpc: "2.0", id: null, error: { code: -32001, message } }, headers);
|
|
256
|
+
if ((req.url ?? "").split("?")[0] !== "/mcp")
|
|
257
|
+
return fail(404, "not found: the endpoint is /mcp");
|
|
258
|
+
// MCP Streamable HTTP: a browser page must not reach a local server (DNS rebinding)
|
|
259
|
+
const origin = req.headers.origin;
|
|
260
|
+
if (origin !== undefined && !sameHost(origin, req.headers.host))
|
|
261
|
+
return fail(403, "bad origin");
|
|
262
|
+
const auth = BEARER.exec(req.headers.authorization ?? "");
|
|
263
|
+
if (!auth?.[1])
|
|
264
|
+
return fail(401, "a Blackbox API key is required (Authorization: Bearer)", {
|
|
265
|
+
"www-authenticate": 'Bearer realm="blackbox"',
|
|
266
|
+
});
|
|
267
|
+
if (req.method !== "POST")
|
|
268
|
+
return fail(405, "only POST is served", { allow: "POST" });
|
|
269
|
+
const parts = [];
|
|
270
|
+
let size = 0;
|
|
271
|
+
for await (const chunk of req) {
|
|
272
|
+
size += chunk.length;
|
|
273
|
+
if (size > MAX_BODY)
|
|
274
|
+
return fail(413, "request body too large");
|
|
275
|
+
parts.push(chunk);
|
|
276
|
+
}
|
|
277
|
+
let parsed;
|
|
278
|
+
try {
|
|
279
|
+
parsed = JSON.parse(Buffer.concat(parts).toString("utf8"));
|
|
280
|
+
}
|
|
281
|
+
catch {
|
|
282
|
+
return send(400, PARSE_ERROR);
|
|
283
|
+
}
|
|
284
|
+
const get = getFor(auth[1]);
|
|
285
|
+
const many = Array.isArray(parsed);
|
|
286
|
+
const replies = (await Promise.all((many ? parsed : [parsed]).map((m) => handleMessage(get, m)))).filter((r) => r !== undefined);
|
|
287
|
+
if (replies.length === 0)
|
|
288
|
+
return send(202, null); // notifications only
|
|
289
|
+
send(200, many ? replies : replies[0]);
|
|
290
|
+
}
|
|
291
|
+
function sameHost(origin, host) {
|
|
292
|
+
try {
|
|
293
|
+
return new URL(origin).host === host;
|
|
294
|
+
}
|
|
295
|
+
catch {
|
|
296
|
+
return false;
|
|
297
|
+
}
|
|
298
|
+
}
|
package/dist/mcp-wrap/index.js
CHANGED
|
@@ -118,8 +118,9 @@ export async function mcpWrap(options) {
|
|
|
118
118
|
? `blackbox mcp-wrap: a ${d} message was not recorded, so not relayed: ${failure}\n`
|
|
119
119
|
: `blackbox mcp-wrap: a ${d} message was refused by the gateway's policy\n`);
|
|
120
120
|
const reply = refusal(d, line, failure);
|
|
121
|
+
// spec/mcp.md §6: a server's own request refused by policy is answered to the server
|
|
121
122
|
if (reply)
|
|
122
|
-
await write(stdout, reply);
|
|
123
|
+
await write(reply.toServer ? child.stdin : stdout, reply.bytes);
|
|
123
124
|
});
|
|
124
125
|
};
|
|
125
126
|
const done = (d, source) => new Promise((resolve) => {
|
|
@@ -145,7 +146,8 @@ export async function mcpWrap(options) {
|
|
|
145
146
|
agent.destroy();
|
|
146
147
|
return code;
|
|
147
148
|
}
|
|
148
|
-
/** What the agent gets instead of a message that couldn't be recorded (spec/mcp.md §5)
|
|
149
|
+
/** What the agent gets instead of a message that couldn't be recorded (spec/mcp.md §5), or, for
|
|
150
|
+
* a server's own request the gateway's policy refused, what the server gets (§6). */
|
|
149
151
|
function refusal(d, line, reason) {
|
|
150
152
|
let message;
|
|
151
153
|
try {
|
|
@@ -158,12 +160,14 @@ function refusal(d, line, reason) {
|
|
|
158
160
|
return null;
|
|
159
161
|
const isRequest = "method" in message;
|
|
160
162
|
const isResponse = "result" in message || "error" in message;
|
|
161
|
-
|
|
163
|
+
const toServer = d === "server" && isRequest && typeof reason !== "string";
|
|
164
|
+
if (!toServer && ((d === "client" && !isRequest) || (d === "server" && !isResponse)))
|
|
162
165
|
return null;
|
|
163
166
|
const error = typeof reason === "string"
|
|
164
167
|
? { code: -32001, message: `blackbox: not recorded, so not relayed (${reason})` }
|
|
165
168
|
: reason.rpcError;
|
|
166
|
-
|
|
169
|
+
const bytes = Buffer.from(`${JSON.stringify({ jsonrpc: "2.0", id: message.id, error })}\n`);
|
|
170
|
+
return { bytes, toServer };
|
|
167
171
|
}
|
|
168
172
|
function quoteWindows(arg) {
|
|
169
173
|
return /^[\w\-./:=@\\]+$/.test(arg) ? arg : `"${arg.replace(/"/g, '""')}"`;
|
package/dist/otlp/index.d.ts
CHANGED
|
@@ -28,6 +28,10 @@ export declare function toOtlp(lines: readonly string[], options?: {
|
|
|
28
28
|
}[];
|
|
29
29
|
}[];
|
|
30
30
|
};
|
|
31
|
+
type AnyAttr = {
|
|
32
|
+
key: string;
|
|
33
|
+
value: unknown;
|
|
34
|
+
};
|
|
31
35
|
/** One event for `POST /v1/sessions/:id/sdk-events`, numbered by the server (spec/sdk.md §1). */
|
|
32
36
|
export interface OtlpEvent {
|
|
33
37
|
event_id: string;
|
|
@@ -45,4 +49,155 @@ export declare function fromOtlp(body: unknown): {
|
|
|
45
49
|
} | {
|
|
46
50
|
error: string;
|
|
47
51
|
};
|
|
52
|
+
/** spec/otlp.md §5: a double as integer micro-units, half up; null when it isn't a finite number
|
|
53
|
+
* or its micro-units aren't a safe integer. */
|
|
54
|
+
export declare function microOf(v: unknown): number | null;
|
|
55
|
+
/**
|
|
56
|
+
* spec/otlp.md §5: OTLP/JSON metrics as session events, one per data point of a GenAI metric
|
|
57
|
+
* (`gen_ai.*`) or a point with a `gen_ai.*` or `blackbox.*` attribute (the resource's count);
|
|
58
|
+
* others are skipped. Doubles become integer micro-units; the event_id is a hash of the point's
|
|
59
|
+
* identity, so a resent point adds nothing.
|
|
60
|
+
*/
|
|
61
|
+
export declare function fromOtlpMetrics(body: unknown): {
|
|
62
|
+
events: OtlpEvent[];
|
|
63
|
+
} | {
|
|
64
|
+
error: string;
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* spec/otlp.md §5: OTLP/JSON logs as session events, one per GenAI record (an event name
|
|
68
|
+
* `gen_ai.*`, or a `gen_ai.*` or `blackbox.*` attribute, the resource's included); others are
|
|
69
|
+
* skipped. The body is recorded as `body_sha256`, and itself only with `bodies: true`.
|
|
70
|
+
*/
|
|
71
|
+
export declare function fromOtlpLogs(body: unknown, options?: {
|
|
72
|
+
bodies?: boolean;
|
|
73
|
+
}): {
|
|
74
|
+
events: OtlpEvent[];
|
|
75
|
+
} | {
|
|
76
|
+
error: string;
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* spec/otlp.md §6: a session's metrics as OTLP/JSON: token usage and call duration (the GenAI
|
|
80
|
+
* client histograms), the calls witnessed, and the priced cost in micro-USD per model from its cost
|
|
81
|
+
* report (spec/cost.md), cumulative from the session's open to its last event.
|
|
82
|
+
*/
|
|
83
|
+
export declare function toOtlpMetrics(lines: readonly string[], cost?: {
|
|
84
|
+
calls: ReadonlyArray<{
|
|
85
|
+
model?: string | null;
|
|
86
|
+
micro_usd: number | null;
|
|
87
|
+
}>;
|
|
88
|
+
} | null): {
|
|
89
|
+
resourceMetrics: {
|
|
90
|
+
resource: {
|
|
91
|
+
attributes: Attr[];
|
|
92
|
+
};
|
|
93
|
+
scopeMetrics: {
|
|
94
|
+
scope: {
|
|
95
|
+
name: string;
|
|
96
|
+
version: string;
|
|
97
|
+
};
|
|
98
|
+
metrics: ({
|
|
99
|
+
name: string;
|
|
100
|
+
description: string;
|
|
101
|
+
unit: string;
|
|
102
|
+
histogram: {
|
|
103
|
+
aggregationTemporality: number;
|
|
104
|
+
dataPoints: {
|
|
105
|
+
startTimeUnixNano: string;
|
|
106
|
+
timeUnixNano: string;
|
|
107
|
+
attributes: AnyAttr[];
|
|
108
|
+
count: string;
|
|
109
|
+
sum: number;
|
|
110
|
+
bucketCounts: string[];
|
|
111
|
+
explicitBounds: number[];
|
|
112
|
+
min: number;
|
|
113
|
+
max: number;
|
|
114
|
+
}[];
|
|
115
|
+
};
|
|
116
|
+
sum?: never;
|
|
117
|
+
} | {
|
|
118
|
+
histogram?: never;
|
|
119
|
+
name: string;
|
|
120
|
+
description: string;
|
|
121
|
+
unit: string;
|
|
122
|
+
sum: {
|
|
123
|
+
aggregationTemporality: number;
|
|
124
|
+
isMonotonic: boolean;
|
|
125
|
+
dataPoints: object[];
|
|
126
|
+
};
|
|
127
|
+
})[];
|
|
128
|
+
}[];
|
|
129
|
+
}[];
|
|
130
|
+
};
|
|
131
|
+
/**
|
|
132
|
+
* spec/otlp.md §6: a session's findings as OTLP/JSON logs, one record each (event name
|
|
133
|
+
* `blackbox.finding`, the code as its body), in the session's trace (the root span's ids).
|
|
134
|
+
*/
|
|
135
|
+
export declare function toOtlpLogs(lines: readonly string[]): {
|
|
136
|
+
resourceLogs: {
|
|
137
|
+
resource: {
|
|
138
|
+
attributes: Attr[];
|
|
139
|
+
};
|
|
140
|
+
scopeLogs: {
|
|
141
|
+
scope: {
|
|
142
|
+
name: string;
|
|
143
|
+
version: string;
|
|
144
|
+
};
|
|
145
|
+
logRecords: {
|
|
146
|
+
timeUnixNano: string;
|
|
147
|
+
severityNumber?: number;
|
|
148
|
+
severityText?: string;
|
|
149
|
+
body: {
|
|
150
|
+
stringValue: string;
|
|
151
|
+
};
|
|
152
|
+
attributes: AnyAttr[];
|
|
153
|
+
traceId: string;
|
|
154
|
+
spanId: string;
|
|
155
|
+
eventName: string;
|
|
156
|
+
}[];
|
|
157
|
+
}[];
|
|
158
|
+
}[];
|
|
159
|
+
};
|
|
160
|
+
/** One request the gateway forwarded, as it measured it (spec/otlp.md §7). */
|
|
161
|
+
export interface GatewayCall {
|
|
162
|
+
request_seq: number;
|
|
163
|
+
response_seq?: number;
|
|
164
|
+
provider: string;
|
|
165
|
+
model?: string;
|
|
166
|
+
status?: number;
|
|
167
|
+
/** The `llm.incomplete` reason, when the call didn't complete. */
|
|
168
|
+
reason?: string;
|
|
169
|
+
start_unix_nano: string;
|
|
170
|
+
end_unix_nano: string;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* spec/otlp.md §7: the gateway's own spans for a session's forwarded requests, in the session's
|
|
174
|
+
* trace: each a SERVER span `forward {provider}`, child of the record's model-call span (§1, §2).
|
|
175
|
+
*/
|
|
176
|
+
export declare function gatewayTrace(sessionId: string, calls: readonly GatewayCall[]): {
|
|
177
|
+
resourceSpans: {
|
|
178
|
+
resource: {
|
|
179
|
+
attributes: Attr[];
|
|
180
|
+
};
|
|
181
|
+
scopeSpans: {
|
|
182
|
+
scope: {
|
|
183
|
+
name: string;
|
|
184
|
+
version: string;
|
|
185
|
+
};
|
|
186
|
+
spans: {
|
|
187
|
+
traceId: string;
|
|
188
|
+
spanId: string;
|
|
189
|
+
parentSpanId: string;
|
|
190
|
+
name: string;
|
|
191
|
+
kind: number;
|
|
192
|
+
startTimeUnixNano: string;
|
|
193
|
+
endTimeUnixNano: string;
|
|
194
|
+
attributes: Attr[];
|
|
195
|
+
status?: {
|
|
196
|
+
code: number;
|
|
197
|
+
message: string;
|
|
198
|
+
};
|
|
199
|
+
}[];
|
|
200
|
+
}[];
|
|
201
|
+
}[];
|
|
202
|
+
};
|
|
48
203
|
export {};
|