patchwork-os 0.2.0-beta.10.canary.110 → 0.2.0-beta.10.canary.111
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.
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model price table — cost-aware routing Phase 2 (dormant data).
|
|
3
|
+
*
|
|
4
|
+
* A `(model id) → USD-per-million-tokens` table plus a loader. Nothing
|
|
5
|
+
* consumes it yet; Phase 3 wires `costUsd()` into RunBudget so `budget.usdMax`
|
|
6
|
+
* can be enforced for *measured* (API) drivers. Lands separately so the price
|
|
7
|
+
* data can be reviewed for accuracy on its own.
|
|
8
|
+
*
|
|
9
|
+
* Design notes (docs/design/cost-aware-routing.md):
|
|
10
|
+
* - Prices are DATA, not code, and OVERRIDABLE without a release:
|
|
11
|
+
* precedence env `PATCHWORK_PRICE_TABLE` (path) >
|
|
12
|
+
* `~/.patchwork/prices.json` >
|
|
13
|
+
* the built-in table below.
|
|
14
|
+
* - FAIL-OPEN everywhere: an unknown model, a malformed override, or a
|
|
15
|
+
* missing file never throws — `costUsd` just returns `undefined` (and the
|
|
16
|
+
* budget layer treats that as "unpriceable → don't enforce").
|
|
17
|
+
* - The built-in default lives here as a TS const (compiled into dist, so it
|
|
18
|
+
* is always available at runtime); overrides are JSON read from disk.
|
|
19
|
+
* - NO runtime network calls. Prices drift; refresh the const below and bump
|
|
20
|
+
* `_generatedAt`. `isPriceTableStale()` is provided for a scheduled check.
|
|
21
|
+
*/
|
|
22
|
+
/** USD per 1,000,000 tokens, split by input vs output. */
|
|
23
|
+
export interface ModelPrice {
|
|
24
|
+
input: number;
|
|
25
|
+
output: number;
|
|
26
|
+
}
|
|
27
|
+
export interface PriceTableMeta {
|
|
28
|
+
/** ISO date (YYYY-MM-DD) the built-in prices were last reviewed. */
|
|
29
|
+
_generatedAt: string;
|
|
30
|
+
_unit: "usd_per_million_tokens";
|
|
31
|
+
_source: string;
|
|
32
|
+
_note: string;
|
|
33
|
+
/** Set by the loader when an override file was merged in. */
|
|
34
|
+
_override?: string;
|
|
35
|
+
}
|
|
36
|
+
export interface PriceTable {
|
|
37
|
+
_meta: PriceTableMeta;
|
|
38
|
+
/** Keyed by exact model id (e.g. "gpt-4o", "claude-haiku-4-5-20251001"). */
|
|
39
|
+
prices: Record<string, ModelPrice>;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Built-in default prices. **Approximate public list prices** (USD / 1M tokens)
|
|
43
|
+
* as of `_generatedAt` — VERIFY before relying on a USD cap; they drift, and a
|
|
44
|
+
* subscription/CLI driver's spend is notional, not real money out. Override via
|
|
45
|
+
* `~/.patchwork/prices.json` or `PATCHWORK_PRICE_TABLE`.
|
|
46
|
+
*/
|
|
47
|
+
export declare const BUILTIN_PRICE_TABLE: PriceTable;
|
|
48
|
+
/**
|
|
49
|
+
* Load the effective price table: the built-in default with any override file
|
|
50
|
+
* merged over its `prices`. Fail-open — an unreadable / malformed override is
|
|
51
|
+
* ignored and the built-in is returned unchanged.
|
|
52
|
+
*
|
|
53
|
+
* Override file shape: `{ "prices": { "<model>": { "input": N, "output": N } } }`
|
|
54
|
+
* (a bare `{ "<model>": { ... } }` map is also accepted for convenience).
|
|
55
|
+
*/
|
|
56
|
+
export declare function loadPriceTable(opts?: {
|
|
57
|
+
env?: NodeJS.ProcessEnv;
|
|
58
|
+
homeDir?: string;
|
|
59
|
+
}): PriceTable;
|
|
60
|
+
/** Price for one model, or undefined if the table does not list it. */
|
|
61
|
+
export declare function priceFor(model: string, table?: PriceTable): ModelPrice | undefined;
|
|
62
|
+
/**
|
|
63
|
+
* USD cost of a call given its token usage, or `undefined` when the model is
|
|
64
|
+
* not priced (fail-open: the budget layer then declines to enforce on it).
|
|
65
|
+
*/
|
|
66
|
+
export declare function costUsd(model: string, usage: {
|
|
67
|
+
inputTokens: number;
|
|
68
|
+
outputTokens: number;
|
|
69
|
+
}, table?: PriceTable): number | undefined;
|
|
70
|
+
/**
|
|
71
|
+
* True when the table's `_generatedAt` is unparseable, in the future, or older
|
|
72
|
+
* than `maxAgeDays` (default 548 ≈ 18 months). Pure — caller supplies `now` —
|
|
73
|
+
* so a scheduled job can fail loudly without baking a wall-clock time-bomb into
|
|
74
|
+
* every PR's test run.
|
|
75
|
+
*/
|
|
76
|
+
export declare function isPriceTableStale(generatedAt: string, now: number, maxAgeDays?: number): boolean;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model price table — cost-aware routing Phase 2 (dormant data).
|
|
3
|
+
*
|
|
4
|
+
* A `(model id) → USD-per-million-tokens` table plus a loader. Nothing
|
|
5
|
+
* consumes it yet; Phase 3 wires `costUsd()` into RunBudget so `budget.usdMax`
|
|
6
|
+
* can be enforced for *measured* (API) drivers. Lands separately so the price
|
|
7
|
+
* data can be reviewed for accuracy on its own.
|
|
8
|
+
*
|
|
9
|
+
* Design notes (docs/design/cost-aware-routing.md):
|
|
10
|
+
* - Prices are DATA, not code, and OVERRIDABLE without a release:
|
|
11
|
+
* precedence env `PATCHWORK_PRICE_TABLE` (path) >
|
|
12
|
+
* `~/.patchwork/prices.json` >
|
|
13
|
+
* the built-in table below.
|
|
14
|
+
* - FAIL-OPEN everywhere: an unknown model, a malformed override, or a
|
|
15
|
+
* missing file never throws — `costUsd` just returns `undefined` (and the
|
|
16
|
+
* budget layer treats that as "unpriceable → don't enforce").
|
|
17
|
+
* - The built-in default lives here as a TS const (compiled into dist, so it
|
|
18
|
+
* is always available at runtime); overrides are JSON read from disk.
|
|
19
|
+
* - NO runtime network calls. Prices drift; refresh the const below and bump
|
|
20
|
+
* `_generatedAt`. `isPriceTableStale()` is provided for a scheduled check.
|
|
21
|
+
*/
|
|
22
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
23
|
+
import { homedir } from "node:os";
|
|
24
|
+
import { join } from "node:path";
|
|
25
|
+
/**
|
|
26
|
+
* Built-in default prices. **Approximate public list prices** (USD / 1M tokens)
|
|
27
|
+
* as of `_generatedAt` — VERIFY before relying on a USD cap; they drift, and a
|
|
28
|
+
* subscription/CLI driver's spend is notional, not real money out. Override via
|
|
29
|
+
* `~/.patchwork/prices.json` or `PATCHWORK_PRICE_TABLE`.
|
|
30
|
+
*/
|
|
31
|
+
export const BUILTIN_PRICE_TABLE = {
|
|
32
|
+
_meta: {
|
|
33
|
+
_generatedAt: "2026-06-03",
|
|
34
|
+
_unit: "usd_per_million_tokens",
|
|
35
|
+
_source: "provider public list prices",
|
|
36
|
+
_note: "Approximate list prices (USD per 1M tokens). Review before relying on budget.usdMax; override via ~/.patchwork/prices.json or PATCHWORK_PRICE_TABLE.",
|
|
37
|
+
},
|
|
38
|
+
prices: {
|
|
39
|
+
// Anthropic (Claude 4.x)
|
|
40
|
+
"claude-opus-4-8": { input: 15, output: 75 },
|
|
41
|
+
"claude-sonnet-4-6": { input: 3, output: 15 },
|
|
42
|
+
"claude-haiku-4-5-20251001": { input: 1, output: 5 },
|
|
43
|
+
// OpenAI
|
|
44
|
+
"gpt-4o": { input: 2.5, output: 10 },
|
|
45
|
+
"gpt-4o-mini": { input: 0.15, output: 0.6 },
|
|
46
|
+
// xAI (Grok)
|
|
47
|
+
"grok-2-latest": { input: 2, output: 10 },
|
|
48
|
+
// Google (Gemini, API-keyed)
|
|
49
|
+
"gemini-2.5-pro": { input: 1.25, output: 10 },
|
|
50
|
+
"gemini-2.5-flash": { input: 0.3, output: 2.5 },
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
const MS_PER_DAY = 86_400_000;
|
|
54
|
+
/** Resolve the override file path per the precedence rule (or undefined). */
|
|
55
|
+
function resolveOverridePath(env, homeDir) {
|
|
56
|
+
const envPath = env.PATCHWORK_PRICE_TABLE;
|
|
57
|
+
if (typeof envPath === "string" && envPath.trim())
|
|
58
|
+
return envPath.trim();
|
|
59
|
+
const filePath = join(homeDir, ".patchwork", "prices.json");
|
|
60
|
+
return existsSync(filePath) ? filePath : undefined;
|
|
61
|
+
}
|
|
62
|
+
/** Extract a valid `{input, output}` price from an unknown override entry. */
|
|
63
|
+
function coercePrice(value) {
|
|
64
|
+
if (!value || typeof value !== "object")
|
|
65
|
+
return undefined;
|
|
66
|
+
const v = value;
|
|
67
|
+
if (typeof v.input === "number" && typeof v.output === "number") {
|
|
68
|
+
return { input: v.input, output: v.output };
|
|
69
|
+
}
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Load the effective price table: the built-in default with any override file
|
|
74
|
+
* merged over its `prices`. Fail-open — an unreadable / malformed override is
|
|
75
|
+
* ignored and the built-in is returned unchanged.
|
|
76
|
+
*
|
|
77
|
+
* Override file shape: `{ "prices": { "<model>": { "input": N, "output": N } } }`
|
|
78
|
+
* (a bare `{ "<model>": { ... } }` map is also accepted for convenience).
|
|
79
|
+
*/
|
|
80
|
+
export function loadPriceTable(opts = {}) {
|
|
81
|
+
const env = opts.env ?? process.env;
|
|
82
|
+
const homeDir = opts.homeDir ?? homedir();
|
|
83
|
+
const overridePath = resolveOverridePath(env, homeDir);
|
|
84
|
+
if (!overridePath)
|
|
85
|
+
return BUILTIN_PRICE_TABLE;
|
|
86
|
+
try {
|
|
87
|
+
const parsed = JSON.parse(readFileSync(overridePath, "utf8"));
|
|
88
|
+
const root = (parsed ?? {});
|
|
89
|
+
const rawPrices = root.prices && typeof root.prices === "object" ? root.prices : root;
|
|
90
|
+
const merged = {
|
|
91
|
+
...BUILTIN_PRICE_TABLE.prices,
|
|
92
|
+
};
|
|
93
|
+
for (const [model, entry] of Object.entries(rawPrices)) {
|
|
94
|
+
const price = coercePrice(entry);
|
|
95
|
+
if (price)
|
|
96
|
+
merged[model] = price;
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
_meta: { ...BUILTIN_PRICE_TABLE._meta, _override: overridePath },
|
|
100
|
+
prices: merged,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
// Malformed / unreadable override → fail open to the built-in table.
|
|
105
|
+
return BUILTIN_PRICE_TABLE;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/** Price for one model, or undefined if the table does not list it. */
|
|
109
|
+
export function priceFor(model, table = loadPriceTable()) {
|
|
110
|
+
return table.prices[model];
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* USD cost of a call given its token usage, or `undefined` when the model is
|
|
114
|
+
* not priced (fail-open: the budget layer then declines to enforce on it).
|
|
115
|
+
*/
|
|
116
|
+
export function costUsd(model, usage, table = loadPriceTable()) {
|
|
117
|
+
const price = priceFor(model, table);
|
|
118
|
+
if (!price)
|
|
119
|
+
return undefined;
|
|
120
|
+
return ((usage.inputTokens / 1_000_000) * price.input +
|
|
121
|
+
(usage.outputTokens / 1_000_000) * price.output);
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* True when the table's `_generatedAt` is unparseable, in the future, or older
|
|
125
|
+
* than `maxAgeDays` (default 548 ≈ 18 months). Pure — caller supplies `now` —
|
|
126
|
+
* so a scheduled job can fail loudly without baking a wall-clock time-bomb into
|
|
127
|
+
* every PR's test run.
|
|
128
|
+
*/
|
|
129
|
+
export function isPriceTableStale(generatedAt, now, maxAgeDays = 548) {
|
|
130
|
+
const t = Date.parse(generatedAt);
|
|
131
|
+
if (Number.isNaN(t))
|
|
132
|
+
return true;
|
|
133
|
+
if (t > now)
|
|
134
|
+
return true;
|
|
135
|
+
return now - t > maxAgeDays * MS_PER_DAY;
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=priceTable.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"priceTable.js","sourceRoot":"","sources":["../../../src/recipes/pricing/priceTable.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "patchwork-os",
|
|
3
|
-
"version": "0.2.0-beta.10.canary.
|
|
3
|
+
"version": "0.2.0-beta.10.canary.111",
|
|
4
4
|
"description": "Your personal AI runtime, local-first. Patchwork OS gives any AI model a consistent set of tools, YAML recipes, a delegation policy with approval queue, and a durable trace memory — all on your machine, all under your policy.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|