myshell-tools 1.0.0 → 2.0.0-alpha.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/CHANGELOG.md +44 -69
- package/LICENSE +21 -21
- package/README.md +178 -318
- package/dist/cli.d.ts +8 -0
- package/dist/cli.js +106 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/cost.d.ts +36 -0
- package/dist/commands/cost.js +103 -0
- package/dist/commands/cost.js.map +1 -0
- package/dist/commands/doctor.d.ts +36 -0
- package/dist/commands/doctor.js +115 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/core/assess.d.ts +25 -0
- package/dist/core/assess.js +142 -0
- package/dist/core/assess.js.map +1 -0
- package/dist/core/classify.d.ts +19 -0
- package/dist/core/classify.js +80 -0
- package/dist/core/classify.js.map +1 -0
- package/dist/core/escalate.d.ts +32 -0
- package/dist/core/escalate.js +57 -0
- package/dist/core/escalate.js.map +1 -0
- package/dist/core/index.d.ts +13 -0
- package/dist/core/index.js +12 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/orchestrate.d.ts +42 -0
- package/dist/core/orchestrate.js +439 -0
- package/dist/core/orchestrate.js.map +1 -0
- package/dist/core/policy.d.ts +9 -0
- package/dist/core/policy.js +27 -0
- package/dist/core/policy.js.map +1 -0
- package/dist/core/prompt.d.ts +26 -0
- package/dist/core/prompt.js +125 -0
- package/dist/core/prompt.js.map +1 -0
- package/dist/core/review.d.ts +46 -0
- package/dist/core/review.js +148 -0
- package/dist/core/review.js.map +1 -0
- package/dist/core/route.d.ts +28 -0
- package/dist/core/route.js +52 -0
- package/dist/core/route.js.map +1 -0
- package/dist/core/types.d.ts +141 -0
- package/dist/core/types.js +14 -0
- package/dist/core/types.js.map +1 -0
- package/dist/infra/atomic.d.ts +53 -0
- package/dist/infra/atomic.js +171 -0
- package/dist/infra/atomic.js.map +1 -0
- package/dist/infra/clock.d.ts +9 -0
- package/dist/infra/clock.js +15 -0
- package/dist/infra/clock.js.map +1 -0
- package/dist/infra/index.d.ts +9 -0
- package/dist/infra/index.js +7 -0
- package/dist/infra/index.js.map +1 -0
- package/dist/infra/ledger.d.ts +49 -0
- package/dist/infra/ledger.js +90 -0
- package/dist/infra/ledger.js.map +1 -0
- package/dist/infra/paths.d.ts +28 -0
- package/dist/infra/paths.js +38 -0
- package/dist/infra/paths.js.map +1 -0
- package/dist/infra/pricing.d.ts +47 -0
- package/dist/infra/pricing.js +151 -0
- package/dist/infra/pricing.js.map +1 -0
- package/dist/infra/session.d.ts +28 -0
- package/dist/infra/session.js +61 -0
- package/dist/infra/session.js.map +1 -0
- package/dist/interface/render.d.ts +27 -0
- package/dist/interface/render.js +134 -0
- package/dist/interface/render.js.map +1 -0
- package/dist/interface/repl.d.ts +23 -0
- package/dist/interface/repl.js +90 -0
- package/dist/interface/repl.js.map +1 -0
- package/dist/interface/run.d.ts +20 -0
- package/dist/interface/run.js +31 -0
- package/dist/interface/run.js.map +1 -0
- package/dist/providers/claude-parse.d.ts +24 -0
- package/dist/providers/claude-parse.js +113 -0
- package/dist/providers/claude-parse.js.map +1 -0
- package/dist/providers/claude.d.ts +45 -0
- package/dist/providers/claude.js +122 -0
- package/dist/providers/claude.js.map +1 -0
- package/dist/providers/codex-parse.d.ts +32 -0
- package/dist/providers/codex-parse.js +145 -0
- package/dist/providers/codex-parse.js.map +1 -0
- package/dist/providers/codex.d.ts +44 -0
- package/dist/providers/codex.js +124 -0
- package/dist/providers/codex.js.map +1 -0
- package/dist/providers/detect.d.ts +49 -0
- package/dist/providers/detect.js +125 -0
- package/dist/providers/detect.js.map +1 -0
- package/dist/providers/errors.d.ts +49 -0
- package/dist/providers/errors.js +189 -0
- package/dist/providers/errors.js.map +1 -0
- package/dist/providers/index.d.ts +9 -0
- package/dist/providers/index.js +7 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/port.d.ts +74 -0
- package/dist/providers/port.js +16 -0
- package/dist/providers/port.js.map +1 -0
- package/dist/providers/registry.d.ts +21 -0
- package/dist/providers/registry.js +34 -0
- package/dist/providers/registry.js.map +1 -0
- package/dist/ui/banner.d.ts +19 -0
- package/dist/ui/banner.js +32 -0
- package/dist/ui/banner.js.map +1 -0
- package/dist/ui/spinner.d.ts +27 -0
- package/dist/ui/spinner.js +67 -0
- package/dist/ui/spinner.js.map +1 -0
- package/dist/ui/theme.d.ts +32 -0
- package/dist/ui/theme.js +56 -0
- package/dist/ui/theme.js.map +1 -0
- package/package.json +55 -49
- package/data/orchestrator.json +0 -113
- package/src/auth/recovery.mjs +0 -328
- package/src/auth/refresh.mjs +0 -373
- package/src/chef.mjs +0 -348
- package/src/cli/doctor.mjs +0 -568
- package/src/cli/reset.mjs +0 -447
- package/src/cli/status.mjs +0 -379
- package/src/cli.mjs +0 -429
- package/src/commands/doctor.mjs +0 -375
- package/src/commands/help.mjs +0 -324
- package/src/commands/status.mjs +0 -331
- package/src/monitor/health.mjs +0 -486
- package/src/monitor/performance.mjs +0 -442
- package/src/monitor/report.mjs +0 -535
- package/src/orchestrator/classify.mjs +0 -391
- package/src/orchestrator/confidence.mjs +0 -151
- package/src/orchestrator/handoffs.mjs +0 -231
- package/src/orchestrator/review.mjs +0 -222
- package/src/providers/balance.mjs +0 -201
- package/src/providers/claude.mjs +0 -236
- package/src/providers/codex.mjs +0 -255
- package/src/providers/detect.mjs +0 -185
- package/src/providers/errors.mjs +0 -373
- package/src/providers/select.mjs +0 -162
- package/src/repl-enhanced.mjs +0 -417
- package/src/repl.mjs +0 -321
- package/src/state/archive.mjs +0 -366
- package/src/state/atomic.mjs +0 -116
- package/src/state/cleanup.mjs +0 -440
- package/src/state/recovery.mjs +0 -461
- package/src/state/session.mjs +0 -147
- package/src/ui/errors.mjs +0 -456
- package/src/ui/formatter.mjs +0 -327
- package/src/ui/icons.mjs +0 -318
- package/src/ui/progress.mjs +0 -468
- package/templates/prompts/confidence-format.txt +0 -14
- package/templates/prompts/ic-with-feedback.txt +0 -41
- package/templates/prompts/ic.txt +0 -13
- package/templates/prompts/manager-review.txt +0 -40
- package/templates/prompts/manager.txt +0 -14
- package/templates/prompts/worker.txt +0 -12
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* atomic.ts — Atomic file operations for safe concurrent access
|
|
3
|
+
* Ported from myshell-tools/src/state/atomic.mjs with the following fixes:
|
|
4
|
+
* - Full TypeScript strict typing
|
|
5
|
+
* - Async (fs/promises) throughout
|
|
6
|
+
* - Async backoff instead of CPU-spinning lock acquisition
|
|
7
|
+
* - O(1) JSONL append via fs.appendFile (no read-then-rewrite)
|
|
8
|
+
*/
|
|
9
|
+
import { open, rename, unlink, stat, appendFile } from 'node:fs/promises';
|
|
10
|
+
import { constants } from 'node:fs';
|
|
11
|
+
import { randomBytes } from 'node:crypto';
|
|
12
|
+
export class LockTimeoutError extends Error {
|
|
13
|
+
constructor(lockPath, timeoutMs) {
|
|
14
|
+
super(`Lock acquisition timed out after ${timeoutMs}ms for: ${lockPath}`);
|
|
15
|
+
this.name = 'LockTimeoutError';
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export class AtomicWriteError extends Error {
|
|
19
|
+
constructor(filePath, cause) {
|
|
20
|
+
super(`Atomic write failed for: ${filePath} — ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
21
|
+
this.name = 'AtomicWriteError';
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
// Internal helpers
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
const DEFAULT_TIMEOUT_MS = 5_000;
|
|
28
|
+
const DEFAULT_STALE_MS = 10_000;
|
|
29
|
+
/** Async sleep. */
|
|
30
|
+
function sleep(ms) {
|
|
31
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
32
|
+
}
|
|
33
|
+
/** Unique suffix so concurrent processes never collide on the tmp file. */
|
|
34
|
+
function tmpSuffix() {
|
|
35
|
+
return `${process.pid}.${randomBytes(4).toString('hex')}`;
|
|
36
|
+
}
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
// Lock primitives
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
/**
|
|
41
|
+
* Acquire a `.lock` file using `O_EXCL` for atomic creation.
|
|
42
|
+
* Retries with exponential backoff until `timeoutMs` is reached.
|
|
43
|
+
* Steals locks whose mtime is older than `staleMs`.
|
|
44
|
+
*
|
|
45
|
+
* Throws `LockTimeoutError` if the lock cannot be acquired in time.
|
|
46
|
+
*/
|
|
47
|
+
export async function acquireLock(lockPath, opts) {
|
|
48
|
+
const timeoutMs = opts?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
49
|
+
const staleMs = opts?.staleMs ?? DEFAULT_STALE_MS;
|
|
50
|
+
const deadline = Date.now() + timeoutMs;
|
|
51
|
+
let attempt = 0;
|
|
52
|
+
while (Date.now() < deadline) {
|
|
53
|
+
try {
|
|
54
|
+
// O_EXCL guarantees atomic creation — only one caller wins
|
|
55
|
+
const fh = await open(lockPath, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL);
|
|
56
|
+
try {
|
|
57
|
+
await fh.writeFile(JSON.stringify({ pid: process.pid, ts: Date.now() }));
|
|
58
|
+
}
|
|
59
|
+
finally {
|
|
60
|
+
await fh.close();
|
|
61
|
+
}
|
|
62
|
+
return; // lock acquired
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
const nodeErr = err;
|
|
66
|
+
if (nodeErr.code !== 'EEXIST')
|
|
67
|
+
throw err;
|
|
68
|
+
// Check whether the existing lock is stale
|
|
69
|
+
try {
|
|
70
|
+
const st = await stat(lockPath);
|
|
71
|
+
if (Date.now() - st.mtimeMs > staleMs) {
|
|
72
|
+
// Stale — the holding process likely crashed; steal it
|
|
73
|
+
try {
|
|
74
|
+
await unlink(lockPath);
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
// Another process may have already removed it; harmless
|
|
78
|
+
}
|
|
79
|
+
continue; // retry immediately
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
// Lock file disappeared between the EEXIST and stat — retry
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
// Back off before the next attempt (50 ms, 100 ms, 200 ms … up to 1 s)
|
|
87
|
+
const waitMs = Math.min(50 * 2 ** attempt, 1_000);
|
|
88
|
+
attempt++;
|
|
89
|
+
// Don't sleep past the deadline
|
|
90
|
+
const remaining = deadline - Date.now();
|
|
91
|
+
if (remaining <= 0)
|
|
92
|
+
break;
|
|
93
|
+
await sleep(Math.min(waitMs, remaining));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
throw new LockTimeoutError(lockPath, timeoutMs);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Release a lock file previously acquired with `acquireLock`.
|
|
100
|
+
* Silently ignores a missing lock file (idempotent).
|
|
101
|
+
*/
|
|
102
|
+
export async function releaseLock(lockPath) {
|
|
103
|
+
try {
|
|
104
|
+
await unlink(lockPath);
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
// Already gone — that's fine
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Convenience wrapper: acquire the lock, run `fn`, then always release.
|
|
112
|
+
* Re-throws any error from `fn` after releasing the lock.
|
|
113
|
+
*/
|
|
114
|
+
export async function withLock(lockPath, fn, opts) {
|
|
115
|
+
await acquireLock(lockPath, opts);
|
|
116
|
+
try {
|
|
117
|
+
return await fn();
|
|
118
|
+
}
|
|
119
|
+
finally {
|
|
120
|
+
await releaseLock(lockPath);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// ---------------------------------------------------------------------------
|
|
124
|
+
// Atomic file operations
|
|
125
|
+
// ---------------------------------------------------------------------------
|
|
126
|
+
/**
|
|
127
|
+
* Atomically write `data` to `filePath` using a tmp-file + rename strategy.
|
|
128
|
+
* The tmp file lives in the same directory to avoid cross-device rename issues.
|
|
129
|
+
*/
|
|
130
|
+
export async function atomicWrite(filePath, data) {
|
|
131
|
+
const tmp = `${filePath}.tmp.${tmpSuffix()}`;
|
|
132
|
+
try {
|
|
133
|
+
const fh = await open(tmp, 'w');
|
|
134
|
+
try {
|
|
135
|
+
await fh.writeFile(data);
|
|
136
|
+
}
|
|
137
|
+
finally {
|
|
138
|
+
await fh.close();
|
|
139
|
+
}
|
|
140
|
+
await rename(tmp, filePath);
|
|
141
|
+
}
|
|
142
|
+
catch (err) {
|
|
143
|
+
// Best-effort cleanup of orphaned tmp file
|
|
144
|
+
try {
|
|
145
|
+
await unlink(tmp);
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
/* ignore */
|
|
149
|
+
}
|
|
150
|
+
throw new AtomicWriteError(filePath, err);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Atomically append a single JSONL entry to `filePath`.
|
|
155
|
+
*
|
|
156
|
+
* This is O(1) — it uses `fs.appendFile` rather than reading the entire file
|
|
157
|
+
* and rewriting it on every call. The caller is responsible for holding a lock
|
|
158
|
+
* when concurrent appends must be strictly ordered.
|
|
159
|
+
*
|
|
160
|
+
* Creates `filePath` if it does not exist.
|
|
161
|
+
*/
|
|
162
|
+
export async function atomicAppendJSONL(filePath, entry) {
|
|
163
|
+
const line = JSON.stringify(entry) + '\n';
|
|
164
|
+
try {
|
|
165
|
+
await appendFile(filePath, line, 'utf8');
|
|
166
|
+
}
|
|
167
|
+
catch (err) {
|
|
168
|
+
throw new AtomicWriteError(filePath, err);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
//# sourceMappingURL=atomic.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"atomic.js","sourceRoot":"","sources":["../../src/infra/atomic.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/infra/clock.ts — System clock implementation of the Clock port.
|
|
3
|
+
*
|
|
4
|
+
* This is the SINGLE place in the codebase where impure time/random globals
|
|
5
|
+
* (Date, Math.random, randomUUID) are allowed. All other modules receive a
|
|
6
|
+
* Clock via injection, keeping them pure and testable.
|
|
7
|
+
*/
|
|
8
|
+
import type { Clock } from '../core/types.js';
|
|
9
|
+
export declare const systemClock: Clock;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/infra/clock.ts — System clock implementation of the Clock port.
|
|
3
|
+
*
|
|
4
|
+
* This is the SINGLE place in the codebase where impure time/random globals
|
|
5
|
+
* (Date, Math.random, randomUUID) are allowed. All other modules receive a
|
|
6
|
+
* Clock via injection, keeping them pure and testable.
|
|
7
|
+
*/
|
|
8
|
+
import { randomUUID } from 'node:crypto';
|
|
9
|
+
export const systemClock = {
|
|
10
|
+
now: () => Date.now(),
|
|
11
|
+
isoNow: () => new Date().toISOString(),
|
|
12
|
+
uuid: () => randomUUID(),
|
|
13
|
+
random: () => Math.random(),
|
|
14
|
+
};
|
|
15
|
+
//# sourceMappingURL=clock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clock.js","sourceRoot":"","sources":["../../src/infra/clock.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,MAAM,CAAC,MAAM,WAAW,GAAU;IAChC,GAAG,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE;IACrB,MAAM,EAAE,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;IACtC,IAAI,EAAE,GAAG,EAAE,CAAC,UAAU,EAAE;IACxB,MAAM,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE;CAC5B,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { acquireLock, releaseLock, withLock, atomicWrite, atomicAppendJSONL } from './atomic.js';
|
|
2
|
+
export type { LockOptions } from './atomic.js';
|
|
3
|
+
export { getModelPricing, calculateCost, getCheapestForTier, isPricingStale, PRICING_TABLE, } from './pricing.js';
|
|
4
|
+
export type { ModelPricing, PricingTable } from './pricing.js';
|
|
5
|
+
export { systemClock } from './clock.js';
|
|
6
|
+
export { getStateDir, getSessionsDir, getSessionFile, getLedgerFile } from './paths.js';
|
|
7
|
+
export { createSessionWriter, readSession } from './session.js';
|
|
8
|
+
export { createLedger, readLedger, summarizeLedger } from './ledger.js';
|
|
9
|
+
export type { ModelSummary, LedgerSummary } from './ledger.js';
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { acquireLock, releaseLock, withLock, atomicWrite, atomicAppendJSONL } from './atomic.js';
|
|
2
|
+
export { getModelPricing, calculateCost, getCheapestForTier, isPricingStale, PRICING_TABLE, } from './pricing.js';
|
|
3
|
+
export { systemClock } from './clock.js';
|
|
4
|
+
export { getStateDir, getSessionsDir, getSessionFile, getLedgerFile } from './paths.js';
|
|
5
|
+
export { createSessionWriter, readSession } from './session.js';
|
|
6
|
+
export { createLedger, readLedger, summarizeLedger } from './ledger.js';
|
|
7
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/infra/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,QAAQ,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAEjG,OAAO,EACL,eAAe,EACf,aAAa,EACb,kBAAkB,EAClB,cAAc,EACd,aAAa,GACd,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AACxF,OAAO,EAAE,mBAAmB,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAChE,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/infra/ledger.ts — LedgerWriter implementation, ledger reader, and summarizer.
|
|
3
|
+
*
|
|
4
|
+
* The ledger is a JSONL file at `.myshell-tools/ledger.jsonl`. Each `record` call appends
|
|
5
|
+
* one LedgerEntry atomically. `readLedger` re-hydrates entries (skipping malformed
|
|
6
|
+
* lines). `summarizeLedger` is a pure reduction used by `myshell-tools cost`.
|
|
7
|
+
*/
|
|
8
|
+
import type { LedgerEntry, LedgerWriter } from '../core/types.js';
|
|
9
|
+
/**
|
|
10
|
+
* Create a LedgerWriter for the given working directory.
|
|
11
|
+
*
|
|
12
|
+
* @param opts.cwd - The project working directory (parent of `.myshell-tools/`).
|
|
13
|
+
*/
|
|
14
|
+
export declare function createLedger(opts: {
|
|
15
|
+
cwd: string;
|
|
16
|
+
}): LedgerWriter;
|
|
17
|
+
/**
|
|
18
|
+
* Read all LedgerEntry records from the ledger JSONL file.
|
|
19
|
+
*
|
|
20
|
+
* Returns an empty array if the file does not exist. Malformed lines are
|
|
21
|
+
* silently skipped.
|
|
22
|
+
*
|
|
23
|
+
* @param cwd - The project working directory.
|
|
24
|
+
*/
|
|
25
|
+
export declare function readLedger(cwd: string): Promise<LedgerEntry[]>;
|
|
26
|
+
/** Per-model aggregation used in the summary. */
|
|
27
|
+
export interface ModelSummary {
|
|
28
|
+
readonly calls: number;
|
|
29
|
+
readonly usd: number;
|
|
30
|
+
}
|
|
31
|
+
/** Aggregate summary returned by `summarizeLedger`. */
|
|
32
|
+
export interface LedgerSummary {
|
|
33
|
+
readonly totalUsd: number;
|
|
34
|
+
readonly calls: number;
|
|
35
|
+
readonly byModel: Record<string, ModelSummary>;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Pure reduction over a ledger entry array.
|
|
39
|
+
*
|
|
40
|
+
* Computes:
|
|
41
|
+
* - `totalUsd` — sum of all `entry.usd` values
|
|
42
|
+
* - `calls` — total number of entries
|
|
43
|
+
* - `byModel` — per-model breakdown keyed by `entry.model`
|
|
44
|
+
*
|
|
45
|
+
* No I/O. Safe to call in tests with hand-built arrays.
|
|
46
|
+
*
|
|
47
|
+
* @param entries - Array of LedgerEntry objects (may be empty).
|
|
48
|
+
*/
|
|
49
|
+
export declare function summarizeLedger(entries: LedgerEntry[]): LedgerSummary;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/infra/ledger.ts — LedgerWriter implementation, ledger reader, and summarizer.
|
|
3
|
+
*
|
|
4
|
+
* The ledger is a JSONL file at `.myshell-tools/ledger.jsonl`. Each `record` call appends
|
|
5
|
+
* one LedgerEntry atomically. `readLedger` re-hydrates entries (skipping malformed
|
|
6
|
+
* lines). `summarizeLedger` is a pure reduction used by `myshell-tools cost`.
|
|
7
|
+
*/
|
|
8
|
+
import { mkdir, readFile } from 'node:fs/promises';
|
|
9
|
+
import { atomicAppendJSONL } from './atomic.js';
|
|
10
|
+
import { getStateDir, getLedgerFile } from './paths.js';
|
|
11
|
+
/**
|
|
12
|
+
* Create a LedgerWriter for the given working directory.
|
|
13
|
+
*
|
|
14
|
+
* @param opts.cwd - The project working directory (parent of `.myshell-tools/`).
|
|
15
|
+
*/
|
|
16
|
+
export function createLedger(opts) {
|
|
17
|
+
const { cwd } = opts;
|
|
18
|
+
return {
|
|
19
|
+
async record(entry) {
|
|
20
|
+
await mkdir(getStateDir(cwd), { recursive: true });
|
|
21
|
+
await atomicAppendJSONL(getLedgerFile(cwd), entry);
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Read all LedgerEntry records from the ledger JSONL file.
|
|
27
|
+
*
|
|
28
|
+
* Returns an empty array if the file does not exist. Malformed lines are
|
|
29
|
+
* silently skipped.
|
|
30
|
+
*
|
|
31
|
+
* @param cwd - The project working directory.
|
|
32
|
+
*/
|
|
33
|
+
export async function readLedger(cwd) {
|
|
34
|
+
let raw;
|
|
35
|
+
try {
|
|
36
|
+
raw = await readFile(getLedgerFile(cwd), 'utf8');
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
const nodeErr = err;
|
|
40
|
+
if (nodeErr.code === 'ENOENT')
|
|
41
|
+
return [];
|
|
42
|
+
throw err;
|
|
43
|
+
}
|
|
44
|
+
const entries = [];
|
|
45
|
+
for (const line of raw.split('\n')) {
|
|
46
|
+
const trimmed = line.trim();
|
|
47
|
+
if (trimmed.length === 0)
|
|
48
|
+
continue;
|
|
49
|
+
try {
|
|
50
|
+
entries.push(JSON.parse(trimmed));
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
// Skip malformed lines
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return entries;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Pure reduction over a ledger entry array.
|
|
60
|
+
*
|
|
61
|
+
* Computes:
|
|
62
|
+
* - `totalUsd` — sum of all `entry.usd` values
|
|
63
|
+
* - `calls` — total number of entries
|
|
64
|
+
* - `byModel` — per-model breakdown keyed by `entry.model`
|
|
65
|
+
*
|
|
66
|
+
* No I/O. Safe to call in tests with hand-built arrays.
|
|
67
|
+
*
|
|
68
|
+
* @param entries - Array of LedgerEntry objects (may be empty).
|
|
69
|
+
*/
|
|
70
|
+
export function summarizeLedger(entries) {
|
|
71
|
+
let totalUsd = 0;
|
|
72
|
+
const byModel = {};
|
|
73
|
+
for (const entry of entries) {
|
|
74
|
+
totalUsd += entry.usd;
|
|
75
|
+
const existing = byModel[entry.model];
|
|
76
|
+
if (existing !== undefined) {
|
|
77
|
+
existing.calls += 1;
|
|
78
|
+
existing.usd += entry.usd;
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
byModel[entry.model] = { calls: 1, usd: entry.usd };
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
totalUsd,
|
|
86
|
+
calls: entries.length,
|
|
87
|
+
byModel,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=ledger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ledger.js","sourceRoot":"","sources":["../../src/infra/ledger.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAEnD,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAExD;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,IAAqB;IAChD,MAAM,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;IAErB,OAAO;QACL,KAAK,CAAC,MAAM,CAAC,KAAkB;YAC7B,MAAM,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YACnD,MAAM,iBAAiB,CAAC,aAAa,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC,CAAC;QACrD,CAAC;KACF,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,GAAW;IAC1C,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACH,GAAG,GAAG,MAAM,QAAQ,CAAC,aAAa,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,CAAC;IACnD,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,OAAO,GAAG,GAA4B,CAAC;QAC7C,IAAI,OAAO,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,EAAE,CAAC;QACzC,MAAM,GAAG,CAAC;IACZ,CAAC;IAED,MAAM,OAAO,GAAkB,EAAE,CAAC;IAClC,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACnC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,SAAS;QACnC,IAAI,CAAC;YACH,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAgB,CAAC,CAAC;QACnD,CAAC;QAAC,MAAM,CAAC;YACP,uBAAuB;QACzB,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAeD;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,eAAe,CAAC,OAAsB;IACpD,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,MAAM,OAAO,GAAmD,EAAE,CAAC;IAEnE,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,QAAQ,IAAI,KAAK,CAAC,GAAG,CAAC;QAEtB,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QACtC,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC3B,QAAQ,CAAC,KAAK,IAAI,CAAC,CAAC;YACpB,QAAQ,CAAC,GAAG,IAAI,KAAK,CAAC,GAAG,CAAC;QAC5B,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,CAAC;QACtD,CAAC;IACH,CAAC;IAED,OAAO;QACL,QAAQ;QACR,KAAK,EAAE,OAAO,CAAC,MAAM;QACrB,OAAO;KACR,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/infra/paths.ts — Pure path helpers for myshell-tools's filesystem layout.
|
|
3
|
+
*
|
|
4
|
+
* All functions are pure path joins (no fs I/O). The canonical layout is:
|
|
5
|
+
* <cwd>/
|
|
6
|
+
* .myshell-tools/
|
|
7
|
+
* sessions/
|
|
8
|
+
* current.jsonl ← active session log
|
|
9
|
+
* ledger.jsonl ← cost/usage ledger
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Returns the path to the `.myshell-tools` directory inside the given working dir.
|
|
13
|
+
*/
|
|
14
|
+
export declare function getStateDir(cwd: string): string;
|
|
15
|
+
/**
|
|
16
|
+
* Returns the path to the `sessions` subdirectory inside `.myshell-tools`.
|
|
17
|
+
*/
|
|
18
|
+
export declare function getSessionsDir(cwd: string): string;
|
|
19
|
+
/**
|
|
20
|
+
* Returns the path to the current session JSONL file.
|
|
21
|
+
* Path: <cwd>/.myshell-tools/sessions/current.jsonl
|
|
22
|
+
*/
|
|
23
|
+
export declare function getSessionFile(cwd: string): string;
|
|
24
|
+
/**
|
|
25
|
+
* Returns the path to the cost/usage ledger JSONL file.
|
|
26
|
+
* Path: <cwd>/.myshell-tools/ledger.jsonl
|
|
27
|
+
*/
|
|
28
|
+
export declare function getLedgerFile(cwd: string): string;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/infra/paths.ts — Pure path helpers for myshell-tools's filesystem layout.
|
|
3
|
+
*
|
|
4
|
+
* All functions are pure path joins (no fs I/O). The canonical layout is:
|
|
5
|
+
* <cwd>/
|
|
6
|
+
* .myshell-tools/
|
|
7
|
+
* sessions/
|
|
8
|
+
* current.jsonl ← active session log
|
|
9
|
+
* ledger.jsonl ← cost/usage ledger
|
|
10
|
+
*/
|
|
11
|
+
import { join } from 'node:path';
|
|
12
|
+
/**
|
|
13
|
+
* Returns the path to the `.myshell-tools` directory inside the given working dir.
|
|
14
|
+
*/
|
|
15
|
+
export function getStateDir(cwd) {
|
|
16
|
+
return join(cwd, '.myshell-tools');
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Returns the path to the `sessions` subdirectory inside `.myshell-tools`.
|
|
20
|
+
*/
|
|
21
|
+
export function getSessionsDir(cwd) {
|
|
22
|
+
return join(getStateDir(cwd), 'sessions');
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Returns the path to the current session JSONL file.
|
|
26
|
+
* Path: <cwd>/.myshell-tools/sessions/current.jsonl
|
|
27
|
+
*/
|
|
28
|
+
export function getSessionFile(cwd) {
|
|
29
|
+
return join(getSessionsDir(cwd), 'current.jsonl');
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Returns the path to the cost/usage ledger JSONL file.
|
|
33
|
+
* Path: <cwd>/.myshell-tools/ledger.jsonl
|
|
34
|
+
*/
|
|
35
|
+
export function getLedgerFile(cwd) {
|
|
36
|
+
return join(getStateDir(cwd), 'ledger.jsonl');
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=paths.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.js","sourceRoot":"","sources":["../../src/infra/paths.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC;;GAEG;AACH,MAAM,UAAU,WAAW,CAAC,GAAW;IACrC,OAAO,IAAI,CAAC,GAAG,EAAE,gBAAgB,CAAC,CAAC;AACrC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,cAAc,CAAC,GAAW;IACxC,OAAO,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,UAAU,CAAC,CAAC;AAC5C,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,GAAW;IACxC,OAAO,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,EAAE,eAAe,CAAC,CAAC;AACpD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,GAAW;IACvC,OAAO,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,cAAc,CAAC,CAAC;AAChD,CAAC"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/infra/pricing.ts
|
|
3
|
+
*
|
|
4
|
+
* Corrected price table — Appendix A of the myshell-tools project plan.
|
|
5
|
+
*
|
|
6
|
+
* Provenance
|
|
7
|
+
* ----------
|
|
8
|
+
* Claude pricing : https://www.anthropic.com/pricing
|
|
9
|
+
* OpenAI pricing : https://platform.openai.com/docs/pricing
|
|
10
|
+
* Captured : 2026-05-29
|
|
11
|
+
*/
|
|
12
|
+
export interface ModelPricing {
|
|
13
|
+
readonly provider: 'claude' | 'codex';
|
|
14
|
+
readonly model: string;
|
|
15
|
+
readonly aliases: readonly string[];
|
|
16
|
+
readonly tier: 'worker' | 'ic' | 'manager';
|
|
17
|
+
readonly inputPer1M: number;
|
|
18
|
+
readonly outputPer1M: number;
|
|
19
|
+
readonly contextWindow: number;
|
|
20
|
+
}
|
|
21
|
+
export interface PricingTable {
|
|
22
|
+
readonly asOf: string;
|
|
23
|
+
readonly sourceUrls: readonly string[];
|
|
24
|
+
readonly models: readonly ModelPricing[];
|
|
25
|
+
}
|
|
26
|
+
export declare const PRICING_TABLE: PricingTable;
|
|
27
|
+
/**
|
|
28
|
+
* Look up a model entry by exact model ID or any of its aliases.
|
|
29
|
+
* The lookup is case-insensitive.
|
|
30
|
+
*/
|
|
31
|
+
export declare function getModelPricing(provider: string, model: string): ModelPricing | undefined;
|
|
32
|
+
/**
|
|
33
|
+
* Calculate the USD cost for a given number of input and output tokens.
|
|
34
|
+
*/
|
|
35
|
+
export declare function calculateCost(inputTokens: number, outputTokens: number, pricing: ModelPricing): number;
|
|
36
|
+
/**
|
|
37
|
+
* Return the cheapest model (lowest inputPer1M) for a given tier,
|
|
38
|
+
* optionally restricted to the supplied provider IDs.
|
|
39
|
+
*
|
|
40
|
+
* Throws if no matching model exists.
|
|
41
|
+
*/
|
|
42
|
+
export declare function getCheapestForTier(tier: 'worker' | 'ic' | 'manager', availableProviders?: string[]): ModelPricing;
|
|
43
|
+
/**
|
|
44
|
+
* Returns true when the pricing table is older than maxAgeDays (default 90).
|
|
45
|
+
* Useful for emitting a staleness warning at runtime.
|
|
46
|
+
*/
|
|
47
|
+
export declare function isPricingStale(maxAgeDays?: number): boolean;
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/infra/pricing.ts
|
|
3
|
+
*
|
|
4
|
+
* Corrected price table — Appendix A of the myshell-tools project plan.
|
|
5
|
+
*
|
|
6
|
+
* Provenance
|
|
7
|
+
* ----------
|
|
8
|
+
* Claude pricing : https://www.anthropic.com/pricing
|
|
9
|
+
* OpenAI pricing : https://platform.openai.com/docs/pricing
|
|
10
|
+
* Captured : 2026-05-29
|
|
11
|
+
*/
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Data
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
export const PRICING_TABLE = {
|
|
16
|
+
asOf: '2026-05-29',
|
|
17
|
+
sourceUrls: [
|
|
18
|
+
'https://www.anthropic.com/pricing',
|
|
19
|
+
'https://platform.openai.com/docs/pricing',
|
|
20
|
+
],
|
|
21
|
+
models: [
|
|
22
|
+
// ---- Anthropic / Claude ------------------------------------------------
|
|
23
|
+
{
|
|
24
|
+
provider: 'claude',
|
|
25
|
+
model: 'claude-opus-4-7',
|
|
26
|
+
aliases: ['opus', 'opus-4.7', 'claude-opus-4.7'],
|
|
27
|
+
tier: 'manager',
|
|
28
|
+
inputPer1M: 5,
|
|
29
|
+
outputPer1M: 25,
|
|
30
|
+
contextWindow: 200_000,
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
provider: 'claude',
|
|
34
|
+
model: 'claude-sonnet-4-6',
|
|
35
|
+
aliases: ['sonnet', 'sonnet-4.6', 'claude-sonnet-4.6'],
|
|
36
|
+
tier: 'ic',
|
|
37
|
+
inputPer1M: 3,
|
|
38
|
+
outputPer1M: 15,
|
|
39
|
+
contextWindow: 200_000,
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
provider: 'claude',
|
|
43
|
+
model: 'claude-haiku-4-5',
|
|
44
|
+
aliases: ['haiku', 'haiku-4.5', 'claude-haiku-4.5'],
|
|
45
|
+
tier: 'worker',
|
|
46
|
+
inputPer1M: 0.8,
|
|
47
|
+
outputPer1M: 4,
|
|
48
|
+
contextWindow: 200_000,
|
|
49
|
+
},
|
|
50
|
+
// ---- OpenAI / Codex ----------------------------------------------------
|
|
51
|
+
{
|
|
52
|
+
provider: 'codex',
|
|
53
|
+
model: 'gpt-5.5',
|
|
54
|
+
aliases: ['gpt5.5', 'gpt-5-5'],
|
|
55
|
+
tier: 'manager',
|
|
56
|
+
inputPer1M: 5,
|
|
57
|
+
outputPer1M: 30,
|
|
58
|
+
contextWindow: 128_000,
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
provider: 'codex',
|
|
62
|
+
model: 'gpt-5.4',
|
|
63
|
+
aliases: ['gpt5.4', 'gpt-5-4'],
|
|
64
|
+
tier: 'ic',
|
|
65
|
+
inputPer1M: 2.5,
|
|
66
|
+
outputPer1M: 15,
|
|
67
|
+
contextWindow: 128_000,
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
provider: 'codex',
|
|
71
|
+
model: 'gpt-5.4-mini',
|
|
72
|
+
aliases: ['gpt5.4-mini', 'gpt-5-4-mini'],
|
|
73
|
+
tier: 'worker',
|
|
74
|
+
inputPer1M: 0.75,
|
|
75
|
+
outputPer1M: 4.5,
|
|
76
|
+
contextWindow: 128_000,
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
provider: 'codex',
|
|
80
|
+
model: 'gpt-5.4-nano',
|
|
81
|
+
aliases: ['gpt5.4-nano', 'gpt-5-4-nano'],
|
|
82
|
+
tier: 'worker',
|
|
83
|
+
inputPer1M: 0.2,
|
|
84
|
+
outputPer1M: 1.25,
|
|
85
|
+
contextWindow: 128_000,
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
provider: 'codex',
|
|
89
|
+
model: 'gpt-5.2-codex',
|
|
90
|
+
aliases: ['codex', 'gpt5.2-codex', 'gpt-5-2-codex'],
|
|
91
|
+
tier: 'ic',
|
|
92
|
+
inputPer1M: 1.75,
|
|
93
|
+
outputPer1M: 14,
|
|
94
|
+
contextWindow: 128_000,
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
};
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
// Helpers
|
|
100
|
+
// ---------------------------------------------------------------------------
|
|
101
|
+
/**
|
|
102
|
+
* Look up a model entry by exact model ID or any of its aliases.
|
|
103
|
+
* The lookup is case-insensitive.
|
|
104
|
+
*/
|
|
105
|
+
export function getModelPricing(provider, model) {
|
|
106
|
+
const needle = model.toLowerCase();
|
|
107
|
+
return PRICING_TABLE.models.find((m) => m.provider === provider &&
|
|
108
|
+
(m.model.toLowerCase() === needle ||
|
|
109
|
+
m.aliases.some((a) => a.toLowerCase() === needle)));
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Calculate the USD cost for a given number of input and output tokens.
|
|
113
|
+
*/
|
|
114
|
+
export function calculateCost(inputTokens, outputTokens, pricing) {
|
|
115
|
+
const inputCost = (inputTokens / 1_000_000) * pricing.inputPer1M;
|
|
116
|
+
const outputCost = (outputTokens / 1_000_000) * pricing.outputPer1M;
|
|
117
|
+
return inputCost + outputCost;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Return the cheapest model (lowest inputPer1M) for a given tier,
|
|
121
|
+
* optionally restricted to the supplied provider IDs.
|
|
122
|
+
*
|
|
123
|
+
* Throws if no matching model exists.
|
|
124
|
+
*/
|
|
125
|
+
export function getCheapestForTier(tier, availableProviders) {
|
|
126
|
+
let candidates = PRICING_TABLE.models.filter((m) => m.tier === tier);
|
|
127
|
+
if (availableProviders && availableProviders.length > 0) {
|
|
128
|
+
candidates = candidates.filter((m) => availableProviders.includes(m.provider));
|
|
129
|
+
}
|
|
130
|
+
if (candidates.length === 0) {
|
|
131
|
+
throw new Error(`No models available for tier "${tier}"` +
|
|
132
|
+
(availableProviders ? ` with providers [${availableProviders.join(', ')}]` : ''));
|
|
133
|
+
}
|
|
134
|
+
// Primary sort: inputPer1M ascending; secondary: outputPer1M ascending
|
|
135
|
+
return candidates.reduce((cheapest, m) => m.inputPer1M < cheapest.inputPer1M ||
|
|
136
|
+
(m.inputPer1M === cheapest.inputPer1M && m.outputPer1M < cheapest.outputPer1M)
|
|
137
|
+
? m
|
|
138
|
+
: cheapest);
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Returns true when the pricing table is older than maxAgeDays (default 90).
|
|
142
|
+
* Useful for emitting a staleness warning at runtime.
|
|
143
|
+
*/
|
|
144
|
+
export function isPricingStale(maxAgeDays = 90) {
|
|
145
|
+
const asOf = new Date(PRICING_TABLE.asOf);
|
|
146
|
+
const now = new Date();
|
|
147
|
+
const diffMs = now.getTime() - asOf.getTime();
|
|
148
|
+
const diffDays = diffMs / (1_000 * 60 * 60 * 24);
|
|
149
|
+
return diffDays > maxAgeDays;
|
|
150
|
+
}
|
|
151
|
+
//# sourceMappingURL=pricing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pricing.js","sourceRoot":"","sources":["../../src/infra/pricing.ts"],"names":[],"mappings":"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"}
|