glassbox-trace 0.11.0 → 0.12.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 +77 -9
- package/dist/glassbox.html +158 -15
- package/package.json +3 -2
- package/src/claims.mjs +270 -36
- package/src/cli.mjs +181 -30
- package/src/fence.mjs +179 -26
- package/src/guard.mjs +5 -3
- package/src/jsonl.mjs +104 -0
- package/src/managed-paths.mjs +32 -0
- package/src/managed.mjs +76 -0
- package/src/memory-audit.mjs +14 -4
- package/src/memory.mjs +158 -7
- package/src/plan.mjs +253 -0
- package/src/statusline.mjs +154 -0
- package/src/trace-core.js +158 -15
package/src/trace-core.js
CHANGED
|
@@ -9,15 +9,20 @@
|
|
|
9
9
|
})(typeof globalThis !== 'undefined' ? globalThis : this, function () {
|
|
10
10
|
'use strict';
|
|
11
11
|
|
|
12
|
-
const VERSION = '0.
|
|
12
|
+
const VERSION = '0.12.0';
|
|
13
13
|
|
|
14
14
|
// ---------------------------------------------------------------------------
|
|
15
15
|
// Rate card (USD per million tokens). Prefix-matched against model ids so dated
|
|
16
16
|
// ids like claude-sonnet-4-5-20250929 resolve. Longest prefix wins.
|
|
17
|
-
// Source: platform.claude.com/docs/en/about-claude/pricing, fetched 2026-
|
|
17
|
+
// Source: platform.claude.com/docs/en/about-claude/pricing, fetched 2026-10-25.
|
|
18
|
+
// `over`: a second price set for requests whose prompt (input + cache reads + cache writes) is over
|
|
19
|
+
// `at` tokens; the whole request pays it (Haiku 5.5).
|
|
18
20
|
// ---------------------------------------------------------------------------
|
|
19
21
|
const RATES = {
|
|
20
22
|
'claude-fable-5-1': { in: 10, out: 50, w5m: 12.5, w1h: 20, read: 0.25 },
|
|
23
|
+
'claude-opus-5-5': { in: 4, out: 20, w5m: 5, w1h: 8, read: 0.2 },
|
|
24
|
+
'claude-sonnet-5-5': { in: 2, out: 10, w5m: 2.5, w1h: 4, read: 0.1 },
|
|
25
|
+
'claude-haiku-5-5': { in: 0.1, out: 0.5, w5m: 0.125, w1h: 0.2, read: 0.01, over: { at: 100000, in: 0.5, out: 2.5, w5m: 0.625, w1h: 1, read: 0.05 } },
|
|
21
26
|
'claude-mythos-5-1': { in: 10, out: 50, w5m: 12.5, w1h: 20, read: 0.25 },
|
|
22
27
|
'claude-fable-5': { in: 10, out: 50, w5m: 12.5, w1h: 20, read: 1.0 },
|
|
23
28
|
'claude-mythos-5': { in: 10, out: 50, w5m: 12.5, w1h: 20, read: 1.0 },
|
|
@@ -29,10 +34,15 @@
|
|
|
29
34
|
'claude-opus-4-1': { in: 15, out: 75, w5m: 18.75, w1h: 30, read: 1.5 },
|
|
30
35
|
'claude-opus-4-2025': { in: 15, out: 75, w5m: 18.75, w1h: 30, read: 1.5 },
|
|
31
36
|
'claude-sonnet-5': { in: 2, out: 10, w5m: 2.5, w1h: 4, read: 0.2 },
|
|
37
|
+
'claude-sonnet-4-6': { in: 3, out: 15, w5m: 3.75, w1h: 6, read: 0.3 },
|
|
38
|
+
'claude-sonnet-4-5': { in: 3, out: 15, w5m: 3.75, w1h: 6, read: 0.3 },
|
|
32
39
|
'claude-sonnet-4': { in: 3, out: 15, w5m: 3.75, w1h: 6, read: 0.3 },
|
|
33
40
|
'claude-haiku-4-5': { in: 1, out: 5, w5m: 1.25, w1h: 2, read: 0.1 },
|
|
34
41
|
'claude-3-5-haiku': { in: 0.8, out: 4, w5m: 1.0, w1h: 1.6, read: 0.08 },
|
|
35
42
|
};
|
|
43
|
+
// The day RATES was checked against the pricing page. rateCardCheck() warns when a session ran long after it.
|
|
44
|
+
const RATES_DATE = '2026-10-25';
|
|
45
|
+
const RATES_STALE_DAYS = 60;
|
|
36
46
|
|
|
37
47
|
const DEFAULTS = {
|
|
38
48
|
retryLoopMin: 3,
|
|
@@ -112,6 +122,37 @@
|
|
|
112
122
|
return Number.isFinite(ms) ? ms : null;
|
|
113
123
|
}
|
|
114
124
|
|
|
125
|
+
// A big transcript's huge strings (multi-MB tool results, images) are kept as head + marker + tail when it is read
|
|
126
|
+
// line by line (src/jsonl.mjs); the marker carries how many characters were left out, so every size below is the
|
|
127
|
+
// original size. Strings without a marker cost one indexOf. DESIGN.md §21.
|
|
128
|
+
// The marker holds a nonce made once per process, so a marker-shaped string already in a transcript (a web page,
|
|
129
|
+
// a file the agent read) counts as ordinary text, and a hash of the cut middle, so two big strings that share a
|
|
130
|
+
// head and a tail are still different (0.12 review).
|
|
131
|
+
const NONCE = Math.random().toString(36).slice(2, 10);
|
|
132
|
+
const ELIDED = 'glassbox-elided:' + NONCE + ':';
|
|
133
|
+
const ELIDED_RE = new RegExp('glassbox-elided:' + NONCE + ':(\\d+):[0-9a-f]+', 'g');
|
|
134
|
+
function fnv(s) { let h = 0x811c9dc5; for (let i = 0; i < s.length; i++) { h ^= s.charCodeAt(i); h = Math.imul(h, 0x01000193); } return (h >>> 0).toString(16); }
|
|
135
|
+
function isElided(s) { return typeof s === 'string' && s.indexOf(ELIDED) >= 0; }
|
|
136
|
+
function charsOf(s) {
|
|
137
|
+
s = s == null ? '' : String(s);
|
|
138
|
+
if (s.indexOf(ELIDED) < 0) return s.length;
|
|
139
|
+
let n = s.length; ELIDED_RE.lastIndex = 0; let m;
|
|
140
|
+
while ((m = ELIDED_RE.exec(s))) n += +m[1] - m[0].length;
|
|
141
|
+
return n;
|
|
142
|
+
}
|
|
143
|
+
function slimString(s, keep) {
|
|
144
|
+
keep = keep || 32768;
|
|
145
|
+
if (typeof s !== 'string' || s.length <= keep * 2 + 64) return s;
|
|
146
|
+
const mid = s.slice(keep, s.length - keep);
|
|
147
|
+
return s.slice(0, keep) + ELIDED + mid.length + ':' + fnv(mid) + '' + s.slice(-keep);
|
|
148
|
+
}
|
|
149
|
+
function slimRecord(v, keep) {
|
|
150
|
+
if (typeof v === 'string') return slimString(v, keep);
|
|
151
|
+
if (Array.isArray(v)) { for (let i = 0; i < v.length; i++) v[i] = slimRecord(v[i], keep); return v; }
|
|
152
|
+
if (v && typeof v === 'object') { for (const k of Object.keys(v)) v[k] = slimRecord(v[k], keep); return v; }
|
|
153
|
+
return v;
|
|
154
|
+
}
|
|
155
|
+
|
|
115
156
|
function textOf(content) {
|
|
116
157
|
if (content == null) return '';
|
|
117
158
|
if (typeof content === 'string') return content;
|
|
@@ -248,10 +289,11 @@
|
|
|
248
289
|
} catch (e) { problems.push({ file: name, line: 0, reason: 'bad meta.json' }); }
|
|
249
290
|
continue;
|
|
250
291
|
}
|
|
251
|
-
|
|
292
|
+
// Big transcripts arrive already parsed, line by line (src/jsonl.mjs): { name, records, problems, bytes }.
|
|
293
|
+
const { records, problems: p } = Array.isArray(f.records) ? { records: f.records, problems: f.problems || [] } : parseLines(f.text, name);
|
|
252
294
|
problems.push(...p);
|
|
253
|
-
fileMeta.push({ name, records: records.length, bytes: f.text ? f.text.length : 0 });
|
|
254
|
-
allRecords.push(...records)
|
|
295
|
+
fileMeta.push({ name, records: records.length, bytes: f.text ? f.text.length : f.bytes || 0 });
|
|
296
|
+
for (const r of records) allRecords.push(r); // not push(...records): a spread of millions overflows the stack
|
|
255
297
|
}
|
|
256
298
|
|
|
257
299
|
// Stable sort by timestamp where available; keep file order otherwise.
|
|
@@ -382,12 +424,13 @@
|
|
|
382
424
|
call.status = tr.is_error ? 'error' : 'ok';
|
|
383
425
|
call.isError = !!tr.is_error;
|
|
384
426
|
call.resultText = text;
|
|
385
|
-
call.resultChars = text
|
|
427
|
+
call.resultChars = charsOf(text);
|
|
428
|
+
if (isElided(text)) call.resultElided = true; // kept as head + tail (src/jsonl.mjs)
|
|
386
429
|
call.resultStructured = structured != null ? structured : null;
|
|
387
430
|
const imgs = Array.isArray(tr.content) ? tr.content.filter((b) => b && b.type === 'image') : [];
|
|
388
431
|
call.resultImages = imgs.length;
|
|
389
|
-
call.imageBytes = imgs.reduce((s, b) => s + Math.round(((b.source && b.source.data) || '')
|
|
390
|
-
if (structured && typeof structured === 'object' && !text && !imgs.length) call.resultChars = JSON.stringify(structured)
|
|
432
|
+
call.imageBytes = imgs.reduce((s, b) => s + Math.round(charsOf((b.source && b.source.data) || '') * 0.75), 0);
|
|
433
|
+
if (structured && typeof structured === 'object' && !text && !imgs.length) call.resultChars = charsOf(JSON.stringify(structured));
|
|
391
434
|
// stream-json puts is_error only in block; Claude Code also stores toolUseResult as an Error string
|
|
392
435
|
if (!tr.is_error && typeof structured === 'string' && /^Error/i.test(structured)) { call.status = 'error'; call.isError = true; }
|
|
393
436
|
if (call.isError) call.denial = denialKind(rec, text);
|
|
@@ -419,7 +462,7 @@
|
|
|
419
462
|
req = {
|
|
420
463
|
id: key, agent: a.id, model: msg.model || null, stopReason: null, start: t, end: t, version: rec.version || null,
|
|
421
464
|
usage: emptyUsage(), blocks: [], turnIndex: turn.index, contextTokens: 0, records: 0,
|
|
422
|
-
isApiError: !!rec.isApiErrorMessage, uuid: rec.uuid || null,
|
|
465
|
+
isApiError: !!rec.isApiErrorMessage, apiError: rec.error || null, uuid: rec.uuid || null,
|
|
423
466
|
};
|
|
424
467
|
requestByKey.set(key, req);
|
|
425
468
|
requests.push(req);
|
|
@@ -434,6 +477,7 @@
|
|
|
434
477
|
if (msg.stop_reason) req.stopReason = msg.stop_reason;
|
|
435
478
|
if (!req.version && rec.version) req.version = rec.version;
|
|
436
479
|
if (rec.isApiErrorMessage) req.isApiError = true;
|
|
480
|
+
if (rec.error && !req.apiError) req.apiError = rec.error;
|
|
437
481
|
const content = Array.isArray(msg.content) ? msg.content : (msg.content != null ? [{ type: 'text', text: String(msg.content) }] : []);
|
|
438
482
|
const blockIndex = typeof rec.apiBlockIndex === 'number' ? rec.apiBlockIndex : req.blocks.length;
|
|
439
483
|
for (const b of content) {
|
|
@@ -443,7 +487,7 @@
|
|
|
443
487
|
id: b.id || (key + ':' + blockIndex), agent: a.id, requestId: req.id, name: b.name || 'unknown', input: b.input || {},
|
|
444
488
|
start: t, end: null, durationMs: null, status: 'orphan', isError: false, resultText: '', resultChars: 0,
|
|
445
489
|
resultStructured: null, category: toolCategory(b.name), turnIndex: req.turnIndex, subagentId: null,
|
|
446
|
-
inputChars: JSON.stringify(b.input || {})
|
|
490
|
+
inputChars: charsOf(JSON.stringify(b.input || {})), resultImages: 0, imageBytes: 0, denial: null,
|
|
447
491
|
};
|
|
448
492
|
toolCalls.push(call);
|
|
449
493
|
toolCallById.set(call.id, call);
|
|
@@ -561,7 +605,7 @@
|
|
|
561
605
|
return {
|
|
562
606
|
id: tr.tool_use_id || ('result:' + rec._seq), agent: agentId, requestId: null, name: '(unmatched result)', input: {},
|
|
563
607
|
start: ts(rec), end: ts(rec), durationMs: 0, status: tr.is_error ? 'error' : 'ok', isError: !!tr.is_error, resultText: text,
|
|
564
|
-
resultChars: text
|
|
608
|
+
resultChars: charsOf(text), resultElided: isElided(text) || undefined, resultStructured: null, category: 'other', turnIndex, subagentId: null, inputChars: 0, unmatched: true,
|
|
565
609
|
};
|
|
566
610
|
}
|
|
567
611
|
|
|
@@ -910,16 +954,115 @@
|
|
|
910
954
|
// ---------------------------------------------------------------------------
|
|
911
955
|
// Cost
|
|
912
956
|
// ---------------------------------------------------------------------------
|
|
913
|
-
|
|
957
|
+
// ---------------------------------------------------------------------------
|
|
958
|
+
// Platforms. Bedrock ids carry the endpoint: `global.anthropic.claude-…-v1:0` is the global endpoint,
|
|
959
|
+
// `us.` / `eu.` / `apac.` … (geographic cross-region) and bare `anthropic.` (in-region) are regional.
|
|
960
|
+
// Vertex ids are `claude-…@20250929`; the endpoint is not in the id. From Sonnet 4.5 / Haiku 4.5 / Opus 4.5
|
|
961
|
+
// on, regional and multi-region endpoints cost 10% more than global (pricing page, 2026-10-25); earlier
|
|
962
|
+
// models keep one price. First-party `inference_geo: "us"` is 1.1× for 4.6 and later. Bedrock's and Vertex's
|
|
963
|
+
// global prices are taken to be Anthropic's list: a cloud price sheet that differs goes in --rates.
|
|
964
|
+
// ---------------------------------------------------------------------------
|
|
965
|
+
const NO_REGIONAL_PREMIUM = new Set(['claude-opus-4-1', 'claude-opus-4-2025', 'claude-sonnet-4', 'claude-3-5-haiku']);
|
|
966
|
+
const NO_GEO_PREMIUM = new Set([...NO_REGIONAL_PREMIUM, 'claude-opus-4-5', 'claude-sonnet-4-5', 'claude-haiku-4-5']);
|
|
967
|
+
function normaliseModel(id) {
|
|
968
|
+
let s = String(id || ''), platform = null, endpoint = null;
|
|
969
|
+
const b = s.match(/^(?:(global|us|us-gov|eu|apac|jp|au|ca)\.)?anthropic\.(claude-.+)$/);
|
|
970
|
+
if (b) { platform = 'bedrock'; endpoint = b[1] === 'global' ? 'global' : 'regional'; s = b[2].replace(/-v\d+(?::\d+)?$/, ''); }
|
|
971
|
+
else if (/^claude-[^@]+@/.test(s)) { platform = 'vertex'; s = s.replace(/@.*$/, ''); }
|
|
972
|
+
return { id: s, platform, endpoint };
|
|
973
|
+
}
|
|
974
|
+
// A card with a platform setting: { platform, endpoint: 'global'|'regional', geo: 'us' }, kept off the
|
|
975
|
+
// enumerable keys so the viewer's rate editor and JSON output never see it.
|
|
976
|
+
function withPricing(rates, pricing) {
|
|
977
|
+
const base = rates || RATES, out = Object.assign({}, base);
|
|
978
|
+
for (const k of ['_custom', '_userKeys']) if (base[k]) Object.defineProperty(out, k, { value: base[k] });
|
|
979
|
+
Object.defineProperty(out, '_pricing', { value: Object.assign({}, pricing) });
|
|
980
|
+
return out;
|
|
981
|
+
}
|
|
982
|
+
const scaled = new WeakMap();
|
|
983
|
+
function scaleRate(rate, f) {
|
|
984
|
+
let byF = scaled.get(rate); if (!byF) { byF = new Map(); scaled.set(rate, byF); }
|
|
985
|
+
if (!byF.has(f)) { const s = (r) => ({ in: r.in * f, out: r.out * f, w5m: r.w5m * f, w1h: r.w1h * f, read: r.read * f }); const o = s(rate); if (rate.over) o.over = Object.assign(s(rate.over), { at: rate.over.at }); byF.set(f, o); }
|
|
986
|
+
return byF.get(f);
|
|
987
|
+
}
|
|
988
|
+
function rateKey(model, rates) {
|
|
914
989
|
rates = rates || RATES;
|
|
915
990
|
if (!model) return null;
|
|
916
|
-
|
|
991
|
+
model = normaliseModel(model).id;
|
|
992
|
+
if (rates[model]) return model;
|
|
917
993
|
let best = null;
|
|
918
994
|
for (const k of Object.keys(rates)) if (model.startsWith(k) && (!best || k.length > best.length)) best = k;
|
|
919
|
-
return best
|
|
995
|
+
return best;
|
|
996
|
+
}
|
|
997
|
+
// The multiplier for one model id under a card's platform setting. The endpoint in a Bedrock id applies on
|
|
998
|
+
// its own, except to a row the user priced themselves (their sheet already has the regional price in it);
|
|
999
|
+
// an explicit --endpoint applies to every row.
|
|
1000
|
+
function premiumFor(model, rates) {
|
|
1001
|
+
const card = rates || RATES, n = normaliseModel(model), k = rateKey(model, card);
|
|
1002
|
+
if (!k) return 1;
|
|
1003
|
+
const p = card._pricing || {}, userRow = card._userKeys && card._userKeys.has(k);
|
|
1004
|
+
const endpoint = p.endpoint || (userRow ? null : n.endpoint);
|
|
1005
|
+
let f = 1;
|
|
1006
|
+
if (endpoint === 'regional' && !NO_REGIONAL_PREMIUM.has(k)) f *= 1.1;
|
|
1007
|
+
if (p.geo === 'us' && (n.platform || p.platform || 'anthropic') === 'anthropic' && !NO_GEO_PREMIUM.has(k)) f *= 1.1;
|
|
1008
|
+
return f;
|
|
1009
|
+
}
|
|
1010
|
+
function rateFor(model, rates) {
|
|
1011
|
+
const k = rateKey(model, rates);
|
|
1012
|
+
if (!k) return null;
|
|
1013
|
+
const rate = (rates || RATES)[k], f = premiumFor(model, rates);
|
|
1014
|
+
return f === 1 ? rate : scaleRate(rate, f);
|
|
1015
|
+
}
|
|
1016
|
+
// One line naming how a session was priced, or null for plain first-party list prices.
|
|
1017
|
+
function pricingNote(models, rates) {
|
|
1018
|
+
const card = rates || RATES, p = card._pricing || {};
|
|
1019
|
+
const seen = { bedrock: new Set(), vertex: false };
|
|
1020
|
+
for (const m of new Set(models || [])) { if (!m || m === '<synthetic>') continue; const n = normaliseModel(m); if (n.platform === 'bedrock') seen.bedrock.add(n.endpoint); if (n.platform === 'vertex') seen.vertex = true; }
|
|
1021
|
+
const platform = p.platform || (seen.bedrock.size ? 'bedrock' : seen.vertex ? 'vertex' : 'anthropic');
|
|
1022
|
+
const parts = [];
|
|
1023
|
+
if (platform === 'bedrock') parts.push(`Bedrock, ${p.endpoint ? `${p.endpoint} endpoint` : seen.bedrock.size ? `${[...seen.bedrock].sort().join(' and ')} endpoint${seen.bedrock.size > 1 ? 's' : ''} from the model ids` : 'global endpoint'}`);
|
|
1024
|
+
else if (platform === 'vertex') parts.push(`Vertex, ${p.endpoint ? `${p.endpoint} endpoint` : 'global endpoint assumed (--endpoint regional if not)'}`);
|
|
1025
|
+
else if (p.endpoint === 'regional') parts.push('a regional endpoint');
|
|
1026
|
+
if (p.geo === 'us') parts.push('US-only inference');
|
|
1027
|
+
if (!parts.length) return null;
|
|
1028
|
+
const extras = [];
|
|
1029
|
+
if (p.endpoint === 'regional' || (p.endpoint == null && seen.bedrock.has('regional'))) extras.push('+10% for regional on Claude 4.5 and later');
|
|
1030
|
+
if (p.geo === 'us') extras.push('+10% for US-only on Claude 4.6 and later');
|
|
1031
|
+
const cloud = platform === 'bedrock' || platform === 'vertex';
|
|
1032
|
+
return `pricing: ${parts.join(', ')}${extras.length ? `; ${extras.join('; ')}` : ''}.${cloud ? ` Global prices taken as Anthropic's list; put your cloud price sheet in --rates if it differs.` : ''}`;
|
|
1033
|
+
}
|
|
1034
|
+
// Is the card still right for these models? A model priced through a shorter row of another version
|
|
1035
|
+
// (claude-opus-5-5 by claude-opus-5 before 0.11) is "approximate": a dated id or a date-prefixed row is not.
|
|
1036
|
+
// `latest` is the newest request time: the card is "old" for a session that ran long after RATES_DATE.
|
|
1037
|
+
// With a custom card (--rates) the age is the user's business and is not reported.
|
|
1038
|
+
function rateCardCheck(models, rates, opts = {}) {
|
|
1039
|
+
const out = { date: RATES_DATE, ageDays: null, old: false, approximate: [], unpriced: [] };
|
|
1040
|
+
for (const m of new Set(models || [])) {
|
|
1041
|
+
if (!m || m === '<synthetic>') continue;
|
|
1042
|
+
const k = rateKey(m, rates);
|
|
1043
|
+
if (!k) { out.unpriced.push(m); continue; }
|
|
1044
|
+
const rest = normaliseModel(m).id.replace(/-\d{8}$/, '').slice(k.length);
|
|
1045
|
+
if (/^-\d/.test(rest)) out.approximate.push({ model: m, as: k });
|
|
1046
|
+
}
|
|
1047
|
+
if (opts.latest != null && !opts.custom) {
|
|
1048
|
+
out.ageDays = Math.floor((opts.latest - Date.parse(RATES_DATE + 'T00:00:00Z')) / 864e5);
|
|
1049
|
+
out.old = out.ageDays > RATES_STALE_DAYS;
|
|
1050
|
+
}
|
|
1051
|
+
out.ok = !out.old && !out.approximate.length && !out.unpriced.length;
|
|
1052
|
+
return out;
|
|
1053
|
+
}
|
|
1054
|
+
// One line for a report, or null when the card is fine.
|
|
1055
|
+
function rateCardLine(rc) {
|
|
1056
|
+
if (!rc || rc.ok) return null;
|
|
1057
|
+
const parts = [];
|
|
1058
|
+
if (rc.approximate.length) parts.push(rc.approximate.map((a) => `${a.model} priced as ${a.as}`).join(', ') + ' (no row of its own)');
|
|
1059
|
+
if (rc.unpriced.length) parts.push(`no price for ${rc.unpriced.join(', ')}`);
|
|
1060
|
+
if (rc.old) parts.push(`the built-in card is from ${rc.date}, ${rc.ageDays} days before this ran`);
|
|
1061
|
+
return `rate card: ${parts.join('; ')}. Check platform.claude.com/docs/en/about-claude/pricing, or pass --rates FILE.`;
|
|
920
1062
|
}
|
|
921
1063
|
function requestCost(u, rate) {
|
|
922
1064
|
if (!rate) return null;
|
|
1065
|
+
if (rate.over && u.input + u.cacheRead + u.cacheWrite > rate.over.at) rate = rate.over;
|
|
923
1066
|
const w5 = u.cacheWrite5m || 0, w1 = u.cacheWrite1h || 0;
|
|
924
1067
|
const write = (w5 || w1) ? (w5 * rate.w5m + w1 * rate.w1h) : (u.cacheWrite * rate.w5m);
|
|
925
1068
|
return (u.input * rate.in + u.cacheRead * rate.read + write + u.output * rate.out) / 1e6;
|
|
@@ -1229,5 +1372,5 @@
|
|
|
1229
1372
|
}
|
|
1230
1373
|
function toJsonl(records) { return records.map((r) => JSON.stringify(r)).join('\n') + '\n'; }
|
|
1231
1374
|
|
|
1232
|
-
return { VERSION, RATES, DEFAULTS, ADVICE, METRICS, parseLines, parseTrace, diagnose, denialKind, estimateCost, rateFor, redact, toJsonl, toolCategory, textOf, fmtDur, fmtInt, fmtMetric, fmtRatio, fmtChange, stableStringify, inputSummary, adviceFor, redactDetail, reportMarkdown, compare, compareMarkdown, toolTable, fileStats, filePathOf, normalisePath };
|
|
1375
|
+
return { VERSION, RATES, DEFAULTS, ADVICE, METRICS, parseLines, normaliseLoose, charsOf, isElided, slimString, slimRecord, parseTrace, diagnose, denialKind, estimateCost, rateFor, redact, toJsonl, toolCategory, textOf, fmtDur, fmtInt, fmtMetric, fmtRatio, fmtChange, stableStringify, inputSummary, adviceFor, redactDetail, reportMarkdown, compare, compareMarkdown, toolTable, fileStats, filePathOf, normalisePath, RATES_DATE, rateKey, rateCardCheck, rateCardLine, normaliseModel, withPricing, premiumFor, pricingNote, requestCost };
|
|
1233
1376
|
});
|