@zanii/blackbox 0.22.0 → 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/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 +172 -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/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 +22 -9
- package/dist/index.js +23 -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 +12 -0
- package/dist/reconcile/record.js +114 -65
- 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/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
package/dist/compliance/index.js
CHANGED
|
@@ -32,6 +32,28 @@ const show = (v, ar = false) => {
|
|
|
32
32
|
}
|
|
33
33
|
return String(v);
|
|
34
34
|
};
|
|
35
|
+
/** spec/compliance.md §3: each fact's Arabic label (the key follows it, so tools still match). */
|
|
36
|
+
export const FACTS_AR = {
|
|
37
|
+
sessions: "الجلسات",
|
|
38
|
+
events: "الأحداث",
|
|
39
|
+
sessions_verified: "الجلسات التي تحققت سلاسلها",
|
|
40
|
+
sessions_anchored: "الجلسات المثبتة على السجل",
|
|
41
|
+
redactions: "الحجب",
|
|
42
|
+
findings_warning: "الملاحظات بمستوى تحذير",
|
|
43
|
+
bypasses: "تجاوزات البوابة",
|
|
44
|
+
false_claims: "الادعاءات غير الصحيحة",
|
|
45
|
+
blocks: "الإيقافات",
|
|
46
|
+
emergency_stops: "الإيقافات الطارئة",
|
|
47
|
+
resumes_with_note: "الاستئنافات المسببة",
|
|
48
|
+
outcomes: "النتائج",
|
|
49
|
+
data_region: "منطقة البيانات",
|
|
50
|
+
model_responses: "إجابات النموذج",
|
|
51
|
+
hallucination_findings: "ملاحظات الهلوسة",
|
|
52
|
+
grounding_checks: "فحوص الإسناد",
|
|
53
|
+
judge_checks: "فحوص المُقيِّم",
|
|
54
|
+
hallucination_controls: "ضوابط الهلوسة",
|
|
55
|
+
detector_accuracy: "دقة الكواشف",
|
|
56
|
+
};
|
|
35
57
|
export function renderReport(r) {
|
|
36
58
|
const ar = r.lang === "ar";
|
|
37
59
|
const out = [
|
|
@@ -47,9 +69,11 @@ export function renderReport(r) {
|
|
|
47
69
|
const head = ar ? "| الدليل | القيمة |" : "| Evidence | Value |";
|
|
48
70
|
out.push("", `## ${s.title}`, "", s.requirement, "", head, "|---|---|");
|
|
49
71
|
for (const [k, v] of Object.entries(s.evidence))
|
|
50
|
-
out.push(`| ${k} | ${show(v, ar)} |`);
|
|
72
|
+
out.push(`| ${ar && FACTS_AR[k] ? `${FACTS_AR[k]} (\`${k}\`)` : k} | ${show(v, ar)} |`);
|
|
51
73
|
}
|
|
52
|
-
|
|
74
|
+
const body = `${out.join("\n")}\n`;
|
|
75
|
+
// Right to left, as the incident report (spec/fleet.md §2)
|
|
76
|
+
return ar ? `<div dir="rtl" lang="ar">\n\n${body}\n</div>\n` : body;
|
|
53
77
|
}
|
|
54
78
|
/** spec/compliance.md §1: the facts over sessions' records (the server adds verified / anchored). */
|
|
55
79
|
export function complianceFacts(sessions, dataRegion) {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Bodies } from "../reconcile/record.ts";
|
|
2
|
+
import { type LoadedPrices, type Tokens } from "./index.ts";
|
|
3
|
+
export interface BatchItem {
|
|
4
|
+
custom_id: string;
|
|
5
|
+
status: string;
|
|
6
|
+
model: string | null;
|
|
7
|
+
joined: boolean;
|
|
8
|
+
tokens: Tokens;
|
|
9
|
+
micro_usd: number | null;
|
|
10
|
+
}
|
|
11
|
+
export interface BatchEntry {
|
|
12
|
+
seq: number;
|
|
13
|
+
request_seq: number;
|
|
14
|
+
provider: string | null;
|
|
15
|
+
source_seq: number | null;
|
|
16
|
+
results: number;
|
|
17
|
+
joined: number;
|
|
18
|
+
micro_usd: number | null;
|
|
19
|
+
items: BatchItem[];
|
|
20
|
+
}
|
|
21
|
+
export interface BatchReport {
|
|
22
|
+
v: 1;
|
|
23
|
+
batches: BatchEntry[];
|
|
24
|
+
}
|
|
25
|
+
/** The session's batch results joined to their requests and priced (spec/cost.md §6). */
|
|
26
|
+
export declare function batchReport(lines: readonly string[], bodies: Bodies, prices: LoadedPrices): BatchReport;
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
// Batch results joined to their requests (spec/cost.md §6, spec/providers.md §6): each results
|
|
2
|
+
// download read again from its recorded chunks, its lines joined by custom_id to the call that
|
|
3
|
+
// carried the batch's requests, each result priced at the batch rate.
|
|
4
|
+
// Mirrors sdks/python/src/zanii_blackbox/batch.py.
|
|
5
|
+
import { priceCall } from "./index.js";
|
|
6
|
+
const decoder = new TextDecoder();
|
|
7
|
+
const isObj = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
8
|
+
const ZERO = { input: 0, output: 0, cache_read: 0, cache_write: 0 };
|
|
9
|
+
function jsonLines(text) {
|
|
10
|
+
const out = [];
|
|
11
|
+
for (const line of text.split("\n")) {
|
|
12
|
+
if (!line.trim())
|
|
13
|
+
continue;
|
|
14
|
+
try {
|
|
15
|
+
const o = JSON.parse(line);
|
|
16
|
+
if (isObj(o))
|
|
17
|
+
out.push(o);
|
|
18
|
+
}
|
|
19
|
+
catch { }
|
|
20
|
+
}
|
|
21
|
+
return out;
|
|
22
|
+
}
|
|
23
|
+
/** One result line as {id, status, model, usage}, or null when it isn't one. */
|
|
24
|
+
function resultOf(o) {
|
|
25
|
+
if (typeof o.custom_id !== "string")
|
|
26
|
+
return null;
|
|
27
|
+
if (isObj(o.result)) {
|
|
28
|
+
// Anthropic: {custom_id, result: {type, message?}}
|
|
29
|
+
const message = isObj(o.result.message) ? o.result.message : {};
|
|
30
|
+
const status = typeof o.result.type === "string" ? o.result.type : "errored";
|
|
31
|
+
return { id: o.custom_id, status, model: message.model, usage: message.usage };
|
|
32
|
+
}
|
|
33
|
+
if (!("response" in o) && !("error" in o))
|
|
34
|
+
return null;
|
|
35
|
+
// OpenAI: {custom_id, response: {status_code, body}, error}
|
|
36
|
+
const response = isObj(o.response) ? o.response : {};
|
|
37
|
+
const code = response.status_code;
|
|
38
|
+
const ok = typeof code === "number" && code >= 200 && code < 300 && o.error == null;
|
|
39
|
+
const body = isObj(response.body) ? response.body : {};
|
|
40
|
+
return {
|
|
41
|
+
id: o.custom_id,
|
|
42
|
+
status: ok ? "succeeded" : "errored",
|
|
43
|
+
model: body.model,
|
|
44
|
+
usage: body.usage,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/** Flattened integer usage, as the gateway records it (spec/api.md §3). */
|
|
48
|
+
function flatten(usage) {
|
|
49
|
+
const out = {};
|
|
50
|
+
const walk = (o, prefix) => {
|
|
51
|
+
for (const [k, v] of Object.entries(o)) {
|
|
52
|
+
if (typeof v === "number" && Number.isSafeInteger(v))
|
|
53
|
+
out[prefix + k] = v;
|
|
54
|
+
else if (isObj(v))
|
|
55
|
+
walk(v, `${prefix}${k}_`);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
if (isObj(usage))
|
|
59
|
+
walk(usage, "");
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
62
|
+
/** The custom_ids a batch's source call carried: Anthropic's requests[], or OpenAI's JSONL file. */
|
|
63
|
+
function customIds(text) {
|
|
64
|
+
const ids = new Set();
|
|
65
|
+
let whole;
|
|
66
|
+
try {
|
|
67
|
+
whole = JSON.parse(text);
|
|
68
|
+
}
|
|
69
|
+
catch { }
|
|
70
|
+
if (isObj(whole) && Array.isArray(whole.requests)) {
|
|
71
|
+
for (const r of whole.requests)
|
|
72
|
+
if (isObj(r) && typeof r.custom_id === "string")
|
|
73
|
+
ids.add(r.custom_id);
|
|
74
|
+
return ids;
|
|
75
|
+
}
|
|
76
|
+
for (const o of jsonLines(text))
|
|
77
|
+
if (typeof o.custom_id === "string")
|
|
78
|
+
ids.add(o.custom_id);
|
|
79
|
+
return ids;
|
|
80
|
+
}
|
|
81
|
+
/** The session's batch results joined to their requests and priced (spec/cost.md §6). */
|
|
82
|
+
export function batchReport(lines, bodies, prices) {
|
|
83
|
+
const events = lines.map((l) => JSON.parse(l));
|
|
84
|
+
const requests = new Map();
|
|
85
|
+
const answers = new Map(); // request seq → its llm.response meta
|
|
86
|
+
const chunks = new Map(); // decoded whole: a character can span chunks
|
|
87
|
+
for (const e of events) {
|
|
88
|
+
const owner = e.meta.request_seq;
|
|
89
|
+
if (e.kind === "llm.request")
|
|
90
|
+
requests.set(e.seq, e);
|
|
91
|
+
else if (e.kind === "llm.chunk" && typeof owner === "number") {
|
|
92
|
+
const list = chunks.get(owner) ?? [];
|
|
93
|
+
list.push(bodies(e.body_hash) ?? new Uint8Array());
|
|
94
|
+
chunks.set(owner, list);
|
|
95
|
+
}
|
|
96
|
+
else if (e.kind === "llm.response" && typeof owner === "number")
|
|
97
|
+
answers.set(owner, e.meta);
|
|
98
|
+
}
|
|
99
|
+
const pathOf = (seq) => {
|
|
100
|
+
const p = requests.get(seq)?.meta.path;
|
|
101
|
+
return typeof p === "string" ? p.split("?")[0] : "";
|
|
102
|
+
};
|
|
103
|
+
const postTo = (seq, tail) => requests.get(seq)?.meta.method === "POST" && tail.test(pathOf(seq));
|
|
104
|
+
/** The latest request before `before` whose answer has `field` = `value` and that passes `ok`. */
|
|
105
|
+
const answered = (field, value, before, ok) => {
|
|
106
|
+
let found = null;
|
|
107
|
+
for (const [seq, meta] of answers)
|
|
108
|
+
if (seq < before && meta[field] === value && ok(seq))
|
|
109
|
+
found = seq;
|
|
110
|
+
return found;
|
|
111
|
+
};
|
|
112
|
+
const batches = [];
|
|
113
|
+
for (const e of events) {
|
|
114
|
+
const requestSeq = e.meta.request_seq;
|
|
115
|
+
if (e.kind !== "llm.response" || !isObj(e.meta.batch) || typeof requestSeq !== "number")
|
|
116
|
+
continue;
|
|
117
|
+
const path = pathOf(requestSeq);
|
|
118
|
+
let source = null;
|
|
119
|
+
const anthropic = /\/messages\/batches\/([^/]+)\/results$/.exec(path);
|
|
120
|
+
const openai = /\/files\/([^/]+)\/content$/.exec(path);
|
|
121
|
+
if (anthropic)
|
|
122
|
+
source = answered("batch_id", anthropic[1], requestSeq, (s) => postTo(s, /\/messages\/batches$/));
|
|
123
|
+
else if (openai) {
|
|
124
|
+
const file = openai[1];
|
|
125
|
+
let input;
|
|
126
|
+
for (const [seq, meta] of answers)
|
|
127
|
+
if (seq < requestSeq && (meta.output_file_id === file || meta.error_file_id === file))
|
|
128
|
+
input = meta.input_file_id;
|
|
129
|
+
if (typeof input === "string")
|
|
130
|
+
source = answered("file_id", input, requestSeq, (s) => postTo(s, /\/files$/));
|
|
131
|
+
}
|
|
132
|
+
const sourceRequest = source === null ? undefined : requests.get(source);
|
|
133
|
+
const sourceBody = sourceRequest ? bodies(sourceRequest.body_hash) : undefined;
|
|
134
|
+
const ids = sourceBody ? customIds(decoder.decode(sourceBody)) : new Set();
|
|
135
|
+
const p = requests.get(requestSeq)?.meta.provider;
|
|
136
|
+
const provider = typeof p === "string" ? p : null;
|
|
137
|
+
const items = [];
|
|
138
|
+
let total = 0;
|
|
139
|
+
for (const line of jsonLines(decoder.decode(Buffer.concat(chunks.get(requestSeq) ?? [])))) {
|
|
140
|
+
const r = resultOf(line);
|
|
141
|
+
if (!r)
|
|
142
|
+
continue;
|
|
143
|
+
const model = typeof r.model === "string" ? r.model : null;
|
|
144
|
+
let tokens = ZERO;
|
|
145
|
+
let micro = 0;
|
|
146
|
+
if (r.status === "succeeded") {
|
|
147
|
+
const priced = priceCall({ model, usage: flatten(r.usage), provider, batch: { requests: 1 } }, prices.table);
|
|
148
|
+
tokens = priced.tokens;
|
|
149
|
+
micro = priced.micro_usd;
|
|
150
|
+
}
|
|
151
|
+
total = total === null || micro === null ? null : total + micro;
|
|
152
|
+
items.push({
|
|
153
|
+
custom_id: r.id,
|
|
154
|
+
status: r.status,
|
|
155
|
+
model,
|
|
156
|
+
joined: ids.has(r.id),
|
|
157
|
+
tokens,
|
|
158
|
+
micro_usd: micro,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
batches.push({
|
|
162
|
+
seq: e.seq,
|
|
163
|
+
request_seq: requestSeq,
|
|
164
|
+
provider,
|
|
165
|
+
source_seq: source,
|
|
166
|
+
results: items.length,
|
|
167
|
+
joined: items.filter((i) => i.joined).length,
|
|
168
|
+
micro_usd: total,
|
|
169
|
+
items,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
return { v: 1, batches };
|
|
173
|
+
}
|
package/dist/cost/index.d.ts
CHANGED
|
@@ -30,6 +30,8 @@ export interface ModelPrice {
|
|
|
30
30
|
service_tiers?: Record<string, ClassPrices>;
|
|
31
31
|
/** `deprecated` feeds the MODEL_DEPRECATED finding; `beta` is informational. */
|
|
32
32
|
status?: "deprecated" | "beta";
|
|
33
|
+
/** The percent of the price a batch result pays (spec/cost.md §1); absent: no known batch rate. */
|
|
34
|
+
batch_percent?: number;
|
|
33
35
|
}
|
|
34
36
|
export interface PriceTable {
|
|
35
37
|
version: string;
|
|
@@ -81,6 +83,14 @@ export interface CallCost {
|
|
|
81
83
|
source?: "reported";
|
|
82
84
|
/** The answer carried no usage (`usage_missing`): unpriced, never counted as free. */
|
|
83
85
|
no_usage?: true;
|
|
86
|
+
/** A batch result (spec/cost.md §2): its result count, and the batch percent it was priced at. */
|
|
87
|
+
batch?: {
|
|
88
|
+
requests: number;
|
|
89
|
+
percent?: number;
|
|
90
|
+
};
|
|
91
|
+
/** spec/cost.md §2: an MCP client's answer to a server's `sampling/createMessage`, or a usage
|
|
92
|
+
* report in a realtime session (spec/providers.md §7). */
|
|
93
|
+
via?: "mcp_sampling" | "realtime";
|
|
84
94
|
}
|
|
85
95
|
export interface CostReport {
|
|
86
96
|
v: 1;
|
|
@@ -145,6 +155,10 @@ export declare function priceCall(call: {
|
|
|
145
155
|
provider?: string | null;
|
|
146
156
|
serviceTier?: string | null;
|
|
147
157
|
reportedMicroUsd?: number | null;
|
|
158
|
+
/** A batch result (spec/cost.md §2): priced at the model's batch_percent, or not at all. */
|
|
159
|
+
batch?: {
|
|
160
|
+
requests: number;
|
|
161
|
+
};
|
|
148
162
|
}, table: PriceTable): Omit<CallCost, "seq" | "model"> & {
|
|
149
163
|
key: string | null;
|
|
150
164
|
};
|
|
@@ -154,5 +168,12 @@ export declare function upliftPercent(table: PriceTable, provider: string | null
|
|
|
154
168
|
/** fils at the CBUAE peg (3.6725 AED / USD), rounded half up. */
|
|
155
169
|
export declare function toAedFils(microUsd: number): number;
|
|
156
170
|
export declare function formatAed(fils: number): string;
|
|
171
|
+
/** A results download's `meta.batch.models` (spec/providers.md §6), the well-formed entries; null
|
|
172
|
+
* when the response isn't one. */
|
|
173
|
+
export declare function batchModels(batch: unknown): Array<{
|
|
174
|
+
model: string;
|
|
175
|
+
requests: number;
|
|
176
|
+
usage: Record<string, unknown>;
|
|
177
|
+
}> | null;
|
|
157
178
|
/** The session's cost report from its recorded lines. */
|
|
158
179
|
export declare function sessionCost(lines: readonly string[], prices: LoadedPrices): CostReport;
|
package/dist/cost/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// The cost engine (spec/cost.md): integer micro-USD from a versioned price table, AED display.
|
|
2
2
|
// Mirrors sdks/python/src/zanii_blackbox/cost.py.
|
|
3
3
|
import { createHash } from "node:crypto";
|
|
4
|
+
import { envelopesOf } from "../reconcile/record.js";
|
|
4
5
|
const CLASSES = ["input", "output", "cache_read", "cache_write"];
|
|
5
6
|
const EXTRA = [
|
|
6
7
|
"cache_write_1h",
|
|
@@ -47,6 +48,9 @@ export function loadPrices(bytes) {
|
|
|
47
48
|
whole(model, `service_tiers.${name}.${c}`, t[c]);
|
|
48
49
|
if (p.status !== undefined && p.status !== "deprecated" && p.status !== "beta")
|
|
49
50
|
throw new Error(`price table: ${model}.status must be deprecated or beta`);
|
|
51
|
+
if (p.batch_percent !== undefined &&
|
|
52
|
+
!(Number.isSafeInteger(p.batch_percent) && p.batch_percent >= 1 && p.batch_percent <= 100))
|
|
53
|
+
throw new Error(`price table: ${model}.batch_percent must be an integer 1-100`);
|
|
50
54
|
}
|
|
51
55
|
if (table.uplifts !== undefined) {
|
|
52
56
|
if (!Array.isArray(table.uplifts))
|
|
@@ -85,7 +89,8 @@ provider) {
|
|
|
85
89
|
return plain !== model ? lookup(plain, table) : null;
|
|
86
90
|
}
|
|
87
91
|
const GEO = /^(?:us|eu|apac|au|ca|jp|global|us-gov)\./;
|
|
88
|
-
|
|
92
|
+
// `google/`: Vertex's OpenAI-compatible route (spec/cost.md §1)
|
|
93
|
+
const VENDOR = /^(?:(?:anthropic|amazon|meta|mistral|cohere|ai21|deepseek|openai|qwen|writer|google)\.|google\/)/;
|
|
89
94
|
function lookup(model, table) {
|
|
90
95
|
const exact = table.models[model];
|
|
91
96
|
if (exact)
|
|
@@ -96,6 +101,12 @@ function lookup(model, table) {
|
|
|
96
101
|
best = key;
|
|
97
102
|
return best === null ? null : { key: best, price: table.models[best] };
|
|
98
103
|
}
|
|
104
|
+
const SONIC = [
|
|
105
|
+
"input_speechTokens",
|
|
106
|
+
"input_textTokens",
|
|
107
|
+
"output_speechTokens",
|
|
108
|
+
"output_textTokens",
|
|
109
|
+
];
|
|
99
110
|
/** Token classes from a recorded, flattened `meta.usage` (spec/cost.md §2). */
|
|
100
111
|
export function tokensOf(usage) {
|
|
101
112
|
const n = (k) => {
|
|
@@ -105,14 +116,16 @@ export function tokensOf(usage) {
|
|
|
105
116
|
if ("promptTokenCount" in usage) {
|
|
106
117
|
// Gemini (spec/providers.md §5): the prompt count includes the cached part; thoughts are output.
|
|
107
118
|
const cached = n("cachedContentTokenCount");
|
|
119
|
+
// Gemini Live names its output `responseTokenCount` (spec/providers.md §7)
|
|
108
120
|
const audioIn = n("promptTokensDetails_AUDIO");
|
|
109
|
-
const audioOut = n("candidatesTokensDetails_AUDIO");
|
|
121
|
+
const audioOut = n("candidatesTokensDetails_AUDIO") + n("responseTokensDetails_AUDIO");
|
|
110
122
|
const imageIn = n("promptTokensDetails_IMAGE");
|
|
111
|
-
const imageOut = n("candidatesTokensDetails_IMAGE");
|
|
123
|
+
const imageOut = n("candidatesTokensDetails_IMAGE") + n("responseTokensDetails_IMAGE");
|
|
112
124
|
return extras({
|
|
113
125
|
input: Math.max(0, n("promptTokenCount") - cached - audioIn - imageIn) +
|
|
114
126
|
n("toolUsePromptTokenCount"),
|
|
115
|
-
output: Math.max(0, n("candidatesTokenCount") - audioOut - imageOut) +
|
|
127
|
+
output: Math.max(0, n("candidatesTokenCount") + n("responseTokenCount") - audioOut - imageOut) +
|
|
128
|
+
n("thoughtsTokenCount"),
|
|
116
129
|
cache_read: cached,
|
|
117
130
|
cache_write: 0,
|
|
118
131
|
}, {
|
|
@@ -122,6 +135,14 @@ export function tokensOf(usage) {
|
|
|
122
135
|
output_image: imageOut,
|
|
123
136
|
});
|
|
124
137
|
}
|
|
138
|
+
if (SONIC.some((k) => k in usage))
|
|
139
|
+
// Nova Sonic's usageEvent delta (spec/providers.md §8): speech is priced as audio
|
|
140
|
+
return extras({
|
|
141
|
+
input: n("input_textTokens"),
|
|
142
|
+
output: n("output_textTokens"),
|
|
143
|
+
cache_read: 0,
|
|
144
|
+
cache_write: 0,
|
|
145
|
+
}, { input_audio: n("input_speechTokens"), output_audio: n("output_speechTokens") });
|
|
125
146
|
if ("inputTokens" in usage)
|
|
126
147
|
// Bedrock Converse (spec/providers.md §5)
|
|
127
148
|
return {
|
|
@@ -146,16 +167,22 @@ export function tokensOf(usage) {
|
|
|
146
167
|
// Anthropic's cache names are its own: they win over the Responses shape, since Anthropic also
|
|
147
168
|
// sends output_tokens_details (thinking_tokens) since Claude Sonnet 5
|
|
148
169
|
const anthropicCache = "cache_read_input_tokens" in usage || "cache_creation_input_tokens" in usage;
|
|
149
|
-
// OpenAI's transcriptions write input_token_details (singular); its images,
|
|
170
|
+
// OpenAI's transcriptions and Realtime write input_token_details (singular); its images,
|
|
171
|
+
// image_tokens. Realtime's audio and image counts include their cached part (spec/providers.md §7).
|
|
150
172
|
if (!anthropicCache &&
|
|
151
173
|
Object.keys(usage).some((k) => k.startsWith("input_tokens_details_") ||
|
|
152
174
|
k.startsWith("output_tokens_details_") ||
|
|
153
|
-
k.startsWith("input_token_details_")
|
|
154
|
-
|
|
175
|
+
k.startsWith("input_token_details_") ||
|
|
176
|
+
k.startsWith("output_token_details_"))) {
|
|
177
|
+
const cached = n("input_tokens_details_cached_tokens") + n("input_token_details_cached_tokens");
|
|
155
178
|
const written = n("input_tokens_details_cache_write_tokens");
|
|
156
|
-
const audioIn = n("input_tokens_details_audio_tokens") +
|
|
157
|
-
|
|
158
|
-
|
|
179
|
+
const audioIn = n("input_tokens_details_audio_tokens") +
|
|
180
|
+
Math.max(0, n("input_token_details_audio_tokens") -
|
|
181
|
+
n("input_token_details_cached_tokens_details_audio_tokens"));
|
|
182
|
+
const audioOut = n("output_tokens_details_audio_tokens") + n("output_token_details_audio_tokens");
|
|
183
|
+
const imageIn = n("input_tokens_details_image_tokens") +
|
|
184
|
+
Math.max(0, n("input_token_details_image_tokens") -
|
|
185
|
+
n("input_token_details_cached_tokens_details_image_tokens"));
|
|
159
186
|
const imageOut = n("output_tokens_details_image_tokens");
|
|
160
187
|
return extras({
|
|
161
188
|
input: Math.max(0, n("input_tokens") - cached - written - audioIn - imageIn),
|
|
@@ -249,7 +276,17 @@ export function priceCall(call, table) {
|
|
|
249
276
|
const match = call.model === null ? null : priceFor(call.model, table, call.provider);
|
|
250
277
|
if (match) {
|
|
251
278
|
const { price, tier } = effectivePrice(match.price, tokens, call.serviceTier);
|
|
252
|
-
const
|
|
279
|
+
const percent = match.price.batch_percent;
|
|
280
|
+
if (call.batch && percent === undefined)
|
|
281
|
+
return {
|
|
282
|
+
key: match.key,
|
|
283
|
+
tokens,
|
|
284
|
+
micro_usd: null,
|
|
285
|
+
...(requests ? { requests } : {}),
|
|
286
|
+
batch: { requests: call.batch.requests },
|
|
287
|
+
};
|
|
288
|
+
const full = callMicroUsd(tokens, price, requests);
|
|
289
|
+
const base = call.batch ? Math.floor((full * percent + 50) / 100) : full;
|
|
253
290
|
const uplift = upliftPercent(table, call.provider ?? null, call.model, call.usage);
|
|
254
291
|
return {
|
|
255
292
|
key: match.key,
|
|
@@ -258,8 +295,19 @@ export function priceCall(call, table) {
|
|
|
258
295
|
...(requests ? { requests } : {}),
|
|
259
296
|
...(tier ? { tier } : {}),
|
|
260
297
|
...(uplift ? { uplift_percent: uplift } : {}),
|
|
298
|
+
...(call.batch
|
|
299
|
+
? { batch: { requests: call.batch.requests, percent: percent } }
|
|
300
|
+
: {}),
|
|
261
301
|
};
|
|
262
302
|
}
|
|
303
|
+
if (call.batch)
|
|
304
|
+
return {
|
|
305
|
+
key: null,
|
|
306
|
+
tokens,
|
|
307
|
+
micro_usd: null,
|
|
308
|
+
...(requests ? { requests } : {}),
|
|
309
|
+
batch: { requests: call.batch.requests },
|
|
310
|
+
};
|
|
263
311
|
if (typeof call.reportedMicroUsd === "number")
|
|
264
312
|
return {
|
|
265
313
|
key: null,
|
|
@@ -294,6 +342,22 @@ export function formatAed(fils) {
|
|
|
294
342
|
const abs = Math.abs(fils);
|
|
295
343
|
return `${sign}AED ${Math.floor(abs / 100)}.${String(abs % 100).padStart(2, "0")}`;
|
|
296
344
|
}
|
|
345
|
+
/** A results download's `meta.batch.models` (spec/providers.md §6), the well-formed entries; null
|
|
346
|
+
* when the response isn't one. */
|
|
347
|
+
export function batchModels(batch) {
|
|
348
|
+
if (typeof batch !== "object" || batch === null)
|
|
349
|
+
return null;
|
|
350
|
+
const models = batch.models;
|
|
351
|
+
if (!Array.isArray(models))
|
|
352
|
+
return null;
|
|
353
|
+
return models.filter((m) => typeof m === "object" &&
|
|
354
|
+
m !== null &&
|
|
355
|
+
typeof m.model === "string" &&
|
|
356
|
+
Number.isSafeInteger(m.requests) &&
|
|
357
|
+
typeof m.usage === "object" &&
|
|
358
|
+
m.usage !== null &&
|
|
359
|
+
!Array.isArray(m.usage));
|
|
360
|
+
}
|
|
297
361
|
/** The session's cost report from its recorded lines. */
|
|
298
362
|
export function sessionCost(lines, prices) {
|
|
299
363
|
const calls = [];
|
|
@@ -304,8 +368,7 @@ export function sessionCost(lines, prices) {
|
|
|
304
368
|
const providers = new Map(); // request seq → the configured provider id
|
|
305
369
|
let webSearch = 0;
|
|
306
370
|
let pinnedSha; // session.open's meta.prices_sha256 (L3.1.5)
|
|
307
|
-
for (const
|
|
308
|
-
const e = JSON.parse(line);
|
|
371
|
+
for (const e of envelopesOf(lines)) {
|
|
309
372
|
if (e.kind === "session.open")
|
|
310
373
|
pinnedSha = e.meta.prices_sha256;
|
|
311
374
|
if (e.kind === "llm.request") {
|
|
@@ -316,10 +379,44 @@ export function sessionCost(lines, prices) {
|
|
|
316
379
|
providers.set(e.seq, e.meta.provider);
|
|
317
380
|
continue;
|
|
318
381
|
}
|
|
319
|
-
|
|
382
|
+
// spec/cost.md §2: a client's sampling reply that names its model and usage is a model call too
|
|
383
|
+
const sampling = e.kind === "tool.sampling" && e.meta.phase === "reply" && typeof e.meta.model === "string";
|
|
384
|
+
// spec/providers.md §7: a realtime session's usage reports are its model calls
|
|
385
|
+
const realtime = e.kind === "llm.realtime.server";
|
|
386
|
+
if (e.kind !== "llm.response" && e.kind !== "llm.incomplete" && !sampling && !realtime)
|
|
320
387
|
continue;
|
|
321
|
-
if (replayed.has(e.meta.request_seq))
|
|
388
|
+
if (!sampling && replayed.has(e.meta.request_seq))
|
|
322
389
|
continue;
|
|
390
|
+
const provider = sampling ? null : (providers.get(e.meta.request_seq) ?? null);
|
|
391
|
+
const via = sampling
|
|
392
|
+
? { via: "mcp_sampling" }
|
|
393
|
+
: realtime
|
|
394
|
+
? { via: "realtime" }
|
|
395
|
+
: {};
|
|
396
|
+
const add = (seq, model, priced) => {
|
|
397
|
+
const { key, ...call } = priced;
|
|
398
|
+
for (const c of CLASSES)
|
|
399
|
+
total[c] += call.tokens[c];
|
|
400
|
+
for (const c of EXTRA)
|
|
401
|
+
if (call.tokens[c] !== undefined)
|
|
402
|
+
total[c] = (total[c] ?? 0) + call.tokens[c];
|
|
403
|
+
webSearch += call.requests?.web_search ?? 0;
|
|
404
|
+
// a model with a price but no batch rate is unpriced, not unknown (spec/cost.md §2)
|
|
405
|
+
if (call.micro_usd === null) {
|
|
406
|
+
if (key === null)
|
|
407
|
+
unknown.add(model ?? "(no model)");
|
|
408
|
+
}
|
|
409
|
+
else
|
|
410
|
+
micro += call.micro_usd;
|
|
411
|
+
calls.push({ seq, model, ...call, ...via });
|
|
412
|
+
};
|
|
413
|
+
// spec/cost.md §2: a batch results download, one call per model, at the batch rate
|
|
414
|
+
const batch = sampling ? null : batchModels(e.meta.batch);
|
|
415
|
+
if (batch) {
|
|
416
|
+
for (const m of batch)
|
|
417
|
+
add(e.seq, m.model, priceCall({ model: m.model, usage: m.usage, provider, batch: { requests: m.requests } }, prices.table));
|
|
418
|
+
continue;
|
|
419
|
+
}
|
|
323
420
|
const usage = e.meta.usage;
|
|
324
421
|
const model = typeof e.meta.model === "string" ? e.meta.model : null;
|
|
325
422
|
if (typeof usage !== "object" || usage === null) {
|
|
@@ -334,27 +431,15 @@ export function sessionCost(lines, prices) {
|
|
|
334
431
|
});
|
|
335
432
|
continue;
|
|
336
433
|
}
|
|
337
|
-
|
|
434
|
+
add(e.seq, model, priceCall({
|
|
338
435
|
model,
|
|
339
436
|
usage: usage,
|
|
340
|
-
provider
|
|
437
|
+
provider,
|
|
341
438
|
serviceTier: typeof e.meta.service_tier === "string" ? e.meta.service_tier : null,
|
|
342
439
|
reportedMicroUsd: Number.isSafeInteger(e.meta.reported_cost_micro_usd)
|
|
343
440
|
? e.meta.reported_cost_micro_usd
|
|
344
441
|
: null,
|
|
345
|
-
}, prices.table);
|
|
346
|
-
const { key: _key, ...call } = priced;
|
|
347
|
-
for (const c of CLASSES)
|
|
348
|
-
total[c] += call.tokens[c];
|
|
349
|
-
for (const c of EXTRA)
|
|
350
|
-
if (call.tokens[c] !== undefined)
|
|
351
|
-
total[c] = (total[c] ?? 0) + call.tokens[c];
|
|
352
|
-
webSearch += call.requests?.web_search ?? 0;
|
|
353
|
-
if (call.micro_usd === null)
|
|
354
|
-
unknown.add(model ?? "(no model)");
|
|
355
|
-
else
|
|
356
|
-
micro += call.micro_usd;
|
|
357
|
-
calls.push({ seq: e.seq, model, ...call });
|
|
442
|
+
}, prices.table));
|
|
358
443
|
}
|
|
359
444
|
return {
|
|
360
445
|
v: 1,
|
package/dist/cv/index.js
CHANGED
|
@@ -17,7 +17,7 @@ export function buildCv(o) {
|
|
|
17
17
|
v: 1,
|
|
18
18
|
type: "agentcv",
|
|
19
19
|
did: o.did,
|
|
20
|
-
owner: o.owner
|
|
20
|
+
...(o.owner != null ? { owner: o.owner } : {}), // left out when unset, as zanii 0.26 does
|
|
21
21
|
summary: o.summary,
|
|
22
22
|
work: o.work ?? [],
|
|
23
23
|
authority: o.authority ?? [],
|
package/dist/duty/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Duty-time limits (spec/duty.md): long sessions and full context windows, detected. Pure; mirrors
|
|
2
2
|
// sdks/python/src/zanii_blackbox/duty.py; pinned by spec/vectors/duty.json.
|
|
3
3
|
import { priceFor, promptTokens, sessionCost } from "../cost/index.js";
|
|
4
|
+
import { envelopesOf } from "../reconcile/record.js";
|
|
4
5
|
const KEYS = ["max_minutes", "max_llm_calls", "max_context_pct"];
|
|
5
6
|
/** spec/duty.md §1: the flight plan's limits win, limit by limit, over the server's defaults. */
|
|
6
7
|
function limitsOf(open, defaults) {
|
|
@@ -15,7 +16,7 @@ function limitsOf(open, defaults) {
|
|
|
15
16
|
}
|
|
16
17
|
/** spec/duty.md §2: at most one DUTY_LIMIT per limit, where it was first crossed. */
|
|
17
18
|
export function dutyFindings(lines, prices, defaults = {}) {
|
|
18
|
-
const events = lines
|
|
19
|
+
const events = envelopesOf(lines);
|
|
19
20
|
const open = events[0]?.kind === "session.open" ? events[0] : undefined;
|
|
20
21
|
const limits = limitsOf(open, defaults);
|
|
21
22
|
const out = [];
|