@amenophis1er/foreman 0.1.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/DESIGN.md +408 -0
- package/LICENSE +15 -0
- package/README.md +133 -0
- package/bin/foreman.mjs +58 -0
- package/package.json +68 -0
- package/scripts/prepare.mjs +48 -0
- package/skills/director/SKILL.md +65 -0
- package/src/anthropic-models.ts +54 -0
- package/src/ask.test.ts +88 -0
- package/src/ask.ts +95 -0
- package/src/attachments.test.ts +33 -0
- package/src/attachments.ts +60 -0
- package/src/cli.test.ts +27 -0
- package/src/cli.ts +297 -0
- package/src/codex.test.ts +328 -0
- package/src/codex.ts +196 -0
- package/src/cost-basis.test.ts +76 -0
- package/src/deck.test.ts +402 -0
- package/src/deck.ts +892 -0
- package/src/fork.test.ts +31 -0
- package/src/gateway/ledger.cjs +326 -0
- package/src/gateway/ledger.test.ts +255 -0
- package/src/gateway/llm-gateway.cjs +1411 -0
- package/src/gateway/llm-gateway.test.ts +478 -0
- package/src/gateway.test.ts +226 -0
- package/src/gateway.ts +309 -0
- package/src/instance.ts +124 -0
- package/src/models.test.ts +147 -0
- package/src/models.ts +158 -0
- package/src/notify/commands.test.ts +28 -0
- package/src/notify/commands.ts +73 -0
- package/src/notify/telegram.ts +259 -0
- package/src/notify.test.ts +343 -0
- package/src/notify.ts +495 -0
- package/src/ollama.test.ts +49 -0
- package/src/ollama.ts +49 -0
- package/src/openai-prices.test.ts +58 -0
- package/src/openai-prices.ts +106 -0
- package/src/orchestrator.test.ts +1147 -0
- package/src/orchestrator.ts +2325 -0
- package/src/planner.test.ts +60 -0
- package/src/planner.ts +505 -0
- package/src/policy.test.ts +411 -0
- package/src/policy.ts +599 -0
- package/src/preflight.ts +348 -0
- package/src/prices.test.ts +69 -0
- package/src/prices.ts +90 -0
- package/src/provider.test.ts +366 -0
- package/src/provider.ts +502 -0
- package/src/secrets.test.ts +143 -0
- package/src/secrets.ts +66 -0
- package/src/server.ts +1992 -0
- package/src/services.test.ts +53 -0
- package/src/services.ts +102 -0
- package/src/sse-events.test.ts +83 -0
- package/src/store.test.ts +119 -0
- package/src/store.ts +346 -0
- package/src/tailscale.test.ts +32 -0
- package/src/tailscale.ts +79 -0
- package/src/title.ts +138 -0
- package/src/types.ts +442 -0
- package/ui/dist/assets/index-LAj0Dy9p.css +1 -0
- package/ui/dist/assets/index-lcBy-uRZ.js +65 -0
- package/ui/dist/favicon.svg +8 -0
- package/ui/dist/index.html +14 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAI list prices — the one table Foreman keeps, by decision.
|
|
3
|
+
*
|
|
4
|
+
* The provider model's rule is that a dollar figure comes from whoever sends
|
|
5
|
+
* the bill. OpenAI's API publishes no rates, and the human wants direct
|
|
6
|
+
* OpenAI rather than a reseller that does, so this table exists — under the
|
|
7
|
+
* one condition that makes it honest: **provenance made visible.** It names
|
|
8
|
+
* its source, it names the day it was checked, and it is the only place in
|
|
9
|
+
* the codebase where a price is written down by a person. A model not in it
|
|
10
|
+
* stays `unpriced`; a wrong entry is a bug with a date on it, not a guess.
|
|
11
|
+
*
|
|
12
|
+
* SOURCE https://developers.openai.com/api/docs/pricing (Standard tier)
|
|
13
|
+
* VERIFIED 2026-09-05
|
|
14
|
+
* UNIT the page lists USD per 1M tokens; stored here per token, the unit
|
|
15
|
+
* `ModelPrice` uses, so the factor of a million lives in exactly one
|
|
16
|
+
* line (`perM`) and nowhere else.
|
|
17
|
+
*
|
|
18
|
+
* KNOWN GAPS, on purpose:
|
|
19
|
+
* - Long-context tiers. gpt-5.5, gpt-5.4 and their pro variants charge more
|
|
20
|
+
* past 272K input tokens; this table holds the base tier. A run that
|
|
21
|
+
* routinely exceeds that will read low — the direction that is not safe —
|
|
22
|
+
* so the description says so.
|
|
23
|
+
* - Batch and Flex tiers are cheaper and not represented. Foreman does not
|
|
24
|
+
* use them.
|
|
25
|
+
* - Dated snapshots (`gpt-5.4-2026-03-01`) resolve to their family by
|
|
26
|
+
* longest-prefix match. A new family not listed here is unpriced until
|
|
27
|
+
* someone updates this file and the VERIFIED line with it.
|
|
28
|
+
*
|
|
29
|
+
* To update: open the source URL, change the numbers, change VERIFIED. That
|
|
30
|
+
* is the whole procedure, and the reason it lives in its own small file.
|
|
31
|
+
*/
|
|
32
|
+
import type { ModelPrice } from './prices.js';
|
|
33
|
+
|
|
34
|
+
/** USD per 1M tokens → USD per token. The only conversion in this file. */
|
|
35
|
+
const perM = (usdPerMillion: number): number => usdPerMillion / 1_000_000;
|
|
36
|
+
|
|
37
|
+
interface Row { input: number; cachedInput?: number; output: number }
|
|
38
|
+
|
|
39
|
+
/** Standard tier, USD per 1M tokens, exactly as listed on VERIFIED date. */
|
|
40
|
+
const LIST: Record<string, Row> = {
|
|
41
|
+
'gpt-6-astra': { input: 10.00, cachedInput: 1.00, output: 50.00 },
|
|
42
|
+
'gpt-5.6-sol': { input: 4.00, cachedInput: 0.40, output: 20.00 },
|
|
43
|
+
'gpt-5.6-terra': { input: 2.00, cachedInput: 0.20, output: 12.00 },
|
|
44
|
+
'gpt-5.6-luna': { input: 0.20, cachedInput: 0.02, output: 1.20 },
|
|
45
|
+
'gpt-5.5': { input: 5.00, cachedInput: 0.50, output: 30.00 }, // <272K
|
|
46
|
+
'gpt-5.5-pro': { input: 30.00, output: 180.00 }, // <272K
|
|
47
|
+
'gpt-5.4': { input: 2.50, cachedInput: 0.25, output: 15.00 }, // <272K
|
|
48
|
+
'gpt-5.4-mini': { input: 0.75, cachedInput: 0.075, output: 4.50 },
|
|
49
|
+
'gpt-5.4-nano': { input: 0.20, cachedInput: 0.02, output: 1.25 },
|
|
50
|
+
'gpt-5.4-pro': { input: 30.00, output: 180.00 }, // <272K
|
|
51
|
+
'gpt-5.2': { input: 1.75, cachedInput: 0.175, output: 14.00 },
|
|
52
|
+
'gpt-5.2-pro': { input: 21.00, output: 168.00 },
|
|
53
|
+
'gpt-5.1': { input: 1.25, cachedInput: 0.125, output: 10.00 },
|
|
54
|
+
'gpt-5': { input: 1.25, cachedInput: 0.125, output: 10.00 },
|
|
55
|
+
'gpt-5-mini': { input: 0.25, cachedInput: 0.025, output: 2.00 },
|
|
56
|
+
'gpt-5-nano': { input: 0.05, cachedInput: 0.005, output: 0.40 },
|
|
57
|
+
'gpt-5-pro': { input: 15.00, output: 120.00 },
|
|
58
|
+
'o1': { input: 15.00, cachedInput: 7.50, output: 60.00 },
|
|
59
|
+
'o1-pro': { input: 150.00, output: 600.00 },
|
|
60
|
+
'o3-pro': { input: 20.00, output: 80.00 },
|
|
61
|
+
'o3': { input: 2.00, cachedInput: 0.50, output: 8.00 },
|
|
62
|
+
'o4-mini': { input: 1.10, cachedInput: 0.275, output: 4.40 },
|
|
63
|
+
'o3-mini': { input: 1.10, cachedInput: 0.55, output: 4.40 },
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export const OPENAI_PRICES_VERIFIED = '2026-09-05';
|
|
67
|
+
export const OPENAI_PRICES_SOURCE = 'https://developers.openai.com/api/docs/pricing';
|
|
68
|
+
|
|
69
|
+
/** Hosts whose models this table prices. Nothing else — a reseller sets its own rates. */
|
|
70
|
+
export function isOpenAiHost(upstreamUrl: string | undefined): boolean {
|
|
71
|
+
try { return !!upstreamUrl && /(^|\.)api\.openai\.com$/i.test(new URL(upstreamUrl).hostname); }
|
|
72
|
+
catch { return false; }
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* The list price for a model id, or null if the table does not know it.
|
|
77
|
+
*
|
|
78
|
+
* Exact id first; otherwise the longest family key the id starts with
|
|
79
|
+
* followed by a dash (so `gpt-5.4-2026-03-01` is `gpt-5.4`, and `gpt-5.4-mini`
|
|
80
|
+
* is not mistaken for `gpt-5.4`). Cached-input goes into `cacheRead`; OpenAI
|
|
81
|
+
* has no cache-write charge, so `cacheWrite` is the input rate — which
|
|
82
|
+
* `priceUsage` uses as the default when the field is absent.
|
|
83
|
+
*/
|
|
84
|
+
export function openaiPrice(modelId: string | undefined): ModelPrice | null {
|
|
85
|
+
if (!modelId) return null;
|
|
86
|
+
const id = modelId.trim().toLowerCase();
|
|
87
|
+
let key: string | undefined = LIST[id] ? id : undefined;
|
|
88
|
+
if (!key) {
|
|
89
|
+
key = Object.keys(LIST)
|
|
90
|
+
.filter((k) => id.startsWith(`${k}-`))
|
|
91
|
+
.sort((a, b) => b.length - a.length)[0];
|
|
92
|
+
}
|
|
93
|
+
if (!key) return null;
|
|
94
|
+
const r = LIST[key];
|
|
95
|
+
const price: ModelPrice = { input: perM(r.input), output: perM(r.output) };
|
|
96
|
+
if (r.cachedInput !== undefined) price.cacheRead = perM(r.cachedInput);
|
|
97
|
+
return price;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** For the picker's note: "$2.50 in / $15.00 out per million tokens · OpenAI list, verified 2026-09-05". */
|
|
101
|
+
export function openaiPriceNote(modelId: string): string | null {
|
|
102
|
+
const p = openaiPrice(modelId);
|
|
103
|
+
if (!p) return null;
|
|
104
|
+
const m = (n: number) => `$${(n * 1_000_000).toFixed(2)}`;
|
|
105
|
+
return `${m(p.input)} in / ${m(p.output)} out per million tokens · OpenAI list price, verified ${OPENAI_PRICES_VERIFIED}`;
|
|
106
|
+
}
|