@krtclcdy/tersio 2.25.5 → 2.25.7
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 +42 -38
- package/dashboard/dist/index.html +15 -15
- package/dist/bench/code-out/pfull-0/debounce.js +13 -0
- package/dist/bench/code-out/pfull-0/deepClone.js +18 -0
- package/dist/bench/code-out/pfull-0/groupBy.js +15 -0
- package/dist/bench/code-out/pfull-1/debounce.js +13 -0
- package/dist/bench/code-out/pfull-1/deepClone.js +17 -0
- package/dist/bench/code-out/pfull-1/groupBy.js +14 -0
- package/dist/bench/code-out/pfull-2/debounce.js +13 -0
- package/dist/bench/code-out/pfull-2/deepClone.js +18 -0
- package/dist/bench/code-out/pfull-2/groupBy.js +15 -0
- package/dist/bench/code-out/pfull-3/debounce.js +12 -0
- package/dist/bench/code-out/pfull-3/deepClone.js +17 -0
- package/dist/bench/code-out/pfull-3/groupBy.js +7 -0
- package/dist/bench/code-out/pfull-4/debounce.js +10 -0
- package/dist/bench/code-out/pfull-4/deepClone.js +17 -0
- package/dist/bench/code-out/pfull-4/groupBy.js +14 -0
- package/dist/bench/code-out/plite-0/debounce.js +13 -0
- package/dist/bench/code-out/plite-0/deepClone.js +18 -0
- package/dist/bench/code-out/plite-0/groupBy.js +15 -0
- package/dist/bench/code-out/plite-1/debounce.js +13 -0
- package/dist/bench/code-out/plite-1/deepClone.js +17 -0
- package/dist/bench/code-out/plite-1/groupBy.js +11 -0
- package/dist/bench/code-out/plite-2/debounce.js +12 -0
- package/dist/bench/code-out/plite-2/deepClone.js +17 -0
- package/dist/bench/code-out/plite-2/groupBy.js +14 -0
- package/dist/bench/code-out/plite-3/debounce.js +12 -0
- package/dist/bench/code-out/plite-3/deepClone.js +17 -0
- package/dist/bench/code-out/plite-3/groupBy.js +14 -0
- package/dist/bench/code-out/plite-4/debounce.js +12 -0
- package/dist/bench/code-out/plite-4/deepClone.js +17 -0
- package/dist/bench/code-out/plite-4/groupBy.js +14 -0
- package/dist/bench/code-out/pultra-0/debounce.js +12 -0
- package/dist/bench/code-out/pultra-0/deepClone.js +20 -0
- package/dist/bench/code-out/pultra-0/groupBy.js +14 -0
- package/dist/bench/code-out/pultra-1/debounce.js +10 -0
- package/dist/bench/code-out/pultra-1/deepClone.js +17 -0
- package/dist/bench/code-out/pultra-1/groupBy.js +7 -0
- package/dist/bench/code-out/pultra-2/debounce.js +13 -0
- package/dist/bench/code-out/pultra-2/deepClone.js +18 -0
- package/dist/bench/code-out/pultra-2/groupBy.js +15 -0
- package/dist/bench/code-out/pultra-3/debounce.js +13 -0
- package/dist/bench/code-out/pultra-3/deepClone.js +18 -0
- package/dist/bench/code-out/pultra-3/groupBy.js +15 -0
- package/dist/bench/code-out/pultra-4/debounce.js +13 -0
- package/dist/bench/code-out/pultra-4/deepClone.js +18 -0
- package/dist/bench/code-out/pultra-4/groupBy.js +15 -0
- package/dist/bench/code-out/run-0/debounce.js +12 -0
- package/dist/bench/code-out/run-0/deepClone.js +17 -0
- package/dist/bench/code-out/run-0/groupBy.js +14 -0
- package/dist/bench/code-out/run-1/debounce.js +13 -0
- package/dist/bench/code-out/run-1/deepClone.js +15 -0
- package/dist/bench/code-out/run-1/groupBy.js +13 -0
- package/dist/bench/code-out/run-2/debounce.js +12 -0
- package/dist/bench/code-out/run-2/deepClone.js +18 -0
- package/dist/bench/code-out/run-2/groupBy.js +13 -0
- package/dist/bench/code-out/run-3/debounce.js +12 -0
- package/dist/bench/code-out/run-3/deepClone.js +18 -0
- package/dist/bench/code-out/run-3/groupBy.js +14 -0
- package/dist/bench/code-out/run-4/debounce.js +12 -0
- package/dist/bench/code-out/run-4/deepClone.js +16 -0
- package/dist/bench/code-out/run-4/groupBy.js +13 -0
- package/dist/cli/backups.js +7 -14
- package/dist/cli/banner.js +3 -3
- package/dist/cli/common.js +29 -13
- package/dist/cli/currency.js +1 -1
- package/dist/cli/dashboard.js +66 -35
- package/dist/cli/doctor-fix.js +204 -19
- package/dist/cli/doctor.js +263 -40
- package/dist/cli/hosts.js +25 -6
- package/dist/cli/install.js +150 -49
- package/dist/cli/interactive.js +3 -3
- package/dist/cli/manifest.js +109 -9
- package/dist/cli/profile.js +13 -8
- package/dist/cli/rtk-wiring.js +1 -1
- package/dist/cli/settings.js +20 -5
- package/dist/cli/uninstall.js +4 -4
- package/dist/cli/update.js +6 -7
- package/dist/cli/usage.js +6 -9
- package/dist/extensions/caveman/index.js +10 -4
- package/dist/extensions/caveman/rule-lite.md +77 -0
- package/dist/extensions/caveman/rule-megacave-lite.md +59 -0
- package/dist/extensions/caveman/rule-megacave-ultra.md +59 -0
- package/dist/extensions/caveman/rule-megacave.md +5 -3
- package/dist/extensions/caveman/rule-ultra.md +4 -4
- package/dist/extensions/caveman/rule.md +55 -45
- package/dist/extensions/combo/index.js +85 -25
- package/dist/extensions/commands/index.js +1 -1
- package/dist/extensions/pstack/index.js +121 -0
- package/dist/extensions/pstack/rule.md +141 -0
- package/dist/extensions/rtk/filter.js +0 -1
- package/dist/extensions/rtk/session.js +20 -12
- package/dist/extensions/server.js +90 -6
- package/dist/extensions/shared/agent-stats.js +214 -278
- package/dist/extensions/shared/brand-marks.js +33 -34
- package/dist/extensions/shared/carbon.js +1 -1
- package/dist/extensions/shared/plugin-settings.js +11 -6
- package/dist/extensions/shared/pricing.js +8 -16
- package/dist/extensions/shared/rtk-eligible.js +84 -0
- package/dist/extensions/shared/rtk-gain.js +5 -10
- package/dist/extensions/shared/session-state.js +15 -10
- package/dist/extensions/shared/sqlite.js +162 -0
- package/dist/extensions/shared/status.js +8 -9
- package/dist/extensions/shared/types.js +1 -1
- package/dist/extensions/shared/usage-ledger.js +155 -61
- package/dist/extensions/shared/usage-store.js +111 -52
- package/dist/extensions/shared/utils.js +5 -5
- package/dist/extensions/timing/index.js +31 -0
- package/dist/extensions/updater/index.js +3 -6
- package/dist/tersio.js +8 -3
- package/extensions/caveman/index.ts +10 -4
- package/extensions/caveman/rule-lite.md +77 -0
- package/extensions/caveman/rule-megacave-lite.md +59 -0
- package/extensions/caveman/rule-megacave-ultra.md +59 -0
- package/extensions/caveman/rule-megacave.md +5 -3
- package/extensions/caveman/rule-ultra.md +4 -4
- package/extensions/caveman/rule.md +55 -45
- package/extensions/combo/index.ts +86 -23
- package/extensions/commands/index.ts +1 -1
- package/extensions/pstack/index.ts +130 -0
- package/extensions/pstack/playbooks/authoring-a-skill.md +12 -0
- package/extensions/pstack/playbooks/autonomous-run.md +13 -0
- package/extensions/pstack/playbooks/autopilot-full.md +13 -0
- package/extensions/pstack/playbooks/autopilot-stack.md +16 -0
- package/extensions/pstack/playbooks/babysit.md +23 -0
- package/extensions/pstack/playbooks/bug-fix.md +15 -0
- package/extensions/pstack/playbooks/eval.md +25 -0
- package/extensions/pstack/playbooks/feature.md +21 -0
- package/extensions/pstack/playbooks/hillclimb.md +21 -0
- package/extensions/pstack/playbooks/investigation.md +14 -0
- package/extensions/pstack/playbooks/multi-phase-plan.md +155 -0
- package/extensions/pstack/playbooks/opening-a-pr.md +34 -0
- package/extensions/pstack/playbooks/orchestrate.md +111 -0
- package/extensions/pstack/playbooks/pause-safely.md +10 -0
- package/extensions/pstack/playbooks/perf-issue.md +25 -0
- package/extensions/pstack/playbooks/prototype.md +14 -0
- package/extensions/pstack/playbooks/refactoring.md +16 -0
- package/extensions/pstack/playbooks/runtime-forensics.md +11 -0
- package/extensions/pstack/playbooks/session-pickup.md +11 -0
- package/extensions/pstack/playbooks/shipping.md +17 -0
- package/extensions/pstack/playbooks/trace-forensics.md +14 -0
- package/extensions/pstack/playbooks/visual-parity.md +11 -0
- package/extensions/pstack/playbooks/worktree-cleanup.md +12 -0
- package/extensions/pstack/principles/attack-the-premise.md +22 -0
- package/extensions/pstack/principles/boundary-discipline.md +33 -0
- package/extensions/pstack/principles/build-the-lever.md +22 -0
- package/extensions/pstack/principles/encode-lessons-in-structure.md +10 -0
- package/extensions/pstack/principles/exhaust-the-design-space.md +8 -0
- package/extensions/pstack/principles/experience-first.md +18 -0
- package/extensions/pstack/principles/explain-the-number.md +16 -0
- package/extensions/pstack/principles/fix-root-causes.md +23 -0
- package/extensions/pstack/principles/foundational-thinking.md +20 -0
- package/extensions/pstack/principles/guard-the-context-window.md +11 -0
- package/extensions/pstack/principles/laziness-protocol.md +9 -0
- package/extensions/pstack/principles/make-operations-idempotent.md +23 -0
- package/extensions/pstack/principles/migrate-callers-then-delete-legacy-apis.md +21 -0
- package/extensions/pstack/principles/minimize-reader-load.md +17 -0
- package/extensions/pstack/principles/model-the-domain.md +23 -0
- package/extensions/pstack/principles/never-block-on-the-human.md +14 -0
- package/extensions/pstack/principles/outcome-oriented-execution.md +20 -0
- package/extensions/pstack/principles/prove-it-works.md +21 -0
- package/extensions/pstack/principles/redesign-from-first-principles.md +15 -0
- package/extensions/pstack/principles/separate-before-serializing-shared-state.md +15 -0
- package/extensions/pstack/principles/sequence-verifiable-units.md +16 -0
- package/extensions/pstack/principles/subtract-before-you-add.md +17 -0
- package/extensions/pstack/principles/test-behavior-not-implementation.md +20 -0
- package/extensions/pstack/principles/type-system-discipline.md +17 -0
- package/extensions/pstack/references/bugbot-triage.md +142 -0
- package/extensions/pstack/rule.md +141 -0
- package/extensions/pstack/skills/architect/SKILL.md +78 -0
- package/extensions/pstack/skills/architect/references/design-red-flags.md +57 -0
- package/extensions/pstack/skills/architect/references/rationale-template.md +35 -0
- package/extensions/pstack/skills/architect/references/runner-prompt.md +20 -0
- package/extensions/pstack/skills/arena/SKILL.md +47 -0
- package/extensions/pstack/skills/benchmark-checklist/SKILL.md +38 -0
- package/extensions/pstack/skills/blast-radius/SKILL.md +49 -0
- package/extensions/pstack/skills/bro/SKILL.md +6 -0
- package/extensions/pstack/skills/correct/SKILL.md +31 -0
- package/extensions/pstack/skills/figure-it-out/SKILL.md +51 -0
- package/extensions/pstack/skills/how/SKILL.md +35 -0
- package/extensions/pstack/skills/how/references/explainer-prompt.md +55 -0
- package/extensions/pstack/skills/how/references/explorer-prompt.md +35 -0
- package/extensions/pstack/skills/interrogate/SKILL.md +73 -0
- package/extensions/pstack/skills/interrogate/references/code-quality-review.md +47 -0
- package/extensions/pstack/skills/interrogate/references/lead-judgment.md +58 -0
- package/extensions/pstack/skills/interrogate/references/reviewer-prompt.md +62 -0
- package/extensions/pstack/skills/interrogate/references/rubric.md +77 -0
- package/extensions/pstack/skills/no-comments/SKILL.md +23 -0
- package/extensions/pstack/skills/reflect/SKILL.md +66 -0
- package/extensions/pstack/skills/reflect/references/divergent-reviewer.md +38 -0
- package/extensions/pstack/skills/reflect/references/judgment-reviewer.md +37 -0
- package/extensions/pstack/skills/reflect/references/synthesizer.md +56 -0
- package/extensions/pstack/skills/reflect/references/tooling-reviewer.md +48 -0
- package/extensions/pstack/skills/show-me-your-work/SKILL.md +81 -0
- package/extensions/pstack/skills/swarm/SKILL.md +45 -0
- package/extensions/pstack/skills/tdd/SKILL.md +41 -0
- package/extensions/pstack/skills/technical-writing/SKILL.md +105 -0
- package/extensions/pstack/skills/unslop/SKILL.md +61 -0
- package/extensions/pstack/skills/why/SKILL.md +97 -0
- package/extensions/pstack/skills/why/references/epistemics.md +71 -0
- package/extensions/pstack/skills/why/references/investigator-prompt.md +84 -0
- package/extensions/pstack/skills/why/references/source-playbook.md +17 -0
- package/extensions/pstack/skills/why/references/sources/code-archaeology.md +51 -0
- package/extensions/pstack/skills/why/references/sources/databricks.md +70 -0
- package/extensions/pstack/skills/why/references/sources/datadog.md +97 -0
- package/extensions/pstack/skills/why/references/sources/incident-postmortem.md +5 -0
- package/extensions/pstack/skills/why/references/sources/linear.md +48 -0
- package/extensions/pstack/skills/why/references/sources/notion.md +55 -0
- package/extensions/pstack/skills/why/references/sources/sentry.md +66 -0
- package/extensions/pstack/skills/why/references/sources/slack.md +17 -0
- package/extensions/pstack/skills/why/references/synthesizer-prompt.md +129 -0
- package/extensions/rtk/filter.ts +0 -1
- package/extensions/rtk/session.ts +19 -11
- package/extensions/server.ts +72 -7
- package/extensions/shared/agent-stats.ts +226 -278
- package/extensions/shared/brand-marks.ts +34 -34
- package/extensions/shared/carbon.ts +1 -1
- package/extensions/shared/plugin-settings.ts +12 -6
- package/extensions/shared/pricing.ts +10 -26
- package/extensions/shared/rtk-eligible.ts +83 -0
- package/extensions/shared/rtk-gain.ts +5 -10
- package/extensions/shared/session-state.ts +18 -13
- package/extensions/shared/sqlite.ts +162 -0
- package/extensions/shared/status.ts +9 -9
- package/extensions/shared/types.ts +2 -1
- package/extensions/shared/usage-ledger.ts +157 -66
- package/extensions/shared/usage-store.ts +120 -52
- package/extensions/shared/utils.ts +5 -5
- package/extensions/timing/index.ts +44 -0
- package/extensions/updater/index.ts +3 -8
- package/package.json +21 -9
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
// One status string every surface renders: host footer
|
|
1
|
+
// One status string every surface renders: host footer, CLI title, dashboard banner. Nothing else formats mode state.
|
|
2
2
|
import { getSharedComboState } from './session-state.js';
|
|
3
|
-
/** OFF collapses
|
|
3
|
+
/** OFF collapses every mode into one word; a preset or a mix names itself. */
|
|
4
4
|
export function formatStatus(state) {
|
|
5
|
-
const { level, caveman, rtk, ponytail } = state;
|
|
6
|
-
if (level === 'off' && caveman === 'off' && rtk === 'off' && ponytail === 'off')
|
|
5
|
+
const { level, caveman, rtk, ponytail, pstack } = state;
|
|
6
|
+
if (level === 'off' && caveman === 'off' && rtk === 'off' && ponytail === 'off' && pstack === 'off')
|
|
7
7
|
return '🧩 combo OFF';
|
|
8
8
|
const label = level === 'custom' ? 'CUSTOM' : level.toUpperCase();
|
|
9
|
-
return `🧩 combo ${label}: 🪨caveman=${caveman.toUpperCase()} ⚡rtk=${rtk.toUpperCase()} 🦥ponytail=${ponytail.toUpperCase()}`;
|
|
9
|
+
return `🧩 combo ${label}: 🪨caveman=${caveman.toUpperCase()} ⚡rtk=${rtk.toUpperCase()} 🦥ponytail=${ponytail.toUpperCase()} 🥔pstack=${pstack.toUpperCase()}`;
|
|
10
10
|
}
|
|
11
|
-
// The string upstream's skill tells the model to relay for `/caveman status
|
|
11
|
+
// The string upstream's skill tells the model to relay for `/caveman status`; `off` is a mode, so it reads as one.
|
|
12
12
|
export function formatCavemanStatus(mode) {
|
|
13
|
-
return `Caveman mode: ${mode
|
|
13
|
+
return `Caveman mode: ${mode || 'unknown'}`;
|
|
14
14
|
}
|
|
15
15
|
// Stale ctx access throws, so never let it abort a mode restore.
|
|
16
16
|
function statusUi(ctx) {
|
|
@@ -26,7 +26,7 @@ function statusUi(ctx) {
|
|
|
26
26
|
let remembered;
|
|
27
27
|
let lastText;
|
|
28
28
|
let lastUi;
|
|
29
|
-
// Paint the host footer bar; same text on the same UI is skipped, a
|
|
29
|
+
// Paint the host footer bar; same text on the same UI is skipped. One key only: the combo line already names the caveman mode, and a second key renders as a second footer line.
|
|
30
30
|
export function announceStatus(ctx) {
|
|
31
31
|
const state = getSharedComboState();
|
|
32
32
|
const text = formatStatus(state);
|
|
@@ -40,5 +40,4 @@ export function announceStatus(ctx) {
|
|
|
40
40
|
lastText = text;
|
|
41
41
|
lastUi = ui;
|
|
42
42
|
ui.setStatus?.('tersio', text);
|
|
43
|
-
ui.setStatus?.('caveman', formatCavemanStatus(state.caveman));
|
|
44
43
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
// Shared OMP extension host types. Structural — the real `pi` object comes from the OMP runtime
|
|
1
|
+
// Shared OMP extension host types. Structural — the real `pi` object comes from the OMP runtime.
|
|
2
2
|
export {};
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
//
|
|
1
|
+
// Usage ledger: append-only JSON lines, read by the CLI, `/tersio`, and the Dashboard. Corrupt lines are skipped.
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
-
import { homeDir, isPiProcess, piAgentDir, resolveRtkBinary, tersioDataPath } from './utils.js';
|
|
5
4
|
import { execFileSync } from 'node:child_process';
|
|
5
|
+
import { homeDir, isPiProcess, piAgentDir, resolveRtkBinary, tersioDataPath } from './utils.js';
|
|
6
|
+
import { eligibleHeads } from './rtk-eligible.js';
|
|
7
|
+
import { sqliteRead } from './sqlite.js';
|
|
6
8
|
export function ledgerPath() {
|
|
7
9
|
const override = process.env.TERSIO_USAGE_FILE;
|
|
8
10
|
if (override)
|
|
@@ -50,7 +52,7 @@ function stampMs(ts) {
|
|
|
50
52
|
export function durOf(v) {
|
|
51
53
|
return typeof v === 'number' && Number.isFinite(v) && v > 0 ? v : undefined;
|
|
52
54
|
}
|
|
53
|
-
// Measured cost: usage.cost.total, or a bare number in old fixtures. Anything else means unrecorded,
|
|
55
|
+
// Measured cost: usage.cost.total, or a bare number in old fixtures. Anything else means unrecorded, not 0.
|
|
54
56
|
export function costOf(usage) {
|
|
55
57
|
const c = usage.cost;
|
|
56
58
|
if (typeof c === 'number')
|
|
@@ -87,7 +89,7 @@ export function sessionsDirs() {
|
|
|
87
89
|
const override = process.env.TERSIO_SESSIONS_DIR;
|
|
88
90
|
if (override)
|
|
89
91
|
return [override];
|
|
90
|
-
// Both hosts, not just the running one: a session
|
|
92
|
+
// Both hosts, not just the running one: a pi session is missing if we stop at the first existing directory.
|
|
91
93
|
const dirs = isPiProcess()
|
|
92
94
|
? [path.join(piAgentDir(), 'sessions'), path.join(homeDir(), '.omp', 'agent', 'sessions')]
|
|
93
95
|
: [path.join(homeDir(), '.omp', 'agent', 'sessions'), path.join(piAgentDir(), 'sessions')];
|
|
@@ -129,22 +131,20 @@ export function opencodeDbPath() {
|
|
|
129
131
|
catch { /* fall through to the platform default */ }
|
|
130
132
|
return path.join(homeDir(), 'Library', 'Application Support', 'opencode', 'opencode.db');
|
|
131
133
|
}
|
|
132
|
-
//
|
|
133
|
-
// current `session_message` (role in the `type` column, model under data.model).
|
|
134
|
-
// Scalars only: the `data` blob holds newlines and tabs, which the sqlite3 CLI would split.
|
|
134
|
+
// `message` vs `session_message`: read scalars only, since the blob's newlines break the row-per-line output.
|
|
135
135
|
const OC_DB_ORDER = ' ORDER BY time_created;';
|
|
136
|
-
const OC_DB_COLUMNS = 'json_extract(data,\'$.time.created\'), json_extract(data,\'$.time.completed\'),' +
|
|
136
|
+
const OC_DB_COLUMNS = 'json_extract(data,\'$.time.created\'), json_extract(data,\'$.time.streamed\'), json_extract(data,\'$.time.completed\'),' +
|
|
137
137
|
' json_extract(data,\'$.tokens.input\'), json_extract(data,\'$.tokens.output\'),' +
|
|
138
138
|
' json_extract(data,\'$.tokens.cache.read\'), json_extract(data,\'$.tokens.cache.write\'), json_extract(data,\'$.cost\')';
|
|
139
|
-
const
|
|
139
|
+
const OC_DB_TOOLS = '(SELECT group_concat(json_extract(j.value,\'$.name\')) FROM json_each(data,\'$.content\') j' +
|
|
140
|
+
' WHERE json_extract(j.value,\'$.type\')=\'tool\')';
|
|
141
|
+
const ocDbLegacy = (since) => `SELECT id, time_created, json_extract(data,'$.role'), json_extract(data,'$.providerID'), json_extract(data,'$.modelID'), ${OC_DB_COLUMNS}, ${OC_DB_TOOLS}` +
|
|
140
142
|
` FROM message WHERE json_extract(data,'$.role')='assistant' AND time_created > ${since}${OC_DB_ORDER}`;
|
|
141
|
-
const ocDbCurrent = (since) => `SELECT id, time_created, 'assistant', json_extract(data,'$.model.providerID'), json_extract(data,'$.model.id'), ${OC_DB_COLUMNS}` +
|
|
143
|
+
const ocDbCurrent = (since) => `SELECT id, time_created, 'assistant', json_extract(data,'$.model.providerID'), json_extract(data,'$.model.id'), ${OC_DB_COLUMNS}, ${OC_DB_TOOLS}` +
|
|
142
144
|
` FROM session_message WHERE type='assistant' AND time_created > ${since}${OC_DB_ORDER}`;
|
|
143
145
|
// A re-scan window covers tokens that land after the row was created, so a late fill is not lost.
|
|
144
146
|
export const OPENCODE_DB_OVERLAP_MS = 3_600_000;
|
|
145
|
-
//
|
|
146
|
-
export const SQLITE_READ_BUFFER = 50 * 1024 * 1024;
|
|
147
|
-
// Empty when sqlite3 is absent or the db is unreadable; that is a sync skip, not a failure.
|
|
147
|
+
// Empty when no SQLite is available or the db is unreadable; that is a sync skip, not a failure.
|
|
148
148
|
export function readOpencodeDbRows(db, sinceMs) {
|
|
149
149
|
if (!Number.isFinite(sinceMs))
|
|
150
150
|
return [];
|
|
@@ -153,7 +153,7 @@ export function readOpencodeDbRows(db, sinceMs) {
|
|
|
153
153
|
return [];
|
|
154
154
|
const since = Math.max(0, Math.floor(sinceMs));
|
|
155
155
|
// Separate calls: a db holding only one table must not cancel the other.
|
|
156
|
-
const run = (sql) =>
|
|
156
|
+
const run = (sql) => sqliteRead(db, sql);
|
|
157
157
|
let out = '';
|
|
158
158
|
try {
|
|
159
159
|
out += run(ocDbLegacy(since));
|
|
@@ -167,17 +167,18 @@ export function readOpencodeDbRows(db, sinceMs) {
|
|
|
167
167
|
for (const line of out.split('\n')) {
|
|
168
168
|
if (!line.trim())
|
|
169
169
|
continue;
|
|
170
|
-
const [id, created, role, providerID, modelID, tCreated, tCompleted, input, output, cacheRead, cacheWrite, cost] = line.split('\t');
|
|
170
|
+
const [id, created, role, providerID, modelID, tCreated, tStreamed, tCompleted, input, output, cacheRead, cacheWrite, cost, tools] = line.split('\t');
|
|
171
171
|
const n = (v) => (v !== undefined && v !== '' && Number.isFinite(Number(v)) ? Number(v) : undefined);
|
|
172
172
|
rows.push({
|
|
173
173
|
id,
|
|
174
174
|
created: Number(created),
|
|
175
|
+
tools: tools ?? '',
|
|
175
176
|
row: {
|
|
176
177
|
role,
|
|
177
178
|
modelID,
|
|
178
179
|
providerID,
|
|
179
180
|
tokens: { input: n(input), output: n(output), cache: { read: n(cacheRead), write: n(cacheWrite) } },
|
|
180
|
-
time: { created: n(tCreated), completed: n(tCompleted) },
|
|
181
|
+
time: { created: n(tCreated), streamed: n(tStreamed), completed: n(tCompleted) },
|
|
181
182
|
cost: n(cost),
|
|
182
183
|
},
|
|
183
184
|
});
|
|
@@ -196,6 +197,60 @@ export function dayKey(ts) {
|
|
|
196
197
|
const p = (n) => String(n).padStart(2, '0');
|
|
197
198
|
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}`;
|
|
198
199
|
}
|
|
200
|
+
// OpenCode tool parts carry the command in the message blob; sqliteRead is line-based, so newlines are flattened in SQL.
|
|
201
|
+
const OC_BASH_CMD = "replace(replace(json_extract(j.value,'$.state.input.command'), char(10), ' '), char(13), ' ')";
|
|
202
|
+
const OC_BASH_TOOL = "json_extract(j.value,'$.type')='tool' AND (json_extract(j.value,'$.name')='bash' OR json_extract(j.value,'$.name')='shell')";
|
|
203
|
+
const ocBashLegacy = `SELECT message.id, json_extract(message.data,'$.sessionID'), ${OC_BASH_CMD} FROM message, json_each(message.data,'$.content') j WHERE json_extract(message.data,'$.role')='assistant' AND ${OC_BASH_TOOL}`;
|
|
204
|
+
const ocBashCurrent = `SELECT session_message.id, session_message.session_id, ${OC_BASH_CMD} FROM session_message, json_each(session_message.data,'$.content') j WHERE session_message.type='assistant' AND ${OC_BASH_TOOL}`;
|
|
205
|
+
/** Every executed bash command in an OpenCode db, v2 first and the legacy table second. */
|
|
206
|
+
export function readOpencodeBashRows(db) {
|
|
207
|
+
const rows = [];
|
|
208
|
+
try {
|
|
209
|
+
if (!fs.existsSync(db))
|
|
210
|
+
return rows;
|
|
211
|
+
let out = '';
|
|
212
|
+
try {
|
|
213
|
+
out += sqliteRead(db, ocBashCurrent);
|
|
214
|
+
}
|
|
215
|
+
catch { /* no v2 table */ }
|
|
216
|
+
try {
|
|
217
|
+
out += sqliteRead(db, ocBashLegacy);
|
|
218
|
+
}
|
|
219
|
+
catch { /* no legacy table */ }
|
|
220
|
+
for (const line of out.split('\n')) {
|
|
221
|
+
if (!line.trim())
|
|
222
|
+
continue;
|
|
223
|
+
const [id, sessionId, command] = line.split('\t');
|
|
224
|
+
if (typeof command !== 'string' || !command.trim())
|
|
225
|
+
continue;
|
|
226
|
+
rows.push({ id: id ?? '', sessionId: sessionId ?? '', command });
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
catch { /* an unreadable db is a skip, not a failure */ }
|
|
230
|
+
return rows;
|
|
231
|
+
}
|
|
232
|
+
// Pre-v2 OpenCode stores one message per JSON file; the same tool-part shape holds the command.
|
|
233
|
+
function opencodeFileBash(file) {
|
|
234
|
+
try {
|
|
235
|
+
const message = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
236
|
+
const parts = Array.isArray(message.content) ? message.content : [];
|
|
237
|
+
const commands = [];
|
|
238
|
+
for (const part of parts) {
|
|
239
|
+
const p = part;
|
|
240
|
+
if (p?.type !== 'tool')
|
|
241
|
+
continue;
|
|
242
|
+
if (p.name !== 'bash' && p.name !== 'shell')
|
|
243
|
+
continue;
|
|
244
|
+
const command = p.state?.input?.command;
|
|
245
|
+
if (typeof command === 'string' && command)
|
|
246
|
+
commands.push(command);
|
|
247
|
+
}
|
|
248
|
+
return { commands, sessionId: typeof message.sessionID === 'string' ? message.sessionID : '' };
|
|
249
|
+
}
|
|
250
|
+
catch {
|
|
251
|
+
return { commands: [], sessionId: '' };
|
|
252
|
+
}
|
|
253
|
+
}
|
|
199
254
|
export function walkJsonl(dir, out, cap, ext = '.jsonl') {
|
|
200
255
|
let entries;
|
|
201
256
|
try {
|
|
@@ -217,15 +272,10 @@ export function walkJsonl(dir, out, cap, ext = '.jsonl') {
|
|
|
217
272
|
// Recent rows are a newest-first window over lifetime history; aggregates keep everything.
|
|
218
273
|
export const RECENT_LIMIT = 2000;
|
|
219
274
|
const FREE_SUFFIX = /(?::free|-free)$/i;
|
|
220
|
-
const RTK_ELIGIBLE_HEADS = new Set([
|
|
221
|
-
'rtk', 'git', 'grep', 'rg', 'find', 'cat', 'ls', 'tree', 'diff', 'log',
|
|
222
|
-
'npm', 'npx', 'pnpm', 'bun', 'bunx', 'cargo', 'go', 'python', 'pytest',
|
|
223
|
-
'ruff', 'mypy', 'docker', 'kubectl', 'psql', 'aws', 'gh', 'glab', 'wc',
|
|
224
|
-
]);
|
|
225
275
|
export function newSessionAccum() {
|
|
226
276
|
return { byModel: {}, byDay: {}, byDayModel: {}, byTool: {}, byModelMessages: {}, byHost: {}, byAgent: {}, byAgentMessages: {}, byModelMeasured: {}, byModelCostMessages: {}, totals: zeroBreakdown(), messages: 0, costMeasured: 0, recent: [], recentFloor: Number.POSITIVE_INFINITY };
|
|
227
277
|
}
|
|
228
|
-
export function ingestSessionRow(accum, model, usage, ts, durMs, run, toolNames, host, id, agent) {
|
|
278
|
+
export function ingestSessionRow(accum, model, usage, ts, durMs, run, toolNames, host, id, agent, ttftMs) {
|
|
229
279
|
const watermark = readResetWatermark();
|
|
230
280
|
const ms = ts === undefined ? NaN : typeof ts === 'number' ? ts : Date.parse(ts);
|
|
231
281
|
if (watermark > 0 && (!Number.isFinite(ms) || ms < watermark))
|
|
@@ -250,11 +300,9 @@ export function ingestSessionRow(accum, model, usage, ts, durMs, run, toolNames,
|
|
|
250
300
|
const num = (v) => (typeof v === 'number' && Number.isFinite(v) && v > 0 ? Math.floor(v) : 0);
|
|
251
301
|
if (Number.isFinite(ms)) {
|
|
252
302
|
const outcome = run ?? { st: 'completed' };
|
|
253
|
-
//
|
|
254
|
-
// anything older than the floor, and re-read the floor when the list is full, so a 50k-row
|
|
255
|
-
// store costs 2000 rows of memory instead of 50k.
|
|
303
|
+
// Only the newest rows are read: skip below the floor, and a 50k-row store costs 2000 rows of memory.
|
|
256
304
|
if (accum.recent.length < RECENT_LIMIT || ms >= accum.recentFloor) {
|
|
257
|
-
accum.recent.push({ m: key, i: num(usage.input), o: num(usage.output), t: ms, d: durOf(durMs), h: host, cr: num(usage.cacheRead), cw: num(usage.cacheWrite), usd: costOf(usage), st: outcome.st, code: outcome.code, note: outcome.note, id });
|
|
305
|
+
accum.recent.push({ m: key, i: num(usage.input), o: num(usage.output), t: ms, d: durOf(durMs), tf: durOf(ttftMs), h: host, cr: num(usage.cacheRead), cw: num(usage.cacheWrite), usd: costOf(usage), st: outcome.st, code: outcome.code, note: outcome.note, id });
|
|
258
306
|
if (accum.recent.length > RECENT_LIMIT) {
|
|
259
307
|
let drop = 0;
|
|
260
308
|
for (let k = 1; k < accum.recent.length; k += 1)
|
|
@@ -326,7 +374,7 @@ export function classifySessionLine(row) {
|
|
|
326
374
|
const command = part.arguments?.command;
|
|
327
375
|
tools.push(`bash:${leadBinary(command)}`);
|
|
328
376
|
}
|
|
329
|
-
return { kind: 'assistant_row', model, usage: msg.usage, durMs: typeof msg.duration === 'number' ? msg.duration : computedDur, run: statusOf(msg), tools };
|
|
377
|
+
return { kind: 'assistant_row', model, usage: msg.usage, durMs: typeof msg.duration === 'number' ? msg.duration : computedDur, ttftMs: typeof msg.ttft === 'number' ? msg.ttft : undefined, run: statusOf(msg), tools };
|
|
330
378
|
}
|
|
331
379
|
// True when at least one token bucket holds a positive finite count.
|
|
332
380
|
export function hasPositiveUsage(usage) {
|
|
@@ -337,6 +385,25 @@ export function hasPositiveUsage(usage) {
|
|
|
337
385
|
}
|
|
338
386
|
return false;
|
|
339
387
|
}
|
|
388
|
+
/** Tool names an OpenCode message called; v2 passes SQL's comma-joined string as the blob holds newlines. */
|
|
389
|
+
export function opencodeToolNames(message, joined) {
|
|
390
|
+
if (joined !== undefined)
|
|
391
|
+
return joined === '' ? [] : joined.split(',');
|
|
392
|
+
const parts = message?.content;
|
|
393
|
+
if (!Array.isArray(parts))
|
|
394
|
+
return [];
|
|
395
|
+
const names = [];
|
|
396
|
+
for (const part of parts) {
|
|
397
|
+
if (!part || typeof part !== 'object')
|
|
398
|
+
continue;
|
|
399
|
+
if (part.type !== 'tool')
|
|
400
|
+
continue;
|
|
401
|
+
const name = part.name;
|
|
402
|
+
if (typeof name === 'string' && name)
|
|
403
|
+
names.push(name);
|
|
404
|
+
}
|
|
405
|
+
return names;
|
|
406
|
+
}
|
|
340
407
|
// One OpenCode message file; assistant rows carry the token buckets.
|
|
341
408
|
export function classifyOpencodeMessage(obj) {
|
|
342
409
|
if (!obj || typeof obj !== 'object' || obj.role !== 'assistant')
|
|
@@ -358,14 +425,16 @@ export function classifyOpencodeMessage(obj) {
|
|
|
358
425
|
const num = (v) => (typeof v === 'number' && Number.isFinite(v) && v > 0 ? v : undefined);
|
|
359
426
|
const created = num(obj.time?.created);
|
|
360
427
|
const completed = num(obj.time?.completed);
|
|
428
|
+
// `streamed` is when the first token arrived, so it is the host's own first-token latency.
|
|
429
|
+
const streamed = num(obj.time?.streamed);
|
|
361
430
|
return {
|
|
362
431
|
model: `opencode/${provider}/${model}`,
|
|
363
432
|
usage,
|
|
364
433
|
ms: created,
|
|
365
434
|
durMs: created !== undefined && completed !== undefined && completed > created ? completed - created : undefined,
|
|
435
|
+
ttftMs: created !== undefined && streamed !== undefined && streamed > created ? streamed - created : undefined,
|
|
366
436
|
};
|
|
367
437
|
}
|
|
368
|
-
// One OpenCode message file into the shared accum. The msg id is the file name.
|
|
369
438
|
export function processOpencodeFile(accum, text, file) {
|
|
370
439
|
let obj;
|
|
371
440
|
try {
|
|
@@ -374,18 +443,19 @@ export function processOpencodeFile(accum, text, file) {
|
|
|
374
443
|
catch {
|
|
375
444
|
return;
|
|
376
445
|
}
|
|
377
|
-
const
|
|
446
|
+
const message = obj;
|
|
447
|
+
const parsed = classifyOpencodeMessage(message);
|
|
378
448
|
if (!parsed)
|
|
379
449
|
return;
|
|
380
450
|
const id = file ? path.basename(file).replace(/\.[^.]+$/, '') || undefined : undefined;
|
|
381
|
-
ingestSessionRow(accum, parsed.model, parsed.usage, parsed.ms, parsed.durMs, { st: 'completed' },
|
|
451
|
+
ingestSessionRow(accum, parsed.model, parsed.usage, parsed.ms, parsed.durMs, { st: 'completed' }, opencodeToolNames(message), 'opencode', id, 'main', parsed.ttftMs);
|
|
382
452
|
}
|
|
383
453
|
// One v2 message row into the shared accum. The msg id is the row primary key.
|
|
384
454
|
export function ingestOpencodeDbRow(accum, row) {
|
|
385
455
|
const parsed = classifyOpencodeMessage(row.row);
|
|
386
456
|
if (!parsed)
|
|
387
457
|
return;
|
|
388
|
-
ingestSessionRow(accum, parsed.model, parsed.usage, parsed.ms, parsed.durMs, { st: 'completed' },
|
|
458
|
+
ingestSessionRow(accum, parsed.model, parsed.usage, parsed.ms, parsed.durMs, { st: 'completed' }, opencodeToolNames(row.row, row.tools), 'opencode', row.id, 'main', parsed.ttftMs);
|
|
389
459
|
}
|
|
390
460
|
// Fold `:free`/`-free` suffixes and case variants into one chart key.
|
|
391
461
|
export function canonicalModelId(model) {
|
|
@@ -396,8 +466,7 @@ export function messageRowId(row) {
|
|
|
396
466
|
const id = row.id;
|
|
397
467
|
return typeof id === 'string' && id ? id : undefined;
|
|
398
468
|
}
|
|
399
|
-
// Vendor names mirror the dashboard map;
|
|
400
|
-
// gateway segments shadow it (minimaxai holds xai, gemma rides nvidia).
|
|
469
|
+
// Vendor names mirror the dashboard map; family first, so gateway segments shadow it (minimaxai holds xai).
|
|
401
470
|
const VENDOR_RES = [
|
|
402
471
|
[/inclusionai|ling-/i, 'InclusionAI'],
|
|
403
472
|
[/minimax/i, 'MiniMax'],
|
|
@@ -533,9 +602,7 @@ export function hostOfSessionFile(file) {
|
|
|
533
602
|
return 'opencode';
|
|
534
603
|
return 'pi';
|
|
535
604
|
}
|
|
536
|
-
//
|
|
537
|
-
// session is a subagent's, and everything else is the main agent. The row is the host's own
|
|
538
|
-
// record, so a mirrored transcript carries the same answer as the stats database.
|
|
605
|
+
// A transcript with a parent session ran as a subagent; the host's own row, so a mirror answers the same.
|
|
539
606
|
export function agentOfSessionText(text) {
|
|
540
607
|
if (!text.includes('parentSession'))
|
|
541
608
|
return 'main';
|
|
@@ -552,7 +619,17 @@ export function agentOfSessionText(text) {
|
|
|
552
619
|
return 'main';
|
|
553
620
|
}
|
|
554
621
|
const adoptionFileCache = new Map();
|
|
555
|
-
function
|
|
622
|
+
function countBashCommands(commands, eligible, counts) {
|
|
623
|
+
for (const command of commands) {
|
|
624
|
+
counts.bashCalls += 1;
|
|
625
|
+
const head = leadBinary(command);
|
|
626
|
+
if (eligible.has(head))
|
|
627
|
+
counts.eligibleCalls += 1;
|
|
628
|
+
if (head === 'rtk')
|
|
629
|
+
counts.rtkCalls += 1;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
function parseAdoptionFile(file, eligible) {
|
|
556
633
|
const counts = { bashCalls: 0, eligibleCalls: 0, rtkCalls: 0 };
|
|
557
634
|
let text;
|
|
558
635
|
try {
|
|
@@ -561,7 +638,7 @@ function parseAdoptionFile(file) {
|
|
|
561
638
|
catch {
|
|
562
639
|
return { counts, sessions: 0 };
|
|
563
640
|
}
|
|
564
|
-
|
|
641
|
+
const commands = [];
|
|
565
642
|
for (const line of text.split('\n')) {
|
|
566
643
|
if (!line.includes('tool_execution_start'))
|
|
567
644
|
continue;
|
|
@@ -570,24 +647,22 @@ function parseAdoptionFile(file) {
|
|
|
570
647
|
if (row.type !== 'custom' || row.customType !== 'tool_execution_start' || row.data?.toolName !== 'bash')
|
|
571
648
|
continue;
|
|
572
649
|
const command = row.data.args?.command;
|
|
573
|
-
if (typeof command
|
|
574
|
-
|
|
575
|
-
sawBash = true;
|
|
576
|
-
counts.bashCalls += 1;
|
|
577
|
-
const head = leadBinary(command);
|
|
578
|
-
if (RTK_ELIGIBLE_HEADS.has(head))
|
|
579
|
-
counts.eligibleCalls += 1;
|
|
580
|
-
if (head === 'rtk')
|
|
581
|
-
counts.rtkCalls += 1;
|
|
650
|
+
if (typeof command === 'string')
|
|
651
|
+
commands.push(command);
|
|
582
652
|
}
|
|
583
653
|
catch { /* skip corrupt line */ }
|
|
584
654
|
}
|
|
585
|
-
|
|
655
|
+
countBashCommands(commands, eligible, counts);
|
|
656
|
+
return { counts, sessions: commands.length > 0 ? 1 : 0 };
|
|
586
657
|
}
|
|
587
658
|
export function clearRtkAdoptionCache() {
|
|
588
659
|
adoptionFileCache.clear();
|
|
589
660
|
}
|
|
590
661
|
export function readRtkAdoption() {
|
|
662
|
+
// The eligible set comes from rtk's rewrite contract, cached per binary version.
|
|
663
|
+
const eligible = eligibleHeads(resolveRtkBinary());
|
|
664
|
+
// A cached count is only valid for the head set that produced it.
|
|
665
|
+
const headsKey = [...eligible].sort().join(',');
|
|
591
666
|
const files = [];
|
|
592
667
|
for (const dir of sessionsDirs())
|
|
593
668
|
walkJsonl(dir, files, 2000);
|
|
@@ -609,9 +684,9 @@ export function readRtkAdoption() {
|
|
|
609
684
|
continue;
|
|
610
685
|
}
|
|
611
686
|
let entry = adoptionFileCache.get(file);
|
|
612
|
-
if (!entry || entry.size !== stat.size || entry.mtimeMs !== stat.mtimeMs) {
|
|
613
|
-
const parsed = parseAdoptionFile(file);
|
|
614
|
-
entry = { size: stat.size, mtimeMs: stat.mtimeMs, counts: parsed.counts };
|
|
687
|
+
if (!entry || entry.size !== stat.size || entry.mtimeMs !== stat.mtimeMs || entry.heads !== headsKey) {
|
|
688
|
+
const parsed = parseAdoptionFile(file, eligible);
|
|
689
|
+
entry = { size: stat.size, mtimeMs: stat.mtimeMs, heads: headsKey, counts: parsed.counts };
|
|
615
690
|
adoptionFileCache.set(file, entry);
|
|
616
691
|
sessions += parsed.sessions;
|
|
617
692
|
}
|
|
@@ -622,6 +697,30 @@ export function readRtkAdoption() {
|
|
|
622
697
|
eligibleCalls += entry.counts.eligibleCalls;
|
|
623
698
|
rtkCalls += entry.counts.rtkCalls;
|
|
624
699
|
}
|
|
700
|
+
// OpenCode keeps its own store: a v2 sqlite db, or one JSON file per message before v2.
|
|
701
|
+
const opencode = { bashCalls: 0, eligibleCalls: 0, rtkCalls: 0 };
|
|
702
|
+
const opencodeSessions = new Set();
|
|
703
|
+
if (process.env.TERSIO_SESSIONS_DIR === undefined || process.env.TERSIO_OPENCODE_DB !== undefined) {
|
|
704
|
+
for (const row of readOpencodeBashRows(opencodeDbPath())) {
|
|
705
|
+
countBashCommands([row.command], eligible, opencode);
|
|
706
|
+
if (row.sessionId)
|
|
707
|
+
opencodeSessions.add(row.sessionId);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
const ocFiles = [];
|
|
711
|
+
if (process.env.TERSIO_SESSIONS_DIR === undefined || process.env.TERSIO_OPENCODE_DIR !== undefined) {
|
|
712
|
+
walkJsonl(opencodeSessionsDir(), ocFiles, 5000, '.json');
|
|
713
|
+
for (const file of ocFiles) {
|
|
714
|
+
const parsed = opencodeFileBash(file);
|
|
715
|
+
countBashCommands(parsed.commands, eligible, opencode);
|
|
716
|
+
if (parsed.sessionId)
|
|
717
|
+
opencodeSessions.add(parsed.sessionId);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
bashCalls += opencode.bashCalls;
|
|
721
|
+
eligibleCalls += opencode.eligibleCalls;
|
|
722
|
+
rtkCalls += opencode.rtkCalls;
|
|
723
|
+
sessions += opencodeSessions.size;
|
|
625
724
|
const missedCalls = Math.max(0, eligibleCalls - rtkCalls);
|
|
626
725
|
return { sessions, bashCalls, eligibleCalls, rtkCalls, missedCalls, adoptionPct: eligibleCalls ? (rtkCalls / eligibleCalls) * 100 : 0 };
|
|
627
726
|
}
|
|
@@ -652,7 +751,7 @@ export function processSessionText(accum, state, text, host, agent) {
|
|
|
652
751
|
}
|
|
653
752
|
if (parsed.kind !== 'assistant_row' || !parsed.model || !parsed.usage)
|
|
654
753
|
continue;
|
|
655
|
-
ingestSessionRow(accum, parsed.model, parsed.usage, row.timestamp, parsed.durMs, parsed.run, parsed.tools, host, rid, agent);
|
|
754
|
+
ingestSessionRow(accum, parsed.model, parsed.usage, row.timestamp, parsed.durMs, parsed.run, parsed.tools, host, rid, agent, parsed.ttftMs);
|
|
656
755
|
}
|
|
657
756
|
catch { /* skip corrupt lines */ }
|
|
658
757
|
}
|
|
@@ -696,16 +795,12 @@ export function leadBinary(command) {
|
|
|
696
795
|
}
|
|
697
796
|
return 'bash';
|
|
698
797
|
}
|
|
699
|
-
//
|
|
798
|
+
// Pricing lives in ./pricing.ts (live LiteLLM cache + fallback table).
|
|
700
799
|
import { DEFAULT_PRICE, PRICE_TIERS_FIRM, canonicalPriceId, priceFor, pricesOverridePath, refreshPricesIfStale } from './pricing.js';
|
|
701
800
|
import { co2GramsFor, energyWhFor } from './carbon.js';
|
|
702
801
|
export { DEFAULT_PRICE, PRICE_TIERS_FIRM, priceFor, pricesOverridePath, refreshPricesIfStale };
|
|
703
802
|
export { co2GramsFor, energyWhFor };
|
|
704
|
-
/**
|
|
705
|
-
* Price one model's tokens. `measured` is the cost the host reported for those tokens; it outranks
|
|
706
|
-
* every rate card, because it is the only figure from the party that billed. Pass undefined rather
|
|
707
|
-
* than 0 for a cost nobody reported: `costOf` keeps the two apart.
|
|
708
|
-
*/
|
|
803
|
+
/** Price one model's tokens; `measured` outranks every rate card, so pass undefined when unreported. */
|
|
709
804
|
export function usdCost(t, model, measured) {
|
|
710
805
|
const m = 1 / 1_000_000;
|
|
711
806
|
if (measured !== undefined) {
|
|
@@ -727,8 +822,7 @@ export function usdCost(t, model, measured) {
|
|
|
727
822
|
};
|
|
728
823
|
return {
|
|
729
824
|
usd: buckets.input + buckets.output + buckets.cacheRead + buckets.cacheWrite,
|
|
730
|
-
//
|
|
731
|
-
// and a report that sums them beside measured cost reads as a bill it is not.
|
|
825
|
+
// `tail`/`default` are estimates; summing them beside measured cost reads as a bill it is not.
|
|
732
826
|
priced: PRICE_TIERS_FIRM.includes(hit.tier),
|
|
733
827
|
tier: hit.tier,
|
|
734
828
|
buckets,
|
|
@@ -759,7 +853,7 @@ export function readUsage() {
|
|
|
759
853
|
}
|
|
760
854
|
return rows;
|
|
761
855
|
}
|
|
762
|
-
// Delete the ledger file and return the rows cleared. Tersio-owned data only: transcripts and the RTK database
|
|
856
|
+
// Delete the ledger file and return the rows cleared. Tersio-owned data only: transcripts and the RTK database stay.
|
|
763
857
|
export function clearUsageLedger() {
|
|
764
858
|
const rows = readUsage().length;
|
|
765
859
|
try {
|