agentfences 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -1
- package/dist/client.d.ts +5 -0
- package/dist/client.js +7 -0
- package/dist/control.d.ts +84 -0
- package/dist/control.js +288 -0
- package/dist/core.d.ts +40 -1
- package/dist/core.js +68 -12
- package/dist/errors.d.ts +13 -2
- package/dist/errors.js +18 -2
- package/dist/index.d.ts +4 -1
- package/dist/index.js +2 -1
- package/dist/prices.generated.d.ts +1 -0
- package/dist/prices.generated.js +2 -2
- package/dist/tools.d.ts +35 -1
- package/dist/tools.js +114 -11
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -59,6 +59,52 @@ init({ apiKey: process.env.FENCES_API_KEY, endpoint: "https://your-fences-host",
|
|
|
59
59
|
|
|
60
60
|
The server is authoritative for limits and sees spend across processes. If it can't be reached, limits are enforced locally; pass `failClosed: true` to stop instead. A rejected key throws `FencesAuthError`; a quarantined agent throws `AgentQuarantined` before any of its code runs. In serverless handlers, `await flush()` before returning so queued decisions and spans are sent.
|
|
61
61
|
|
|
62
|
+
## Live control (from 0.4.0)
|
|
63
|
+
|
|
64
|
+
In cloud mode a heartbeat (every 15 seconds, `init({ heartbeatS })`) lets whoever is on call act on a running agent from the dashboard:
|
|
65
|
+
|
|
66
|
+
- **Stop:** the next `checkpoint()` returns `breachType: "stopped_by_user"`, so the agent wraps up gracefully. If the run doesn't reach a checkpoint within `stopGraceS` (default 30) seconds, say it's stuck in a tool, the governed call rejects with `FencesStop` and the run's `signal` aborts (from 0.5.0). JavaScript can't stop running code, so pass `getActiveRun()?.signal` to `fetch` and other cancellable work for a stuck run to really stop. `init({ stopGraceS: null })` turns this off.
|
|
67
|
+
- **Pause and resume:** a paused run waits inside its next `checkpoint()` until it's resumed; after `pauseTimeoutS` (default one hour) it returns `breachType: "paused"`.
|
|
68
|
+
- **Limits** changed in the dashboard apply to the run immediately. **Quarantine** makes the governed call throw `AgentQuarantined` before any agent code runs; runs already going hold at their next checkpoint, and their guarded tools are blocked.
|
|
69
|
+
|
|
70
|
+
An agent can ask a person before doing something risky:
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
import { requestApproval } from "agentfences";
|
|
74
|
+
|
|
75
|
+
const approval = await requestApproval("Refund $240 to order 1182?", { amountUsd: 240, timeoutS: 600 });
|
|
76
|
+
if (!approval.granted) return `I didn't issue the refund: ${approval.note}`;
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
An approved amount raises the run's budget. It never hangs or throws: in local mode, outside a run, or on a server without approvals it's denied right away, and with no answer it's denied after `timeoutS`. The heartbeat's timer never keeps your process alive on its own.
|
|
80
|
+
|
|
81
|
+
**Containment hooks** (from 0.5.0) run in your process when a stop or quarantine arrives, to contain what Fences can't reach, such as a credential the agent holds:
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
init({ apiKey: "fc_...", onQuarantine: async (event) => vault.revoke(`agent/${event.agentName}`), onStop: (event) => { ... } });
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
`onStop` runs once per run, and `onQuarantine` once per quarantine however many runs it reaches. Each gets `{ type, runId, agentName, id, by, reason }`. Fences records whether it worked, threw (and why) or ran past 30 seconds. A process that's about to exit waits for its hooks first; in serverless handlers, `await flush()` does the same. The SDK also acknowledges every dashboard command it receives, so the dashboard can tell "the agent has it" from "the agent hasn't acknowledged".
|
|
88
|
+
|
|
89
|
+
## Guarded tools (from 0.5.0)
|
|
90
|
+
|
|
91
|
+
For actions with consequences (refunds, payments, writes to production), guard the tool, and it asks Fences **before** it runs:
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
import { tool, ToolBlocked } from "agentfences";
|
|
95
|
+
|
|
96
|
+
const issueRefund = tool(refund, { costUsd: (orderId: string, amount: number) => amount, approve: true });
|
|
97
|
+
const deploy = tool(deployImpl, { guard: true, timeoutS: 30 });
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
- The tool runs only if its run isn't stopped, paused, over a limit or quarantined. Otherwise it throws `ToolBlocked` (with `reason` and `breachType`) and its code never runs.
|
|
101
|
+
- `costUsd` (a number, or a function of the tool's arguments) is **reserved** before the call and charged after, so parallel calls can't overspend the budget together. A call that throws is released uncharged; one that times out is charged, because it may have happened.
|
|
102
|
+
- `approve: true` asks a person on the dashboard before each call. The dashboard can also require approval for a tool, and the SDK then asks for it.
|
|
103
|
+
- **Guards fail closed:** if Fences can't be reached, or doesn't support guarded tools, the tool is blocked. In local mode the same rules apply in-process, and `approve` is always denied.
|
|
104
|
+
- `timeoutS` works on any tool: past it, the call rejects with a `TimeoutError`. The tool's own code carries on unless it uses `getActiveRun()?.signal`.
|
|
105
|
+
|
|
106
|
+
A guarded or timed tool always returns a promise. What each part guarantees: [ENFORCEMENT.md](../ENFORCEMENT.md).
|
|
107
|
+
|
|
62
108
|
## Not yet in the TypeScript SDK
|
|
63
109
|
|
|
64
|
-
The Python SDK also
|
|
110
|
+
The Python SDK also records OpenAI/Anthropic calls automatically (`instrument`) and integrates with LangGraph and the OpenAI Agents SDK. Those are also how it checks a model call against the budget **before** it's sent, so the TypeScript SDK doesn't do that yet: a model call is checked afterwards, by `checkpoint()`, and a run can go over its budget by one call. Everything here follows the same server protocol ([PROTOCOL.md](../PROTOCOL.md)).
|
package/dist/client.d.ts
CHANGED
|
@@ -9,6 +9,11 @@ export declare class FencesClient {
|
|
|
9
9
|
logDecision(runId: string, body: Record<string, unknown>): Promise<Reply>;
|
|
10
10
|
endRun(runId: string, body: Record<string, unknown>): Promise<Reply>;
|
|
11
11
|
logSpans(runId: string, spans: unknown[]): Promise<Reply>;
|
|
12
|
+
/** Report live runs and acknowledge commands; the reply may carry commands for them. */
|
|
13
|
+
heartbeat(runs: unknown[], acks?: unknown[]): Promise<Reply>;
|
|
14
|
+
/** Check a guarded tool call before it runs ({ phase: "check" }), or settle it after ({ phase: "settle" }). */
|
|
15
|
+
guard(runId: string, body: Record<string, unknown>): Promise<Reply>;
|
|
16
|
+
requestApproval(runId: string, body: Record<string, unknown>): Promise<Reply>;
|
|
12
17
|
/**
|
|
13
18
|
* optional: an endpoint older servers may not have. A missing route returns
|
|
14
19
|
* { unsupported: true } and nothing throws, not even a rejected key.
|
package/dist/client.js
CHANGED
|
@@ -17,6 +17,13 @@ export class FencesClient {
|
|
|
17
17
|
logDecision(runId, body) { return this.post(`/api/runs/${runId}/decisions`, body); }
|
|
18
18
|
endRun(runId, body) { return this.post(`/api/runs/${runId}/end`, body); }
|
|
19
19
|
logSpans(runId, spans) { return this.post(`/api/runs/${runId}/spans`, { spans }, true); }
|
|
20
|
+
/** Report live runs and acknowledge commands; the reply may carry commands for them. */
|
|
21
|
+
heartbeat(runs, acks = []) {
|
|
22
|
+
return this.post("/api/heartbeat", acks.length ? { runs, acks } : { runs }, true);
|
|
23
|
+
}
|
|
24
|
+
/** Check a guarded tool call before it runs ({ phase: "check" }), or settle it after ({ phase: "settle" }). */
|
|
25
|
+
guard(runId, body) { return this.post(`/api/runs/${runId}/guard`, body, true); }
|
|
26
|
+
requestApproval(runId, body) { return this.post(`/api/runs/${runId}/approvals`, body, true); }
|
|
20
27
|
/**
|
|
21
28
|
* optional: an endpoint older servers may not have. A missing route returns
|
|
22
29
|
* { unsupported: true } and nothing throws, not even a rejected key.
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { FencesClient, Reply } from "./client.js";
|
|
2
|
+
import type { Run } from "./core.js";
|
|
3
|
+
export interface Approval {
|
|
4
|
+
granted: boolean;
|
|
5
|
+
/** Who answered, as the dashboard reports it. */
|
|
6
|
+
by?: string | null;
|
|
7
|
+
/** Their note, or why no one could answer. */
|
|
8
|
+
note?: string | null;
|
|
9
|
+
/** The amount approved, if one was asked for. */
|
|
10
|
+
amountUsd?: number | null;
|
|
11
|
+
approvalId?: string | null;
|
|
12
|
+
}
|
|
13
|
+
/** What onStop and onQuarantine receive. */
|
|
14
|
+
export interface ControlEvent {
|
|
15
|
+
type: "stop" | "quarantine";
|
|
16
|
+
runId: string;
|
|
17
|
+
agentName: string;
|
|
18
|
+
/** The command's id, if the server sent one. */
|
|
19
|
+
id: string | null;
|
|
20
|
+
/** Who pressed the button, as the dashboard reports it. */
|
|
21
|
+
by: string | null;
|
|
22
|
+
reason: string | null;
|
|
23
|
+
}
|
|
24
|
+
export type Hook = (event: ControlEvent) => unknown;
|
|
25
|
+
/** Live-control state a run carries. */
|
|
26
|
+
export interface Control {
|
|
27
|
+
stopped: boolean;
|
|
28
|
+
stoppedAt: number;
|
|
29
|
+
/** The agent got its stop from a checkpoint (or a blocked tool): it's never cancelled. */
|
|
30
|
+
stopSeen: boolean;
|
|
31
|
+
cancelled: boolean;
|
|
32
|
+
/** Set by governed(): halts the call when a stop goes unseen past stopGraceS. */
|
|
33
|
+
onCancel: (() => void) | null;
|
|
34
|
+
paused: boolean;
|
|
35
|
+
pauseExpired: boolean;
|
|
36
|
+
quarantined: boolean;
|
|
37
|
+
/** approval id -> the answer, or null while waiting */
|
|
38
|
+
approvals: Map<string, Approval | null>;
|
|
39
|
+
awaiting: number;
|
|
40
|
+
/** command ids already acknowledged */
|
|
41
|
+
acked: Set<string>;
|
|
42
|
+
}
|
|
43
|
+
export declare const newControl: () => Control;
|
|
44
|
+
export declare let pauseTimeoutMs: number;
|
|
45
|
+
export declare const supported: {
|
|
46
|
+
heartbeat: boolean;
|
|
47
|
+
approvals: boolean;
|
|
48
|
+
guard: boolean;
|
|
49
|
+
};
|
|
50
|
+
export interface ControlOptions {
|
|
51
|
+
heartbeatS: number;
|
|
52
|
+
pauseTimeoutS: number;
|
|
53
|
+
stopGraceS: number | null;
|
|
54
|
+
onStop?: Hook | null;
|
|
55
|
+
onQuarantine?: Hook | null;
|
|
56
|
+
}
|
|
57
|
+
export declare function configure(c: FencesClient | null, o: ControlOptions): void;
|
|
58
|
+
export declare function register(run: Run): void;
|
|
59
|
+
export declare function unregister(run: Run): void;
|
|
60
|
+
export declare function unsupported(kind: keyof typeof supported, what: string, consequence: string): void;
|
|
61
|
+
/** Apply the commands addressed to this run (from a heartbeat or a checkpoint reply), and acknowledge them. Each is safe to apply twice. */
|
|
62
|
+
export declare function apply(run: Run, commands: Reply[] | undefined): void;
|
|
63
|
+
/**
|
|
64
|
+
* Wait for running hooks, then send the acknowledgements still waiting. flush() calls it, and
|
|
65
|
+
* it runs before the process exits, so a script that ends right after a stop still finishes
|
|
66
|
+
* its containment, and says so.
|
|
67
|
+
*/
|
|
68
|
+
export declare function drain(timeoutMs?: number): Promise<void>;
|
|
69
|
+
/** Hold the agent at its checkpoint while its run is paused, up to pauseTimeoutS. */
|
|
70
|
+
export declare function waitIfPaused(run: Run): Promise<void>;
|
|
71
|
+
/**
|
|
72
|
+
* Ask a person, through the Fences dashboard, to approve something, and wait for the answer:
|
|
73
|
+
*
|
|
74
|
+
* const approval = await requestApproval("Refund $240 to order 1182?", { amountUsd: 240 });
|
|
75
|
+
* if (!approval.granted) return `I didn't issue the refund: ${approval.note}`;
|
|
76
|
+
*
|
|
77
|
+
* If an amount is approved, the run's budget goes up by that much. Returns denied right away in
|
|
78
|
+
* local mode, outside a run, or when the server can't take requests, and denied after timeoutS
|
|
79
|
+
* (default 600) with no answer: it never hangs and never throws.
|
|
80
|
+
*/
|
|
81
|
+
export declare function requestApproval(reason: string, options?: {
|
|
82
|
+
amountUsd?: number;
|
|
83
|
+
timeoutS?: number;
|
|
84
|
+
}): Promise<Approval>;
|
package/dist/control.js
ADDED
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Live control from the Fences dashboard: a heartbeat that reports live runs and brings back
|
|
3
|
+
* commands (stop, pause, resume, quarantine, limits, approval answers) and acknowledges them,
|
|
4
|
+
* containment hooks, pausing at checkpoints, cancelling a run stuck after a stop, and approval
|
|
5
|
+
* requests. Cloud mode only; a server without these endpoints gets one warning and runs carry
|
|
6
|
+
* on as before. Same behaviour as the Python SDK (PROTOCOL.md, "Live control").
|
|
7
|
+
*/
|
|
8
|
+
import { randomUUID } from "node:crypto";
|
|
9
|
+
const FAST_MS = 2000; // heartbeat interval while a run is paused, waiting for an approval, or stopping
|
|
10
|
+
const HOOK_TIMEOUT_MS = 30_000; // a hook still running after this is reported as timed out
|
|
11
|
+
export const newControl = () => ({
|
|
12
|
+
stopped: false, stoppedAt: 0, stopSeen: false, cancelled: false, onCancel: null, paused: false, pauseExpired: false,
|
|
13
|
+
quarantined: false, approvals: new Map(), awaiting: 0, acked: new Set(),
|
|
14
|
+
});
|
|
15
|
+
const live = new Map(); // run id -> runs in progress
|
|
16
|
+
let client = null;
|
|
17
|
+
let intervalMs = 15_000;
|
|
18
|
+
export let pauseTimeoutMs = 3_600_000;
|
|
19
|
+
let stopGraceMs = 30_000;
|
|
20
|
+
const hooks = { stop: null, quarantine: null };
|
|
21
|
+
export const supported = { heartbeat: true, approvals: true, guard: true };
|
|
22
|
+
let timer = null;
|
|
23
|
+
let beating = false;
|
|
24
|
+
let acks = []; // acknowledgements waiting for the next heartbeat
|
|
25
|
+
const hooked = new Set(); // quarantine ids whose hook has run in this process
|
|
26
|
+
const runningHooks = new Set();
|
|
27
|
+
export function configure(c, o) {
|
|
28
|
+
client = c;
|
|
29
|
+
intervalMs = o.heartbeatS * 1000;
|
|
30
|
+
pauseTimeoutMs = o.pauseTimeoutS * 1000;
|
|
31
|
+
stopGraceMs = o.stopGraceS === null ? null : o.stopGraceS * 1000;
|
|
32
|
+
hooks.stop = o.onStop ?? null;
|
|
33
|
+
hooks.quarantine = o.onQuarantine ?? null;
|
|
34
|
+
supported.heartbeat = supported.approvals = supported.guard = true;
|
|
35
|
+
schedule(0); // a new client or interval takes effect now, not after the old wait
|
|
36
|
+
}
|
|
37
|
+
export function register(run) {
|
|
38
|
+
live.set(run.runId, run);
|
|
39
|
+
if (!timer)
|
|
40
|
+
schedule(nextDelay());
|
|
41
|
+
}
|
|
42
|
+
export function unregister(run) {
|
|
43
|
+
live.delete(run.runId);
|
|
44
|
+
}
|
|
45
|
+
export function unsupported(kind, what, consequence) {
|
|
46
|
+
if (!supported[kind])
|
|
47
|
+
return;
|
|
48
|
+
supported[kind] = false;
|
|
49
|
+
console.warn(`agentfences: this Fences server doesn't support ${what} yet, so ${consequence}`);
|
|
50
|
+
}
|
|
51
|
+
// ── Commands ──────────────────────────────────────────────────────────────────
|
|
52
|
+
const LIMITS = { budget_usd: "budgetUsd", max_iterations: "maxIterations", max_duration_ms: "maxDurationMs", max_tokens: "maxTokens" };
|
|
53
|
+
/** Apply the commands addressed to this run (from a heartbeat or a checkpoint reply), and acknowledge them. Each is safe to apply twice. */
|
|
54
|
+
export function apply(run, commands) {
|
|
55
|
+
for (const c of commands ?? []) {
|
|
56
|
+
if (c.run_id !== undefined && c.run_id !== run.runId)
|
|
57
|
+
continue;
|
|
58
|
+
const ctl = run.control;
|
|
59
|
+
if (c.type === "stop") {
|
|
60
|
+
if (!ctl.stopped) {
|
|
61
|
+
ctl.stopped = true;
|
|
62
|
+
ctl.stoppedAt = Date.now();
|
|
63
|
+
schedule(0); // watch for the agent to see it, or cancel it
|
|
64
|
+
hook("stop", run, c); // once per run
|
|
65
|
+
}
|
|
66
|
+
ctl.paused = ctl.quarantined = false;
|
|
67
|
+
}
|
|
68
|
+
else if (c.type === "pause" || c.type === "quarantine") { // a quarantined agent's runs hold at their next checkpoint
|
|
69
|
+
ctl.paused = true;
|
|
70
|
+
schedule(0);
|
|
71
|
+
if (c.type === "quarantine") {
|
|
72
|
+
ctl.quarantined = true; // guarded tools are blocked here too, even if the server can't be reached
|
|
73
|
+
hook("quarantine", run, c);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
else if (c.type === "resume") {
|
|
77
|
+
ctl.paused = ctl.quarantined = false; // releasing a quarantine resumes its runs
|
|
78
|
+
}
|
|
79
|
+
else if (c.type === "limits") {
|
|
80
|
+
for (const [wire, field] of Object.entries(LIMITS)) {
|
|
81
|
+
if (c[wire] !== undefined && c[wire] !== null)
|
|
82
|
+
run[field] = c[wire]; // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
else if (c.type === "approval") {
|
|
86
|
+
if (ctl.approvals.has(c.approval_id)) {
|
|
87
|
+
ctl.approvals.set(c.approval_id, { granted: !!c.granted, by: c.by ?? null, note: c.note ?? null, amountUsd: c.amount_usd ?? null });
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
continue; // unknown: ignored, and not acknowledged
|
|
92
|
+
}
|
|
93
|
+
if (c.id && !ctl.acked.has(c.id)) {
|
|
94
|
+
ctl.acked.add(c.id);
|
|
95
|
+
ack({ run_id: run.runId, id: c.id, type: c.type, received_at: Date.now() / 1000 });
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
// ── Acknowledgements and containment hooks ────────────────────────────────────
|
|
100
|
+
function ack(entry) {
|
|
101
|
+
acks.push(entry);
|
|
102
|
+
schedule(0); // send it now, not at the next interval
|
|
103
|
+
}
|
|
104
|
+
function hook(kind, run, c) {
|
|
105
|
+
const fn = hooks[kind];
|
|
106
|
+
if (!fn)
|
|
107
|
+
return;
|
|
108
|
+
if (kind === "quarantine") {
|
|
109
|
+
const key = c.id ?? `run:${run.runId}`; // every server that sends quarantine gives it an id
|
|
110
|
+
if (hooked.has(key))
|
|
111
|
+
return; // once per process per quarantine
|
|
112
|
+
hooked.add(key);
|
|
113
|
+
}
|
|
114
|
+
const event = { type: kind, runId: run.runId, agentName: run.agentName, id: c.id ?? null, by: c.by ?? null, reason: c.reason ?? null };
|
|
115
|
+
const p = runHook(fn, event).finally(() => runningHooks.delete(p));
|
|
116
|
+
runningHooks.add(p);
|
|
117
|
+
}
|
|
118
|
+
async function runHook(fn, event) {
|
|
119
|
+
const { errorText } = await import("./events.js");
|
|
120
|
+
let result = "ok", detail = null;
|
|
121
|
+
try {
|
|
122
|
+
const timedOut = Symbol("timeout");
|
|
123
|
+
const out = await Promise.race([
|
|
124
|
+
Promise.resolve().then(() => fn(event)),
|
|
125
|
+
new Promise((ok) => setTimeout(ok, HOOK_TIMEOUT_MS, timedOut).unref()), // ponytail: a hung hook can't be stopped; it's reported and left running
|
|
126
|
+
]);
|
|
127
|
+
if (out === timedOut)
|
|
128
|
+
result = "timeout";
|
|
129
|
+
}
|
|
130
|
+
catch (e) {
|
|
131
|
+
[result, detail] = ["failed", errorText(e).slice(0, 300)];
|
|
132
|
+
}
|
|
133
|
+
if (result !== "ok")
|
|
134
|
+
console.warn(`agentfences: the on${event.type === "stop" ? "Stop" : "Quarantine"} hook ${result === "timeout" ? "timed out" : `failed: ${detail}`}`);
|
|
135
|
+
if (event.id) {
|
|
136
|
+
ack({ run_id: event.runId, id: event.id, type: event.type, hook: result, ...(detail ? { detail } : {}), finished_at: Date.now() / 1000 });
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Wait for running hooks, then send the acknowledgements still waiting. flush() calls it, and
|
|
141
|
+
* it runs before the process exits, so a script that ends right after a stop still finishes
|
|
142
|
+
* its containment, and says so.
|
|
143
|
+
*/
|
|
144
|
+
export async function drain(timeoutMs = HOOK_TIMEOUT_MS) {
|
|
145
|
+
if (runningHooks.size) {
|
|
146
|
+
await Promise.race([Promise.allSettled([...runningHooks]), new Promise((ok) => setTimeout(ok, timeoutMs).unref())]);
|
|
147
|
+
}
|
|
148
|
+
await sendAcks();
|
|
149
|
+
}
|
|
150
|
+
async function sendAcks() {
|
|
151
|
+
if (!acks.length || !client || !supported.heartbeat)
|
|
152
|
+
return;
|
|
153
|
+
const batch = acks;
|
|
154
|
+
acks = [];
|
|
155
|
+
const resp = await client.heartbeat([], batch).catch(() => ({ network_error: "heartbeat failed" }));
|
|
156
|
+
if (resp.network_error || (resp.ok === false && (resp.status ?? 500) >= 500))
|
|
157
|
+
acks = [...batch, ...acks];
|
|
158
|
+
}
|
|
159
|
+
// Node exits once nothing keeps it busy: beforeExit lets acks queued by a finished hook still go out.
|
|
160
|
+
// One try per batch: a server that's gone mustn't keep the process alive by failing and requeueing forever.
|
|
161
|
+
let triedAtExit = false;
|
|
162
|
+
if (typeof process?.on === "function") { // edge runtimes have no process events: flush() still drains there
|
|
163
|
+
process.on("beforeExit", () => {
|
|
164
|
+
if (triedAtExit || !acks.length || !client)
|
|
165
|
+
return;
|
|
166
|
+
triedAtExit = true;
|
|
167
|
+
void sendAcks();
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
// ── Heartbeat ─────────────────────────────────────────────────────────────────
|
|
171
|
+
const unseenStop = (r) => r.control.stopped && !r.control.stopSeen && !r.control.cancelled && r.control.onCancel !== null;
|
|
172
|
+
const busy = () => [...live.values()].some((r) => r.control.paused || r.control.awaiting > 0 || unseenStop(r));
|
|
173
|
+
const nextDelay = () => (busy() ? Math.min(FAST_MS, intervalMs, stopGraceMs ?? FAST_MS) : intervalMs); // faster while busy, never slower
|
|
174
|
+
function schedule(delayMs) {
|
|
175
|
+
if (timer)
|
|
176
|
+
clearTimeout(timer);
|
|
177
|
+
timer = null;
|
|
178
|
+
if (!client || !supported.heartbeat || !(live.size || acks.length))
|
|
179
|
+
return;
|
|
180
|
+
timer = setTimeout(beat, delayMs);
|
|
181
|
+
timer.unref(); // the heartbeat never keeps the process alive on its own
|
|
182
|
+
}
|
|
183
|
+
/** Halt governed calls that haven't reached a checkpoint within stopGraceS of a stop. */
|
|
184
|
+
function cancelOverdue() {
|
|
185
|
+
if (stopGraceMs === null)
|
|
186
|
+
return;
|
|
187
|
+
for (const r of live.values()) {
|
|
188
|
+
if (unseenStop(r) && Date.now() - r.control.stoppedAt >= stopGraceMs) {
|
|
189
|
+
r.control.cancelled = true;
|
|
190
|
+
r.control.onCancel();
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
async function beat() {
|
|
195
|
+
timer = null;
|
|
196
|
+
cancelOverdue();
|
|
197
|
+
if (beating || !client || !(live.size || acks.length))
|
|
198
|
+
return schedule(nextDelay());
|
|
199
|
+
beating = true;
|
|
200
|
+
const runs = [...live.values()], batch = acks;
|
|
201
|
+
acks = [];
|
|
202
|
+
try {
|
|
203
|
+
const resp = await client.heartbeat(runs.map((r) => ({ run_id: r.runId, iterations: r.iterations, spent_usd: r.costUsd,
|
|
204
|
+
tokens_used: r.tokensUsed, paused: r.control.paused, stopped: r.control.stopped })), batch);
|
|
205
|
+
if (resp.network_error || (resp.ok === false && !resp.unsupported)) {
|
|
206
|
+
if (resp.network_error || (resp.status ?? 500) >= 500)
|
|
207
|
+
acks = [...batch, ...acks]; // at least once; a 4xx won't get better
|
|
208
|
+
}
|
|
209
|
+
else if (resp.unsupported) {
|
|
210
|
+
unsupported("heartbeat", "the heartbeat", "a stop from the dashboard applies at the next checkpoint and pause isn't available");
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
for (const run of runs)
|
|
215
|
+
apply(run, resp.commands?.filter((c) => c.run_id === run.runId));
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
acks = [...batch, ...acks]; // a failed heartbeat is retried next time
|
|
220
|
+
}
|
|
221
|
+
finally {
|
|
222
|
+
beating = false;
|
|
223
|
+
}
|
|
224
|
+
schedule(nextDelay());
|
|
225
|
+
}
|
|
226
|
+
// ── Pausing ───────────────────────────────────────────────────────────────────
|
|
227
|
+
/** Hold the agent at its checkpoint while its run is paused, up to pauseTimeoutS. */
|
|
228
|
+
export async function waitIfPaused(run) {
|
|
229
|
+
const deadline = Date.now() + pauseTimeoutMs;
|
|
230
|
+
while (run.control.paused) {
|
|
231
|
+
if (Date.now() >= deadline) {
|
|
232
|
+
run.control.paused = false;
|
|
233
|
+
run.control.pauseExpired = true;
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
await new Promise((ok) => setTimeout(ok, 200));
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
// ── Approvals ─────────────────────────────────────────────────────────────────
|
|
240
|
+
/**
|
|
241
|
+
* Ask a person, through the Fences dashboard, to approve something, and wait for the answer:
|
|
242
|
+
*
|
|
243
|
+
* const approval = await requestApproval("Refund $240 to order 1182?", { amountUsd: 240 });
|
|
244
|
+
* if (!approval.granted) return `I didn't issue the refund: ${approval.note}`;
|
|
245
|
+
*
|
|
246
|
+
* If an amount is approved, the run's budget goes up by that much. Returns denied right away in
|
|
247
|
+
* local mode, outside a run, or when the server can't take requests, and denied after timeoutS
|
|
248
|
+
* (default 600) with no answer: it never hangs and never throws.
|
|
249
|
+
*/
|
|
250
|
+
export async function requestApproval(reason, options = {}) {
|
|
251
|
+
const { getActiveRun, recordSpan } = await import("./core.js");
|
|
252
|
+
const run = getActiveRun();
|
|
253
|
+
const timeoutS = options.timeoutS ?? 600, amountUsd = options.amountUsd ?? null;
|
|
254
|
+
if (!run)
|
|
255
|
+
return { granted: false, note: "requestApproval() was called outside a governed run" };
|
|
256
|
+
if (!client)
|
|
257
|
+
return { granted: false, note: "Local mode: there is no one to approve this request" };
|
|
258
|
+
if (!supported.approvals)
|
|
259
|
+
return { granted: false, note: "This Fences server doesn't support approvals" };
|
|
260
|
+
const approvalId = randomUUID(), started = performance.now();
|
|
261
|
+
// timeout_s lets the server expire the request when we stop waiting
|
|
262
|
+
const resp = await client.requestApproval(run.runId, { approval_id: approvalId, reason, amount_usd: amountUsd, timeout_s: timeoutS });
|
|
263
|
+
if (resp.unsupported) {
|
|
264
|
+
unsupported("approvals", "approval requests", "they are denied");
|
|
265
|
+
return { granted: false, note: "This Fences server doesn't support approvals" };
|
|
266
|
+
}
|
|
267
|
+
if (resp.network_error)
|
|
268
|
+
return { granted: false, note: "Couldn't reach Fences to ask for approval" };
|
|
269
|
+
run.control.approvals.set(approvalId, null);
|
|
270
|
+
run.control.awaiting += 1;
|
|
271
|
+
schedule(0); // poll fast while someone decides
|
|
272
|
+
const deadline = Date.now() + timeoutS * 1000;
|
|
273
|
+
while (run.control.approvals.get(approvalId) === null && Date.now() < deadline) {
|
|
274
|
+
await new Promise((ok) => setTimeout(ok, 100));
|
|
275
|
+
}
|
|
276
|
+
run.control.awaiting -= 1;
|
|
277
|
+
const answer = run.control.approvals.get(approvalId) ?? { granted: false, note: "No answer before the timeout" };
|
|
278
|
+
answer.approvalId = approvalId;
|
|
279
|
+
run.control.approvals.delete(approvalId);
|
|
280
|
+
if (answer.granted && answer.amountUsd && run.budgetUsd !== null)
|
|
281
|
+
run.budgetUsd += answer.amountUsd; // an unlimited run stays unlimited
|
|
282
|
+
recordSpan("fences.approval", performance.now() - started, {
|
|
283
|
+
"fences.approval.id": approvalId, "fences.approval.reason": reason, "fences.approval.amount_usd": amountUsd,
|
|
284
|
+
"fences.approval.granted": answer.granted, "fences.approval.by": answer.by, "fences.approval.note": answer.note,
|
|
285
|
+
"fences.approval.granted_usd": answer.amountUsd,
|
|
286
|
+
});
|
|
287
|
+
return answer;
|
|
288
|
+
}
|
package/dist/core.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { FencesClient } from "./client.js";
|
|
2
|
+
import * as control from "./control.js";
|
|
2
3
|
import * as events from "./events.js";
|
|
3
4
|
import { type CustomPrice } from "./pricing.js";
|
|
4
5
|
export interface CheckpointResult {
|
|
@@ -55,6 +56,19 @@ export declare class Run {
|
|
|
55
56
|
lastBreach: string | null;
|
|
56
57
|
exception?: events.ExceptionInfo;
|
|
57
58
|
warned: boolean;
|
|
59
|
+
/** Live control from the dashboard (see control.ts). */
|
|
60
|
+
readonly control: control.Control;
|
|
61
|
+
/** Held by guarded tools that are running. */
|
|
62
|
+
reservedUsd: number;
|
|
63
|
+
private readonly abort;
|
|
64
|
+
/**
|
|
65
|
+
* Aborts when Fences halts the run after a stop it didn't reach a checkpoint for (see
|
|
66
|
+
* stopGraceS). Pass it to fetch and other cancellable work so a stuck run really stops:
|
|
67
|
+
* await fetch(url, { signal: getActiveRun()?.signal });
|
|
68
|
+
*/
|
|
69
|
+
get signal(): AbortSignal;
|
|
70
|
+
/** @internal */
|
|
71
|
+
_abort(reason: unknown): void;
|
|
58
72
|
constructor(agentName: string, budgetUsd: number | null, maxIterations: number, maxDurationMs: number, maxTokens: number, context: Record<string, string>);
|
|
59
73
|
get durationMs(): number;
|
|
60
74
|
}
|
|
@@ -77,11 +91,31 @@ export interface InitOptions {
|
|
|
77
91
|
release?: string;
|
|
78
92
|
/** Sees every outgoing event and returns it (changed or not), or null to drop it. */
|
|
79
93
|
redact?: events.RedactHook;
|
|
94
|
+
/** Seconds between heartbeats reporting live runs (cloud mode). Default 15. */
|
|
95
|
+
heartbeatS?: number;
|
|
96
|
+
/** How long a paused run waits at its checkpoint before stopping with breach "paused". Default 3600. */
|
|
97
|
+
pauseTimeoutS?: number;
|
|
98
|
+
/**
|
|
99
|
+
* If a run doesn't reach a checkpoint within this many seconds of a dashboard stop (say, it's
|
|
100
|
+
* stuck in a tool), the governed call rejects with FencesStop and the run's signal aborts.
|
|
101
|
+
* Default 30; null never does.
|
|
102
|
+
*/
|
|
103
|
+
stopGraceS?: number | null;
|
|
104
|
+
/**
|
|
105
|
+
* Run in the agent's process when a stop arrives from the dashboard (once per run), to
|
|
106
|
+
* contain what Fences can't reach, e.g. revoke a credential the agent holds. Whether it
|
|
107
|
+
* worked, threw or ran past 30 seconds is reported to Fences.
|
|
108
|
+
*/
|
|
109
|
+
onStop?: control.Hook;
|
|
110
|
+
/** The same, when the agent is quarantined (once per quarantine, however many runs it reaches). */
|
|
111
|
+
onQuarantine?: control.Hook;
|
|
80
112
|
}
|
|
81
113
|
/** Call once at startup, before any governed function runs. */
|
|
82
114
|
export declare function init(options?: InitOptions): void;
|
|
83
115
|
/** @internal For tests: point the SDK at a fake server client. */
|
|
84
116
|
export declare function _setClient(c: FencesClient | null): void;
|
|
117
|
+
/** @internal */
|
|
118
|
+
export declare const _client: () => FencesClient | null;
|
|
85
119
|
/**
|
|
86
120
|
* Make each call of fn a governed run with these limits:
|
|
87
121
|
*
|
|
@@ -112,7 +146,12 @@ export interface Extra {
|
|
|
112
146
|
* Never throws for a breach. Outside a governed run it does nothing.
|
|
113
147
|
*/
|
|
114
148
|
export declare function checkpoint(response?: unknown, extra?: Extra): Promise<CheckpointResult>;
|
|
115
|
-
/**
|
|
149
|
+
/** @internal The breach the run is in by local rules, if any. */
|
|
150
|
+
export declare function localBreach(run: Run): string | null;
|
|
151
|
+
/**
|
|
152
|
+
* Wait up to timeoutMs for queued decisions and spans to reach the server, and for onStop and
|
|
153
|
+
* onQuarantine hooks to finish. Call before a serverless handler returns.
|
|
154
|
+
*/
|
|
116
155
|
export declare function flush(timeoutMs?: number): Promise<void>;
|
|
117
156
|
/** Record the agent's reasoning at this step. Does nothing outside a governed run. */
|
|
118
157
|
export declare function logDecision(reasoning: string, action?: string | null): void;
|
package/dist/core.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import { FencesClient } from "./client.js";
|
|
4
|
+
import * as control from "./control.js";
|
|
4
5
|
import { AgentQuarantined, FencesStop } from "./errors.js";
|
|
5
6
|
import * as events from "./events.js";
|
|
6
7
|
import { costOf, setCustomPrices } from "./pricing.js";
|
|
@@ -57,6 +58,19 @@ export class Run {
|
|
|
57
58
|
lastBreach = null;
|
|
58
59
|
exception;
|
|
59
60
|
warned = false;
|
|
61
|
+
/** Live control from the dashboard (see control.ts). */
|
|
62
|
+
control = control.newControl();
|
|
63
|
+
/** Held by guarded tools that are running. */
|
|
64
|
+
reservedUsd = 0;
|
|
65
|
+
abort = new AbortController();
|
|
66
|
+
/**
|
|
67
|
+
* Aborts when Fences halts the run after a stop it didn't reach a checkpoint for (see
|
|
68
|
+
* stopGraceS). Pass it to fetch and other cancellable work so a stuck run really stops:
|
|
69
|
+
* await fetch(url, { signal: getActiveRun()?.signal });
|
|
70
|
+
*/
|
|
71
|
+
get signal() { return this.abort.signal; }
|
|
72
|
+
/** @internal */
|
|
73
|
+
_abort(reason) { this.abort.abort(reason); }
|
|
60
74
|
constructor(agentName, budgetUsd, maxIterations, maxDurationMs, maxTokens, context) {
|
|
61
75
|
this.agentName = agentName;
|
|
62
76
|
this.budgetUsd = budgetUsd;
|
|
@@ -83,15 +97,23 @@ export function init(options = {}) {
|
|
|
83
97
|
failClosed = !!options.failClosed;
|
|
84
98
|
if (localOnly) {
|
|
85
99
|
client = null;
|
|
86
|
-
return;
|
|
87
100
|
}
|
|
88
|
-
if (!options.apiKey) {
|
|
101
|
+
else if (!options.apiKey) {
|
|
89
102
|
throw new Error("apiKey is required unless localOnly is set. Use init({ localOnly: true }) for local usage.");
|
|
90
103
|
}
|
|
91
|
-
|
|
104
|
+
else {
|
|
105
|
+
client = new FencesClient(options.apiKey, options.endpoint ?? "http://localhost:8000");
|
|
106
|
+
}
|
|
107
|
+
control.configure(client, { heartbeatS: options.heartbeatS ?? 15, pauseTimeoutS: options.pauseTimeoutS ?? 3600,
|
|
108
|
+
stopGraceS: options.stopGraceS === undefined ? 30 : options.stopGraceS, onStop: options.onStop, onQuarantine: options.onQuarantine });
|
|
92
109
|
}
|
|
93
110
|
/** @internal For tests: point the SDK at a fake server client. */
|
|
94
|
-
export function _setClient(c) {
|
|
111
|
+
export function _setClient(c) {
|
|
112
|
+
client = c;
|
|
113
|
+
control.configure(c, { heartbeatS: 15, pauseTimeoutS: 3600, stopGraceS: 30 });
|
|
114
|
+
}
|
|
115
|
+
/** @internal */
|
|
116
|
+
export const _client = () => client;
|
|
95
117
|
function requireInit() {
|
|
96
118
|
if (!localOnly && !client) {
|
|
97
119
|
throw new Error("Call agentfences.init() before using Fences. For local usage: init({ localOnly: true })");
|
|
@@ -118,15 +140,26 @@ export function governed(fn, options = {}) {
|
|
|
118
140
|
return async (...args) => {
|
|
119
141
|
requireInit();
|
|
120
142
|
const run = await startRun(name, options);
|
|
143
|
+
// A stop the agent doesn't reach a checkpoint for within stopGraceS halts the call here
|
|
144
|
+
const halted = new Promise((_, reject) => {
|
|
145
|
+
run.control.onCancel = () => {
|
|
146
|
+
const stop = new FencesStop(finalResult(run));
|
|
147
|
+
run._abort(stop);
|
|
148
|
+
reject(stop);
|
|
149
|
+
};
|
|
150
|
+
});
|
|
151
|
+
control.register(run);
|
|
121
152
|
let error, failed = false;
|
|
122
153
|
try {
|
|
123
|
-
|
|
154
|
+
// ponytail: JS can't stop running code; the agent's own work goes on unless it uses run.signal
|
|
155
|
+
return await Promise.race([store.run(run, () => fn(...args)), halted]);
|
|
124
156
|
}
|
|
125
157
|
catch (e) {
|
|
126
158
|
[error, failed] = [e, true];
|
|
127
159
|
throw e;
|
|
128
160
|
}
|
|
129
161
|
finally {
|
|
162
|
+
run.control.onCancel = null; // first, so a late cancel can't land after the call is over
|
|
130
163
|
await endRun(run, ...outcome(run, failed, error));
|
|
131
164
|
}
|
|
132
165
|
};
|
|
@@ -135,7 +168,7 @@ function outcome(run, failed, error) {
|
|
|
135
168
|
if (!failed)
|
|
136
169
|
return [run.lastBreach ? "breached" : "success", null];
|
|
137
170
|
if (error instanceof FencesStop)
|
|
138
|
-
return ["breached", null]; // a
|
|
171
|
+
return ["breached", null]; // halted after a stop it missed (stopGraceS)
|
|
139
172
|
run.exception = events.exceptionInfo(error);
|
|
140
173
|
const e = run.exception;
|
|
141
174
|
return ["error", e.message ? `${e.type}: ${e.message}` : e.type];
|
|
@@ -179,6 +212,7 @@ function noBudget(agentName, how) {
|
|
|
179
212
|
events.warnOnce(`${agentName}:no_budget`, `${agentName} has no budget, so its spend isn't capped. ${how}`);
|
|
180
213
|
}
|
|
181
214
|
async function endRun(run, status, error) {
|
|
215
|
+
control.unregister(run);
|
|
182
216
|
if (!client)
|
|
183
217
|
return;
|
|
184
218
|
// The run must end even if redaction drops its error details
|
|
@@ -186,6 +220,8 @@ async function endRun(run, status, error) {
|
|
|
186
220
|
const body = { status, error: sent?.error ?? null };
|
|
187
221
|
if (sent?.exception)
|
|
188
222
|
body.exception = sent.exception;
|
|
223
|
+
if (status === "breached" && run.lastBreach)
|
|
224
|
+
body.breach = run.lastBreach; // matters for breaches the SDK decided
|
|
189
225
|
await client.endRun(run.runId, body);
|
|
190
226
|
}
|
|
191
227
|
/**
|
|
@@ -214,9 +250,15 @@ export async function checkpoint(response, extra = {}) {
|
|
|
214
250
|
const resp = client ? await client.checkpoint(run.runId, {
|
|
215
251
|
cost_delta_usd: stepCost, tokens_used: stepTokens, iterations: run.iterations, duration_ms: run.durationMs
|
|
216
252
|
}) : null;
|
|
217
|
-
|
|
253
|
+
const result = decide(run, resp);
|
|
254
|
+
if (run.control.paused && !result.breached) { // paused from the dashboard: wait here until resumed
|
|
255
|
+
await control.waitIfPaused(run);
|
|
256
|
+
return finalResult(run);
|
|
257
|
+
}
|
|
258
|
+
return result;
|
|
218
259
|
}
|
|
219
|
-
|
|
260
|
+
/** @internal The breach the run is in by local rules, if any. */
|
|
261
|
+
export function localBreach(run) {
|
|
220
262
|
// Limits trip only once exceeded, same rule as the server; round away float drift (0.02*5 != 0.10)
|
|
221
263
|
if (run.budgetUsd !== null && Math.round(run.costUsd * 1e9) / 1e9 > run.budgetUsd)
|
|
222
264
|
return "budget_exceeded";
|
|
@@ -240,6 +282,7 @@ function decide(run, resp) {
|
|
|
240
282
|
run.lastBreach = localBreach(run);
|
|
241
283
|
}
|
|
242
284
|
else {
|
|
285
|
+
control.apply(run, resp.commands);
|
|
243
286
|
// The server is authoritative: adopt its totals and (on a breach) its current limits
|
|
244
287
|
run.costUsd = resp.spent_usd ?? run.costUsd;
|
|
245
288
|
run.iterations = resp.iterations ?? run.iterations;
|
|
@@ -251,6 +294,15 @@ function decide(run, resp) {
|
|
|
251
294
|
// A fresh breach, or a 409 because the run is still fenced from an earlier one
|
|
252
295
|
run.lastBreach = resp.ok ? null : (resp.breach ?? run.lastBreach ?? "limit_reached");
|
|
253
296
|
}
|
|
297
|
+
return finalResult(run);
|
|
298
|
+
}
|
|
299
|
+
function finalResult(run) {
|
|
300
|
+
if (run.control.stopped) { // a stop from the dashboard sticks, whatever a later reply says
|
|
301
|
+
run.lastBreach = "stopped_by_user";
|
|
302
|
+
run.control.stopSeen = true;
|
|
303
|
+
}
|
|
304
|
+
else if (run.control.pauseExpired)
|
|
305
|
+
run.lastBreach = "paused";
|
|
254
306
|
return run.lastBreach ? breachResult(run.lastBreach, run) : okResult(run.costUsd);
|
|
255
307
|
}
|
|
256
308
|
// ── Decision trail and spans ──────────────────────────────────────────────────
|
|
@@ -287,11 +339,15 @@ async function drain() {
|
|
|
287
339
|
catch { /* never let delivery break the agent */ }
|
|
288
340
|
}
|
|
289
341
|
}
|
|
290
|
-
/**
|
|
342
|
+
/**
|
|
343
|
+
* Wait up to timeoutMs for queued decisions and spans to reach the server, and for onStop and
|
|
344
|
+
* onQuarantine hooks to finish. Call before a serverless handler returns.
|
|
345
|
+
*/
|
|
291
346
|
export async function flush(timeoutMs = 5000) {
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
347
|
+
const deadline = Date.now() + timeoutMs;
|
|
348
|
+
if (draining)
|
|
349
|
+
await Promise.race([draining, new Promise((r) => setTimeout(r, timeoutMs).unref())]);
|
|
350
|
+
await control.drain(Math.max(0, deadline - Date.now()));
|
|
295
351
|
}
|
|
296
352
|
/** Record the agent's reasoning at this step. Does nothing outside a governed run. */
|
|
297
353
|
export function logDecision(reasoning, action = null) {
|
package/dist/errors.d.ts
CHANGED
|
@@ -11,8 +11,19 @@ export declare class AgentQuarantined extends FencesError {
|
|
|
11
11
|
constructor(agentName: string, detail?: string);
|
|
12
12
|
}
|
|
13
13
|
/**
|
|
14
|
-
* Thrown
|
|
15
|
-
*
|
|
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
|
|
21
|
-
*
|
|
20
|
+
* Thrown instead of running a guarded tool (tool(fn, { guard: true })) that Fences didn't
|
|
21
|
+
* allow: the run is stopped, over budget, unapproved or quarantined, or Fences couldn't be
|
|
22
|
+
* reached. The tool's code never ran. reason says why; breachType is set when a limit is the cause.
|
|
23
|
+
*/
|
|
24
|
+
export class ToolBlocked extends FencesError {
|
|
25
|
+
tool;
|
|
26
|
+
reason;
|
|
27
|
+
breachType;
|
|
28
|
+
constructor(tool, reason, breachType = null) {
|
|
29
|
+
super(`Fences blocked ${tool}: ${reason}`);
|
|
30
|
+
this.tool = tool;
|
|
31
|
+
this.reason = reason;
|
|
32
|
+
this.breachType = breachType;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Thrown when a run is halted from outside the agent's own code: a governed call stuck after a
|
|
37
|
+
* dashboard stop (see stopGraceS). Carries the same fields as a breached CheckpointResult.
|
|
22
38
|
*/
|
|
23
39
|
export class FencesStop extends FencesError {
|
|
24
40
|
result;
|
package/dist/index.d.ts
CHANGED
|
@@ -3,7 +3,10 @@ export type { CheckpointResult, InitOptions, Limits, Extra, Span } from "./core.
|
|
|
3
3
|
export { context } from "./events.js";
|
|
4
4
|
export type { RedactHook, Event } from "./events.js";
|
|
5
5
|
export { tool } from "./tools.js";
|
|
6
|
+
export type { GuardedToolOptions } from "./tools.js";
|
|
7
|
+
export { requestApproval } from "./control.js";
|
|
8
|
+
export type { Approval, ControlEvent, Hook } from "./control.js";
|
|
6
9
|
export { priceOf } from "./pricing.js";
|
|
7
10
|
export type { CustomPrice } from "./pricing.js";
|
|
8
|
-
export { FencesError, FencesAuthError, AgentQuarantined, FencesStop } from "./errors.js";
|
|
11
|
+
export { FencesError, FencesAuthError, AgentQuarantined, FencesStop, ToolBlocked } from "./errors.js";
|
|
9
12
|
export { VERSION } from "./version.js";
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { init, governed, checkpoint, logDecision, flush, getActiveRun, Run } from "./core.js";
|
|
2
2
|
export { context } from "./events.js";
|
|
3
3
|
export { tool } from "./tools.js";
|
|
4
|
+
export { requestApproval } from "./control.js";
|
|
4
5
|
export { priceOf } from "./pricing.js";
|
|
5
|
-
export { FencesError, FencesAuthError, AgentQuarantined, FencesStop } from "./errors.js";
|
|
6
|
+
export { FencesError, FencesAuthError, AgentQuarantined, FencesStop, ToolBlocked } from "./errors.js";
|
|
6
7
|
export { VERSION } from "./version.js";
|
package/dist/prices.generated.js
CHANGED
|
@@ -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-
|
|
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 {
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import * as control from "./control.js";
|
|
3
|
+
import { _client, getActiveRun, localBreach } from "./core.js";
|
|
4
|
+
import { ToolBlocked } from "./errors.js";
|
|
5
|
+
import * as events from "./events.js";
|
|
2
6
|
import { MAX_ARGS, recordToolCall } from "./spans.js";
|
|
3
7
|
/** `{"query":"cats","limit":5}`: the arguments as compact JSON (one object argument on its own), capped. */
|
|
4
8
|
export function argsSummary(args) {
|
|
@@ -11,18 +15,117 @@ export function argsSummary(args) {
|
|
|
11
15
|
}
|
|
12
16
|
return text.length <= MAX_ARGS ? text : text.slice(0, MAX_ARGS - 1) + "…";
|
|
13
17
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
// ── Guards ────────────────────────────────────────────────────────────────────
|
|
19
|
+
function block(run, name, reason, breach = null) {
|
|
20
|
+
if (run.control.stopped)
|
|
21
|
+
run.control.stopSeen = true; // the agent learns of the stop here, so don't cancel it
|
|
22
|
+
throw new ToolBlocked(name, reason, breach);
|
|
23
|
+
}
|
|
24
|
+
const fits = (run, amount) => run.budgetUsd === null || Math.round((run.costUsd + run.reservedUsd + amount) * 1e9) / 1e9 <= run.budgetUsd;
|
|
25
|
+
async function approve(run, name, summary, amount) {
|
|
26
|
+
const answer = await control.requestApproval(`${name}(${summary})`, { amountUsd: amount || undefined });
|
|
27
|
+
if (!answer.granted)
|
|
28
|
+
block(run, name, answer.note ? `not approved: ${answer.note}` : "not approved");
|
|
29
|
+
return answer.approvalId;
|
|
30
|
+
}
|
|
31
|
+
/** Check a guarded call and reserve its amount; returns the guard id, or throws ToolBlocked. */
|
|
32
|
+
async function open(run, name, summary, amount, needsApproval) {
|
|
33
|
+
if (run.control.quarantined)
|
|
34
|
+
block(run, name, "the agent is quarantined in Fences"); // now, not when the quarantine's pause ends
|
|
35
|
+
if (run.control.paused)
|
|
36
|
+
await control.waitIfPaused(run);
|
|
37
|
+
if (run.control.pauseExpired)
|
|
38
|
+
block(run, name, "the run was paused from the Fences dashboard and not resumed in time", "paused");
|
|
39
|
+
let approvalId = needsApproval ? await approve(run, name, summary, amount) : null;
|
|
40
|
+
if (run.control.stopped)
|
|
41
|
+
block(run, name, "the run was stopped from the Fences dashboard", "stopped_by_user");
|
|
42
|
+
const client = _client();
|
|
43
|
+
if (!client) { // local mode: the same rules, applied here
|
|
44
|
+
const breach = run.lastBreach ?? localBreach(run);
|
|
45
|
+
if (breach)
|
|
46
|
+
block(run, name, `the run is over a limit (${breach})`, breach);
|
|
47
|
+
if (!fits(run, amount))
|
|
48
|
+
block(run, name, `$${amount.toFixed(2)} more would take the run past its $${run.budgetUsd.toFixed(2)} budget`, "budget_exceeded");
|
|
49
|
+
run.reservedUsd += amount;
|
|
50
|
+
return randomUUID();
|
|
51
|
+
}
|
|
52
|
+
// Check against what this process already holds before asking the server, which can't see it; reserve until it answers
|
|
53
|
+
if (!fits(run, amount))
|
|
54
|
+
block(run, name, `$${amount.toFixed(2)} more could take the run past its $${run.budgetUsd.toFixed(2)} budget`, "budget_exceeded");
|
|
55
|
+
run.reservedUsd += amount;
|
|
56
|
+
try {
|
|
57
|
+
const sent = events.redact({ type: "guard", tool: name, arguments: summary }) ?? {}; // the check goes ahead without them
|
|
58
|
+
let resp = {}; // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
59
|
+
for (let attempt = 0; attempt < 2; attempt++) { // once more after an approval the server asked for
|
|
60
|
+
const guardId = randomUUID();
|
|
61
|
+
resp = await client.guard(run.runId, { guard_id: guardId, phase: "check", tool: name, arguments: sent.arguments ?? null,
|
|
62
|
+
amount_usd: amount, approval_id: approvalId });
|
|
63
|
+
control.apply(run, resp.commands);
|
|
64
|
+
if (resp.allow === true)
|
|
65
|
+
return guardId;
|
|
66
|
+
if (!(resp.needs_approval && approvalId === null))
|
|
67
|
+
break;
|
|
68
|
+
approvalId = await approve(run, name, summary, amount);
|
|
69
|
+
}
|
|
70
|
+
if (resp.unsupported) {
|
|
71
|
+
control.unsupported("guard", "guarded tools", "they are blocked");
|
|
72
|
+
block(run, name, "this Fences server doesn't support guarded tools");
|
|
73
|
+
}
|
|
74
|
+
if (resp.network_error || !("allow" in resp))
|
|
75
|
+
block(run, name, "couldn't reach Fences to check this call"); // no answer: fail closed
|
|
76
|
+
block(run, name, resp.reason ?? "not allowed", resp.breach ?? null);
|
|
77
|
+
}
|
|
78
|
+
catch (e) {
|
|
79
|
+
run.reservedUsd -= amount;
|
|
80
|
+
throw e;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/** Release the reservation and charge the run: in full unless the tool threw (a timeout may have done it). */
|
|
84
|
+
async function settle(run, guardId, amount, error, failed) {
|
|
85
|
+
const outcome = !failed ? "ok" : error?.name === "TimeoutError" ? "unknown" : "error";
|
|
86
|
+
const charged = outcome === "error" ? 0 : amount;
|
|
87
|
+
run.reservedUsd -= amount;
|
|
88
|
+
run.costUsd += charged;
|
|
89
|
+
await _client()?.guard(run.runId, { guard_id: guardId, phase: "settle", outcome, cost_usd: charged });
|
|
90
|
+
}
|
|
91
|
+
function timed(p, timeoutS, name) {
|
|
92
|
+
if (timeoutS === undefined)
|
|
93
|
+
return p;
|
|
94
|
+
let timer;
|
|
95
|
+
const late = new Promise((_, reject) => {
|
|
96
|
+
timer = setTimeout(() => reject(new DOMException(`${name} took longer than ${timeoutS}s`, "TimeoutError")), timeoutS * 1000);
|
|
97
|
+
});
|
|
98
|
+
return Promise.race([p, late]).finally(() => clearTimeout(timer));
|
|
99
|
+
}
|
|
24
100
|
export function tool(fn, options = {}) {
|
|
25
101
|
const name = options.name ?? (fn.name || "tool");
|
|
102
|
+
const guarded = !!options.guard || options.costUsd !== undefined || !!options.approve;
|
|
103
|
+
if (guarded || options.timeoutS !== undefined) {
|
|
104
|
+
return async (...args) => {
|
|
105
|
+
const run = getActiveRun();
|
|
106
|
+
if (!run)
|
|
107
|
+
return timed(Promise.resolve().then(() => fn(...args)), options.timeoutS, name);
|
|
108
|
+
const summary = argsSummary(args), started = performance.now();
|
|
109
|
+
let gate = null;
|
|
110
|
+
try {
|
|
111
|
+
if (guarded) {
|
|
112
|
+
const c = options.costUsd, amount = Number((typeof c === "function" ? c(...args) : c) ?? 0);
|
|
113
|
+
gate = [await open(run, name, summary, amount, !!options.approve), amount];
|
|
114
|
+
}
|
|
115
|
+
const out = await timed(Promise.resolve().then(() => fn(...args)), options.timeoutS, name);
|
|
116
|
+
recordToolCall(name, summary, started);
|
|
117
|
+
if (gate)
|
|
118
|
+
await settle(run, ...gate, undefined, false);
|
|
119
|
+
return out;
|
|
120
|
+
}
|
|
121
|
+
catch (e) {
|
|
122
|
+
recordToolCall(name, summary, started, e);
|
|
123
|
+
if (gate)
|
|
124
|
+
await settle(run, ...gate, e, true);
|
|
125
|
+
throw e;
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
}
|
|
26
129
|
return (...args) => {
|
|
27
130
|
if (!getActiveRun())
|
|
28
131
|
return fn(...args);
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** The one place the version lives; sent to Fences on every request. Keep in step with package.json. */
|
|
2
|
-
export declare const VERSION = "0.
|
|
2
|
+
export declare const VERSION = "0.5.0";
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** The one place the version lives; sent to Fences on every request. Keep in step with package.json. */
|
|
2
|
-
export const VERSION = "0.
|
|
2
|
+
export const VERSION = "0.5.0";
|
package/package.json
CHANGED