agentfences 0.4.0 → 0.6.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
@@ -61,11 +61,11 @@ The server is authoritative for limits and sees spend across processes. If it ca
61
61
 
62
62
  ## Live control (from 0.4.0)
63
63
 
64
- From version 0.4.0, which isn't on npm yet: in cloud mode a heartbeat (every 15 seconds, `init({ heartbeatS })`) lets whoever is on call act on a running agent from the dashboard:
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
65
 
66
- - **Stop:** the next `checkpoint()` returns `breachType: "stopped_by_user"`, so the agent wraps up gracefully.
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
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.
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
69
 
70
70
  An agent can ask a person before doing something risky:
71
71
 
@@ -78,6 +78,35 @@ if (!approval.granted) return `I didn't issue the refund: ${approval.note}`;
78
78
 
79
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
80
 
81
+ **Waiting at a limit** (from 0.6.0): with `init({ onBreach: "hold" })` (or `governed(fn, { onBreach: "hold" })`), a limit the server says is crossed makes `checkpoint()` wait instead of returning the breach, so whoever is on call can raise it from the dashboard and the agent carries on. A guarded tool denied for budget waits the same way. A stop ends the wait; after `pauseTimeoutS` the agent gets the breach it was held at. Cloud only. Don't hold an agent a person is waiting on.
82
+
83
+ **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:
84
+
85
+ ```ts
86
+ init({ apiKey: "fc_...", onQuarantine: async (event) => vault.revoke(`agent/${event.agentName}`), onStop: (event) => { ... } });
87
+ ```
88
+
89
+ `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".
90
+
91
+ ## Guarded tools (from 0.5.0)
92
+
93
+ For actions with consequences (refunds, payments, writes to production), guard the tool, and it asks Fences **before** it runs:
94
+
95
+ ```ts
96
+ import { tool, ToolBlocked } from "agentfences";
97
+
98
+ const issueRefund = tool(refund, { costUsd: (orderId: string, amount: number) => amount, approve: true });
99
+ const deploy = tool(deployImpl, { guard: true, timeoutS: 30 });
100
+ ```
101
+
102
+ - 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.
103
+ - `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.
104
+ - `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.
105
+ - **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.
106
+ - `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`.
107
+
108
+ A guarded or timed tool always returns a promise. What each part guarantees: [ENFORCEMENT.md](../ENFORCEMENT.md).
109
+
81
110
  ## Not yet in the TypeScript SDK
82
111
 
83
- The Python SDK also records OpenAI/Anthropic calls automatically (`instrument`) and integrates with LangGraph and the OpenAI Agents SDK. Everything here follows the same server protocol ([PROTOCOL.md](../PROTOCOL.md)).
112
+ 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,8 +9,10 @@ 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; the reply may carry commands for them. */
13
- heartbeat(runs: 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>;
14
16
  requestApproval(runId: string, body: Record<string, unknown>): Promise<Reply>;
15
17
  /**
16
18
  * optional: an endpoint older servers may not have. A missing route returns
package/dist/client.js CHANGED
@@ -17,8 +17,12 @@ 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; the reply may carry commands for them. */
21
- heartbeat(runs) { return this.post("/api/heartbeat", { runs }, 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); }
22
26
  requestApproval(runId, body) { return this.post(`/api/runs/${runId}/approvals`, body, true); }
23
27
  /**
24
28
  * optional: an endpoint older servers may not have. A missing route returns
package/dist/control.d.ts CHANGED
@@ -8,23 +8,75 @@ export interface Approval {
8
8
  note?: string | null;
9
9
  /** The amount approved, if one was asked for. */
10
10
  amountUsd?: number | null;
11
+ approvalId?: string | null;
11
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;
12
25
  /** Live-control state a run carries. */
13
26
  export interface Control {
14
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;
15
34
  paused: boolean;
16
35
  pauseExpired: boolean;
36
+ quarantined: boolean;
37
+ /** While waiting at a limit: "breach" or "budget". */
38
+ held: "breach" | "budget" | null;
17
39
  /** approval id -> the answer, or null while waiting */
18
40
  approvals: Map<string, Approval | null>;
19
41
  awaiting: number;
42
+ /** command ids already acknowledged */
43
+ acked: Set<string>;
20
44
  }
21
45
  export declare const newControl: () => Control;
22
46
  export declare let pauseTimeoutMs: number;
23
- export declare function configure(c: FencesClient | null, heartbeatS: number, pauseTimeoutS: number): void;
47
+ export declare const supported: {
48
+ heartbeat: boolean;
49
+ approvals: boolean;
50
+ guard: boolean;
51
+ };
52
+ export interface ControlOptions {
53
+ heartbeatS: number;
54
+ pauseTimeoutS: number;
55
+ stopGraceS: number | null;
56
+ onStop?: Hook | null;
57
+ onQuarantine?: Hook | null;
58
+ }
59
+ export declare function configure(c: FencesClient | null, o: ControlOptions): void;
24
60
  export declare function register(run: Run): void;
25
61
  export declare function unregister(run: Run): void;
26
- /** Apply the commands addressed to this run (from a heartbeat or a checkpoint reply). Each is safe to apply twice. */
62
+ export declare function unsupported(kind: keyof typeof supported, what: string, consequence: string): void;
63
+ /** Apply the commands addressed to this run (from a heartbeat or a checkpoint reply), and acknowledge them. Each is safe to apply twice. */
27
64
  export declare function apply(run: Run, commands: Reply[] | undefined): void;
65
+ /**
66
+ * Wait for running hooks, then send the acknowledgements still waiting. flush() calls it, and
67
+ * it runs before the process exits, so a script that ends right after a stop still finishes
68
+ * its containment, and says so.
69
+ */
70
+ export declare function drain(timeoutMs?: number): Promise<void>;
71
+ /** Breaches a person can lift by raising a limit, so onBreach "hold" waits at them. */
72
+ export declare const HOLDABLE: Set<string>;
73
+ export declare const BUDGET_BREACHES: Set<string>;
74
+ /** Whether this run waits at a limit: onBreach "hold", in cloud mode (someone can raise it), and not stopped. */
75
+ export declare const holds: (run: Run) => boolean;
76
+ /** Pause the run at a limit: "breach" (a checkpoint breached) or "budget" (a call refused for budget). */
77
+ export declare function startHold(run: Run, kind: "breach" | "budget"): void;
78
+ /** After waiting: "released" (an operator raised the limit), "stopped", or "timeout". */
79
+ export declare function endHold(run: Run): "released" | "stopped" | "timeout";
28
80
  /** Hold the agent at its checkpoint while its run is paused, up to pauseTimeoutS. */
29
81
  export declare function waitIfPaused(run: Run): Promise<void>;
30
82
  /**
package/dist/control.js CHANGED
@@ -1,24 +1,39 @@
1
1
  /**
2
2
  * Live control from the Fences dashboard: a heartbeat that reports live runs and brings back
3
- * commands (stop, pause, resume, limits, approval answers), pausing at checkpoints, and
4
- * approval requests. Cloud mode only; a server without these endpoints gets one warning and
5
- * runs carry on as before. Same behaviour as the Python SDK (PROTOCOL.md, "Live control").
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").
6
7
  */
7
8
  import { randomUUID } from "node:crypto";
8
- const FAST_MS = 2000; // heartbeat interval while a run is paused or waiting for an approval
9
- export const newControl = () => ({ stopped: false, paused: false, pauseExpired: false, approvals: new Map(), awaiting: 0 });
9
+ /** Heartbeat statuses whose acks go again with the next heartbeat: rate limited, or a server error. */
10
+ const retryable = (status) => status === undefined || status === 429 || status >= 500;
11
+ const FAST_MS = 2000; // heartbeat interval while a run is paused, waiting for an approval, or stopping
12
+ const HOOK_TIMEOUT_MS = 30_000; // a hook still running after this is reported as timed out
13
+ export const newControl = () => ({
14
+ stopped: false, stoppedAt: 0, stopSeen: false, cancelled: false, onCancel: null, paused: false, pauseExpired: false,
15
+ quarantined: false, held: null, approvals: new Map(), awaiting: 0, acked: new Set(),
16
+ });
10
17
  const live = new Map(); // run id -> runs in progress
11
18
  let client = null;
12
19
  let intervalMs = 15_000;
13
20
  export let pauseTimeoutMs = 3_600_000;
14
- const supported = { heartbeat: true, approvals: true };
21
+ let stopGraceMs = 30_000;
22
+ const hooks = { stop: null, quarantine: null };
23
+ export const supported = { heartbeat: true, approvals: true, guard: true };
15
24
  let timer = null;
16
25
  let beating = false;
17
- export function configure(c, heartbeatS, pauseTimeoutS) {
26
+ let acks = []; // acknowledgements waiting for the next heartbeat
27
+ const hooked = new Set(); // quarantine ids whose hook has run in this process
28
+ const runningHooks = new Set();
29
+ export function configure(c, o) {
18
30
  client = c;
19
- intervalMs = heartbeatS * 1000;
20
- pauseTimeoutMs = pauseTimeoutS * 1000;
21
- supported.heartbeat = supported.approvals = true;
31
+ intervalMs = o.heartbeatS * 1000;
32
+ pauseTimeoutMs = o.pauseTimeoutS * 1000;
33
+ stopGraceMs = o.stopGraceS === null ? null : o.stopGraceS * 1000;
34
+ hooks.stop = o.onStop ?? null;
35
+ hooks.quarantine = o.onQuarantine ?? null;
36
+ supported.heartbeat = supported.approvals = supported.guard = true;
22
37
  schedule(0); // a new client or interval takes effect now, not after the old wait
23
38
  }
24
39
  export function register(run) {
@@ -29,7 +44,7 @@ export function register(run) {
29
44
  export function unregister(run) {
30
45
  live.delete(run.runId);
31
46
  }
32
- function unsupported(kind, what, consequence) {
47
+ export function unsupported(kind, what, consequence) {
33
48
  if (!supported[kind])
34
49
  return;
35
50
  supported[kind] = false;
@@ -37,22 +52,32 @@ function unsupported(kind, what, consequence) {
37
52
  }
38
53
  // ── Commands ──────────────────────────────────────────────────────────────────
39
54
  const LIMITS = { budget_usd: "budgetUsd", max_iterations: "maxIterations", max_duration_ms: "maxDurationMs", max_tokens: "maxTokens" };
40
- /** Apply the commands addressed to this run (from a heartbeat or a checkpoint reply). Each is safe to apply twice. */
55
+ /** Apply the commands addressed to this run (from a heartbeat or a checkpoint reply), and acknowledge them. Each is safe to apply twice. */
41
56
  export function apply(run, commands) {
42
- for (const c of commands ?? []) {
57
+ // Limits first: a resume in the same reply may release a call held for budget, which re-checks against them at once
58
+ for (const c of [...(commands ?? [])].sort((a, b) => Number(a.type !== "limits") - Number(b.type !== "limits"))) {
43
59
  if (c.run_id !== undefined && c.run_id !== run.runId)
44
60
  continue;
45
61
  const ctl = run.control;
46
62
  if (c.type === "stop") {
47
- ctl.stopped = true;
48
- ctl.paused = false;
63
+ if (!ctl.stopped) {
64
+ ctl.stopped = true;
65
+ ctl.stoppedAt = Date.now();
66
+ schedule(0); // watch for the agent to see it, or cancel it
67
+ hook("stop", run, c); // once per run
68
+ }
69
+ ctl.paused = ctl.quarantined = false;
49
70
  }
50
- else if (c.type === "pause") {
71
+ else if (c.type === "pause" || c.type === "quarantine") { // a quarantined agent's runs hold at their next checkpoint
51
72
  ctl.paused = true;
52
73
  schedule(0);
74
+ if (c.type === "quarantine") {
75
+ ctl.quarantined = true; // guarded tools are blocked here too, even if the server can't be reached
76
+ hook("quarantine", run, c);
77
+ }
53
78
  }
54
79
  else if (c.type === "resume") {
55
- ctl.paused = false;
80
+ ctl.paused = ctl.quarantined = false; // releasing a quarantine resumes its runs
56
81
  }
57
82
  else if (c.type === "limits") {
58
83
  for (const [wire, field] of Object.entries(LIMITS)) {
@@ -60,44 +85,174 @@ export function apply(run, commands) {
60
85
  run[field] = c[wire]; // eslint-disable-line @typescript-eslint/no-explicit-any
61
86
  }
62
87
  }
63
- else if (c.type === "approval" && ctl.approvals.has(c.approval_id)) {
64
- ctl.approvals.set(c.approval_id, { granted: !!c.granted, by: c.by ?? null, note: c.note ?? null, amountUsd: c.amount_usd ?? null });
88
+ else if (c.type === "approval") {
89
+ if (ctl.approvals.has(c.approval_id)) {
90
+ ctl.approvals.set(c.approval_id, { granted: !!c.granted, by: c.by ?? null, note: c.note ?? null, amountUsd: c.amount_usd ?? null });
91
+ }
92
+ }
93
+ else {
94
+ continue; // unknown: ignored, and not acknowledged
95
+ }
96
+ if (c.id && !ctl.acked.has(c.id)) {
97
+ ctl.acked.add(c.id);
98
+ ack({ run_id: run.runId, id: c.id, type: c.type, received_at: Date.now() / 1000 });
65
99
  }
66
100
  }
67
101
  }
68
- const busy = () => [...live.values()].some((r) => r.control.paused || r.control.awaiting > 0);
69
- const nextDelay = () => (busy() ? Math.min(FAST_MS, intervalMs) : intervalMs); // faster while busy, never slower
102
+ // ── Acknowledgements and containment hooks ────────────────────────────────────
103
+ function ack(entry) {
104
+ acks.push(entry);
105
+ schedule(0); // send it now, not at the next interval
106
+ }
107
+ function hook(kind, run, c) {
108
+ const fn = hooks[kind];
109
+ if (!fn)
110
+ return;
111
+ if (kind === "quarantine") {
112
+ const key = c.id ?? `run:${run.runId}`; // every server that sends quarantine gives it an id
113
+ if (hooked.has(key))
114
+ return; // once per process per quarantine
115
+ hooked.add(key);
116
+ }
117
+ const event = { type: kind, runId: run.runId, agentName: run.agentName, id: c.id ?? null, by: c.by ?? null, reason: c.reason ?? null };
118
+ const p = runHook(fn, event).finally(() => runningHooks.delete(p));
119
+ runningHooks.add(p);
120
+ }
121
+ async function runHook(fn, event) {
122
+ const { errorText } = await import("./events.js");
123
+ let result = "ok", detail = null;
124
+ try {
125
+ const timedOut = Symbol("timeout");
126
+ const out = await Promise.race([
127
+ Promise.resolve().then(() => fn(event)),
128
+ new Promise((ok) => setTimeout(ok, HOOK_TIMEOUT_MS, timedOut).unref()), // ponytail: a hung hook can't be stopped; it's reported and left running
129
+ ]);
130
+ if (out === timedOut)
131
+ result = "timeout";
132
+ }
133
+ catch (e) {
134
+ [result, detail] = ["failed", errorText(e).slice(0, 300)];
135
+ }
136
+ if (result !== "ok")
137
+ console.warn(`agentfences: the on${event.type === "stop" ? "Stop" : "Quarantine"} hook ${result === "timeout" ? "timed out" : `failed: ${detail}`}`);
138
+ if (event.id) {
139
+ ack({ run_id: event.runId, id: event.id, type: event.type, hook: result, ...(detail ? { detail } : {}), finished_at: Date.now() / 1000 });
140
+ }
141
+ }
142
+ /**
143
+ * Wait for running hooks, then send the acknowledgements still waiting. flush() calls it, and
144
+ * it runs before the process exits, so a script that ends right after a stop still finishes
145
+ * its containment, and says so.
146
+ */
147
+ export async function drain(timeoutMs = HOOK_TIMEOUT_MS) {
148
+ if (runningHooks.size) {
149
+ await Promise.race([Promise.allSettled([...runningHooks]), new Promise((ok) => setTimeout(ok, timeoutMs).unref())]);
150
+ }
151
+ await sendAcks();
152
+ }
153
+ async function sendAcks() {
154
+ if (!acks.length || !client || !supported.heartbeat)
155
+ return;
156
+ const batch = acks;
157
+ acks = [];
158
+ const resp = await client.heartbeat([], batch).catch(() => ({ network_error: "heartbeat failed" }));
159
+ if (resp.network_error || (resp.ok === false && retryable(resp.status)))
160
+ acks = [...batch, ...acks];
161
+ }
162
+ // Node exits once nothing keeps it busy: beforeExit lets acks queued by a finished hook still go out.
163
+ // One try per batch: a server that's gone mustn't keep the process alive by failing and requeueing forever.
164
+ let triedAtExit = false;
165
+ if (typeof process?.on === "function") { // edge runtimes have no process events: flush() still drains there
166
+ process.on("beforeExit", () => {
167
+ if (triedAtExit || !acks.length || !client)
168
+ return;
169
+ triedAtExit = true;
170
+ void sendAcks();
171
+ });
172
+ }
173
+ // ── Heartbeat ─────────────────────────────────────────────────────────────────
174
+ const unseenStop = (r) => r.control.stopped && !r.control.stopSeen && !r.control.cancelled && r.control.onCancel !== null;
175
+ const busy = () => [...live.values()].some((r) => r.control.paused || r.control.awaiting > 0 || unseenStop(r));
176
+ const nextDelay = () => (busy() ? Math.min(FAST_MS, intervalMs, stopGraceMs ?? FAST_MS) : intervalMs); // faster while busy, never slower
70
177
  function schedule(delayMs) {
71
178
  if (timer)
72
179
  clearTimeout(timer);
73
180
  timer = null;
74
- if (!client || !supported.heartbeat || !live.size)
181
+ if (!client || !supported.heartbeat || !(live.size || acks.length))
75
182
  return;
76
183
  timer = setTimeout(beat, delayMs);
77
184
  timer.unref(); // the heartbeat never keeps the process alive on its own
78
185
  }
186
+ /** Halt governed calls that haven't reached a checkpoint within stopGraceS of a stop. */
187
+ function cancelOverdue() {
188
+ if (stopGraceMs === null)
189
+ return;
190
+ for (const r of live.values()) {
191
+ if (unseenStop(r) && Date.now() - r.control.stoppedAt >= stopGraceMs) {
192
+ r.control.cancelled = true;
193
+ r.control.onCancel();
194
+ }
195
+ }
196
+ }
79
197
  async function beat() {
80
198
  timer = null;
81
- if (beating || !client || !live.size)
199
+ cancelOverdue();
200
+ if (beating || !client || !(live.size || acks.length))
82
201
  return schedule(nextDelay());
83
202
  beating = true;
203
+ const runs = [...live.values()], batch = acks;
204
+ acks = [];
84
205
  try {
85
- const runs = [...live.values()];
86
206
  const resp = await client.heartbeat(runs.map((r) => ({ run_id: r.runId, iterations: r.iterations, spent_usd: r.costUsd,
87
- tokens_used: r.tokensUsed, paused: r.control.paused })));
88
- if (resp.unsupported) {
207
+ tokens_used: r.tokensUsed, paused: r.control.paused, stopped: r.control.stopped,
208
+ ...(r.control.held ? { held: r.control.held } : {}) })), batch);
209
+ if (resp.network_error || (resp.ok === false && !resp.unsupported)) {
210
+ if (resp.network_error || retryable(resp.status))
211
+ acks = [...batch, ...acks]; // at least once; any other 4xx won't get better
212
+ }
213
+ else if (resp.unsupported) {
89
214
  unsupported("heartbeat", "the heartbeat", "a stop from the dashboard applies at the next checkpoint and pause isn't available");
90
215
  return;
91
216
  }
92
- for (const run of runs)
93
- apply(run, resp.commands?.filter((c) => c.run_id === run.runId));
217
+ else {
218
+ for (const run of runs)
219
+ apply(run, resp.commands?.filter((c) => c.run_id === run.runId));
220
+ }
221
+ }
222
+ catch {
223
+ acks = [...batch, ...acks]; // a failed heartbeat is retried next time
94
224
  }
95
- catch { /* a failed heartbeat is retried next time */ }
96
225
  finally {
97
226
  beating = false;
98
227
  }
99
228
  schedule(nextDelay());
100
229
  }
230
+ // ── Waiting at a limit ────────────────────────────────────────────────────────
231
+ /** Breaches a person can lift by raising a limit, so onBreach "hold" waits at them. */
232
+ export const HOLDABLE = new Set(["budget_exceeded", "iteration_limit", "time_limit", "token_limit", "key_daily_budget",
233
+ "key_monthly_budget", "limit_reached"]);
234
+ export const BUDGET_BREACHES = new Set(["budget_exceeded", "key_daily_budget", "key_monthly_budget"]);
235
+ /** Whether this run waits at a limit: onBreach "hold", in cloud mode (someone can raise it), and not stopped. */
236
+ export const holds = (run) => run.onBreach === "hold" && client !== null && !run.control.stopped;
237
+ /** Pause the run at a limit: "breach" (a checkpoint breached) or "budget" (a call refused for budget). */
238
+ export function startHold(run, kind) {
239
+ run.control.held = kind;
240
+ if (!run.control.paused) {
241
+ run.control.paused = true;
242
+ schedule(0);
243
+ }
244
+ }
245
+ /** After waiting: "released" (an operator raised the limit), "stopped", or "timeout". */
246
+ export function endHold(run) {
247
+ run.control.held = null;
248
+ if (run.control.stopped)
249
+ return "stopped";
250
+ if (run.control.pauseExpired) {
251
+ run.control.pauseExpired = false; // the run ends with the limit it was held at, not "paused"
252
+ return "timeout";
253
+ }
254
+ return "released";
255
+ }
101
256
  // ── Pausing ───────────────────────────────────────────────────────────────────
102
257
  /** Hold the agent at its checkpoint while its run is paused, up to pauseTimeoutS. */
103
258
  export async function waitIfPaused(run) {
@@ -150,6 +305,7 @@ export async function requestApproval(reason, options = {}) {
150
305
  }
151
306
  run.control.awaiting -= 1;
152
307
  const answer = run.control.approvals.get(approvalId) ?? { granted: false, note: "No answer before the timeout" };
308
+ answer.approvalId = approvalId;
153
309
  run.control.approvals.delete(approvalId);
154
310
  if (answer.granted && answer.amountUsd && run.budgetUsd !== null)
155
311
  run.budgetUsd += answer.amountUsd; // an unlimited run stays unlimited
package/dist/core.d.ts CHANGED
@@ -58,6 +58,19 @@ export declare class Run {
58
58
  warned: boolean;
59
59
  /** Live control from the dashboard (see control.ts). */
60
60
  readonly control: control.Control;
61
+ /** Held by guarded tools that are running. */
62
+ reservedUsd: number;
63
+ /** "hold": wait at a limit for someone to raise it (see InitOptions.onBreach). */
64
+ onBreach: "hold" | null;
65
+ private readonly abort;
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(): AbortSignal;
72
+ /** @internal */
73
+ _abort(reason: unknown): void;
61
74
  constructor(agentName: string, budgetUsd: number | null, maxIterations: number, maxDurationMs: number, maxTokens: number, context: Record<string, string>);
62
75
  get durationMs(): number;
63
76
  }
@@ -84,11 +97,34 @@ export interface InitOptions {
84
97
  heartbeatS?: number;
85
98
  /** How long a paused run waits at its checkpoint before stopping with breach "paused". Default 3600. */
86
99
  pauseTimeoutS?: number;
100
+ /**
101
+ * If a run doesn't reach a checkpoint within this many seconds of a dashboard stop (say, it's
102
+ * stuck in a tool), the governed call rejects with FencesStop and the run's signal aborts.
103
+ * Default 30; null never does.
104
+ */
105
+ stopGraceS?: number | null;
106
+ /**
107
+ * Run in the agent's process when a stop arrives from the dashboard (once per run), to
108
+ * contain what Fences can't reach, e.g. revoke a credential the agent holds. Whether it
109
+ * worked, threw or ran past 30 seconds is reported to Fences.
110
+ */
111
+ onStop?: control.Hook;
112
+ /** The same, when the agent is quarantined (once per quarantine, however many runs it reaches). */
113
+ onQuarantine?: control.Hook;
114
+ /**
115
+ * "hold" makes runs wait at a limit instead of ending: when the server says a limit is crossed,
116
+ * checkpoint() waits (as if paused) until someone raises it and resumes the run, then returns ok
117
+ * and the agent carries on. A guarded tool denied for budget waits the same way. A stop ends the
118
+ * wait, and after pauseTimeoutS the breach is returned as usual. Cloud only; override per agent in governed().
119
+ */
120
+ onBreach?: "hold" | null;
87
121
  }
88
122
  /** Call once at startup, before any governed function runs. */
89
123
  export declare function init(options?: InitOptions): void;
90
124
  /** @internal For tests: point the SDK at a fake server client. */
91
125
  export declare function _setClient(c: FencesClient | null): void;
126
+ /** @internal */
127
+ export declare const _client: () => FencesClient | null;
92
128
  /**
93
129
  * Make each call of fn a governed run with these limits:
94
130
  *
@@ -100,6 +136,7 @@ export declare function _setClient(c: FencesClient | null): void;
100
136
  */
101
137
  export declare function governed<A extends unknown[], R>(fn: (...args: A) => R | Promise<R>, options?: Limits & {
102
138
  name?: string;
139
+ onBreach?: "hold" | null;
103
140
  }): (...args: A) => Promise<Awaited<R>>;
104
141
  export interface Extra {
105
142
  /** Extra spend for this step in USD, e.g. a paid tool call. Without a response, the step's whole cost. */
@@ -119,7 +156,12 @@ export interface Extra {
119
156
  * Never throws for a breach. Outside a governed run it does nothing.
120
157
  */
121
158
  export declare function checkpoint(response?: unknown, extra?: Extra): Promise<CheckpointResult>;
122
- /** Wait up to timeoutMs for queued decisions and spans to reach the server. Call before a serverless handler returns. */
159
+ /** @internal The breach the run is in by local rules, if any. */
160
+ export declare function localBreach(run: Run): string | null;
161
+ /**
162
+ * Wait up to timeoutMs for queued decisions and spans to reach the server, and for onStop and
163
+ * onQuarantine hooks to finish. Call before a serverless handler returns.
164
+ */
123
165
  export declare function flush(timeoutMs?: number): Promise<void>;
124
166
  /** Record the agent's reasoning at this step. Does nothing outside a governed run. */
125
167
  export declare function logDecision(reasoning: string, action?: string | null): void;
package/dist/core.js CHANGED
@@ -60,6 +60,19 @@ export class Run {
60
60
  warned = false;
61
61
  /** Live control from the dashboard (see control.ts). */
62
62
  control = control.newControl();
63
+ /** Held by guarded tools that are running. */
64
+ reservedUsd = 0;
65
+ /** "hold": wait at a limit for someone to raise it (see InitOptions.onBreach). */
66
+ onBreach = null;
67
+ abort = new AbortController();
68
+ /**
69
+ * Aborts when Fences halts the run after a stop it didn't reach a checkpoint for (see
70
+ * stopGraceS). Pass it to fetch and other cancellable work so a stuck run really stops:
71
+ * await fetch(url, { signal: getActiveRun()?.signal });
72
+ */
73
+ get signal() { return this.abort.signal; }
74
+ /** @internal */
75
+ _abort(reason) { this.abort.abort(reason); }
63
76
  constructor(agentName, budgetUsd, maxIterations, maxDurationMs, maxTokens, context) {
64
77
  this.agentName = agentName;
65
78
  this.budgetUsd = budgetUsd;
@@ -78,12 +91,21 @@ export function getActiveRun() {
78
91
  let client = null;
79
92
  let localOnly = false;
80
93
  let failClosed = false;
94
+ let defaultOnBreach = null;
95
+ function checkOnBreach(value) {
96
+ if (value === undefined || value === null)
97
+ return null;
98
+ if (value !== "hold")
99
+ throw new TypeError(`onBreach must be "hold" or null, not ${JSON.stringify(value)}`);
100
+ return value;
101
+ }
81
102
  /** Call once at startup, before any governed function runs. */
82
103
  export function init(options = {}) {
83
104
  setCustomPrices(options.prices);
84
105
  events.configure(options.environment, options.release, options.redact);
85
106
  localOnly = !!options.localOnly;
86
107
  failClosed = !!options.failClosed;
108
+ defaultOnBreach = checkOnBreach(options.onBreach);
87
109
  if (localOnly) {
88
110
  client = null;
89
111
  }
@@ -93,10 +115,16 @@ export function init(options = {}) {
93
115
  else {
94
116
  client = new FencesClient(options.apiKey, options.endpoint ?? "http://localhost:8000");
95
117
  }
96
- control.configure(client, options.heartbeatS ?? 15, options.pauseTimeoutS ?? 3600);
118
+ control.configure(client, { heartbeatS: options.heartbeatS ?? 15, pauseTimeoutS: options.pauseTimeoutS ?? 3600,
119
+ stopGraceS: options.stopGraceS === undefined ? 30 : options.stopGraceS, onStop: options.onStop, onQuarantine: options.onQuarantine });
97
120
  }
98
121
  /** @internal For tests: point the SDK at a fake server client. */
99
- export function _setClient(c) { client = c; control.configure(c, 15, 3600); }
122
+ export function _setClient(c) {
123
+ client = c;
124
+ control.configure(c, { heartbeatS: 15, pauseTimeoutS: 3600, stopGraceS: 30 });
125
+ }
126
+ /** @internal */
127
+ export const _client = () => client;
100
128
  function requireInit() {
101
129
  if (!localOnly && !client) {
102
130
  throw new Error("Call agentfences.init() before using Fences. For local usage: init({ localOnly: true })");
@@ -120,19 +148,31 @@ function warnUnreachable(run, err) {
120
148
  */
121
149
  export function governed(fn, options = {}) {
122
150
  const name = options.name ?? (fn.name || "agent");
151
+ checkOnBreach(options.onBreach);
123
152
  return async (...args) => {
124
153
  requireInit();
125
154
  const run = await startRun(name, options);
155
+ run.onBreach = checkOnBreach(options.onBreach) ?? defaultOnBreach;
156
+ // A stop the agent doesn't reach a checkpoint for within stopGraceS halts the call here
157
+ const halted = new Promise((_, reject) => {
158
+ run.control.onCancel = () => {
159
+ const stop = new FencesStop(finalResult(run));
160
+ run._abort(stop);
161
+ reject(stop);
162
+ };
163
+ });
126
164
  control.register(run);
127
165
  let error, failed = false;
128
166
  try {
129
- return await store.run(run, () => fn(...args));
167
+ // ponytail: JS can't stop running code; the agent's own work goes on unless it uses run.signal
168
+ return await Promise.race([store.run(run, () => fn(...args)), halted]);
130
169
  }
131
170
  catch (e) {
132
171
  [error, failed] = [e, true];
133
172
  throw e;
134
173
  }
135
174
  finally {
175
+ run.control.onCancel = null; // first, so a late cancel can't land after the call is over
136
176
  await endRun(run, ...outcome(run, failed, error));
137
177
  }
138
178
  };
@@ -141,7 +181,7 @@ function outcome(run, failed, error) {
141
181
  if (!failed)
142
182
  return [run.lastBreach ? "breached" : "success", null];
143
183
  if (error instanceof FencesStop)
144
- return ["breached", null]; // a framework integration halted the agent
184
+ return ["breached", null]; // halted after a stop it missed (stopGraceS)
145
185
  run.exception = events.exceptionInfo(error);
146
186
  const e = run.exception;
147
187
  return ["error", e.message ? `${e.type}: ${e.message}` : e.type];
@@ -193,6 +233,8 @@ async function endRun(run, status, error) {
193
233
  const body = { status, error: sent?.error ?? null };
194
234
  if (sent?.exception)
195
235
  body.exception = sent.exception;
236
+ if (status === "breached" && run.lastBreach)
237
+ body.breach = run.lastBreach; // matters for breaches the SDK decided
196
238
  await client.endRun(run.runId, body);
197
239
  }
198
240
  /**
@@ -222,13 +264,25 @@ export async function checkpoint(response, extra = {}) {
222
264
  cost_delta_usd: stepCost, tokens_used: stepTokens, iterations: run.iterations, duration_ms: run.durationMs
223
265
  }) : null;
224
266
  const result = decide(run, resp);
267
+ // onBreach "hold": wait here for someone to raise the limit. Only a breach the server decided: an unreachable one can't be raised
268
+ if (result.breachType && control.HOLDABLE.has(result.breachType) && control.holds(run) && resp && !resp.network_error) {
269
+ control.startHold(run, "breach");
270
+ await control.waitIfPaused(run);
271
+ const outcome = control.endHold(run);
272
+ if (outcome === "released") { // an operator raised the limit and resumed the run
273
+ run.lastBreach = null;
274
+ return okResult(run.costUsd);
275
+ }
276
+ return outcome === "stopped" ? finalResult(run) : result; // timed out: the limit it was held at
277
+ }
225
278
  if (run.control.paused && !result.breached) { // paused from the dashboard: wait here until resumed
226
279
  await control.waitIfPaused(run);
227
280
  return finalResult(run);
228
281
  }
229
282
  return result;
230
283
  }
231
- function localBreach(run) {
284
+ /** @internal The breach the run is in by local rules, if any. */
285
+ export function localBreach(run) {
232
286
  // Limits trip only once exceeded, same rule as the server; round away float drift (0.02*5 != 0.10)
233
287
  if (run.budgetUsd !== null && Math.round(run.costUsd * 1e9) / 1e9 > run.budgetUsd)
234
288
  return "budget_exceeded";
@@ -267,8 +321,10 @@ function decide(run, resp) {
267
321
  return finalResult(run);
268
322
  }
269
323
  function finalResult(run) {
270
- if (run.control.stopped)
271
- run.lastBreach = "stopped_by_user"; // a stop from the dashboard sticks, whatever a later reply says
324
+ if (run.control.stopped) { // a stop from the dashboard sticks, whatever a later reply says
325
+ run.lastBreach = "stopped_by_user";
326
+ run.control.stopSeen = true;
327
+ }
272
328
  else if (run.control.pauseExpired)
273
329
  run.lastBreach = "paused";
274
330
  return run.lastBreach ? breachResult(run.lastBreach, run) : okResult(run.costUsd);
@@ -307,11 +363,15 @@ async function drain() {
307
363
  catch { /* never let delivery break the agent */ }
308
364
  }
309
365
  }
310
- /** Wait up to timeoutMs for queued decisions and spans to reach the server. Call before a serverless handler returns. */
366
+ /**
367
+ * Wait up to timeoutMs for queued decisions and spans to reach the server, and for onStop and
368
+ * onQuarantine hooks to finish. Call before a serverless handler returns.
369
+ */
311
370
  export async function flush(timeoutMs = 5000) {
312
- if (!draining)
313
- return;
314
- await Promise.race([draining, new Promise((r) => setTimeout(r, timeoutMs).unref())]);
371
+ const deadline = Date.now() + timeoutMs;
372
+ if (draining)
373
+ await Promise.race([draining, new Promise((r) => setTimeout(r, timeoutMs).unref())]);
374
+ await control.drain(Math.max(0, deadline - Date.now()));
315
375
  }
316
376
  /** Record the agent's reasoning at this step. Does nothing outside a governed run. */
317
377
  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,9 +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";
6
7
  export { requestApproval } from "./control.js";
7
- export type { Approval } from "./control.js";
8
+ export type { Approval, ControlEvent, Hook } from "./control.js";
8
9
  export { priceOf } from "./pricing.js";
9
10
  export type { CustomPrice } from "./pricing.js";
10
- export { FencesError, FencesAuthError, AgentQuarantined, FencesStop } from "./errors.js";
11
+ export { FencesError, FencesAuthError, AgentQuarantined, FencesStop, ToolBlocked } from "./errors.js";
11
12
  export { VERSION } from "./version.js";
package/dist/index.js CHANGED
@@ -3,5 +3,5 @@ export { context } from "./events.js";
3
3
  export { tool } from "./tools.js";
4
4
  export { requestApproval } from "./control.js";
5
5
  export { priceOf } from "./pricing.js";
6
- export { FencesError, FencesAuthError, AgentQuarantined, FencesStop } from "./errors.js";
6
+ export { FencesError, FencesAuthError, AgentQuarantined, FencesStop, ToolBlocked } from "./errors.js";
7
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,137 @@ 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
+ const 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
+ const call = { guardId: randomUUID(), approvalId }; // one id per call: the server answers a re-check of a denied id afresh
53
+ for (;;) {
54
+ try {
55
+ return await check(run, client, name, summary, amount, call);
56
+ }
57
+ catch (e) {
58
+ if (!(e instanceof ToolBlocked && control.BUDGET_BREACHES.has(e.breachType ?? "") && control.holds(run)))
59
+ throw e;
60
+ control.startHold(run, "budget"); // onBreach "hold": wait for someone to raise the budget, then check again
61
+ await control.waitIfPaused(run);
62
+ const outcome = control.endHold(run);
63
+ if (outcome === "stopped")
64
+ block(run, name, "the run was stopped from the Fences dashboard", "stopped_by_user");
65
+ if (outcome !== "released")
66
+ throw e;
67
+ }
68
+ }
69
+ }
70
+ async function check(run, client, name, summary, amount, call) {
71
+ if (run.control.stopped)
72
+ block(run, name, "the run was stopped from the Fences dashboard", "stopped_by_user");
73
+ // Check against what this process already holds before asking the server, which can't see it; reserve until it answers
74
+ if (!fits(run, amount))
75
+ block(run, name, `$${amount.toFixed(2)} more could take the run past its $${run.budgetUsd.toFixed(2)} budget`, "budget_exceeded");
76
+ run.reservedUsd += amount;
77
+ try {
78
+ const sent = events.redact({ type: "guard", tool: name, arguments: summary }) ?? {}; // the check goes ahead without them
79
+ let resp = {}; // eslint-disable-line @typescript-eslint/no-explicit-any
80
+ for (let attempt = 0; attempt < 2; attempt++) { // once more after an approval the server asked for
81
+ resp = await client.guard(run.runId, { guard_id: call.guardId, phase: "check", tool: name, arguments: sent.arguments ?? null,
82
+ amount_usd: amount, approval_id: call.approvalId });
83
+ control.apply(run, resp.commands);
84
+ if (resp.allow === true)
85
+ return call.guardId;
86
+ if (!(resp.needs_approval && call.approvalId === null))
87
+ break;
88
+ call.approvalId = await approve(run, name, summary, amount);
89
+ }
90
+ if (resp.unsupported) {
91
+ control.unsupported("guard", "guarded tools", "they are blocked");
92
+ block(run, name, "this Fences server doesn't support guarded tools");
93
+ }
94
+ if (resp.network_error || !("allow" in resp))
95
+ block(run, name, "couldn't reach Fences to check this call"); // no answer: fail closed
96
+ block(run, name, resp.reason ?? "not allowed", resp.breach ?? null);
97
+ }
98
+ catch (e) {
99
+ run.reservedUsd -= amount;
100
+ throw e;
101
+ }
102
+ }
103
+ /** Release the reservation and charge the run: in full unless the tool threw (a timeout may have done it). */
104
+ async function settle(run, guardId, amount, error, failed) {
105
+ const outcome = !failed ? "ok" : error?.name === "TimeoutError" ? "unknown" : "error";
106
+ const charged = outcome === "error" ? 0 : amount;
107
+ run.reservedUsd -= amount;
108
+ run.costUsd += charged;
109
+ await _client()?.guard(run.runId, { guard_id: guardId, phase: "settle", outcome, cost_usd: charged });
110
+ }
111
+ function timed(p, timeoutS, name) {
112
+ if (timeoutS === undefined)
113
+ return p;
114
+ let timer;
115
+ const late = new Promise((_, reject) => {
116
+ timer = setTimeout(() => reject(new DOMException(`${name} took longer than ${timeoutS}s`, "TimeoutError")), timeoutS * 1000);
117
+ });
118
+ return Promise.race([p, late]).finally(() => clearTimeout(timer));
119
+ }
24
120
  export function tool(fn, options = {}) {
25
121
  const name = options.name ?? (fn.name || "tool");
122
+ const guarded = !!options.guard || options.costUsd !== undefined || !!options.approve;
123
+ if (guarded || options.timeoutS !== undefined) {
124
+ return async (...args) => {
125
+ const run = getActiveRun();
126
+ if (!run)
127
+ return timed(Promise.resolve().then(() => fn(...args)), options.timeoutS, name);
128
+ const summary = argsSummary(args), started = performance.now();
129
+ let gate = null;
130
+ try {
131
+ if (guarded) {
132
+ const c = options.costUsd, amount = Number((typeof c === "function" ? c(...args) : c) ?? 0);
133
+ gate = [await open(run, name, summary, amount, !!options.approve), amount];
134
+ }
135
+ const out = await timed(Promise.resolve().then(() => fn(...args)), options.timeoutS, name);
136
+ recordToolCall(name, summary, started);
137
+ if (gate)
138
+ await settle(run, ...gate, undefined, false);
139
+ return out;
140
+ }
141
+ catch (e) {
142
+ recordToolCall(name, summary, started, e);
143
+ if (gate)
144
+ await settle(run, ...gate, e, true);
145
+ throw e;
146
+ }
147
+ };
148
+ }
26
149
  return (...args) => {
27
150
  if (!getActiveRun())
28
151
  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.4.0";
2
+ export declare const VERSION = "0.6.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.4.0";
2
+ export const VERSION = "0.6.0";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentfences",
3
- "version": "0.4.0",
3
+ "version": "0.6.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",