agentfences 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -59,6 +59,52 @@ init({ apiKey: process.env.FENCES_API_KEY, endpoint: "https://your-fences-host",
59
59
 
60
60
  The server is authoritative for limits and sees spend across processes. If it can't be reached, limits are enforced locally; pass `failClosed: true` to stop instead. A rejected key throws `FencesAuthError`; a quarantined agent throws `AgentQuarantined` before any of its code runs. In serverless handlers, `await flush()` before returning so queued decisions and spans are sent.
61
61
 
62
+ ## Live control (from 0.4.0)
63
+
64
+ In cloud mode a heartbeat (every 15 seconds, `init({ heartbeatS })`) lets whoever is on call act on a running agent from the dashboard:
65
+
66
+ - **Stop:** the next `checkpoint()` returns `breachType: "stopped_by_user"`, so the agent wraps up gracefully. If the run doesn't reach a checkpoint within `stopGraceS` (default 30) seconds, say it's stuck in a tool, the governed call rejects with `FencesStop` and the run's `signal` aborts (from 0.5.0). JavaScript can't stop running code, so pass `getActiveRun()?.signal` to `fetch` and other cancellable work for a stuck run to really stop. `init({ stopGraceS: null })` turns this off.
67
+ - **Pause and resume:** a paused run waits inside its next `checkpoint()` until it's resumed; after `pauseTimeoutS` (default one hour) it returns `breachType: "paused"`.
68
+ - **Limits** changed in the dashboard apply to the run immediately. **Quarantine** makes the governed call throw `AgentQuarantined` before any agent code runs; runs already going hold at their next checkpoint, and their guarded tools are blocked.
69
+
70
+ An agent can ask a person before doing something risky:
71
+
72
+ ```ts
73
+ import { requestApproval } from "agentfences";
74
+
75
+ const approval = await requestApproval("Refund $240 to order 1182?", { amountUsd: 240, timeoutS: 600 });
76
+ if (!approval.granted) return `I didn't issue the refund: ${approval.note}`;
77
+ ```
78
+
79
+ An approved amount raises the run's budget. It never hangs or throws: in local mode, outside a run, or on a server without approvals it's denied right away, and with no answer it's denied after `timeoutS`. The heartbeat's timer never keeps your process alive on its own.
80
+
81
+ **Containment hooks** (from 0.5.0) run in your process when a stop or quarantine arrives, to contain what Fences can't reach, such as a credential the agent holds:
82
+
83
+ ```ts
84
+ init({ apiKey: "fc_...", onQuarantine: async (event) => vault.revoke(`agent/${event.agentName}`), onStop: (event) => { ... } });
85
+ ```
86
+
87
+ `onStop` runs once per run, and `onQuarantine` once per quarantine however many runs it reaches. Each gets `{ type, runId, agentName, id, by, reason }`. Fences records whether it worked, threw (and why) or ran past 30 seconds. A process that's about to exit waits for its hooks first; in serverless handlers, `await flush()` does the same. The SDK also acknowledges every dashboard command it receives, so the dashboard can tell "the agent has it" from "the agent hasn't acknowledged".
88
+
89
+ ## Guarded tools (from 0.5.0)
90
+
91
+ For actions with consequences (refunds, payments, writes to production), guard the tool, and it asks Fences **before** it runs:
92
+
93
+ ```ts
94
+ import { tool, ToolBlocked } from "agentfences";
95
+
96
+ const issueRefund = tool(refund, { costUsd: (orderId: string, amount: number) => amount, approve: true });
97
+ const deploy = tool(deployImpl, { guard: true, timeoutS: 30 });
98
+ ```
99
+
100
+ - The tool runs only if its run isn't stopped, paused, over a limit or quarantined. Otherwise it throws `ToolBlocked` (with `reason` and `breachType`) and its code never runs.
101
+ - `costUsd` (a number, or a function of the tool's arguments) is **reserved** before the call and charged after, so parallel calls can't overspend the budget together. A call that throws is released uncharged; one that times out is charged, because it may have happened.
102
+ - `approve: true` asks a person on the dashboard before each call. The dashboard can also require approval for a tool, and the SDK then asks for it.
103
+ - **Guards fail closed:** if Fences can't be reached, or doesn't support guarded tools, the tool is blocked. In local mode the same rules apply in-process, and `approve` is always denied.
104
+ - `timeoutS` works on any tool: past it, the call rejects with a `TimeoutError`. The tool's own code carries on unless it uses `getActiveRun()?.signal`.
105
+
106
+ A guarded or timed tool always returns a promise. What each part guarantees: [ENFORCEMENT.md](../ENFORCEMENT.md).
107
+
62
108
  ## Not yet in the TypeScript SDK
63
109
 
64
- The Python SDK also has live control from the dashboard (pause, approvals), automatic recording of OpenAI/Anthropic calls (`instrument`), and LangGraph / OpenAI Agents integrations. They're next. Everything here follows the same server protocol ([PROTOCOL.md](../PROTOCOL.md)).
110
+ The Python SDK also records OpenAI/Anthropic calls automatically (`instrument`) and integrates with LangGraph and the OpenAI Agents SDK. Those are also how it checks a model call against the budget **before** it's sent, so the TypeScript SDK doesn't do that yet: a model call is checked afterwards, by `checkpoint()`, and a run can go over its budget by one call. Everything here follows the same server protocol ([PROTOCOL.md](../PROTOCOL.md)).
package/dist/client.d.ts CHANGED
@@ -9,6 +9,11 @@ export declare class FencesClient {
9
9
  logDecision(runId: string, body: Record<string, unknown>): Promise<Reply>;
10
10
  endRun(runId: string, body: Record<string, unknown>): Promise<Reply>;
11
11
  logSpans(runId: string, spans: unknown[]): Promise<Reply>;
12
+ /** Report live runs and acknowledge commands; the reply may carry commands for them. */
13
+ heartbeat(runs: unknown[], acks?: unknown[]): Promise<Reply>;
14
+ /** Check a guarded tool call before it runs ({ phase: "check" }), or settle it after ({ phase: "settle" }). */
15
+ guard(runId: string, body: Record<string, unknown>): Promise<Reply>;
16
+ requestApproval(runId: string, body: Record<string, unknown>): Promise<Reply>;
12
17
  /**
13
18
  * optional: an endpoint older servers may not have. A missing route returns
14
19
  * { unsupported: true } and nothing throws, not even a rejected key.
package/dist/client.js CHANGED
@@ -17,6 +17,13 @@ export class FencesClient {
17
17
  logDecision(runId, body) { return this.post(`/api/runs/${runId}/decisions`, body); }
18
18
  endRun(runId, body) { return this.post(`/api/runs/${runId}/end`, body); }
19
19
  logSpans(runId, spans) { return this.post(`/api/runs/${runId}/spans`, { spans }, true); }
20
+ /** Report live runs and acknowledge commands; the reply may carry commands for them. */
21
+ heartbeat(runs, acks = []) {
22
+ return this.post("/api/heartbeat", acks.length ? { runs, acks } : { runs }, true);
23
+ }
24
+ /** Check a guarded tool call before it runs ({ phase: "check" }), or settle it after ({ phase: "settle" }). */
25
+ guard(runId, body) { return this.post(`/api/runs/${runId}/guard`, body, true); }
26
+ requestApproval(runId, body) { return this.post(`/api/runs/${runId}/approvals`, body, true); }
20
27
  /**
21
28
  * optional: an endpoint older servers may not have. A missing route returns
22
29
  * { unsupported: true } and nothing throws, not even a rejected key.
@@ -0,0 +1,84 @@
1
+ import type { FencesClient, Reply } from "./client.js";
2
+ import type { Run } from "./core.js";
3
+ export interface Approval {
4
+ granted: boolean;
5
+ /** Who answered, as the dashboard reports it. */
6
+ by?: string | null;
7
+ /** Their note, or why no one could answer. */
8
+ note?: string | null;
9
+ /** The amount approved, if one was asked for. */
10
+ amountUsd?: number | null;
11
+ approvalId?: string | null;
12
+ }
13
+ /** What onStop and onQuarantine receive. */
14
+ export interface ControlEvent {
15
+ type: "stop" | "quarantine";
16
+ runId: string;
17
+ agentName: string;
18
+ /** The command's id, if the server sent one. */
19
+ id: string | null;
20
+ /** Who pressed the button, as the dashboard reports it. */
21
+ by: string | null;
22
+ reason: string | null;
23
+ }
24
+ export type Hook = (event: ControlEvent) => unknown;
25
+ /** Live-control state a run carries. */
26
+ export interface Control {
27
+ stopped: boolean;
28
+ stoppedAt: number;
29
+ /** The agent got its stop from a checkpoint (or a blocked tool): it's never cancelled. */
30
+ stopSeen: boolean;
31
+ cancelled: boolean;
32
+ /** Set by governed(): halts the call when a stop goes unseen past stopGraceS. */
33
+ onCancel: (() => void) | null;
34
+ paused: boolean;
35
+ pauseExpired: boolean;
36
+ quarantined: boolean;
37
+ /** approval id -> the answer, or null while waiting */
38
+ approvals: Map<string, Approval | null>;
39
+ awaiting: number;
40
+ /** command ids already acknowledged */
41
+ acked: Set<string>;
42
+ }
43
+ export declare const newControl: () => Control;
44
+ export declare let pauseTimeoutMs: number;
45
+ export declare const supported: {
46
+ heartbeat: boolean;
47
+ approvals: boolean;
48
+ guard: boolean;
49
+ };
50
+ export interface ControlOptions {
51
+ heartbeatS: number;
52
+ pauseTimeoutS: number;
53
+ stopGraceS: number | null;
54
+ onStop?: Hook | null;
55
+ onQuarantine?: Hook | null;
56
+ }
57
+ export declare function configure(c: FencesClient | null, o: ControlOptions): void;
58
+ export declare function register(run: Run): void;
59
+ export declare function unregister(run: Run): void;
60
+ export declare function unsupported(kind: keyof typeof supported, what: string, consequence: string): void;
61
+ /** Apply the commands addressed to this run (from a heartbeat or a checkpoint reply), and acknowledge them. Each is safe to apply twice. */
62
+ export declare function apply(run: Run, commands: Reply[] | undefined): void;
63
+ /**
64
+ * Wait for running hooks, then send the acknowledgements still waiting. flush() calls it, and
65
+ * it runs before the process exits, so a script that ends right after a stop still finishes
66
+ * its containment, and says so.
67
+ */
68
+ export declare function drain(timeoutMs?: number): Promise<void>;
69
+ /** Hold the agent at its checkpoint while its run is paused, up to pauseTimeoutS. */
70
+ export declare function waitIfPaused(run: Run): Promise<void>;
71
+ /**
72
+ * Ask a person, through the Fences dashboard, to approve something, and wait for the answer:
73
+ *
74
+ * const approval = await requestApproval("Refund $240 to order 1182?", { amountUsd: 240 });
75
+ * if (!approval.granted) return `I didn't issue the refund: ${approval.note}`;
76
+ *
77
+ * If an amount is approved, the run's budget goes up by that much. Returns denied right away in
78
+ * local mode, outside a run, or when the server can't take requests, and denied after timeoutS
79
+ * (default 600) with no answer: it never hangs and never throws.
80
+ */
81
+ export declare function requestApproval(reason: string, options?: {
82
+ amountUsd?: number;
83
+ timeoutS?: number;
84
+ }): Promise<Approval>;
@@ -0,0 +1,288 @@
1
+ /**
2
+ * Live control from the Fences dashboard: a heartbeat that reports live runs and brings back
3
+ * commands (stop, pause, resume, quarantine, limits, approval answers) and acknowledges them,
4
+ * containment hooks, pausing at checkpoints, cancelling a run stuck after a stop, and approval
5
+ * requests. Cloud mode only; a server without these endpoints gets one warning and runs carry
6
+ * on as before. Same behaviour as the Python SDK (PROTOCOL.md, "Live control").
7
+ */
8
+ import { randomUUID } from "node:crypto";
9
+ const FAST_MS = 2000; // heartbeat interval while a run is paused, waiting for an approval, or stopping
10
+ const HOOK_TIMEOUT_MS = 30_000; // a hook still running after this is reported as timed out
11
+ export const newControl = () => ({
12
+ stopped: false, stoppedAt: 0, stopSeen: false, cancelled: false, onCancel: null, paused: false, pauseExpired: false,
13
+ quarantined: false, approvals: new Map(), awaiting: 0, acked: new Set(),
14
+ });
15
+ const live = new Map(); // run id -> runs in progress
16
+ let client = null;
17
+ let intervalMs = 15_000;
18
+ export let pauseTimeoutMs = 3_600_000;
19
+ let stopGraceMs = 30_000;
20
+ const hooks = { stop: null, quarantine: null };
21
+ export const supported = { heartbeat: true, approvals: true, guard: true };
22
+ let timer = null;
23
+ let beating = false;
24
+ let acks = []; // acknowledgements waiting for the next heartbeat
25
+ const hooked = new Set(); // quarantine ids whose hook has run in this process
26
+ const runningHooks = new Set();
27
+ export function configure(c, o) {
28
+ client = c;
29
+ intervalMs = o.heartbeatS * 1000;
30
+ pauseTimeoutMs = o.pauseTimeoutS * 1000;
31
+ stopGraceMs = o.stopGraceS === null ? null : o.stopGraceS * 1000;
32
+ hooks.stop = o.onStop ?? null;
33
+ hooks.quarantine = o.onQuarantine ?? null;
34
+ supported.heartbeat = supported.approvals = supported.guard = true;
35
+ schedule(0); // a new client or interval takes effect now, not after the old wait
36
+ }
37
+ export function register(run) {
38
+ live.set(run.runId, run);
39
+ if (!timer)
40
+ schedule(nextDelay());
41
+ }
42
+ export function unregister(run) {
43
+ live.delete(run.runId);
44
+ }
45
+ export function unsupported(kind, what, consequence) {
46
+ if (!supported[kind])
47
+ return;
48
+ supported[kind] = false;
49
+ console.warn(`agentfences: this Fences server doesn't support ${what} yet, so ${consequence}`);
50
+ }
51
+ // ── Commands ──────────────────────────────────────────────────────────────────
52
+ const LIMITS = { budget_usd: "budgetUsd", max_iterations: "maxIterations", max_duration_ms: "maxDurationMs", max_tokens: "maxTokens" };
53
+ /** Apply the commands addressed to this run (from a heartbeat or a checkpoint reply), and acknowledge them. Each is safe to apply twice. */
54
+ export function apply(run, commands) {
55
+ for (const c of commands ?? []) {
56
+ if (c.run_id !== undefined && c.run_id !== run.runId)
57
+ continue;
58
+ const ctl = run.control;
59
+ if (c.type === "stop") {
60
+ if (!ctl.stopped) {
61
+ ctl.stopped = true;
62
+ ctl.stoppedAt = Date.now();
63
+ schedule(0); // watch for the agent to see it, or cancel it
64
+ hook("stop", run, c); // once per run
65
+ }
66
+ ctl.paused = ctl.quarantined = false;
67
+ }
68
+ else if (c.type === "pause" || c.type === "quarantine") { // a quarantined agent's runs hold at their next checkpoint
69
+ ctl.paused = true;
70
+ schedule(0);
71
+ if (c.type === "quarantine") {
72
+ ctl.quarantined = true; // guarded tools are blocked here too, even if the server can't be reached
73
+ hook("quarantine", run, c);
74
+ }
75
+ }
76
+ else if (c.type === "resume") {
77
+ ctl.paused = ctl.quarantined = false; // releasing a quarantine resumes its runs
78
+ }
79
+ else if (c.type === "limits") {
80
+ for (const [wire, field] of Object.entries(LIMITS)) {
81
+ if (c[wire] !== undefined && c[wire] !== null)
82
+ run[field] = c[wire]; // eslint-disable-line @typescript-eslint/no-explicit-any
83
+ }
84
+ }
85
+ else if (c.type === "approval") {
86
+ if (ctl.approvals.has(c.approval_id)) {
87
+ ctl.approvals.set(c.approval_id, { granted: !!c.granted, by: c.by ?? null, note: c.note ?? null, amountUsd: c.amount_usd ?? null });
88
+ }
89
+ }
90
+ else {
91
+ continue; // unknown: ignored, and not acknowledged
92
+ }
93
+ if (c.id && !ctl.acked.has(c.id)) {
94
+ ctl.acked.add(c.id);
95
+ ack({ run_id: run.runId, id: c.id, type: c.type, received_at: Date.now() / 1000 });
96
+ }
97
+ }
98
+ }
99
+ // ── Acknowledgements and containment hooks ────────────────────────────────────
100
+ function ack(entry) {
101
+ acks.push(entry);
102
+ schedule(0); // send it now, not at the next interval
103
+ }
104
+ function hook(kind, run, c) {
105
+ const fn = hooks[kind];
106
+ if (!fn)
107
+ return;
108
+ if (kind === "quarantine") {
109
+ const key = c.id ?? `run:${run.runId}`; // every server that sends quarantine gives it an id
110
+ if (hooked.has(key))
111
+ return; // once per process per quarantine
112
+ hooked.add(key);
113
+ }
114
+ const event = { type: kind, runId: run.runId, agentName: run.agentName, id: c.id ?? null, by: c.by ?? null, reason: c.reason ?? null };
115
+ const p = runHook(fn, event).finally(() => runningHooks.delete(p));
116
+ runningHooks.add(p);
117
+ }
118
+ async function runHook(fn, event) {
119
+ const { errorText } = await import("./events.js");
120
+ let result = "ok", detail = null;
121
+ try {
122
+ const timedOut = Symbol("timeout");
123
+ const out = await Promise.race([
124
+ Promise.resolve().then(() => fn(event)),
125
+ new Promise((ok) => setTimeout(ok, HOOK_TIMEOUT_MS, timedOut).unref()), // ponytail: a hung hook can't be stopped; it's reported and left running
126
+ ]);
127
+ if (out === timedOut)
128
+ result = "timeout";
129
+ }
130
+ catch (e) {
131
+ [result, detail] = ["failed", errorText(e).slice(0, 300)];
132
+ }
133
+ if (result !== "ok")
134
+ console.warn(`agentfences: the on${event.type === "stop" ? "Stop" : "Quarantine"} hook ${result === "timeout" ? "timed out" : `failed: ${detail}`}`);
135
+ if (event.id) {
136
+ ack({ run_id: event.runId, id: event.id, type: event.type, hook: result, ...(detail ? { detail } : {}), finished_at: Date.now() / 1000 });
137
+ }
138
+ }
139
+ /**
140
+ * Wait for running hooks, then send the acknowledgements still waiting. flush() calls it, and
141
+ * it runs before the process exits, so a script that ends right after a stop still finishes
142
+ * its containment, and says so.
143
+ */
144
+ export async function drain(timeoutMs = HOOK_TIMEOUT_MS) {
145
+ if (runningHooks.size) {
146
+ await Promise.race([Promise.allSettled([...runningHooks]), new Promise((ok) => setTimeout(ok, timeoutMs).unref())]);
147
+ }
148
+ await sendAcks();
149
+ }
150
+ async function sendAcks() {
151
+ if (!acks.length || !client || !supported.heartbeat)
152
+ return;
153
+ const batch = acks;
154
+ acks = [];
155
+ const resp = await client.heartbeat([], batch).catch(() => ({ network_error: "heartbeat failed" }));
156
+ if (resp.network_error || (resp.ok === false && (resp.status ?? 500) >= 500))
157
+ acks = [...batch, ...acks];
158
+ }
159
+ // Node exits once nothing keeps it busy: beforeExit lets acks queued by a finished hook still go out.
160
+ // One try per batch: a server that's gone mustn't keep the process alive by failing and requeueing forever.
161
+ let triedAtExit = false;
162
+ if (typeof process?.on === "function") { // edge runtimes have no process events: flush() still drains there
163
+ process.on("beforeExit", () => {
164
+ if (triedAtExit || !acks.length || !client)
165
+ return;
166
+ triedAtExit = true;
167
+ void sendAcks();
168
+ });
169
+ }
170
+ // ── Heartbeat ─────────────────────────────────────────────────────────────────
171
+ const unseenStop = (r) => r.control.stopped && !r.control.stopSeen && !r.control.cancelled && r.control.onCancel !== null;
172
+ const busy = () => [...live.values()].some((r) => r.control.paused || r.control.awaiting > 0 || unseenStop(r));
173
+ const nextDelay = () => (busy() ? Math.min(FAST_MS, intervalMs, stopGraceMs ?? FAST_MS) : intervalMs); // faster while busy, never slower
174
+ function schedule(delayMs) {
175
+ if (timer)
176
+ clearTimeout(timer);
177
+ timer = null;
178
+ if (!client || !supported.heartbeat || !(live.size || acks.length))
179
+ return;
180
+ timer = setTimeout(beat, delayMs);
181
+ timer.unref(); // the heartbeat never keeps the process alive on its own
182
+ }
183
+ /** Halt governed calls that haven't reached a checkpoint within stopGraceS of a stop. */
184
+ function cancelOverdue() {
185
+ if (stopGraceMs === null)
186
+ return;
187
+ for (const r of live.values()) {
188
+ if (unseenStop(r) && Date.now() - r.control.stoppedAt >= stopGraceMs) {
189
+ r.control.cancelled = true;
190
+ r.control.onCancel();
191
+ }
192
+ }
193
+ }
194
+ async function beat() {
195
+ timer = null;
196
+ cancelOverdue();
197
+ if (beating || !client || !(live.size || acks.length))
198
+ return schedule(nextDelay());
199
+ beating = true;
200
+ const runs = [...live.values()], batch = acks;
201
+ acks = [];
202
+ try {
203
+ const resp = await client.heartbeat(runs.map((r) => ({ run_id: r.runId, iterations: r.iterations, spent_usd: r.costUsd,
204
+ tokens_used: r.tokensUsed, paused: r.control.paused, stopped: r.control.stopped })), batch);
205
+ if (resp.network_error || (resp.ok === false && !resp.unsupported)) {
206
+ if (resp.network_error || (resp.status ?? 500) >= 500)
207
+ acks = [...batch, ...acks]; // at least once; a 4xx won't get better
208
+ }
209
+ else if (resp.unsupported) {
210
+ unsupported("heartbeat", "the heartbeat", "a stop from the dashboard applies at the next checkpoint and pause isn't available");
211
+ return;
212
+ }
213
+ else {
214
+ for (const run of runs)
215
+ apply(run, resp.commands?.filter((c) => c.run_id === run.runId));
216
+ }
217
+ }
218
+ catch {
219
+ acks = [...batch, ...acks]; // a failed heartbeat is retried next time
220
+ }
221
+ finally {
222
+ beating = false;
223
+ }
224
+ schedule(nextDelay());
225
+ }
226
+ // ── Pausing ───────────────────────────────────────────────────────────────────
227
+ /** Hold the agent at its checkpoint while its run is paused, up to pauseTimeoutS. */
228
+ export async function waitIfPaused(run) {
229
+ const deadline = Date.now() + pauseTimeoutMs;
230
+ while (run.control.paused) {
231
+ if (Date.now() >= deadline) {
232
+ run.control.paused = false;
233
+ run.control.pauseExpired = true;
234
+ return;
235
+ }
236
+ await new Promise((ok) => setTimeout(ok, 200));
237
+ }
238
+ }
239
+ // ── Approvals ─────────────────────────────────────────────────────────────────
240
+ /**
241
+ * Ask a person, through the Fences dashboard, to approve something, and wait for the answer:
242
+ *
243
+ * const approval = await requestApproval("Refund $240 to order 1182?", { amountUsd: 240 });
244
+ * if (!approval.granted) return `I didn't issue the refund: ${approval.note}`;
245
+ *
246
+ * If an amount is approved, the run's budget goes up by that much. Returns denied right away in
247
+ * local mode, outside a run, or when the server can't take requests, and denied after timeoutS
248
+ * (default 600) with no answer: it never hangs and never throws.
249
+ */
250
+ export async function requestApproval(reason, options = {}) {
251
+ const { getActiveRun, recordSpan } = await import("./core.js");
252
+ const run = getActiveRun();
253
+ const timeoutS = options.timeoutS ?? 600, amountUsd = options.amountUsd ?? null;
254
+ if (!run)
255
+ return { granted: false, note: "requestApproval() was called outside a governed run" };
256
+ if (!client)
257
+ return { granted: false, note: "Local mode: there is no one to approve this request" };
258
+ if (!supported.approvals)
259
+ return { granted: false, note: "This Fences server doesn't support approvals" };
260
+ const approvalId = randomUUID(), started = performance.now();
261
+ // timeout_s lets the server expire the request when we stop waiting
262
+ const resp = await client.requestApproval(run.runId, { approval_id: approvalId, reason, amount_usd: amountUsd, timeout_s: timeoutS });
263
+ if (resp.unsupported) {
264
+ unsupported("approvals", "approval requests", "they are denied");
265
+ return { granted: false, note: "This Fences server doesn't support approvals" };
266
+ }
267
+ if (resp.network_error)
268
+ return { granted: false, note: "Couldn't reach Fences to ask for approval" };
269
+ run.control.approvals.set(approvalId, null);
270
+ run.control.awaiting += 1;
271
+ schedule(0); // poll fast while someone decides
272
+ const deadline = Date.now() + timeoutS * 1000;
273
+ while (run.control.approvals.get(approvalId) === null && Date.now() < deadline) {
274
+ await new Promise((ok) => setTimeout(ok, 100));
275
+ }
276
+ run.control.awaiting -= 1;
277
+ const answer = run.control.approvals.get(approvalId) ?? { granted: false, note: "No answer before the timeout" };
278
+ answer.approvalId = approvalId;
279
+ run.control.approvals.delete(approvalId);
280
+ if (answer.granted && answer.amountUsd && run.budgetUsd !== null)
281
+ run.budgetUsd += answer.amountUsd; // an unlimited run stays unlimited
282
+ recordSpan("fences.approval", performance.now() - started, {
283
+ "fences.approval.id": approvalId, "fences.approval.reason": reason, "fences.approval.amount_usd": amountUsd,
284
+ "fences.approval.granted": answer.granted, "fences.approval.by": answer.by, "fences.approval.note": answer.note,
285
+ "fences.approval.granted_usd": answer.amountUsd,
286
+ });
287
+ return answer;
288
+ }
package/dist/core.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { FencesClient } from "./client.js";
2
+ import * as control from "./control.js";
2
3
  import * as events from "./events.js";
3
4
  import { type CustomPrice } from "./pricing.js";
4
5
  export interface CheckpointResult {
@@ -55,6 +56,19 @@ export declare class Run {
55
56
  lastBreach: string | null;
56
57
  exception?: events.ExceptionInfo;
57
58
  warned: boolean;
59
+ /** Live control from the dashboard (see control.ts). */
60
+ readonly control: control.Control;
61
+ /** Held by guarded tools that are running. */
62
+ reservedUsd: number;
63
+ private readonly abort;
64
+ /**
65
+ * Aborts when Fences halts the run after a stop it didn't reach a checkpoint for (see
66
+ * stopGraceS). Pass it to fetch and other cancellable work so a stuck run really stops:
67
+ * await fetch(url, { signal: getActiveRun()?.signal });
68
+ */
69
+ get signal(): AbortSignal;
70
+ /** @internal */
71
+ _abort(reason: unknown): void;
58
72
  constructor(agentName: string, budgetUsd: number | null, maxIterations: number, maxDurationMs: number, maxTokens: number, context: Record<string, string>);
59
73
  get durationMs(): number;
60
74
  }
@@ -77,11 +91,31 @@ export interface InitOptions {
77
91
  release?: string;
78
92
  /** Sees every outgoing event and returns it (changed or not), or null to drop it. */
79
93
  redact?: events.RedactHook;
94
+ /** Seconds between heartbeats reporting live runs (cloud mode). Default 15. */
95
+ heartbeatS?: number;
96
+ /** How long a paused run waits at its checkpoint before stopping with breach "paused". Default 3600. */
97
+ pauseTimeoutS?: number;
98
+ /**
99
+ * If a run doesn't reach a checkpoint within this many seconds of a dashboard stop (say, it's
100
+ * stuck in a tool), the governed call rejects with FencesStop and the run's signal aborts.
101
+ * Default 30; null never does.
102
+ */
103
+ stopGraceS?: number | null;
104
+ /**
105
+ * Run in the agent's process when a stop arrives from the dashboard (once per run), to
106
+ * contain what Fences can't reach, e.g. revoke a credential the agent holds. Whether it
107
+ * worked, threw or ran past 30 seconds is reported to Fences.
108
+ */
109
+ onStop?: control.Hook;
110
+ /** The same, when the agent is quarantined (once per quarantine, however many runs it reaches). */
111
+ onQuarantine?: control.Hook;
80
112
  }
81
113
  /** Call once at startup, before any governed function runs. */
82
114
  export declare function init(options?: InitOptions): void;
83
115
  /** @internal For tests: point the SDK at a fake server client. */
84
116
  export declare function _setClient(c: FencesClient | null): void;
117
+ /** @internal */
118
+ export declare const _client: () => FencesClient | null;
85
119
  /**
86
120
  * Make each call of fn a governed run with these limits:
87
121
  *
@@ -112,7 +146,12 @@ export interface Extra {
112
146
  * Never throws for a breach. Outside a governed run it does nothing.
113
147
  */
114
148
  export declare function checkpoint(response?: unknown, extra?: Extra): Promise<CheckpointResult>;
115
- /** Wait up to timeoutMs for queued decisions and spans to reach the server. Call before a serverless handler returns. */
149
+ /** @internal The breach the run is in by local rules, if any. */
150
+ export declare function localBreach(run: Run): string | null;
151
+ /**
152
+ * Wait up to timeoutMs for queued decisions and spans to reach the server, and for onStop and
153
+ * onQuarantine hooks to finish. Call before a serverless handler returns.
154
+ */
116
155
  export declare function flush(timeoutMs?: number): Promise<void>;
117
156
  /** Record the agent's reasoning at this step. Does nothing outside a governed run. */
118
157
  export declare function logDecision(reasoning: string, action?: string | null): void;
package/dist/core.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { AsyncLocalStorage } from "node:async_hooks";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import { FencesClient } from "./client.js";
4
+ import * as control from "./control.js";
4
5
  import { AgentQuarantined, FencesStop } from "./errors.js";
5
6
  import * as events from "./events.js";
6
7
  import { costOf, setCustomPrices } from "./pricing.js";
@@ -57,6 +58,19 @@ export class Run {
57
58
  lastBreach = null;
58
59
  exception;
59
60
  warned = false;
61
+ /** Live control from the dashboard (see control.ts). */
62
+ control = control.newControl();
63
+ /** Held by guarded tools that are running. */
64
+ reservedUsd = 0;
65
+ abort = new AbortController();
66
+ /**
67
+ * Aborts when Fences halts the run after a stop it didn't reach a checkpoint for (see
68
+ * stopGraceS). Pass it to fetch and other cancellable work so a stuck run really stops:
69
+ * await fetch(url, { signal: getActiveRun()?.signal });
70
+ */
71
+ get signal() { return this.abort.signal; }
72
+ /** @internal */
73
+ _abort(reason) { this.abort.abort(reason); }
60
74
  constructor(agentName, budgetUsd, maxIterations, maxDurationMs, maxTokens, context) {
61
75
  this.agentName = agentName;
62
76
  this.budgetUsd = budgetUsd;
@@ -83,15 +97,23 @@ export function init(options = {}) {
83
97
  failClosed = !!options.failClosed;
84
98
  if (localOnly) {
85
99
  client = null;
86
- return;
87
100
  }
88
- if (!options.apiKey) {
101
+ else if (!options.apiKey) {
89
102
  throw new Error("apiKey is required unless localOnly is set. Use init({ localOnly: true }) for local usage.");
90
103
  }
91
- client = new FencesClient(options.apiKey, options.endpoint ?? "http://localhost:8000");
104
+ else {
105
+ client = new FencesClient(options.apiKey, options.endpoint ?? "http://localhost:8000");
106
+ }
107
+ control.configure(client, { heartbeatS: options.heartbeatS ?? 15, pauseTimeoutS: options.pauseTimeoutS ?? 3600,
108
+ stopGraceS: options.stopGraceS === undefined ? 30 : options.stopGraceS, onStop: options.onStop, onQuarantine: options.onQuarantine });
92
109
  }
93
110
  /** @internal For tests: point the SDK at a fake server client. */
94
- export function _setClient(c) { client = c; }
111
+ export function _setClient(c) {
112
+ client = c;
113
+ control.configure(c, { heartbeatS: 15, pauseTimeoutS: 3600, stopGraceS: 30 });
114
+ }
115
+ /** @internal */
116
+ export const _client = () => client;
95
117
  function requireInit() {
96
118
  if (!localOnly && !client) {
97
119
  throw new Error("Call agentfences.init() before using Fences. For local usage: init({ localOnly: true })");
@@ -118,15 +140,26 @@ export function governed(fn, options = {}) {
118
140
  return async (...args) => {
119
141
  requireInit();
120
142
  const run = await startRun(name, options);
143
+ // A stop the agent doesn't reach a checkpoint for within stopGraceS halts the call here
144
+ const halted = new Promise((_, reject) => {
145
+ run.control.onCancel = () => {
146
+ const stop = new FencesStop(finalResult(run));
147
+ run._abort(stop);
148
+ reject(stop);
149
+ };
150
+ });
151
+ control.register(run);
121
152
  let error, failed = false;
122
153
  try {
123
- return await store.run(run, () => fn(...args));
154
+ // ponytail: JS can't stop running code; the agent's own work goes on unless it uses run.signal
155
+ return await Promise.race([store.run(run, () => fn(...args)), halted]);
124
156
  }
125
157
  catch (e) {
126
158
  [error, failed] = [e, true];
127
159
  throw e;
128
160
  }
129
161
  finally {
162
+ run.control.onCancel = null; // first, so a late cancel can't land after the call is over
130
163
  await endRun(run, ...outcome(run, failed, error));
131
164
  }
132
165
  };
@@ -135,7 +168,7 @@ function outcome(run, failed, error) {
135
168
  if (!failed)
136
169
  return [run.lastBreach ? "breached" : "success", null];
137
170
  if (error instanceof FencesStop)
138
- return ["breached", null]; // a framework integration halted the agent
171
+ return ["breached", null]; // halted after a stop it missed (stopGraceS)
139
172
  run.exception = events.exceptionInfo(error);
140
173
  const e = run.exception;
141
174
  return ["error", e.message ? `${e.type}: ${e.message}` : e.type];
@@ -179,6 +212,7 @@ function noBudget(agentName, how) {
179
212
  events.warnOnce(`${agentName}:no_budget`, `${agentName} has no budget, so its spend isn't capped. ${how}`);
180
213
  }
181
214
  async function endRun(run, status, error) {
215
+ control.unregister(run);
182
216
  if (!client)
183
217
  return;
184
218
  // The run must end even if redaction drops its error details
@@ -186,6 +220,8 @@ async function endRun(run, status, error) {
186
220
  const body = { status, error: sent?.error ?? null };
187
221
  if (sent?.exception)
188
222
  body.exception = sent.exception;
223
+ if (status === "breached" && run.lastBreach)
224
+ body.breach = run.lastBreach; // matters for breaches the SDK decided
189
225
  await client.endRun(run.runId, body);
190
226
  }
191
227
  /**
@@ -214,9 +250,15 @@ export async function checkpoint(response, extra = {}) {
214
250
  const resp = client ? await client.checkpoint(run.runId, {
215
251
  cost_delta_usd: stepCost, tokens_used: stepTokens, iterations: run.iterations, duration_ms: run.durationMs
216
252
  }) : null;
217
- return decide(run, resp);
253
+ const result = decide(run, resp);
254
+ if (run.control.paused && !result.breached) { // paused from the dashboard: wait here until resumed
255
+ await control.waitIfPaused(run);
256
+ return finalResult(run);
257
+ }
258
+ return result;
218
259
  }
219
- function localBreach(run) {
260
+ /** @internal The breach the run is in by local rules, if any. */
261
+ export function localBreach(run) {
220
262
  // Limits trip only once exceeded, same rule as the server; round away float drift (0.02*5 != 0.10)
221
263
  if (run.budgetUsd !== null && Math.round(run.costUsd * 1e9) / 1e9 > run.budgetUsd)
222
264
  return "budget_exceeded";
@@ -240,6 +282,7 @@ function decide(run, resp) {
240
282
  run.lastBreach = localBreach(run);
241
283
  }
242
284
  else {
285
+ control.apply(run, resp.commands);
243
286
  // The server is authoritative: adopt its totals and (on a breach) its current limits
244
287
  run.costUsd = resp.spent_usd ?? run.costUsd;
245
288
  run.iterations = resp.iterations ?? run.iterations;
@@ -251,6 +294,15 @@ function decide(run, resp) {
251
294
  // A fresh breach, or a 409 because the run is still fenced from an earlier one
252
295
  run.lastBreach = resp.ok ? null : (resp.breach ?? run.lastBreach ?? "limit_reached");
253
296
  }
297
+ return finalResult(run);
298
+ }
299
+ function finalResult(run) {
300
+ if (run.control.stopped) { // a stop from the dashboard sticks, whatever a later reply says
301
+ run.lastBreach = "stopped_by_user";
302
+ run.control.stopSeen = true;
303
+ }
304
+ else if (run.control.pauseExpired)
305
+ run.lastBreach = "paused";
254
306
  return run.lastBreach ? breachResult(run.lastBreach, run) : okResult(run.costUsd);
255
307
  }
256
308
  // ── Decision trail and spans ──────────────────────────────────────────────────
@@ -287,11 +339,15 @@ async function drain() {
287
339
  catch { /* never let delivery break the agent */ }
288
340
  }
289
341
  }
290
- /** Wait up to timeoutMs for queued decisions and spans to reach the server. Call before a serverless handler returns. */
342
+ /**
343
+ * Wait up to timeoutMs for queued decisions and spans to reach the server, and for onStop and
344
+ * onQuarantine hooks to finish. Call before a serverless handler returns.
345
+ */
291
346
  export async function flush(timeoutMs = 5000) {
292
- if (!draining)
293
- return;
294
- await Promise.race([draining, new Promise((r) => setTimeout(r, timeoutMs).unref())]);
347
+ const deadline = Date.now() + timeoutMs;
348
+ if (draining)
349
+ await Promise.race([draining, new Promise((r) => setTimeout(r, timeoutMs).unref())]);
350
+ await control.drain(Math.max(0, deadline - Date.now()));
295
351
  }
296
352
  /** Record the agent's reasoning at this step. Does nothing outside a governed run. */
297
353
  export function logDecision(reasoning, action = null) {
package/dist/errors.d.ts CHANGED
@@ -11,8 +11,19 @@ export declare class AgentQuarantined extends FencesError {
11
11
  constructor(agentName: string, detail?: string);
12
12
  }
13
13
  /**
14
- * Thrown by framework integrations when a limit is crossed, because a framework callback
15
- * can't hand a result back to the agent. Carries the same fields as a breached CheckpointResult.
14
+ * Thrown instead of running a guarded tool (tool(fn, { guard: true })) that Fences didn't
15
+ * allow: the run is stopped, over budget, unapproved or quarantined, or Fences couldn't be
16
+ * reached. The tool's code never ran. reason says why; breachType is set when a limit is the cause.
17
+ */
18
+ export declare class ToolBlocked extends FencesError {
19
+ readonly tool: string;
20
+ readonly reason: string;
21
+ readonly breachType: string | null;
22
+ constructor(tool: string, reason: string, breachType?: string | null);
23
+ }
24
+ /**
25
+ * Thrown when a run is halted from outside the agent's own code: a governed call stuck after a
26
+ * dashboard stop (see stopGraceS). Carries the same fields as a breached CheckpointResult.
16
27
  */
17
28
  export declare class FencesStop extends FencesError {
18
29
  readonly result: CheckpointResult;
package/dist/errors.js CHANGED
@@ -17,8 +17,24 @@ export class AgentQuarantined extends FencesError {
17
17
  }
18
18
  }
19
19
  /**
20
- * Thrown by framework integrations when a limit is crossed, because a framework callback
21
- * can't hand a result back to the agent. Carries the same fields as a breached CheckpointResult.
20
+ * Thrown instead of running a guarded tool (tool(fn, { guard: true })) that Fences didn't
21
+ * allow: the run is stopped, over budget, unapproved or quarantined, or Fences couldn't be
22
+ * reached. The tool's code never ran. reason says why; breachType is set when a limit is the cause.
23
+ */
24
+ export class ToolBlocked extends FencesError {
25
+ tool;
26
+ reason;
27
+ breachType;
28
+ constructor(tool, reason, breachType = null) {
29
+ super(`Fences blocked ${tool}: ${reason}`);
30
+ this.tool = tool;
31
+ this.reason = reason;
32
+ this.breachType = breachType;
33
+ }
34
+ }
35
+ /**
36
+ * Thrown when a run is halted from outside the agent's own code: a governed call stuck after a
37
+ * dashboard stop (see stopGraceS). Carries the same fields as a breached CheckpointResult.
22
38
  */
23
39
  export class FencesStop extends FencesError {
24
40
  result;
package/dist/index.d.ts CHANGED
@@ -3,7 +3,10 @@ export type { CheckpointResult, InitOptions, Limits, Extra, Span } from "./core.
3
3
  export { context } from "./events.js";
4
4
  export type { RedactHook, Event } from "./events.js";
5
5
  export { tool } from "./tools.js";
6
+ export type { GuardedToolOptions } from "./tools.js";
7
+ export { requestApproval } from "./control.js";
8
+ export type { Approval, ControlEvent, Hook } from "./control.js";
6
9
  export { priceOf } from "./pricing.js";
7
10
  export type { CustomPrice } from "./pricing.js";
8
- export { FencesError, FencesAuthError, AgentQuarantined, FencesStop } from "./errors.js";
11
+ export { FencesError, FencesAuthError, AgentQuarantined, FencesStop, ToolBlocked } from "./errors.js";
9
12
  export { VERSION } from "./version.js";
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  export { init, governed, checkpoint, logDecision, flush, getActiveRun, Run } from "./core.js";
2
2
  export { context } from "./events.js";
3
3
  export { tool } from "./tools.js";
4
+ export { requestApproval } from "./control.js";
4
5
  export { priceOf } from "./pricing.js";
5
- export { FencesError, FencesAuthError, AgentQuarantined, FencesStop } from "./errors.js";
6
+ export { FencesError, FencesAuthError, AgentQuarantined, FencesStop, ToolBlocked } from "./errors.js";
6
7
  export { VERSION } from "./version.js";
@@ -8,4 +8,5 @@ export interface Rates {
8
8
  export interface PriceEntry extends Rates {
9
9
  above?: number;
10
10
  tier?: Rates;
11
+ max_out?: number;
11
12
  }
@@ -1,3 +1,3 @@
1
1
  // Generated by scripts/prices.mjs from sdk/agentfences/prices.json. Do not edit.
2
- // LiteLLM model_prices_and_context_window.json, MIT License, Copyright (c) 2023 Berri AI, fetched 2026-10-09
3
- export const PRICES = { "chat-latest": { "in": 0.000005, "out": 0.00003, "cache_read": 5e-7 }, "claude-fable-5": { "in": 0.00001, "out": 0.00005, "cache_read": 0.000001, "cache_write": 0.0000125 }, "claude-fable-5-1": { "in": 0.00001, "out": 0.00005, "cache_read": 2.5e-7, "cache_write": 0.0000125 }, "claude-haiku-4-5": { "in": 0.000001, "out": 0.000005, "cache_read": 1e-7, "cache_write": 0.00000125 }, "claude-haiku-4-5-20251001": { "in": 0.000001, "out": 0.000005, "cache_read": 1e-7, "cache_write": 0.00000125 }, "claude-haiku-5-5": { "in": 1e-7, "out": 5e-7, "cache_read": 1e-8, "cache_write": 1.25e-7 }, "claude-mythos-5": { "in": 0.00001, "out": 0.00005, "cache_read": 0.000001, "cache_write": 0.0000125 }, "claude-mythos-5-1": { "in": 0.00001, "out": 0.00005, "cache_read": 2.5e-7, "cache_write": 0.0000125 }, "claude-mythos-preview": { "in": 0.00001, "out": 0.00005, "cache_read": 0.000001, "cache_write": 0.0000125 }, "claude-opus-4-5": { "in": 0.000005, "out": 0.000025, "cache_read": 5e-7, "cache_write": 0.00000625 }, "claude-opus-4-5-20251101": { "in": 0.000005, "out": 0.000025, "cache_read": 5e-7, "cache_write": 0.00000625 }, "claude-opus-4-6": { "in": 0.000005, "out": 0.000025, "cache_read": 5e-7, "cache_write": 0.00000625 }, "claude-opus-4-6-20260205": { "in": 0.000005, "out": 0.000025, "cache_read": 5e-7, "cache_write": 0.00000625 }, "claude-opus-4-7": { "in": 0.000005, "out": 0.000025, "cache_read": 5e-7, "cache_write": 0.00000625 }, "claude-opus-4-7-20260416": { "in": 0.000005, "out": 0.000025, "cache_read": 5e-7, "cache_write": 0.00000625 }, "claude-opus-4-8": { "in": 0.000005, "out": 0.000025, "cache_read": 5e-7, "cache_write": 0.00000625 }, "claude-opus-5": { "in": 0.000005, "out": 0.000025, "cache_read": 5e-7, "cache_write": 0.00000625 }, "claude-opus-5-5": { "in": 0.000004, "out": 0.00002, "cache_read": 2e-7, "cache_write": 0.000005 }, "claude-sonnet-4-5": { "in": 0.000003, "out": 0.000015, "cache_read": 3e-7, "cache_write": 0.00000375 }, "claude-sonnet-4-5-20250929": { "in": 0.000003, "out": 0.000015, "cache_read": 3e-7, "cache_write": 0.00000375 }, "claude-sonnet-4-6": { "in": 0.000003, "out": 0.000015, "cache_read": 3e-7, "cache_write": 0.00000375 }, "claude-sonnet-5": { "in": 0.000002, "out": 0.00001, "cache_read": 2e-7, "cache_write": 0.0000025 }, "claude-sonnet-5-5": { "in": 0.000002, "out": 0.00001, "cache_read": 1e-7, "cache_write": 0.0000025 }, "codestral-2508": { "in": 3e-7, "out": 9e-7, "cache_read": 3e-8 }, "codestral-latest": { "in": 3e-7, "out": 9e-7, "cache_read": 3e-8 }, "codestral-mamba-latest": { "in": 2.5e-7, "out": 2.5e-7, "cache_read": 2.5e-8 }, "computer-use-preview": { "in": 0.000003, "out": 0.000012 }, "daybreak-blue-latest": { "in": 0.000004, "out": 0.00002, "cache_read": 4e-7, "cache_write": 0.000005, "above": 272000, "tier": { "in": 0.000008, "out": 0.00003, "cache_read": 8e-7, "cache_write": 0.00001 } }, "daybreak-red-latest": { "in": 0.0000125, "out": 0.000075, "cache_read": 0.00000125, "cache_write": 0.000015625, "above": 272000, "tier": { "in": 0.000025, "out": 0.0001125, "cache_read": 0.0000025, "cache_write": 0.00003125 } }, "deep-research-max-preview-04-2026": { "in": 0.000002, "out": 0.000012 }, "deep-research-preview-04-2026": { "in": 0.000002, "out": 0.000012 }, "deep-research-pro-preview-12-2025": { "in": 0.000002, "out": 0.000012 }, "deepseek-chat": { "in": 2.8e-7, "out": 4.2e-7, "cache_read": 2.8e-8 }, "deepseek-coder": { "in": 1.4e-7, "out": 2.8e-7, "cache_read": 1.4e-8 }, "deepseek-flash": { "in": 3e-7, "out": 0.0000012, "cache_read": 6e-9, "cache_write": 0 }, "deepseek-r1": { "in": 5.5e-7, "out": 0.00000219, "cache_read": 1.4e-7 }, "deepseek-reasoner": { "in": 2.8e-7, "out": 4.2e-7, "cache_read": 2.8e-8 }, "deepseek-v3": { "in": 2.7e-7, "out": 0.0000011, "cache_read": 7e-8, "cache_write": 0 }, "deepseek-v3.2": { "in": 2.8e-7, "out": 4e-7, "cache_read": 2.8e-8 }, "deepseek-v4-flash": { "in": 3e-7, "out": 0.0000012, "cache_read": 6e-9, "cache_write": 0 }, "deepseek-v4-flash-vision-exp": { "in": 3e-7, "out": 0.0000012, "cache_read": 6e-9, "cache_write": 0 }, "deepseek-v4-pro": { "in": 0.00000132, "out": 0.00000396, "cache_read": 4.4e-8, "cache_write": 0 }, "devstral-latest": { "in": 4e-7, "out": 0.000002, "cache_read": 4e-8 }, "devstral-medium-latest": { "in": 4e-7, "out": 0.000002, "cache_read": 4e-8 }, "devstral-small-latest": { "in": 1e-7, "out": 3e-7, "cache_read": 1e-8 }, "ft:gpt-3.5-turbo": { "in": 0.000003, "out": 0.000006 }, "ft:gpt-3.5-turbo-0125": { "in": 0.000003, "out": 0.000006 }, "ft:gpt-3.5-turbo-0613": { "in": 0.000003, "out": 0.000006 }, "ft:gpt-3.5-turbo-1106": { "in": 0.000003, "out": 0.000006 }, "ft:gpt-4-0613": { "in": 0.00003, "out": 0.00006 }, "ft:gpt-4.1-2025-04-14": { "in": 0.000003, "out": 0.000012, "cache_read": 7.5e-7 }, "ft:gpt-4.1-mini-2025-04-14": { "in": 8e-7, "out": 0.0000032, "cache_read": 2e-7 }, "ft:gpt-4.1-nano-2025-04-14": { "in": 2e-7, "out": 8e-7, "cache_read": 5e-8 }, "ft:gpt-4o-2024-08-06": { "in": 0.00000375, "out": 0.000015, "cache_read": 0.000001875 }, "ft:gpt-4o-2024-11-20": { "in": 0.00000375, "out": 0.000015, "cache_write": 0.000001875 }, "ft:gpt-4o-mini-2024-07-18": { "in": 3e-7, "out": 0.0000012, "cache_read": 1.5e-7 }, "ft:o4-mini-2025-04-16": { "in": 0.000004, "out": 0.000016, "cache_read": 0.000001 }, "gemini-2.5-computer-use-preview-10-2025": { "in": 0.00000125, "out": 0.00001, "above": 200000, "tier": { "in": 0.0000025, "out": 0.000015 } }, "gemini-2.5-flash": { "in": 3e-7, "out": 0.0000025, "cache_read": 3e-8 }, "gemini-2.5-flash-lite": { "in": 1e-7, "out": 4e-7, "cache_read": 1e-8 }, "gemini-2.5-flash-lite-preview-09-2025": { "in": 1e-7, "out": 4e-7, "cache_read": 1e-8 }, "gemini-2.5-flash-preview-09-2025": { "in": 3e-7, "out": 0.0000025, "cache_read": 3e-8 }, "gemini-2.5-pro": { "in": 0.00000125, "out": 0.00001, "cache_read": 1.25e-7, "above": 200000, "tier": { "in": 0.0000025, "out": 0.000015, "cache_read": 2.5e-7 } }, "gemini-2.5-pro-preview-tts": { "in": 0.000001, "out": 0.00002, "cache_read": 1.25e-7 }, "gemini-3-flash-preview": { "in": 5e-7, "out": 0.000003, "cache_read": 5e-8 }, "gemini-3.1-flash-lite": { "in": 2.5e-7, "out": 0.0000015, "cache_read": 2.5e-8 }, "gemini-3.1-flash-lite-preview": { "in": 2.5e-7, "out": 0.0000015, "cache_read": 2.5e-8 }, "gemini-3.1-pro-preview": { "in": 0.000002, "out": 0.000012, "cache_read": 2e-7, "above": 200000, "tier": { "in": 0.000004, "out": 0.000018, "cache_read": 4e-7 } }, "gemini-3.1-pro-preview-customtools": { "in": 0.000002, "out": 0.000012, "cache_read": 2e-7, "above": 200000, "tier": { "in": 0.000004, "out": 0.000018, "cache_read": 4e-7 } }, "gemini-3.5-flash": { "in": 0.0000015, "out": 0.000009, "cache_read": 1.5e-7 }, "gemini-3.5-flash-lite": { "in": 3e-7, "out": 0.0000025, "cache_read": 3e-8 }, "gemini-3.6-flash": { "in": 7.5e-7, "out": 0.00000375, "cache_read": 7.5e-8 }, "gemini-3.7-flash": { "in": 7.5e-7, "out": 0.00000375, "cache_read": 7.5e-8 }, "gemini-3.8-flash": { "in": 7.5e-7, "out": 0.00000375, "cache_read": 7.5e-8 }, "gemini-3.8-flash-cyber": { "in": 0.0000015, "out": 0.0000075, "cache_read": 1.5e-7 }, "gemini-exp-1114": { "in": 0, "out": 0 }, "gemini-exp-1206": { "in": 0, "out": 0 }, "gemini-flash-latest": { "in": 7.5e-7, "out": 0.00000375, "cache_read": 7.5e-8 }, "gemini-flash-lite-latest": { "in": 3e-7, "out": 0.0000025, "cache_read": 3e-8 }, "gemini-gemma-2-27b-it": { "in": 3.5e-7, "out": 0.00000105 }, "gemini-gemma-2-9b-it": { "in": 3.5e-7, "out": 0.00000105 }, "gemini-omni-1.1-flash": { "in": 0.0000015, "out": 0.000009 }, "gemini-omni-flash-preview": { "in": 0.0000015, "out": 0.000009 }, "gemini-pro-latest": { "in": 0.000002, "out": 0.000012, "cache_read": 2e-7, "above": 200000, "tier": { "in": 0.000004, "out": 0.000018, "cache_read": 4e-7 } }, "gemini-robotics-er-1.5-preview": { "in": 3e-7, "out": 0.0000025, "cache_read": 0 }, "gemini-robotics-er-2-preview": { "in": 0.000001, "out": 0.000005, "cache_read": 1e-7 }, "gemini-robotics-er-2-streaming-preview": { "in": 0.000001, "out": 0.000005 }, "gemma-3-27b-it": { "in": 0, "out": 0 }, "gemma-4-26b-a4b-it": { "in": 0, "out": 0 }, "gemma-4-31b-it": { "in": 0, "out": 0 }, "glm-5-2": { "in": 0.0000014, "out": 0.0000044, "cache_read": 1.4e-7 }, "gpt-3.5-turbo": { "in": 5e-7, "out": 0.0000015 }, "gpt-3.5-turbo-0125": { "in": 5e-7, "out": 0.0000015 }, "gpt-3.5-turbo-1106": { "in": 0.000001, "out": 0.000002 }, "gpt-3.5-turbo-16k": { "in": 0.000003, "out": 0.000004 }, "gpt-4": { "in": 0.00003, "out": 0.00006 }, "gpt-4-0613": { "in": 0.00003, "out": 0.00006 }, "gpt-4-1106-preview": { "in": 0.00001, "out": 0.00003 }, "gpt-4-turbo": { "in": 0.00001, "out": 0.00003 }, "gpt-4-turbo-2024-04-09": { "in": 0.00001, "out": 0.00003 }, "gpt-4.1": { "in": 0.000002, "out": 0.000008, "cache_read": 5e-7 }, "gpt-4.1-2025-04-14": { "in": 0.000002, "out": 0.000008, "cache_read": 5e-7 }, "gpt-4.1-mini": { "in": 4e-7, "out": 0.0000016, "cache_read": 1e-7 }, "gpt-4.1-mini-2025-04-14": { "in": 4e-7, "out": 0.0000016, "cache_read": 1e-7 }, "gpt-4.1-nano": { "in": 1e-7, "out": 4e-7, "cache_read": 2.5e-8 }, "gpt-4.1-nano-2025-04-14": { "in": 1e-7, "out": 4e-7, "cache_read": 2.5e-8 }, "gpt-4o": { "in": 0.0000025, "out": 0.00001, "cache_read": 0.00000125 }, "gpt-4o-2024-05-13": { "in": 0.000005, "out": 0.000015 }, "gpt-4o-2024-08-06": { "in": 0.0000025, "out": 0.00001, "cache_read": 0.00000125 }, "gpt-4o-2024-11-20": { "in": 0.0000025, "out": 0.00001, "cache_read": 0.00000125 }, "gpt-4o-audio-preview-2024-12-17": { "in": 0.0000025, "out": 0.00001 }, "gpt-4o-audio-preview-2025-06-03": { "in": 0.0000025, "out": 0.00001 }, "gpt-4o-mini": { "in": 1.5e-7, "out": 6e-7, "cache_read": 7.5e-8 }, "gpt-4o-mini-2024-07-18": { "in": 1.5e-7, "out": 6e-7, "cache_read": 7.5e-8 }, "gpt-4o-mini-audio-preview-2024-12-17": { "in": 1.5e-7, "out": 6e-7 }, "gpt-4o-mini-search-preview": { "in": 1.5e-7, "out": 6e-7, "cache_read": 7.5e-8 }, "gpt-4o-search-preview": { "in": 0.0000025, "out": 0.00001, "cache_read": 0.00000125 }, "gpt-5": { "in": 0.00000125, "out": 0.00001, "cache_read": 1.25e-7 }, "gpt-5-2025-08-07": { "in": 0.00000125, "out": 0.00001, "cache_read": 1.25e-7 }, "gpt-5-chat": { "in": 0.00000125, "out": 0.00001, "cache_read": 1.25e-7 }, "gpt-5-chat-latest": { "in": 0.00000125, "out": 0.00001, "cache_read": 1.25e-7 }, "gpt-5-codex": { "in": 0.00000125, "out": 0.00001, "cache_read": 1.25e-7 }, "gpt-5-mini": { "in": 2.5e-7, "out": 0.000002, "cache_read": 2.5e-8 }, "gpt-5-mini-2025-08-07": { "in": 2.5e-7, "out": 0.000002, "cache_read": 2.5e-8 }, "gpt-5-nano": { "in": 5e-8, "out": 4e-7, "cache_read": 5e-9 }, "gpt-5-nano-2025-08-07": { "in": 5e-8, "out": 4e-7, "cache_read": 5e-9 }, "gpt-5-pro": { "in": 0.000015, "out": 0.00012 }, "gpt-5-pro-2025-10-06": { "in": 0.000015, "out": 0.00012 }, "gpt-5-search-api": { "in": 0.00000125, "out": 0.00001, "cache_read": 1.25e-7 }, "gpt-5-search-api-2025-10-14": { "in": 0.00000125, "out": 0.00001, "cache_read": 1.25e-7 }, "gpt-5.1": { "in": 0.00000125, "out": 0.00001, "cache_read": 1.25e-7 }, "gpt-5.1-2025-11-13": { "in": 0.00000125, "out": 0.00001, "cache_read": 1.25e-7 }, "gpt-5.1-chat-latest": { "in": 0.00000125, "out": 0.00001, "cache_read": 1.25e-7 }, "gpt-5.1-codex": { "in": 0.00000125, "out": 0.00001, "cache_read": 1.25e-7 }, "gpt-5.1-codex-max": { "in": 0.00000125, "out": 0.00001, "cache_read": 1.25e-7 }, "gpt-5.1-codex-mini": { "in": 2.5e-7, "out": 0.000002, "cache_read": 2.5e-8 }, "gpt-5.2": { "in": 0.00000175, "out": 0.000014, "cache_read": 1.75e-7 }, "gpt-5.2-2025-12-11": { "in": 0.00000175, "out": 0.000014, "cache_read": 1.75e-7 }, "gpt-5.2-chat-latest": { "in": 0.00000175, "out": 0.000014, "cache_read": 1.75e-7 }, "gpt-5.2-codex": { "in": 0.00000175, "out": 0.000014, "cache_read": 1.75e-7 }, "gpt-5.2-pro": { "in": 0.000021, "out": 0.000168 }, "gpt-5.2-pro-2025-12-11": { "in": 0.000021, "out": 0.000168 }, "gpt-5.3-chat-latest": { "in": 0.00000175, "out": 0.000014, "cache_read": 1.75e-7 }, "gpt-5.3-codex": { "in": 0.00000175, "out": 0.000014, "cache_read": 1.75e-7 }, "gpt-5.4": { "in": 0.0000025, "out": 0.000015, "cache_read": 2.5e-7, "above": 272000, "tier": { "in": 0.000005, "out": 0.0000225, "cache_read": 5e-7 } }, "gpt-5.4-2026-03-05": { "in": 0.0000025, "out": 0.000015, "cache_read": 2.5e-7, "above": 272000, "tier": { "in": 0.000005, "out": 0.0000225, "cache_read": 5e-7 } }, "gpt-5.4-mini": { "in": 7.5e-7, "out": 0.0000045, "cache_read": 7.5e-8 }, "gpt-5.4-mini-2026-03-17": { "in": 7.5e-7, "out": 0.0000045, "cache_read": 7.5e-8 }, "gpt-5.4-nano": { "in": 2e-7, "out": 0.00000125, "cache_read": 2e-8 }, "gpt-5.4-nano-2026-03-17": { "in": 2e-7, "out": 0.00000125, "cache_read": 2e-8 }, "gpt-5.4-pro": { "in": 0.00003, "out": 0.00018, "above": 272000, "tier": { "in": 0.00006, "out": 0.00027 } }, "gpt-5.4-pro-2026-03-05": { "in": 0.00003, "out": 0.00018, "above": 272000, "tier": { "in": 0.00006, "out": 0.00027 } }, "gpt-5.5": { "in": 0.000005, "out": 0.00003, "cache_read": 5e-7, "above": 272000, "tier": { "in": 0.00001, "out": 0.000045, "cache_read": 0.000001 } }, "gpt-5.5-2026-04-23": { "in": 0.000005, "out": 0.00003, "cache_read": 5e-7, "above": 272000, "tier": { "in": 0.00001, "out": 0.000045, "cache_read": 0.000001 } }, "gpt-5.5-cyber": { "in": 0.0000125, "out": 0.000075, "cache_read": 0.00000125 }, "gpt-5.5-pro": { "in": 0.00003, "out": 0.00018, "above": 272000, "tier": { "in": 0.00006, "out": 0.00027 } }, "gpt-5.5-pro-2026-04-23": { "in": 0.00003, "out": 0.00018, "above": 272000, "tier": { "in": 0.00006, "out": 0.00027 } }, "gpt-5.6": { "in": 0.000004, "out": 0.00002, "cache_read": 4e-7, "cache_write": 0.000005, "above": 272000, "tier": { "in": 0.000008, "out": 0.00003, "cache_read": 8e-7, "cache_write": 0.00001 } }, "gpt-5.6-cyber": { "in": 0.0000125, "out": 0.000075, "cache_read": 0.00000125, "cache_write": 0.000015625, "above": 272000, "tier": { "in": 0.000025, "out": 0.0001125, "cache_read": 0.0000025, "cache_write": 0.00003125 } }, "gpt-5.6-luna": { "in": 2e-7, "out": 0.0000012, "cache_read": 2e-8, "cache_write": 2.5e-7, "above": 272000, "tier": { "in": 4e-7, "out": 0.0000018, "cache_read": 4e-8, "cache_write": 5e-7 } }, "gpt-5.6-sol": { "in": 0.000004, "out": 0.00002, "cache_read": 4e-7, "cache_write": 0.000005, "above": 272000, "tier": { "in": 0.000008, "out": 0.00003, "cache_read": 8e-7, "cache_write": 0.00001 } }, "gpt-5.6-terra": { "in": 0.000002, "out": 0.000012, "cache_read": 2e-7, "cache_write": 0.0000025, "above": 272000, "tier": { "in": 0.000004, "out": 0.000018, "cache_read": 4e-7, "cache_write": 0.000005 } }, "gpt-6-astra": { "in": 0.00001, "out": 0.00005, "cache_read": 0.000001, "cache_write": 0.0000125, "above": 272000, "tier": { "in": 0.00002, "out": 0.000075, "cache_read": 0.000002, "cache_write": 0.000025 } }, "gpt-6-luna": { "in": 1e-7, "out": 5e-7, "cache_read": 1e-8, "cache_write": 1.25e-7, "above": 272000, "tier": { "in": 2e-7, "out": 7.5e-7, "cache_read": 2e-8, "cache_write": 2.5e-7 } }, "gpt-6-sol": { "in": 0.000002, "out": 0.00001, "cache_read": 2e-7, "cache_write": 0.0000025, "above": 272000, "tier": { "in": 0.000004, "out": 0.000015, "cache_read": 4e-7, "cache_write": 0.000005 } }, "gpt-6.1-sol": { "in": 0.000002, "out": 0.00001, "cache_read": 1e-7, "cache_write": 0.0000025, "above": 272000, "tier": { "in": 0.000004, "out": 0.000015, "cache_read": 2e-7, "cache_write": 0.000005 } }, "gpt-audio": { "in": 0.0000025, "out": 0.00001 }, "gpt-audio-1.5": { "in": 0.0000025, "out": 0.00001 }, "gpt-audio-2025-08-28": { "in": 0.0000025, "out": 0.00001 }, "gpt-audio-mini": { "in": 6e-7, "out": 0.0000024 }, "gpt-audio-mini-2025-12-15": { "in": 6e-7, "out": 0.0000024 }, "gpt-daybreak-blue-latest": { "in": 0.000004, "out": 0.00002, "cache_read": 4e-7, "cache_write": 0.000005, "above": 272000, "tier": { "in": 0.000008, "out": 0.00003, "cache_read": 8e-7, "cache_write": 0.00001 } }, "gpt-daybreak-red-latest": { "in": 0.0000125, "out": 0.000075, "cache_read": 0.00000125, "cache_write": 0.000015625, "above": 272000, "tier": { "in": 0.000025, "out": 0.0001125, "cache_read": 0.0000025, "cache_write": 0.00003125 } }, "gpt-oss-120b": { "in": 1.5e-7, "out": 6e-7, "cache_read": 7.5e-8 }, "gpt-oss-20b": { "in": 7.5e-8, "out": 3e-7, "cache_read": 3.75e-8 }, "gpt-oss-safeguard-20b": { "in": 7.5e-8, "out": 3e-7, "cache_read": 3.7e-8 }, "gpt-rosalind-discovery": { "in": 0.000005, "out": 0.000025, "cache_read": 5e-7 }, "gpt-rosalind-research": { "in": 0.000005, "out": 0.000025, "cache_read": 5e-7 }, "grok-4.20": { "in": 0.00000125, "out": 0.0000025, "cache_read": 2e-7, "above": 200000, "tier": { "in": 0.0000025, "out": 0.000005, "cache_read": 4e-7 } }, "grok-4.20-0309": { "in": 0.00000125, "out": 0.0000025, "cache_read": 2e-7, "above": 200000, "tier": { "in": 0.0000025, "out": 0.000005, "cache_read": 4e-7 } }, "grok-4.20-0309-non-reasoning": { "in": 0.00000125, "out": 0.0000025, "cache_read": 2e-7, "above": 200000, "tier": { "in": 0.0000025, "out": 0.000005, "cache_read": 4e-7 } }, "grok-4.20-0309-reasoning": { "in": 0.00000125, "out": 0.0000025, "cache_read": 2e-7, "above": 200000, "tier": { "in": 0.0000025, "out": 0.000005, "cache_read": 4e-7 } }, "grok-4.20-beta": { "in": 0.00000125, "out": 0.0000025, "cache_read": 2e-7, "above": 200000, "tier": { "in": 0.0000025, "out": 0.000005, "cache_read": 4e-7 } }, "grok-4.20-beta-0309": { "in": 0.00000125, "out": 0.0000025, "cache_read": 2e-7, "above": 200000, "tier": { "in": 0.0000025, "out": 0.000005, "cache_read": 4e-7 } }, "grok-4.20-beta-0309-non-reasoning": { "in": 0.00000125, "out": 0.0000025, "cache_read": 2e-7, "above": 200000, "tier": { "in": 0.0000025, "out": 0.000005, "cache_read": 4e-7 } }, "grok-4.20-beta-0309-reasoning": { "in": 0.00000125, "out": 0.0000025, "cache_read": 2e-7, "above": 200000, "tier": { "in": 0.0000025, "out": 0.000005, "cache_read": 4e-7 } }, "grok-4.20-beta-latest": { "in": 0.00000125, "out": 0.0000025, "cache_read": 2e-7, "above": 200000, "tier": { "in": 0.0000025, "out": 0.000005, "cache_read": 4e-7 } }, "grok-4.20-beta-latest-non-reasoning": { "in": 0.00000125, "out": 0.0000025, "cache_read": 2e-7, "above": 200000, "tier": { "in": 0.0000025, "out": 0.000005, "cache_read": 4e-7 } }, "grok-4.20-beta-latest-reasoning": { "in": 0.00000125, "out": 0.0000025, "cache_read": 2e-7, "above": 200000, "tier": { "in": 0.0000025, "out": 0.000005, "cache_read": 4e-7 } }, "grok-4.20-beta-non-reasoning": { "in": 0.00000125, "out": 0.0000025, "cache_read": 2e-7, "above": 200000, "tier": { "in": 0.0000025, "out": 0.000005, "cache_read": 4e-7 } }, "grok-4.20-beta-reasoning": { "in": 0.00000125, "out": 0.0000025, "cache_read": 2e-7, "above": 200000, "tier": { "in": 0.0000025, "out": 0.000005, "cache_read": 4e-7 } }, "grok-4.20-experimental-beta-0304": { "in": 0.00000125, "out": 0.0000025, "cache_read": 2e-7, "above": 200000, "tier": { "in": 0.0000025, "out": 0.000005, "cache_read": 4e-7 } }, "grok-4.20-experimental-beta-0304-non-reasoning": { "in": 0.00000125, "out": 0.0000025, "cache_read": 2e-7, "above": 200000, "tier": { "in": 0.0000025, "out": 0.000005, "cache_read": 4e-7 } }, "grok-4.20-experimental-beta-0304-reasoning": { "in": 0.00000125, "out": 0.0000025, "cache_read": 2e-7, "above": 200000, "tier": { "in": 0.0000025, "out": 0.000005, "cache_read": 4e-7 } }, "grok-4.20-experimental-beta-latest": { "in": 0.00000125, "out": 0.0000025, "cache_read": 2e-7, "above": 200000, "tier": { "in": 0.0000025, "out": 0.000005, "cache_read": 4e-7 } }, "grok-4.20-experimental-beta-non-reasoning-latest": { "in": 0.00000125, "out": 0.0000025, "cache_read": 2e-7, "above": 200000, "tier": { "in": 0.0000025, "out": 0.000005, "cache_read": 4e-7 } }, "grok-4.20-experimental-beta-reasoning-latest": { "in": 0.00000125, "out": 0.0000025, "cache_read": 2e-7, "above": 200000, "tier": { "in": 0.0000025, "out": 0.000005, "cache_read": 4e-7 } }, "grok-4.20-multi-agent": { "in": 0.00000125, "out": 0.0000025, "cache_read": 2e-7, "above": 200000, "tier": { "in": 0.0000025, "out": 0.000005, "cache_read": 4e-7 } }, "grok-4.20-multi-agent-0309": { "in": 0.00000125, "out": 0.0000025, "cache_read": 2e-7, "above": 200000, "tier": { "in": 0.0000025, "out": 0.000005, "cache_read": 4e-7 } }, "grok-4.20-multi-agent-beta-0309": { "in": 0.00000125, "out": 0.0000025, "cache_read": 2e-7, "above": 200000, "tier": { "in": 0.0000025, "out": 0.000005, "cache_read": 4e-7 } }, "grok-4.20-multi-agent-beta-latest": { "in": 0.00000125, "out": 0.0000025, "cache_read": 2e-7, "above": 200000, "tier": { "in": 0.0000025, "out": 0.000005, "cache_read": 4e-7 } }, "grok-4.20-multi-agent-experimental-beta-0304": { "in": 0.00000125, "out": 0.0000025, "cache_read": 2e-7, "above": 200000, "tier": { "in": 0.0000025, "out": 0.000005, "cache_read": 4e-7 } }, "grok-4.20-multi-agent-experimental-beta-latest": { "in": 0.00000125, "out": 0.0000025, "cache_read": 2e-7, "above": 200000, "tier": { "in": 0.0000025, "out": 0.000005, "cache_read": 4e-7 } }, "grok-4.20-multi-agent-latest": { "in": 0.00000125, "out": 0.0000025, "cache_read": 2e-7, "above": 200000, "tier": { "in": 0.0000025, "out": 0.000005, "cache_read": 4e-7 } }, "grok-4.20-non-reasoning": { "in": 0.00000125, "out": 0.0000025, "cache_read": 2e-7, "above": 200000, "tier": { "in": 0.0000025, "out": 0.000005, "cache_read": 4e-7 } }, "grok-4.20-non-reasoning-gv2": { "in": 0.00000125, "out": 0.0000025, "cache_read": 2e-7, "above": 200000, "tier": { "in": 0.0000025, "out": 0.000005, "cache_read": 4e-7 } }, "grok-4.20-non-reasoning-latest": { "in": 0.00000125, "out": 0.0000025, "cache_read": 2e-7, "above": 200000, "tier": { "in": 0.0000025, "out": 0.000005, "cache_read": 4e-7 } }, "grok-4.20-reasoning": { "in": 0.00000125, "out": 0.0000025, "cache_read": 2e-7, "above": 200000, "tier": { "in": 0.0000025, "out": 0.000005, "cache_read": 4e-7 } }, "grok-4.20-reasoning-gv2": { "in": 0.00000125, "out": 0.0000025, "cache_read": 2e-7, "above": 200000, "tier": { "in": 0.0000025, "out": 0.000005, "cache_read": 4e-7 } }, "grok-4.20-reasoning-latest": { "in": 0.00000125, "out": 0.0000025, "cache_read": 2e-7, "above": 200000, "tier": { "in": 0.0000025, "out": 0.000005, "cache_read": 4e-7 } }, "grok-4.3": { "in": 0.00000125, "out": 0.0000025, "cache_read": 2e-7, "above": 200000, "tier": { "in": 0.0000025, "out": 0.000005, "cache_read": 4e-7 } }, "grok-4.3-latest": { "in": 0.00000125, "out": 0.0000025, "cache_read": 2e-7, "above": 200000, "tier": { "in": 0.0000025, "out": 0.000005, "cache_read": 4e-7 } }, "grok-4.5": { "in": 0.000002, "out": 0.000006, "cache_read": 3e-7, "above": 200000, "tier": { "in": 0.000004, "out": 0.000012, "cache_read": 6e-7 } }, "grok-4.5-latest": { "in": 0.000002, "out": 0.000006, "cache_read": 3e-7, "above": 200000, "tier": { "in": 0.000004, "out": 0.000012, "cache_read": 6e-7 } }, "grok-4.6": { "in": 0.000002, "out": 0.000006, "cache_read": 5e-7, "above": 200000, "tier": { "in": 0.000004, "out": 0.000012, "cache_read": 0.000001 } }, "grok-4.7": { "in": 0.000002, "out": 0.000006, "cache_read": 5e-7, "above": 200000, "tier": { "in": 0.000004, "out": 0.000012, "cache_read": 0.000001 } }, "grok-build-0.1": { "in": 0.000001, "out": 0.000002, "cache_read": 2e-7, "above": 200000, "tier": { "in": 0.000002, "out": 0.000004, "cache_read": 4e-7 } }, "grok-build-latest": { "in": 0.000002, "out": 0.000006, "cache_read": 3e-7, "above": 200000, "tier": { "in": 0.000004, "out": 0.000012, "cache_read": 6e-7 } }, "grok-code-fast": { "in": 0.000001, "out": 0.000002, "cache_read": 2e-7, "above": 200000, "tier": { "in": 0.000002, "out": 0.000004, "cache_read": 4e-7 } }, "grok-code-fast-1": { "in": 0.000001, "out": 0.000002, "cache_read": 2e-7, "above": 200000, "tier": { "in": 0.000002, "out": 0.000004, "cache_read": 4e-7 } }, "grok-code-fast-1-0825": { "in": 0.000001, "out": 0.000002, "cache_read": 2e-7, "above": 200000, "tier": { "in": 0.000002, "out": 0.000004, "cache_read": 4e-7 } }, "labs-leanstral-1-5": { "in": 0, "out": 0 }, "labs-leanstral-1-5-1": { "in": 0, "out": 0 }, "learnlm-1.5-pro-experimental": { "in": 0, "out": 0 }, "llama-guard-3-8b": { "in": 2e-7, "out": 2e-7 }, "llama-prompt-guard-2-22m": { "in": 3e-8, "out": 3e-8 }, "llama-prompt-guard-2-86m": { "in": 4e-8, "out": 4e-8 }, "lyria-3-clip-preview": { "in": 0, "out": 0 }, "lyria-3-pro-preview": { "in": 0, "out": 0 }, "lyria-3.5": { "in": 0, "out": 0 }, "lyria-3.5-clip-preview": { "in": 0, "out": 0 }, "lyria-3.5-pro-preview": { "in": 0, "out": 0 }, "lyria-realtime-exp": { "in": 0, "out": 0 }, "magistral-medium-latest": { "in": 0.0000015, "out": 0.0000075, "cache_read": 1.5e-7 }, "magistral-small-latest": { "in": 1.5e-7, "out": 6e-7, "cache_read": 1.5e-8 }, "ministral-14b-2512": { "in": 2e-7, "out": 2e-7, "cache_read": 2e-8 }, "ministral-14b-latest": { "in": 2e-7, "out": 2e-7, "cache_read": 2e-8 }, "ministral-3-14b-2512": { "in": 2e-7, "out": 2e-7, "cache_read": 2e-8 }, "ministral-3-3b-2512": { "in": 1e-7, "out": 1e-7, "cache_read": 1e-8 }, "ministral-3-8b-2512": { "in": 1.5e-7, "out": 1.5e-7, "cache_read": 1.5e-8 }, "ministral-3b-2512": { "in": 1e-7, "out": 1e-7, "cache_read": 1e-8 }, "ministral-3b-latest": { "in": 1e-7, "out": 1e-7, "cache_read": 1e-8 }, "ministral-8b-2512": { "in": 1.5e-7, "out": 1.5e-7, "cache_read": 1.5e-8 }, "ministral-8b-latest": { "in": 1.5e-7, "out": 1.5e-7, "cache_read": 1.5e-8 }, "mistral-code-agent-latest": { "in": 4e-7, "out": 0.000002, "cache_read": 4e-8 }, "mistral-code-fim-latest": { "in": 3e-7, "out": 9e-7, "cache_read": 3e-8 }, "mistral-code-latest": { "in": 3e-7, "out": 9e-7, "cache_read": 3e-8 }, "mistral-large-2512": { "in": 5e-7, "out": 0.0000015, "cache_read": 5e-8 }, "mistral-large-3": { "in": 5e-7, "out": 0.0000015, "cache_read": 5e-8 }, "mistral-large-4": { "in": 6.8e-7, "out": 0.00000209, "cache_read": 6.8e-8 }, "mistral-large-4-0": { "in": 6.8e-7, "out": 0.00000209, "cache_read": 6.8e-8 }, "mistral-large-latest": { "in": 5e-7, "out": 0.0000015, "cache_read": 5e-8 }, "mistral-medium": { "in": 0.0000015, "out": 0.0000075, "cache_read": 1.5e-7 }, "mistral-medium-2604": { "in": 0.0000015, "out": 0.0000075, "cache_read": 1.5e-7 }, "mistral-medium-3": { "in": 0.0000015, "out": 0.0000075, "cache_read": 1.5e-7 }, "mistral-medium-3-5": { "in": 0.0000015, "out": 0.0000075, "cache_read": 1.5e-7 }, "mistral-medium-3.5": { "in": 0.0000015, "out": 0.0000075, "cache_read": 1.5e-7 }, "mistral-medium-latest": { "in": 0.0000015, "out": 0.0000075, "cache_read": 1.5e-7 }, "mistral-small": { "in": 1e-7, "out": 3e-7, "cache_read": 1e-8 }, "mistral-small-2603": { "in": 1.5e-7, "out": 6e-7, "cache_read": 1.5e-8 }, "mistral-small-latest": { "in": 1.5e-7, "out": 6e-7, "cache_read": 1.5e-8 }, "mistral-tiny": { "in": 2.5e-7, "out": 2.5e-7, "cache_read": 2.5e-8 }, "mistral-vibe-cli-fast": { "in": 1.5e-7, "out": 6e-7, "cache_read": 1.5e-8 }, "mistral-vibe-cli-latest": { "in": 0.0000015, "out": 0.0000075, "cache_read": 1.5e-7 }, "mistral-vibe-cli-with-tools": { "in": 0.0000015, "out": 0.0000075, "cache_read": 1.5e-7 }, "o1": { "in": 0.000015, "out": 0.00006, "cache_read": 0.0000075 }, "o1-2024-12-17": { "in": 0.000015, "out": 0.00006, "cache_read": 0.0000075 }, "o1-pro": { "in": 0.00015, "out": 0.0006 }, "o1-pro-2025-03-19": { "in": 0.00015, "out": 0.0006 }, "o3": { "in": 0.000002, "out": 0.000008, "cache_read": 5e-7 }, "o3-2025-04-16": { "in": 0.000002, "out": 0.000008, "cache_read": 5e-7 }, "o3-deep-research": { "in": 0.00001, "out": 0.00004, "cache_read": 0.0000025 }, "o3-mini": { "in": 0.0000011, "out": 0.0000044, "cache_read": 5.5e-7 }, "o3-mini-2025-01-31": { "in": 0.0000011, "out": 0.0000044, "cache_read": 5.5e-7 }, "o3-pro": { "in": 0.00002, "out": 0.00008 }, "o3-pro-2025-06-10": { "in": 0.00002, "out": 0.00008 }, "o4-mini": { "in": 0.0000011, "out": 0.0000044, "cache_read": 2.75e-7 }, "o4-mini-2025-04-16": { "in": 0.0000011, "out": 0.0000044, "cache_read": 2.75e-7 }, "o4-mini-deep-research": { "in": 0.000002, "out": 0.000008, "cache_read": 5e-7 }, "open-mistral-nemo": { "in": 3e-7, "out": 3e-7, "cache_read": 3e-8 }, "pixtral-large-latest": { "in": 0.000002, "out": 0.000006, "cache_read": 2e-7 }, "qwen3.8-27b": { "in": 8e-7, "out": 0.000004 }, "voxtral-small-2507": { "in": 1e-7, "out": 4e-7, "cache_read": 1e-8 }, "voxtral-small-latest": { "in": 1e-7, "out": 4e-7, "cache_read": 1e-8 }, "zai-glm-5": { "in": 0.0000014, "out": 0.0000044, "cache_read": 1.4e-7 }, "zai-glm-5-2": { "in": 0.0000014, "out": 0.0000044, "cache_read": 1.4e-7 }, "zai-glm-5-3": { "in": 0.0000014, "out": 0.0000044, "cache_read": 1.4e-7 }, "zai-glm-latest": { "in": 0.0000014, "out": 0.0000044, "cache_read": 1.4e-7 } };
2
+ // LiteLLM model_prices_and_context_window.json, MIT License, Copyright (c) 2023 Berri AI, fetched 2026-10-13
3
+ export const PRICES = { "chat-latest": { "in": 0.000005, "out": 0.00003, "cache_read": 5e-7, "max_out": 128000 }, "claude-fable-5": { "in": 0.00001, "out": 0.00005, "cache_read": 0.000001, "cache_write": 0.0000125, "max_out": 128000 }, "claude-fable-5-1": { "in": 0.00001, "out": 0.00005, "cache_read": 2.5e-7, "cache_write": 0.0000125, "max_out": 128000 }, "claude-haiku-4-5": { "in": 0.000001, "out": 0.000005, "cache_read": 1e-7, "cache_write": 0.00000125, "max_out": 64000 }, "claude-haiku-4-5-20251001": { "in": 0.000001, "out": 0.000005, "cache_read": 1e-7, "cache_write": 0.00000125, "max_out": 64000 }, "claude-haiku-5-5": { "in": 1e-7, "out": 5e-7, "cache_read": 1e-8, "cache_write": 1.25e-7, "max_out": 128000 }, "claude-mythos-5": { "in": 0.00001, "out": 0.00005, "cache_read": 0.000001, "cache_write": 0.0000125, "max_out": 128000 }, "claude-mythos-5-1": { "in": 0.00001, "out": 0.00005, "cache_read": 2.5e-7, "cache_write": 0.0000125, "max_out": 128000 }, "claude-mythos-preview": { "in": 0.00001, "out": 0.00005, "cache_read": 0.000001, "cache_write": 0.0000125, "max_out": 128000 }, "claude-opus-4-5": { "in": 0.000005, "out": 0.000025, "cache_read": 5e-7, "cache_write": 0.00000625, "max_out": 64000 }, "claude-opus-4-5-20251101": { "in": 0.000005, "out": 0.000025, "cache_read": 5e-7, "cache_write": 0.00000625, "max_out": 64000 }, "claude-opus-4-6": { "in": 0.000005, "out": 0.000025, "cache_read": 5e-7, "cache_write": 0.00000625, "max_out": 128000 }, "claude-opus-4-6-20260205": { "in": 0.000005, "out": 0.000025, "cache_read": 5e-7, "cache_write": 0.00000625, "max_out": 128000 }, "claude-opus-4-7": { "in": 0.000005, "out": 0.000025, "cache_read": 5e-7, "cache_write": 0.00000625, "max_out": 128000 }, "claude-opus-4-7-20260416": { "in": 0.000005, "out": 0.000025, "cache_read": 5e-7, "cache_write": 0.00000625, "max_out": 128000 }, "claude-opus-4-8": { "in": 0.000005, "out": 0.000025, "cache_read": 5e-7, "cache_write": 0.00000625, "max_out": 128000 }, "claude-opus-5": { "in": 0.000005, "out": 0.000025, "cache_read": 5e-7, "cache_write": 0.00000625, "max_out": 128000 }, "claude-opus-5-5": { "in": 0.000004, "out": 0.00002, "cache_read": 2e-7, "cache_write": 0.000005, "max_out": 128000 }, "claude-sonnet-4-5": { "in": 0.000003, "out": 0.000015, "cache_read": 3e-7, "cache_write": 0.00000375, "max_out": 64000 }, "claude-sonnet-4-5-20250929": { "in": 0.000003, "out": 0.000015, "cache_read": 3e-7, "cache_write": 0.00000375, "max_out": 64000 }, "claude-sonnet-4-6": { "in": 0.000003, "out": 0.000015, "cache_read": 3e-7, "cache_write": 0.00000375, "max_out": 128000 }, "claude-sonnet-5": { "in": 0.000002, "out": 0.00001, "cache_read": 2e-7, "cache_write": 0.0000025, "max_out": 128000 }, "claude-sonnet-5-5": { "in": 0.000002, "out": 0.00001, "cache_read": 1e-7, "cache_write": 0.0000025, "max_out": 128000 }, "codestral-2508": { "in": 3e-7, "out": 9e-7, "cache_read": 3e-8, "max_out": 128000 }, "codestral-latest": { "in": 3e-7, "out": 9e-7, "cache_read": 3e-8, "max_out": 128000 }, "codestral-mamba-latest": { "in": 2.5e-7, "out": 2.5e-7, "cache_read": 2.5e-8, "max_out": 256000 }, "computer-use-preview": { "in": 0.000003, "out": 0.000012, "max_out": 1024 }, "computer-use-preview-2025-03-11": { "in": 0.000003, "out": 0.000012, "max_out": 1024 }, "daybreak-blue-latest": { "in": 0.000004, "out": 0.00002, "cache_read": 4e-7, "cache_write": 0.000005, "max_out": 128000, "above": 272000, "tier": { "in": 0.000008, "out": 0.00003, "cache_read": 8e-7, "cache_write": 0.00001 } }, "daybreak-red-latest": { "in": 0.0000125, "out": 0.000075, "cache_read": 0.00000125, "cache_write": 0.000015625, "max_out": 128000, "above": 272000, "tier": { "in": 0.000025, "out": 0.0001125, "cache_read": 0.0000025, "cache_write": 0.00003125 } }, "deep-research-max-preview-04-2026": { "in": 0.000002, "out": 0.000012, "max_out": 65536 }, "deep-research-preview-04-2026": { "in": 0.000002, "out": 0.000012, "max_out": 65536 }, "deep-research-pro-preview-12-2025": { "in": 0.000002, "out": 0.000012, "max_out": 65536 }, "deepseek-chat": { "in": 2.8e-7, "out": 4.2e-7, "cache_read": 2.8e-8, "max_out": 8192 }, "deepseek-coder": { "in": 1.4e-7, "out": 2.8e-7, "cache_read": 1.4e-8, "max_out": 4096 }, "deepseek-flash": { "in": 3e-7, "out": 0.0000012, "cache_read": 6e-9, "cache_write": 0, "max_out": 393216 }, "deepseek-r1": { "in": 5.5e-7, "out": 0.00000219, "cache_read": 1.4e-7, "max_out": 8192 }, "deepseek-reasoner": { "in": 2.8e-7, "out": 4.2e-7, "cache_read": 2.8e-8, "max_out": 65536 }, "deepseek-v3": { "in": 2.7e-7, "out": 0.0000011, "cache_read": 7e-8, "cache_write": 0, "max_out": 8192 }, "deepseek-v3.2": { "in": 2.8e-7, "out": 4e-7, "cache_read": 2.8e-8, "max_out": 163840 }, "deepseek-v4-flash": { "in": 3e-7, "out": 0.0000012, "cache_read": 6e-9, "cache_write": 0, "max_out": 393216 }, "deepseek-v4-flash-vision-exp": { "in": 3e-7, "out": 0.0000012, "cache_read": 6e-9, "cache_write": 0, "max_out": 393216 }, "deepseek-v4-pro": { "in": 0.00000132, "out": 0.00000396, "cache_read": 4.4e-8, "cache_write": 0, "max_out": 393216 }, "devstral-latest": { "in": 4e-7, "out": 0.000002, "cache_read": 4e-8, "max_out": 256000 }, "devstral-medium-latest": { "in": 4e-7, "out": 0.000002, "cache_read": 4e-8, "max_out": 256000 }, "devstral-small-latest": { "in": 1e-7, "out": 3e-7, "cache_read": 1e-8, "max_out": 256000 }, "ft:gpt-3.5-turbo": { "in": 0.000003, "out": 0.000006, "max_out": 4096 }, "ft:gpt-3.5-turbo-0125": { "in": 0.000003, "out": 0.000006, "max_out": 4096 }, "ft:gpt-3.5-turbo-0613": { "in": 0.000003, "out": 0.000006, "max_out": 4096 }, "ft:gpt-3.5-turbo-1106": { "in": 0.000003, "out": 0.000006, "max_out": 4096 }, "ft:gpt-4-0613": { "in": 0.00003, "out": 0.00006, "max_out": 4096 }, "ft:gpt-4.1-2025-04-14": { "in": 0.000003, "out": 0.000012, "cache_read": 7.5e-7, "max_out": 32768 }, "ft:gpt-4.1-mini-2025-04-14": { "in": 8e-7, "out": 0.0000032, "cache_read": 2e-7, "max_out": 32768 }, "ft:gpt-4.1-nano-2025-04-14": { "in": 2e-7, "out": 8e-7, "cache_read": 5e-8, "max_out": 32768 }, "ft:gpt-4o-2024-08-06": { "in": 0.00000375, "out": 0.000015, "cache_read": 0.000001875, "max_out": 16384 }, "ft:gpt-4o-2024-11-20": { "in": 0.00000375, "out": 0.000015, "cache_write": 0.000001875, "max_out": 16384 }, "ft:gpt-4o-mini-2024-07-18": { "in": 3e-7, "out": 0.0000012, "cache_read": 1.5e-7, "max_out": 16384 }, "ft:o4-mini-2025-04-16": { "in": 0.000004, "out": 0.000016, "cache_read": 0.000001, "max_out": 100000 }, "gemini-2.5-computer-use-preview-10-2025": { "in": 0.00000125, "out": 0.00001, "max_out": 64000, "above": 200000, "tier": { "in": 0.0000025, "out": 0.000015 } }, "gemini-2.5-flash": { "in": 3e-7, "out": 0.0000025, "cache_read": 3e-8, "max_out": 65536 }, "gemini-2.5-flash-lite": { "in": 1e-7, "out": 4e-7, "cache_read": 1e-8, "max_out": 65536 }, "gemini-2.5-flash-lite-preview-09-2025": { "in": 1e-7, "out": 4e-7, "cache_read": 1e-8, "max_out": 65535 }, "gemini-2.5-flash-preview-09-2025": { "in": 3e-7, "out": 0.0000025, "cache_read": 3e-8, "max_out": 65535 }, "gemini-2.5-pro": { "in": 0.00000125, "out": 0.00001, "cache_read": 1.25e-7, "max_out": 65536, "above": 200000, "tier": { "in": 0.0000025, "out": 0.000015, "cache_read": 2.5e-7 } }, "gemini-2.5-pro-preview-tts": { "in": 0.000001, "out": 0.00002, "cache_read": 1.25e-7, "max_out": 16384 }, "gemini-3-flash-preview": { "in": 5e-7, "out": 0.000003, "cache_read": 5e-8, "max_out": 65536 }, "gemini-3.1-flash-lite": { "in": 2.5e-7, "out": 0.0000015, "cache_read": 2.5e-8, "max_out": 65536 }, "gemini-3.1-flash-lite-preview": { "in": 2.5e-7, "out": 0.0000015, "cache_read": 2.5e-8, "max_out": 65536 }, "gemini-3.1-pro-preview": { "in": 0.000002, "out": 0.000012, "cache_read": 2e-7, "max_out": 65536, "above": 200000, "tier": { "in": 0.000004, "out": 0.000018, "cache_read": 4e-7 } }, "gemini-3.1-pro-preview-customtools": { "in": 0.000002, "out": 0.000012, "cache_read": 2e-7, "max_out": 65536, "above": 200000, "tier": { "in": 0.000004, "out": 0.000018, "cache_read": 4e-7 } }, "gemini-3.5-flash": { "in": 0.0000015, "out": 0.000009, "cache_read": 1.5e-7, "max_out": 65536 }, "gemini-3.5-flash-lite": { "in": 3e-7, "out": 0.0000025, "cache_read": 3e-8, "max_out": 65536 }, "gemini-3.6-flash": { "in": 7.5e-7, "out": 0.00000375, "cache_read": 7.5e-8, "max_out": 65536 }, "gemini-3.7-flash": { "in": 7.5e-7, "out": 0.00000375, "cache_read": 7.5e-8, "max_out": 65536 }, "gemini-3.8-flash": { "in": 7.5e-7, "out": 0.00000375, "cache_read": 7.5e-8, "max_out": 65536 }, "gemini-3.8-flash-cyber": { "in": 0.0000015, "out": 0.0000075, "cache_read": 1.5e-7, "max_out": 65536 }, "gemini-4-argon": { "in": 0.000002, "out": 0.00001, "cache_read": 1e-7, "max_out": 65536 }, "gemini-exp-1114": { "in": 0, "out": 0, "max_out": 8192 }, "gemini-exp-1206": { "in": 0, "out": 0, "max_out": 8192 }, "gemini-flash-latest": { "in": 7.5e-7, "out": 0.00000375, "cache_read": 7.5e-8, "max_out": 65536 }, "gemini-flash-lite-latest": { "in": 3e-7, "out": 0.0000025, "cache_read": 3e-8, "max_out": 65536 }, "gemini-gemma-2-27b-it": { "in": 3.5e-7, "out": 0.00000105, "max_out": 8192 }, "gemini-gemma-2-9b-it": { "in": 3.5e-7, "out": 0.00000105, "max_out": 8192 }, "gemini-omni-1.1-flash": { "in": 0.0000015, "out": 0.000009, "max_out": 65536 }, "gemini-omni-flash-preview": { "in": 0.0000015, "out": 0.000009, "max_out": 65536 }, "gemini-pro-latest": { "in": 0.000002, "out": 0.000012, "cache_read": 2e-7, "max_out": 65536, "above": 200000, "tier": { "in": 0.000004, "out": 0.000018, "cache_read": 4e-7 } }, "gemini-robotics-er-1.5-preview": { "in": 3e-7, "out": 0.0000025, "cache_read": 0, "max_out": 65535 }, "gemini-robotics-er-2-preview": { "in": 0.000001, "out": 0.000005, "cache_read": 1e-7, "max_out": 65536 }, "gemini-robotics-er-2-streaming-preview": { "in": 0.000001, "out": 0.000005, "max_out": 65536 }, "gemma-3-27b-it": { "in": 0, "out": 0, "max_out": 8192 }, "gemma-4-26b-a4b-it": { "in": 0, "out": 0, "max_out": 32768 }, "gemma-4-31b-it": { "in": 0, "out": 0, "max_out": 32768 }, "glm-5-2": { "in": 0.0000014, "out": 0.0000044, "cache_read": 1.4e-7, "max_out": 131072 }, "gpt-3.5-turbo": { "in": 5e-7, "out": 0.0000015, "max_out": 4096 }, "gpt-3.5-turbo-0125": { "in": 5e-7, "out": 0.0000015, "max_out": 4096 }, "gpt-3.5-turbo-1106": { "in": 0.000001, "out": 0.000002, "max_out": 4096 }, "gpt-3.5-turbo-16k": { "in": 0.000003, "out": 0.000004, "max_out": 4096 }, "gpt-4": { "in": 0.00003, "out": 0.00006, "max_out": 4096 }, "gpt-4-0613": { "in": 0.00003, "out": 0.00006, "max_out": 4096 }, "gpt-4-1106-preview": { "in": 0.00001, "out": 0.00003, "max_out": 4096 }, "gpt-4-turbo": { "in": 0.00001, "out": 0.00003, "max_out": 4096 }, "gpt-4-turbo-2024-04-09": { "in": 0.00001, "out": 0.00003, "max_out": 4096 }, "gpt-4.1": { "in": 0.000002, "out": 0.000008, "cache_read": 5e-7, "max_out": 32768 }, "gpt-4.1-2025-04-14": { "in": 0.000002, "out": 0.000008, "cache_read": 5e-7, "max_out": 32768 }, "gpt-4.1-mini": { "in": 4e-7, "out": 0.0000016, "cache_read": 1e-7, "max_out": 32768 }, "gpt-4.1-mini-2025-04-14": { "in": 4e-7, "out": 0.0000016, "cache_read": 1e-7, "max_out": 32768 }, "gpt-4.1-nano": { "in": 1e-7, "out": 4e-7, "cache_read": 2.5e-8, "max_out": 32768 }, "gpt-4.1-nano-2025-04-14": { "in": 1e-7, "out": 4e-7, "cache_read": 2.5e-8, "max_out": 32768 }, "gpt-4o": { "in": 0.0000025, "out": 0.00001, "cache_read": 0.00000125, "max_out": 16384 }, "gpt-4o-2024-05-13": { "in": 0.000005, "out": 0.000015, "max_out": 4096 }, "gpt-4o-2024-08-06": { "in": 0.0000025, "out": 0.00001, "cache_read": 0.00000125, "max_out": 16384 }, "gpt-4o-2024-11-20": { "in": 0.0000025, "out": 0.00001, "cache_read": 0.00000125, "max_out": 16384 }, "gpt-4o-audio-preview-2024-12-17": { "in": 0.0000025, "out": 0.00001, "max_out": 16384 }, "gpt-4o-audio-preview-2025-06-03": { "in": 0.0000025, "out": 0.00001, "max_out": 16384 }, "gpt-4o-mini": { "in": 1.5e-7, "out": 6e-7, "cache_read": 7.5e-8, "max_out": 16384 }, "gpt-4o-mini-2024-07-18": { "in": 1.5e-7, "out": 6e-7, "cache_read": 7.5e-8, "max_out": 16384 }, "gpt-4o-mini-audio-preview-2024-12-17": { "in": 1.5e-7, "out": 6e-7, "max_out": 16384 }, "gpt-4o-mini-search-preview": { "in": 1.5e-7, "out": 6e-7, "cache_read": 7.5e-8, "max_out": 16384 }, "gpt-4o-mini-search-preview-2025-03-11": { "in": 1.5e-7, "out": 6e-7, "cache_read": 7.5e-8, "max_out": 16384 }, "gpt-4o-search-preview": { "in": 0.0000025, "out": 0.00001, "cache_read": 0.00000125, "max_out": 16384 }, "gpt-4o-search-preview-2025-03-11": { "in": 0.0000025, "out": 0.00001, "cache_read": 0.00000125, "max_out": 16384 }, "gpt-5": { "in": 0.00000125, "out": 0.00001, "cache_read": 1.25e-7, "max_out": 128000 }, "gpt-5-2025-08-07": { "in": 0.00000125, "out": 0.00001, "cache_read": 1.25e-7, "max_out": 128000 }, "gpt-5-chat": { "in": 0.00000125, "out": 0.00001, "cache_read": 1.25e-7, "max_out": 16384 }, "gpt-5-chat-latest": { "in": 0.00000125, "out": 0.00001, "cache_read": 1.25e-7, "max_out": 16384 }, "gpt-5-codex": { "in": 0.00000125, "out": 0.00001, "cache_read": 1.25e-7, "max_out": 128000 }, "gpt-5-mini": { "in": 2.5e-7, "out": 0.000002, "cache_read": 2.5e-8, "max_out": 128000 }, "gpt-5-mini-2025-08-07": { "in": 2.5e-7, "out": 0.000002, "cache_read": 2.5e-8, "max_out": 128000 }, "gpt-5-nano": { "in": 5e-8, "out": 4e-7, "cache_read": 5e-9, "max_out": 128000 }, "gpt-5-nano-2025-08-07": { "in": 5e-8, "out": 4e-7, "cache_read": 5e-9, "max_out": 128000 }, "gpt-5-pro": { "in": 0.000015, "out": 0.00012, "max_out": 272000 }, "gpt-5-pro-2025-10-06": { "in": 0.000015, "out": 0.00012, "max_out": 272000 }, "gpt-5-search-api": { "in": 0.00000125, "out": 0.00001, "cache_read": 1.25e-7, "max_out": 128000 }, "gpt-5-search-api-2025-10-14": { "in": 0.00000125, "out": 0.00001, "cache_read": 1.25e-7, "max_out": 128000 }, "gpt-5.1": { "in": 0.00000125, "out": 0.00001, "cache_read": 1.25e-7, "max_out": 128000 }, "gpt-5.1-2025-11-13": { "in": 0.00000125, "out": 0.00001, "cache_read": 1.25e-7, "max_out": 128000 }, "gpt-5.1-chat-latest": { "in": 0.00000125, "out": 0.00001, "cache_read": 1.25e-7, "max_out": 16384 }, "gpt-5.1-codex": { "in": 0.00000125, "out": 0.00001, "cache_read": 1.25e-7, "max_out": 128000 }, "gpt-5.1-codex-max": { "in": 0.00000125, "out": 0.00001, "cache_read": 1.25e-7, "max_out": 128000 }, "gpt-5.1-codex-mini": { "in": 2.5e-7, "out": 0.000002, "cache_read": 2.5e-8, "max_out": 128000 }, "gpt-5.2": { "in": 0.00000175, "out": 0.000014, "cache_read": 1.75e-7, "max_out": 128000 }, "gpt-5.2-2025-12-11": { "in": 0.00000175, "out": 0.000014, "cache_read": 1.75e-7, "max_out": 128000 }, "gpt-5.2-chat-latest": { "in": 0.00000175, "out": 0.000014, "cache_read": 1.75e-7, "max_out": 16384 }, "gpt-5.2-codex": { "in": 0.00000175, "out": 0.000014, "cache_read": 1.75e-7, "max_out": 128000 }, "gpt-5.2-pro": { "in": 0.000021, "out": 0.000168, "max_out": 128000 }, "gpt-5.2-pro-2025-12-11": { "in": 0.000021, "out": 0.000168, "max_out": 128000 }, "gpt-5.3-chat-latest": { "in": 0.00000175, "out": 0.000014, "cache_read": 1.75e-7, "max_out": 16384 }, "gpt-5.3-codex": { "in": 0.00000175, "out": 0.000014, "cache_read": 1.75e-7, "max_out": 128000 }, "gpt-5.4": { "in": 0.0000025, "out": 0.000015, "cache_read": 2.5e-7, "max_out": 128000, "above": 272000, "tier": { "in": 0.000005, "out": 0.0000225, "cache_read": 5e-7 } }, "gpt-5.4-2026-03-05": { "in": 0.0000025, "out": 0.000015, "cache_read": 2.5e-7, "max_out": 128000, "above": 272000, "tier": { "in": 0.000005, "out": 0.0000225, "cache_read": 5e-7 } }, "gpt-5.4-mini": { "in": 7.5e-7, "out": 0.0000045, "cache_read": 7.5e-8, "max_out": 128000 }, "gpt-5.4-mini-2026-03-17": { "in": 7.5e-7, "out": 0.0000045, "cache_read": 7.5e-8, "max_out": 128000 }, "gpt-5.4-nano": { "in": 2e-7, "out": 0.00000125, "cache_read": 2e-8, "max_out": 128000 }, "gpt-5.4-nano-2026-03-17": { "in": 2e-7, "out": 0.00000125, "cache_read": 2e-8, "max_out": 128000 }, "gpt-5.4-pro": { "in": 0.00003, "out": 0.00018, "max_out": 128000, "above": 272000, "tier": { "in": 0.00006, "out": 0.00027 } }, "gpt-5.4-pro-2026-03-05": { "in": 0.00003, "out": 0.00018, "max_out": 128000, "above": 272000, "tier": { "in": 0.00006, "out": 0.00027 } }, "gpt-5.5": { "in": 0.000005, "out": 0.00003, "cache_read": 5e-7, "max_out": 128000, "above": 272000, "tier": { "in": 0.00001, "out": 0.000045, "cache_read": 0.000001 } }, "gpt-5.5-2026-04-23": { "in": 0.000005, "out": 0.00003, "cache_read": 5e-7, "max_out": 128000, "above": 272000, "tier": { "in": 0.00001, "out": 0.000045, "cache_read": 0.000001 } }, "gpt-5.5-cyber": { "in": 0.0000125, "out": 0.000075, "cache_read": 0.00000125 }, "gpt-5.5-pro": { "in": 0.00003, "out": 0.00018, "max_out": 128000, "above": 272000, "tier": { "in": 0.00006, "out": 0.00027 } }, "gpt-5.5-pro-2026-04-23": { "in": 0.00003, "out": 0.00018, "max_out": 128000, "above": 272000, "tier": { "in": 0.00006, "out": 0.00027 } }, "gpt-5.6": { "in": 0.000004, "out": 0.00002, "cache_read": 4e-7, "cache_write": 0.000005, "max_out": 128000, "above": 272000, "tier": { "in": 0.000008, "out": 0.00003, "cache_read": 8e-7, "cache_write": 0.00001 } }, "gpt-5.6-cyber": { "in": 0.0000125, "out": 0.000075, "cache_read": 0.00000125, "cache_write": 0.000015625, "max_out": 128000, "above": 272000, "tier": { "in": 0.000025, "out": 0.0001125, "cache_read": 0.0000025, "cache_write": 0.00003125 } }, "gpt-5.6-luna": { "in": 2e-7, "out": 0.0000012, "cache_read": 2e-8, "cache_write": 2.5e-7, "max_out": 128000, "above": 272000, "tier": { "in": 4e-7, "out": 0.0000018, "cache_read": 4e-8, "cache_write": 5e-7 } }, "gpt-5.6-sol": { "in": 0.000004, "out": 0.00002, "cache_read": 4e-7, "cache_write": 0.000005, "max_out": 128000, "above": 272000, "tier": { "in": 0.000008, "out": 0.00003, "cache_read": 8e-7, "cache_write": 0.00001 } }, "gpt-5.6-terra": { "in": 0.000002, "out": 0.000012, "cache_read": 2e-7, "cache_write": 0.0000025, "max_out": 128000, "above": 272000, "tier": { "in": 0.000004, "out": 0.000018, "cache_read": 4e-7, "cache_write": 0.000005 } }, "gpt-6-astra": { "in": 0.00001, "out": 0.00005, "cache_read": 0.000001, "cache_write": 0.0000125, "max_out": 128000, "above": 272000, "tier": { "in": 0.00002, "out": 0.000075, "cache_read": 0.000002, "cache_write": 0.000025 } }, "gpt-6-luna": { "in": 1e-7, "out": 5e-7, "cache_read": 1e-8, "cache_write": 1.25e-7, "max_out": 128000, "above": 272000, "tier": { "in": 2e-7, "out": 7.5e-7, "cache_read": 2e-8, "cache_write": 2.5e-7 } }, "gpt-6-sol": { "in": 0.000002, "out": 0.00001, "cache_read": 2e-7, "cache_write": 0.0000025, "max_out": 128000, "above": 272000, "tier": { "in": 0.000004, "out": 0.000015, "cache_read": 4e-7, "cache_write": 0.000005 } }, "gpt-6.1-sol": { "in": 0.000002, "out": 0.00001, "cache_read": 1e-7, "cache_write": 0.0000025, "max_out": 128000, "above": 272000, "tier": { "in": 0.000004, "out": 0.000015, "cache_read": 2e-7, "cache_write": 0.000005 } }, "gpt-audio": { "in": 0.0000025, "out": 0.00001, "max_out": 16384 }, "gpt-audio-1.5": { "in": 0.0000025, "out": 0.00001, "max_out": 16384 }, "gpt-audio-2025-08-28": { "in": 0.0000025, "out": 0.00001, "max_out": 16384 }, "gpt-audio-mini": { "in": 6e-7, "out": 0.0000024, "max_out": 16384 }, "gpt-audio-mini-2025-10-06": { "in": 6e-7, "out": 0.0000024, "max_out": 16384 }, "gpt-audio-mini-2025-12-15": { "in": 6e-7, "out": 0.0000024, "max_out": 16384 }, "gpt-daybreak-blue-latest": { "in": 0.000004, "out": 0.00002, "cache_read": 4e-7, "cache_write": 0.000005, "max_out": 128000, "above": 272000, "tier": { "in": 0.000008, "out": 0.00003, "cache_read": 8e-7, "cache_write": 0.00001 } }, "gpt-daybreak-red-latest": { "in": 0.0000125, "out": 0.000075, "cache_read": 0.00000125, "cache_write": 0.000015625, "max_out": 128000, "above": 272000, "tier": { "in": 0.000025, "out": 0.0001125, "cache_read": 0.0000025, "cache_write": 0.00003125 } }, "gpt-oss-120b": { "in": 1.5e-7, "out": 6e-7, "cache_read": 7.5e-8, "max_out": 65536 }, "gpt-oss-20b": { "in": 7.5e-8, "out": 3e-7, "cache_read": 3.75e-8, "max_out": 65536 }, "gpt-oss-safeguard-20b": { "in": 7.5e-8, "out": 3e-7, "cache_read": 3.7e-8, "max_out": 65536 }, "gpt-rosalind-discovery": { "in": 0.000005, "out": 0.000025, "cache_read": 5e-7 }, "gpt-rosalind-research": { "in": 0.000005, "out": 0.000025, "cache_read": 5e-7 }, "grok-4.20": { "in": 0.00000125, "out": 0.0000025, "cache_read": 2e-7, "max_out": 1000000, "above": 200000, "tier": { "in": 0.0000025, "out": 0.000005, "cache_read": 4e-7 } }, "grok-4.20-0309": { "in": 0.00000125, "out": 0.0000025, "cache_read": 2e-7, "max_out": 1000000, "above": 200000, "tier": { "in": 0.0000025, "out": 0.000005, "cache_read": 4e-7 } }, "grok-4.20-0309-non-reasoning": { "in": 0.00000125, "out": 0.0000025, "cache_read": 2e-7, "max_out": 1000000, "above": 200000, "tier": { "in": 0.0000025, "out": 0.000005, "cache_read": 4e-7 } }, "grok-4.20-0309-reasoning": { "in": 0.00000125, "out": 0.0000025, "cache_read": 2e-7, "max_out": 1000000, "above": 200000, "tier": { "in": 0.0000025, "out": 0.000005, "cache_read": 4e-7 } }, "grok-4.20-beta": { "in": 0.00000125, "out": 0.0000025, "cache_read": 2e-7, "max_out": 1000000, "above": 200000, "tier": { "in": 0.0000025, "out": 0.000005, "cache_read": 4e-7 } }, "grok-4.20-beta-0309": { "in": 0.00000125, "out": 0.0000025, "cache_read": 2e-7, "max_out": 1000000, "above": 200000, "tier": { "in": 0.0000025, "out": 0.000005, "cache_read": 4e-7 } }, "grok-4.20-beta-0309-non-reasoning": { "in": 0.00000125, "out": 0.0000025, "cache_read": 2e-7, "max_out": 1000000, "above": 200000, "tier": { "in": 0.0000025, "out": 0.000005, "cache_read": 4e-7 } }, "grok-4.20-beta-0309-reasoning": { "in": 0.00000125, "out": 0.0000025, "cache_read": 2e-7, "max_out": 1000000, "above": 200000, "tier": { "in": 0.0000025, "out": 0.000005, "cache_read": 4e-7 } }, "grok-4.20-beta-latest": { "in": 0.00000125, "out": 0.0000025, "cache_read": 2e-7, "max_out": 1000000, "above": 200000, "tier": { "in": 0.0000025, "out": 0.000005, "cache_read": 4e-7 } }, "grok-4.20-beta-latest-non-reasoning": { "in": 0.00000125, "out": 0.0000025, "cache_read": 2e-7, "max_out": 1000000, "above": 200000, "tier": { "in": 0.0000025, "out": 0.000005, "cache_read": 4e-7 } }, "grok-4.20-beta-latest-reasoning": { "in": 0.00000125, "out": 0.0000025, "cache_read": 2e-7, "max_out": 1000000, "above": 200000, "tier": { "in": 0.0000025, "out": 0.000005, "cache_read": 4e-7 } }, "grok-4.20-beta-non-reasoning": { "in": 0.00000125, "out": 0.0000025, "cache_read": 2e-7, "max_out": 1000000, "above": 200000, "tier": { "in": 0.0000025, "out": 0.000005, "cache_read": 4e-7 } }, "grok-4.20-beta-reasoning": { "in": 0.00000125, "out": 0.0000025, "cache_read": 2e-7, "max_out": 1000000, "above": 200000, "tier": { "in": 0.0000025, "out": 0.000005, "cache_read": 4e-7 } }, "grok-4.20-experimental-beta-0304": { "in": 0.00000125, "out": 0.0000025, "cache_read": 2e-7, "max_out": 1000000, "above": 200000, "tier": { "in": 0.0000025, "out": 0.000005, "cache_read": 4e-7 } }, "grok-4.20-experimental-beta-0304-non-reasoning": { "in": 0.00000125, "out": 0.0000025, "cache_read": 2e-7, "max_out": 1000000, "above": 200000, "tier": { "in": 0.0000025, "out": 0.000005, "cache_read": 4e-7 } }, "grok-4.20-experimental-beta-0304-reasoning": { "in": 0.00000125, "out": 0.0000025, "cache_read": 2e-7, "max_out": 1000000, "above": 200000, "tier": { "in": 0.0000025, "out": 0.000005, "cache_read": 4e-7 } }, "grok-4.20-experimental-beta-latest": { "in": 0.00000125, "out": 0.0000025, "cache_read": 2e-7, "max_out": 1000000, "above": 200000, "tier": { "in": 0.0000025, "out": 0.000005, "cache_read": 4e-7 } }, "grok-4.20-experimental-beta-non-reasoning-latest": { "in": 0.00000125, "out": 0.0000025, "cache_read": 2e-7, "max_out": 1000000, "above": 200000, "tier": { "in": 0.0000025, "out": 0.000005, "cache_read": 4e-7 } }, "grok-4.20-experimental-beta-reasoning-latest": { "in": 0.00000125, "out": 0.0000025, "cache_read": 2e-7, "max_out": 1000000, "above": 200000, "tier": { "in": 0.0000025, "out": 0.000005, "cache_read": 4e-7 } }, "grok-4.20-multi-agent": { "in": 0.00000125, "out": 0.0000025, "cache_read": 2e-7, "max_out": 1000000, "above": 200000, "tier": { "in": 0.0000025, "out": 0.000005, "cache_read": 4e-7 } }, "grok-4.20-multi-agent-0309": { "in": 0.00000125, "out": 0.0000025, "cache_read": 2e-7, "max_out": 1000000, "above": 200000, "tier": { "in": 0.0000025, "out": 0.000005, "cache_read": 4e-7 } }, "grok-4.20-multi-agent-beta-0309": { "in": 0.00000125, "out": 0.0000025, "cache_read": 2e-7, "max_out": 1000000, "above": 200000, "tier": { "in": 0.0000025, "out": 0.000005, "cache_read": 4e-7 } }, "grok-4.20-multi-agent-beta-latest": { "in": 0.00000125, "out": 0.0000025, "cache_read": 2e-7, "max_out": 1000000, "above": 200000, "tier": { "in": 0.0000025, "out": 0.000005, "cache_read": 4e-7 } }, "grok-4.20-multi-agent-experimental-beta-0304": { "in": 0.00000125, "out": 0.0000025, "cache_read": 2e-7, "max_out": 1000000, "above": 200000, "tier": { "in": 0.0000025, "out": 0.000005, "cache_read": 4e-7 } }, "grok-4.20-multi-agent-experimental-beta-latest": { "in": 0.00000125, "out": 0.0000025, "cache_read": 2e-7, "max_out": 1000000, "above": 200000, "tier": { "in": 0.0000025, "out": 0.000005, "cache_read": 4e-7 } }, "grok-4.20-multi-agent-latest": { "in": 0.00000125, "out": 0.0000025, "cache_read": 2e-7, "max_out": 1000000, "above": 200000, "tier": { "in": 0.0000025, "out": 0.000005, "cache_read": 4e-7 } }, "grok-4.20-non-reasoning": { "in": 0.00000125, "out": 0.0000025, "cache_read": 2e-7, "max_out": 1000000, "above": 200000, "tier": { "in": 0.0000025, "out": 0.000005, "cache_read": 4e-7 } }, "grok-4.20-non-reasoning-gv2": { "in": 0.00000125, "out": 0.0000025, "cache_read": 2e-7, "max_out": 1000000, "above": 200000, "tier": { "in": 0.0000025, "out": 0.000005, "cache_read": 4e-7 } }, "grok-4.20-non-reasoning-latest": { "in": 0.00000125, "out": 0.0000025, "cache_read": 2e-7, "max_out": 1000000, "above": 200000, "tier": { "in": 0.0000025, "out": 0.000005, "cache_read": 4e-7 } }, "grok-4.20-reasoning": { "in": 0.00000125, "out": 0.0000025, "cache_read": 2e-7, "max_out": 1000000, "above": 200000, "tier": { "in": 0.0000025, "out": 0.000005, "cache_read": 4e-7 } }, "grok-4.20-reasoning-gv2": { "in": 0.00000125, "out": 0.0000025, "cache_read": 2e-7, "max_out": 1000000, "above": 200000, "tier": { "in": 0.0000025, "out": 0.000005, "cache_read": 4e-7 } }, "grok-4.20-reasoning-latest": { "in": 0.00000125, "out": 0.0000025, "cache_read": 2e-7, "max_out": 1000000, "above": 200000, "tier": { "in": 0.0000025, "out": 0.000005, "cache_read": 4e-7 } }, "grok-4.3": { "in": 0.00000125, "out": 0.0000025, "cache_read": 2e-7, "max_out": 1000000, "above": 200000, "tier": { "in": 0.0000025, "out": 0.000005, "cache_read": 4e-7 } }, "grok-4.3-latest": { "in": 0.00000125, "out": 0.0000025, "cache_read": 2e-7, "max_out": 1000000, "above": 200000, "tier": { "in": 0.0000025, "out": 0.000005, "cache_read": 4e-7 } }, "grok-4.5": { "in": 0.000002, "out": 0.000006, "cache_read": 3e-7, "max_out": 500000, "above": 200000, "tier": { "in": 0.000004, "out": 0.000012, "cache_read": 6e-7 } }, "grok-4.5-latest": { "in": 0.000002, "out": 0.000006, "cache_read": 3e-7, "max_out": 500000, "above": 200000, "tier": { "in": 0.000004, "out": 0.000012, "cache_read": 6e-7 } }, "grok-4.6": { "in": 0.000002, "out": 0.000006, "cache_read": 5e-7, "max_out": 500000, "above": 200000, "tier": { "in": 0.000004, "out": 0.000012, "cache_read": 0.000001 } }, "grok-4.7": { "in": 0.000002, "out": 0.000006, "cache_read": 5e-7, "max_out": 500000, "above": 200000, "tier": { "in": 0.000004, "out": 0.000012, "cache_read": 0.000001 } }, "grok-build-0.1": { "in": 0.000001, "out": 0.000002, "cache_read": 2e-7, "max_out": 256000, "above": 200000, "tier": { "in": 0.000002, "out": 0.000004, "cache_read": 4e-7 } }, "grok-build-latest": { "in": 0.000002, "out": 0.000006, "cache_read": 3e-7, "max_out": 500000, "above": 200000, "tier": { "in": 0.000004, "out": 0.000012, "cache_read": 6e-7 } }, "grok-code-fast": { "in": 0.000001, "out": 0.000002, "cache_read": 2e-7, "max_out": 256000, "above": 200000, "tier": { "in": 0.000002, "out": 0.000004, "cache_read": 4e-7 } }, "grok-code-fast-1": { "in": 0.000001, "out": 0.000002, "cache_read": 2e-7, "max_out": 256000, "above": 200000, "tier": { "in": 0.000002, "out": 0.000004, "cache_read": 4e-7 } }, "grok-code-fast-1-0825": { "in": 0.000001, "out": 0.000002, "cache_read": 2e-7, "max_out": 256000, "above": 200000, "tier": { "in": 0.000002, "out": 0.000004, "cache_read": 4e-7 } }, "labs-leanstral-1-5": { "in": 0, "out": 0, "max_out": 131072 }, "labs-leanstral-1-5-1": { "in": 0, "out": 0, "max_out": 131072 }, "learnlm-1.5-pro-experimental": { "in": 0, "out": 0, "max_out": 8192 }, "llama-guard-3-8b": { "in": 2e-7, "out": 2e-7 }, "llama-prompt-guard-2-22m": { "in": 3e-8, "out": 3e-8, "max_out": 512 }, "llama-prompt-guard-2-86m": { "in": 4e-8, "out": 4e-8, "max_out": 512 }, "lyria-3-clip-preview": { "in": 0, "out": 0, "max_out": 8192 }, "lyria-3-pro-preview": { "in": 0, "out": 0, "max_out": 8192 }, "lyria-3.5": { "in": 0, "out": 0, "max_out": 65536 }, "lyria-3.5-clip-preview": { "in": 0, "out": 0, "max_out": 8192 }, "lyria-3.5-pro-preview": { "in": 0, "out": 0, "max_out": 8192 }, "lyria-realtime-exp": { "in": 0, "out": 0, "max_out": 65536 }, "magistral-medium-latest": { "in": 0.0000015, "out": 0.0000075, "cache_read": 1.5e-7, "max_out": 262144 }, "magistral-small-latest": { "in": 1.5e-7, "out": 6e-7, "cache_read": 1.5e-8, "max_out": 262144 }, "ministral-14b-2512": { "in": 2e-7, "out": 2e-7, "cache_read": 2e-8, "max_out": 262144 }, "ministral-14b-latest": { "in": 2e-7, "out": 2e-7, "cache_read": 2e-8, "max_out": 262144 }, "ministral-3-14b-2512": { "in": 2e-7, "out": 2e-7, "cache_read": 2e-8, "max_out": 262144 }, "ministral-3-3b-2512": { "in": 1e-7, "out": 1e-7, "cache_read": 1e-8, "max_out": 131072 }, "ministral-3-8b-2512": { "in": 1.5e-7, "out": 1.5e-7, "cache_read": 1.5e-8, "max_out": 262144 }, "ministral-3b-2512": { "in": 1e-7, "out": 1e-7, "cache_read": 1e-8, "max_out": 131072 }, "ministral-3b-latest": { "in": 1e-7, "out": 1e-7, "cache_read": 1e-8, "max_out": 131072 }, "ministral-8b-2512": { "in": 1.5e-7, "out": 1.5e-7, "cache_read": 1.5e-8, "max_out": 262144 }, "ministral-8b-latest": { "in": 1.5e-7, "out": 1.5e-7, "cache_read": 1.5e-8, "max_out": 262144 }, "mistral-code-agent-latest": { "in": 4e-7, "out": 0.000002, "cache_read": 4e-8, "max_out": 256000 }, "mistral-code-fim-latest": { "in": 3e-7, "out": 9e-7, "cache_read": 3e-8, "max_out": 128000 }, "mistral-code-latest": { "in": 3e-7, "out": 9e-7, "cache_read": 3e-8, "max_out": 128000 }, "mistral-large-2512": { "in": 5e-7, "out": 0.0000015, "cache_read": 5e-8, "max_out": 262144 }, "mistral-large-3": { "in": 5e-7, "out": 0.0000015, "cache_read": 5e-8, "max_out": 262144 }, "mistral-large-4": { "in": 6.8e-7, "out": 0.00000209, "cache_read": 6.8e-8 }, "mistral-large-4-0": { "in": 6.8e-7, "out": 0.00000209, "cache_read": 6.8e-8 }, "mistral-large-latest": { "in": 5e-7, "out": 0.0000015, "cache_read": 5e-8, "max_out": 262144 }, "mistral-medium": { "in": 0.0000015, "out": 0.0000075, "cache_read": 1.5e-7, "max_out": 262144 }, "mistral-medium-2604": { "in": 0.0000015, "out": 0.0000075, "cache_read": 1.5e-7, "max_out": 262144 }, "mistral-medium-3": { "in": 0.0000015, "out": 0.0000075, "cache_read": 1.5e-7, "max_out": 262144 }, "mistral-medium-3-5": { "in": 0.0000015, "out": 0.0000075, "cache_read": 1.5e-7, "max_out": 262144 }, "mistral-medium-3.5": { "in": 0.0000015, "out": 0.0000075, "cache_read": 1.5e-7, "max_out": 262144 }, "mistral-medium-latest": { "in": 0.0000015, "out": 0.0000075, "cache_read": 1.5e-7, "max_out": 262144 }, "mistral-small": { "in": 1e-7, "out": 3e-7, "cache_read": 1e-8, "max_out": 8191 }, "mistral-small-2603": { "in": 1.5e-7, "out": 6e-7, "cache_read": 1.5e-8, "max_out": 262144 }, "mistral-small-latest": { "in": 1.5e-7, "out": 6e-7, "cache_read": 1.5e-8, "max_out": 262144 }, "mistral-tiny": { "in": 2.5e-7, "out": 2.5e-7, "cache_read": 2.5e-8, "max_out": 8191 }, "mistral-vibe-cli-fast": { "in": 1.5e-7, "out": 6e-7, "cache_read": 1.5e-8, "max_out": 262144 }, "mistral-vibe-cli-latest": { "in": 0.0000015, "out": 0.0000075, "cache_read": 1.5e-7, "max_out": 262144 }, "mistral-vibe-cli-with-tools": { "in": 0.0000015, "out": 0.0000075, "cache_read": 1.5e-7, "max_out": 262144 }, "o1": { "in": 0.000015, "out": 0.00006, "cache_read": 0.0000075, "max_out": 100000 }, "o1-2024-12-17": { "in": 0.000015, "out": 0.00006, "cache_read": 0.0000075, "max_out": 100000 }, "o1-pro": { "in": 0.00015, "out": 0.0006, "max_out": 100000 }, "o1-pro-2025-03-19": { "in": 0.00015, "out": 0.0006, "max_out": 100000 }, "o3": { "in": 0.000002, "out": 0.000008, "cache_read": 5e-7, "max_out": 100000 }, "o3-2025-04-16": { "in": 0.000002, "out": 0.000008, "cache_read": 5e-7, "max_out": 100000 }, "o3-deep-research": { "in": 0.00001, "out": 0.00004, "cache_read": 0.0000025, "max_out": 100000 }, "o3-deep-research-2025-06-26": { "in": 0.00001, "out": 0.00004, "cache_read": 0.0000025, "max_out": 100000 }, "o3-mini": { "in": 0.0000011, "out": 0.0000044, "cache_read": 5.5e-7, "max_out": 100000 }, "o3-mini-2025-01-31": { "in": 0.0000011, "out": 0.0000044, "cache_read": 5.5e-7, "max_out": 100000 }, "o3-pro": { "in": 0.00002, "out": 0.00008, "max_out": 100000 }, "o3-pro-2025-06-10": { "in": 0.00002, "out": 0.00008, "max_out": 100000 }, "o4-mini": { "in": 0.0000011, "out": 0.0000044, "cache_read": 2.75e-7, "max_out": 100000 }, "o4-mini-2025-04-16": { "in": 0.0000011, "out": 0.0000044, "cache_read": 2.75e-7, "max_out": 100000 }, "o4-mini-deep-research": { "in": 0.000002, "out": 0.000008, "cache_read": 5e-7, "max_out": 100000 }, "o4-mini-deep-research-2025-06-26": { "in": 0.000002, "out": 0.000008, "cache_read": 5e-7, "max_out": 100000 }, "open-mistral-nemo": { "in": 3e-7, "out": 3e-7, "cache_read": 3e-8, "max_out": 128000 }, "pixtral-large-latest": { "in": 0.000002, "out": 0.000006, "cache_read": 2e-7, "max_out": 128000 }, "qwen3.8-27b": { "in": 8e-7, "out": 0.000004, "max_out": 16384 }, "voxtral-small-2507": { "in": 1e-7, "out": 4e-7, "cache_read": 1e-8, "max_out": 32768 }, "voxtral-small-latest": { "in": 1e-7, "out": 4e-7, "cache_read": 1e-8, "max_out": 32768 }, "zai-glm-5": { "in": 0.0000014, "out": 0.0000044, "cache_read": 1.4e-7, "max_out": 131072 }, "zai-glm-5-2": { "in": 0.0000014, "out": 0.0000044, "cache_read": 1.4e-7, "max_out": 131072 }, "zai-glm-5-3": { "in": 0.0000014, "out": 0.0000044, "cache_read": 1.4e-7, "max_out": 131072 }, "zai-glm-latest": { "in": 0.0000014, "out": 0.0000044, "cache_read": 1.4e-7, "max_out": 131072 } };
package/dist/tools.d.ts CHANGED
@@ -1,15 +1,49 @@
1
1
  /** `{"query":"cats","limit":5}`: the arguments as compact JSON (one object argument on its own), capped. */
2
2
  export declare function argsSummary(args: unknown[]): string;
3
+ interface ToolFields<A extends unknown[]> {
4
+ name?: string;
5
+ /** Reject with a TimeoutError if the call takes longer. JS can't stop the tool's code; pass run.signal to its fetches. */
6
+ timeoutS?: number;
7
+ /** Ask Fences before each call; it runs only if allowed. */
8
+ guard?: boolean;
9
+ /** What a call costs the run, reserved before and charged after: a number, or a function of the tool's arguments. */
10
+ costUsd?: number | ((...args: A) => number);
11
+ /** A person approves each call from the dashboard first. */
12
+ approve?: boolean;
13
+ }
14
+ /** Options that make a tool async: a guard (guard, costUsd or approve) or a timeout. */
15
+ export type GuardedToolOptions<A extends unknown[]> = ToolFields<A> & ({
16
+ guard: true;
17
+ } | {
18
+ costUsd: number | ((...args: A) => number);
19
+ } | {
20
+ approve: true;
21
+ } | {
22
+ timeoutS: number;
23
+ });
3
24
  /**
4
25
  * Record every call of an agent tool on the active run:
5
26
  *
6
27
  * const webSearch = tool(async (query: string) => { ... });
7
- * const fetchPage = tool(fetchPageImpl, { name: "fetch" });
28
+ * const fetchPage = tool(fetchPageImpl, { name: "fetch", timeoutS: 30 });
8
29
  *
9
30
  * Records the tool's name, a short summary of its arguments, whether it succeeded (and the
10
31
  * error if not) and how long it took. Errors still reach the caller; sync tools stay sync.
11
32
  * Outside a governed run it just calls the function.
33
+ *
34
+ * Guarded tools ask Fences before they run, for actions with consequences:
35
+ *
36
+ * const issueRefund = tool(refund, { costUsd: (orderId, amount) => amount, approve: true });
37
+ *
38
+ * guard: true runs the tool only if the run isn't stopped, paused, over a limit or
39
+ * quarantined; otherwise it throws ToolBlocked and the tool's code never runs. It's blocked
40
+ * too if Fences can't be reached (guards fail closed). costUsd is reserved before the call,
41
+ * so parallel calls can't overspend together, and charged after (unless the tool threw; a
42
+ * timeout is charged, as it may have happened). approve: true asks a person first.
43
+ * Setting costUsd or approve guards the tool. A guarded or timed tool returns a promise.
12
44
  */
45
+ export declare function tool<A extends unknown[], R>(fn: (...args: A) => R, options: GuardedToolOptions<A>): (...args: A) => Promise<Awaited<R>>;
13
46
  export declare function tool<A extends unknown[], R>(fn: (...args: A) => R, options?: {
14
47
  name?: string;
15
48
  }): (...args: A) => R;
49
+ export {};
package/dist/tools.js CHANGED
@@ -1,4 +1,8 @@
1
- import { getActiveRun } from "./core.js";
1
+ import { randomUUID } from "node:crypto";
2
+ import * as control from "./control.js";
3
+ import { _client, getActiveRun, localBreach } from "./core.js";
4
+ import { ToolBlocked } from "./errors.js";
5
+ import * as events from "./events.js";
2
6
  import { MAX_ARGS, recordToolCall } from "./spans.js";
3
7
  /** `{"query":"cats","limit":5}`: the arguments as compact JSON (one object argument on its own), capped. */
4
8
  export function argsSummary(args) {
@@ -11,18 +15,117 @@ export function argsSummary(args) {
11
15
  }
12
16
  return text.length <= MAX_ARGS ? text : text.slice(0, MAX_ARGS - 1) + "…";
13
17
  }
14
- /**
15
- * Record every call of an agent tool on the active run:
16
- *
17
- * const webSearch = tool(async (query: string) => { ... });
18
- * const fetchPage = tool(fetchPageImpl, { name: "fetch" });
19
- *
20
- * Records the tool's name, a short summary of its arguments, whether it succeeded (and the
21
- * error if not) and how long it took. Errors still reach the caller; sync tools stay sync.
22
- * Outside a governed run it just calls the function.
23
- */
18
+ // ── Guards ────────────────────────────────────────────────────────────────────
19
+ function block(run, name, reason, breach = null) {
20
+ if (run.control.stopped)
21
+ run.control.stopSeen = true; // the agent learns of the stop here, so don't cancel it
22
+ throw new ToolBlocked(name, reason, breach);
23
+ }
24
+ const fits = (run, amount) => run.budgetUsd === null || Math.round((run.costUsd + run.reservedUsd + amount) * 1e9) / 1e9 <= run.budgetUsd;
25
+ async function approve(run, name, summary, amount) {
26
+ const answer = await control.requestApproval(`${name}(${summary})`, { amountUsd: amount || undefined });
27
+ if (!answer.granted)
28
+ block(run, name, answer.note ? `not approved: ${answer.note}` : "not approved");
29
+ return answer.approvalId;
30
+ }
31
+ /** Check a guarded call and reserve its amount; returns the guard id, or throws ToolBlocked. */
32
+ async function open(run, name, summary, amount, needsApproval) {
33
+ if (run.control.quarantined)
34
+ block(run, name, "the agent is quarantined in Fences"); // now, not when the quarantine's pause ends
35
+ if (run.control.paused)
36
+ await control.waitIfPaused(run);
37
+ if (run.control.pauseExpired)
38
+ block(run, name, "the run was paused from the Fences dashboard and not resumed in time", "paused");
39
+ let approvalId = needsApproval ? await approve(run, name, summary, amount) : null;
40
+ if (run.control.stopped)
41
+ block(run, name, "the run was stopped from the Fences dashboard", "stopped_by_user");
42
+ const client = _client();
43
+ if (!client) { // local mode: the same rules, applied here
44
+ const breach = run.lastBreach ?? localBreach(run);
45
+ if (breach)
46
+ block(run, name, `the run is over a limit (${breach})`, breach);
47
+ if (!fits(run, amount))
48
+ block(run, name, `$${amount.toFixed(2)} more would take the run past its $${run.budgetUsd.toFixed(2)} budget`, "budget_exceeded");
49
+ run.reservedUsd += amount;
50
+ return randomUUID();
51
+ }
52
+ // Check against what this process already holds before asking the server, which can't see it; reserve until it answers
53
+ if (!fits(run, amount))
54
+ block(run, name, `$${amount.toFixed(2)} more could take the run past its $${run.budgetUsd.toFixed(2)} budget`, "budget_exceeded");
55
+ run.reservedUsd += amount;
56
+ try {
57
+ const sent = events.redact({ type: "guard", tool: name, arguments: summary }) ?? {}; // the check goes ahead without them
58
+ let resp = {}; // eslint-disable-line @typescript-eslint/no-explicit-any
59
+ for (let attempt = 0; attempt < 2; attempt++) { // once more after an approval the server asked for
60
+ const guardId = randomUUID();
61
+ resp = await client.guard(run.runId, { guard_id: guardId, phase: "check", tool: name, arguments: sent.arguments ?? null,
62
+ amount_usd: amount, approval_id: approvalId });
63
+ control.apply(run, resp.commands);
64
+ if (resp.allow === true)
65
+ return guardId;
66
+ if (!(resp.needs_approval && approvalId === null))
67
+ break;
68
+ approvalId = await approve(run, name, summary, amount);
69
+ }
70
+ if (resp.unsupported) {
71
+ control.unsupported("guard", "guarded tools", "they are blocked");
72
+ block(run, name, "this Fences server doesn't support guarded tools");
73
+ }
74
+ if (resp.network_error || !("allow" in resp))
75
+ block(run, name, "couldn't reach Fences to check this call"); // no answer: fail closed
76
+ block(run, name, resp.reason ?? "not allowed", resp.breach ?? null);
77
+ }
78
+ catch (e) {
79
+ run.reservedUsd -= amount;
80
+ throw e;
81
+ }
82
+ }
83
+ /** Release the reservation and charge the run: in full unless the tool threw (a timeout may have done it). */
84
+ async function settle(run, guardId, amount, error, failed) {
85
+ const outcome = !failed ? "ok" : error?.name === "TimeoutError" ? "unknown" : "error";
86
+ const charged = outcome === "error" ? 0 : amount;
87
+ run.reservedUsd -= amount;
88
+ run.costUsd += charged;
89
+ await _client()?.guard(run.runId, { guard_id: guardId, phase: "settle", outcome, cost_usd: charged });
90
+ }
91
+ function timed(p, timeoutS, name) {
92
+ if (timeoutS === undefined)
93
+ return p;
94
+ let timer;
95
+ const late = new Promise((_, reject) => {
96
+ timer = setTimeout(() => reject(new DOMException(`${name} took longer than ${timeoutS}s`, "TimeoutError")), timeoutS * 1000);
97
+ });
98
+ return Promise.race([p, late]).finally(() => clearTimeout(timer));
99
+ }
24
100
  export function tool(fn, options = {}) {
25
101
  const name = options.name ?? (fn.name || "tool");
102
+ const guarded = !!options.guard || options.costUsd !== undefined || !!options.approve;
103
+ if (guarded || options.timeoutS !== undefined) {
104
+ return async (...args) => {
105
+ const run = getActiveRun();
106
+ if (!run)
107
+ return timed(Promise.resolve().then(() => fn(...args)), options.timeoutS, name);
108
+ const summary = argsSummary(args), started = performance.now();
109
+ let gate = null;
110
+ try {
111
+ if (guarded) {
112
+ const c = options.costUsd, amount = Number((typeof c === "function" ? c(...args) : c) ?? 0);
113
+ gate = [await open(run, name, summary, amount, !!options.approve), amount];
114
+ }
115
+ const out = await timed(Promise.resolve().then(() => fn(...args)), options.timeoutS, name);
116
+ recordToolCall(name, summary, started);
117
+ if (gate)
118
+ await settle(run, ...gate, undefined, false);
119
+ return out;
120
+ }
121
+ catch (e) {
122
+ recordToolCall(name, summary, started, e);
123
+ if (gate)
124
+ await settle(run, ...gate, e, true);
125
+ throw e;
126
+ }
127
+ };
128
+ }
26
129
  return (...args) => {
27
130
  if (!getActiveRun())
28
131
  return fn(...args);
package/dist/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  /** The one place the version lives; sent to Fences on every request. Keep in step with package.json. */
2
- export declare const VERSION = "0.3.0";
2
+ export declare const VERSION = "0.5.0";
package/dist/version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  /** The one place the version lives; sent to Fences on every request. Keep in step with package.json. */
2
- export const VERSION = "0.3.0";
2
+ export const VERSION = "0.5.0";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentfences",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "On-call for AI agents: limits your agents can't raise, cost tracking, a decision trail, and a stop button",
5
5
  "license": "MIT",
6
6
  "author": "Bhavik Sardar",