patchwork-os 0.2.0-beta.10.canary.98 → 0.2.0-beta.11
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 +20 -1
- package/dist/adapters/grok.js +6 -1
- package/dist/adapters/grok.js.map +1 -1
- package/dist/approvalHttp.js +61 -66
- package/dist/approvalHttp.js.map +1 -1
- package/dist/bridge.js +3 -2
- package/dist/bridge.js.map +1 -1
- package/dist/claudeOrchestrator.js +15 -0
- package/dist/claudeOrchestrator.js.map +1 -1
- package/dist/commands/connect.d.ts +47 -0
- package/dist/commands/connect.js +419 -0
- package/dist/commands/connect.js.map +1 -0
- package/dist/commands/patchworkInit.d.ts +7 -0
- package/dist/commands/patchworkInit.js +26 -0
- package/dist/commands/patchworkInit.js.map +1 -1
- package/dist/commands/recipe.d.ts +14 -0
- package/dist/commands/recipe.js +51 -0
- package/dist/commands/recipe.js.map +1 -1
- package/dist/commands/recipeInstall.js +15 -0
- package/dist/commands/recipeInstall.js.map +1 -1
- package/dist/connectors/postgres.d.ts +7 -0
- package/dist/connectors/postgres.js +16 -2
- package/dist/connectors/postgres.js.map +1 -1
- package/dist/connectors/snowflake.js +6 -0
- package/dist/connectors/snowflake.js.map +1 -1
- package/dist/connectors/tokenStorage.d.ts +8 -0
- package/dist/connectors/tokenStorage.js +42 -3
- package/dist/connectors/tokenStorage.js.map +1 -1
- package/dist/drivers/claude/api.js +15 -2
- package/dist/drivers/claude/api.js.map +1 -1
- package/dist/drivers/claude/subprocess.js +1 -29
- package/dist/drivers/claude/subprocess.js.map +1 -1
- package/dist/drivers/gemini/index.js +9 -5
- package/dist/drivers/gemini/index.js.map +1 -1
- package/dist/drivers/openai/index.js +29 -4
- package/dist/drivers/openai/index.js.map +1 -1
- package/dist/drivers/outputCap.d.ts +27 -0
- package/dist/drivers/outputCap.js +50 -0
- package/dist/drivers/outputCap.js.map +1 -0
- package/dist/featureFlags.d.ts +9 -0
- package/dist/featureFlags.js +16 -0
- package/dist/featureFlags.js.map +1 -1
- package/dist/fp/commandDescription.d.ts +6 -0
- package/dist/fp/commandDescription.js +4 -4
- package/dist/fp/commandDescription.js.map +1 -1
- package/dist/fp/policyParser.js +13 -7
- package/dist/fp/policyParser.js.map +1 -1
- package/dist/haltPushDispatch.js +7 -22
- package/dist/haltPushDispatch.js.map +1 -1
- package/dist/index.js +148 -0
- package/dist/index.js.map +1 -1
- package/dist/mcpRoutes.js +1 -1
- package/dist/mcpRoutes.js.map +1 -1
- package/dist/oauth.d.ts +13 -0
- package/dist/oauth.js +34 -1
- package/dist/oauth.js.map +1 -1
- package/dist/recipeOrchestration.js +10 -0
- package/dist/recipeOrchestration.js.map +1 -1
- package/dist/recipeRoutes.d.ts +39 -0
- package/dist/recipeRoutes.js +214 -12
- package/dist/recipeRoutes.js.map +1 -1
- package/dist/recipes/agentExecutor.d.ts +20 -0
- package/dist/recipes/agentExecutor.js +32 -10
- package/dist/recipes/agentExecutor.js.map +1 -1
- package/dist/recipes/chainedRunner.d.ts +41 -2
- package/dist/recipes/chainedRunner.js +134 -6
- package/dist/recipes/chainedRunner.js.map +1 -1
- package/dist/recipes/connectorPreflight.js +30 -0
- package/dist/recipes/connectorPreflight.js.map +1 -1
- package/dist/recipes/haltCategory.d.ts +5 -0
- package/dist/recipes/haltCategory.js +7 -1
- package/dist/recipes/haltCategory.js.map +1 -1
- package/dist/recipes/judgeVerdict.d.ts +10 -0
- package/dist/recipes/judgeVerdict.js +10 -0
- package/dist/recipes/judgeVerdict.js.map +1 -1
- package/dist/recipes/names.d.ts +5 -0
- package/dist/recipes/names.js +10 -5
- package/dist/recipes/names.js.map +1 -1
- package/dist/recipes/parser.js +32 -9
- package/dist/recipes/parser.js.map +1 -1
- package/dist/recipes/pricing/costRouter.d.ts +43 -0
- package/dist/recipes/pricing/costRouter.js +44 -0
- package/dist/recipes/pricing/costRouter.js.map +1 -0
- package/dist/recipes/pricing/priceTable.d.ts +76 -0
- package/dist/recipes/pricing/priceTable.js +144 -0
- package/dist/recipes/pricing/priceTable.js.map +1 -0
- package/dist/recipes/runBudget.d.ts +82 -32
- package/dist/recipes/runBudget.js +182 -49
- package/dist/recipes/runBudget.js.map +1 -1
- package/dist/recipes/schema.d.ts +25 -4
- package/dist/recipes/schemaGenerator.d.ts +9 -0
- package/dist/recipes/schemaGenerator.js +386 -75
- package/dist/recipes/schemaGenerator.js.map +1 -1
- package/dist/recipes/simulation/aggregateRunRisk.d.ts +43 -0
- package/dist/recipes/simulation/aggregateRunRisk.js +117 -0
- package/dist/recipes/simulation/aggregateRunRisk.js.map +1 -0
- package/dist/recipes/simulation/costProjector.d.ts +32 -0
- package/dist/recipes/simulation/costProjector.js +194 -0
- package/dist/recipes/simulation/costProjector.js.map +1 -0
- package/dist/recipes/simulation/sideEffects.d.ts +32 -0
- package/dist/recipes/simulation/sideEffects.js +62 -0
- package/dist/recipes/simulation/sideEffects.js.map +1 -0
- package/dist/recipes/simulation/simulate.d.ts +36 -0
- package/dist/recipes/simulation/simulate.js +264 -0
- package/dist/recipes/simulation/simulate.js.map +1 -0
- package/dist/recipes/simulation/simulateMockedRun.d.ts +52 -0
- package/dist/recipes/simulation/simulateMockedRun.js +72 -0
- package/dist/recipes/simulation/simulateMockedRun.js.map +1 -0
- package/dist/recipes/simulation/synthesizeMockedOutputs.d.ts +31 -0
- package/dist/recipes/simulation/synthesizeMockedOutputs.js +50 -0
- package/dist/recipes/simulation/synthesizeMockedOutputs.js.map +1 -0
- package/dist/recipes/simulation/types.d.ts +198 -0
- package/dist/recipes/simulation/types.js +30 -0
- package/dist/recipes/simulation/types.js.map +1 -0
- package/dist/recipes/stepObservation.d.ts +12 -0
- package/dist/recipes/stepObservation.js +21 -1
- package/dist/recipes/stepObservation.js.map +1 -1
- package/dist/recipes/tools/airtable.d.ts +15 -0
- package/dist/recipes/tools/airtable.js +240 -0
- package/dist/recipes/tools/airtable.js.map +1 -0
- package/dist/recipes/tools/caldiy.d.ts +13 -0
- package/dist/recipes/tools/caldiy.js +214 -0
- package/dist/recipes/tools/caldiy.js.map +1 -0
- package/dist/recipes/tools/circleci.d.ts +10 -0
- package/dist/recipes/tools/circleci.js +204 -0
- package/dist/recipes/tools/circleci.js.map +1 -0
- package/dist/recipes/tools/cloudflare.d.ts +13 -0
- package/dist/recipes/tools/cloudflare.js +211 -0
- package/dist/recipes/tools/cloudflare.js.map +1 -0
- package/dist/recipes/tools/docs.d.ts +18 -0
- package/dist/recipes/tools/docs.js +94 -0
- package/dist/recipes/tools/docs.js.map +1 -0
- package/dist/recipes/tools/elasticsearch.d.ts +11 -0
- package/dist/recipes/tools/elasticsearch.js +156 -0
- package/dist/recipes/tools/elasticsearch.js.map +1 -0
- package/dist/recipes/tools/figma.d.ts +12 -0
- package/dist/recipes/tools/figma.js +194 -0
- package/dist/recipes/tools/figma.js.map +1 -0
- package/dist/recipes/tools/grafana.d.ts +11 -0
- package/dist/recipes/tools/grafana.js +216 -0
- package/dist/recipes/tools/grafana.js.map +1 -0
- package/dist/recipes/tools/http.d.ts +1 -1
- package/dist/recipes/tools/http.js +10 -28
- package/dist/recipes/tools/http.js.map +1 -1
- package/dist/recipes/tools/index.d.ts +26 -0
- package/dist/recipes/tools/index.js +26 -0
- package/dist/recipes/tools/index.js.map +1 -1
- package/dist/recipes/tools/monday.d.ts +22 -0
- package/dist/recipes/tools/monday.js +242 -0
- package/dist/recipes/tools/monday.js.map +1 -0
- package/dist/recipes/tools/obsidian.d.ts +15 -0
- package/dist/recipes/tools/obsidian.js +172 -0
- package/dist/recipes/tools/obsidian.js.map +1 -0
- package/dist/recipes/tools/paystack.d.ts +11 -0
- package/dist/recipes/tools/paystack.js +211 -0
- package/dist/recipes/tools/paystack.js.map +1 -0
- package/dist/recipes/tools/pipedrive.d.ts +16 -0
- package/dist/recipes/tools/pipedrive.js +233 -0
- package/dist/recipes/tools/pipedrive.js.map +1 -0
- package/dist/recipes/tools/postgres.d.ts +15 -0
- package/dist/recipes/tools/postgres.js +185 -0
- package/dist/recipes/tools/postgres.js.map +1 -0
- package/dist/recipes/tools/posthog.d.ts +16 -0
- package/dist/recipes/tools/posthog.js +219 -0
- package/dist/recipes/tools/posthog.js.map +1 -0
- package/dist/recipes/tools/redis.d.ts +9 -0
- package/dist/recipes/tools/redis.js +140 -0
- package/dist/recipes/tools/redis.js.map +1 -0
- package/dist/recipes/tools/resend.d.ts +8 -0
- package/dist/recipes/tools/resend.js +153 -0
- package/dist/recipes/tools/resend.js.map +1 -0
- package/dist/recipes/tools/salesforce.d.ts +16 -0
- package/dist/recipes/tools/salesforce.js +184 -0
- package/dist/recipes/tools/salesforce.js.map +1 -0
- package/dist/recipes/tools/sendgrid.d.ts +9 -0
- package/dist/recipes/tools/sendgrid.js +174 -0
- package/dist/recipes/tools/sendgrid.js.map +1 -0
- package/dist/recipes/tools/shopify.d.ts +16 -0
- package/dist/recipes/tools/shopify.js +265 -0
- package/dist/recipes/tools/shopify.js.map +1 -0
- package/dist/recipes/tools/snowflake.d.ts +16 -0
- package/dist/recipes/tools/snowflake.js +173 -0
- package/dist/recipes/tools/snowflake.js.map +1 -0
- package/dist/recipes/tools/stripe.js +13 -12
- package/dist/recipes/tools/stripe.js.map +1 -1
- package/dist/recipes/tools/supabase.d.ts +9 -0
- package/dist/recipes/tools/supabase.js +132 -0
- package/dist/recipes/tools/supabase.js.map +1 -0
- package/dist/recipes/tools/todoist.d.ts +15 -0
- package/dist/recipes/tools/todoist.js +227 -0
- package/dist/recipes/tools/todoist.js.map +1 -0
- package/dist/recipes/tools/twilio.d.ts +11 -0
- package/dist/recipes/tools/twilio.js +180 -0
- package/dist/recipes/tools/twilio.js.map +1 -0
- package/dist/recipes/tools/vercel.d.ts +9 -0
- package/dist/recipes/tools/vercel.js +145 -0
- package/dist/recipes/tools/vercel.js.map +1 -0
- package/dist/recipes/tools/webflow.d.ts +11 -0
- package/dist/recipes/tools/webflow.js +243 -0
- package/dist/recipes/tools/webflow.js.map +1 -0
- package/dist/recipes/tools/woocommerce.d.ts +18 -0
- package/dist/recipes/tools/woocommerce.js +259 -0
- package/dist/recipes/tools/woocommerce.js.map +1 -0
- package/dist/recipes/tools/wrapConnectorExecute.d.ts +25 -0
- package/dist/recipes/tools/wrapConnectorExecute.js +37 -0
- package/dist/recipes/tools/wrapConnectorExecute.js.map +1 -0
- package/dist/recipes/validation.js +264 -0
- package/dist/recipes/validation.js.map +1 -1
- package/dist/recipes/yamlRunner.d.ts +112 -3
- package/dist/recipes/yamlRunner.js +482 -19
- package/dist/recipes/yamlRunner.js.map +1 -1
- package/dist/runLog.d.ts +46 -0
- package/dist/runLog.js +4 -0
- package/dist/runLog.js.map +1 -1
- package/dist/schemas/recipe.v1.json +217 -11
- package/dist/server.d.ts +3 -0
- package/dist/server.js +4 -0
- package/dist/server.js.map +1 -1
- package/dist/streamableHttp.js +3 -2
- package/dist/streamableHttp.js.map +1 -1
- package/dist/tools/gitWrite.js +5 -1
- package/dist/tools/gitWrite.js.map +1 -1
- package/dist/tools/terminal.js +15 -49
- package/dist/tools/terminal.js.map +1 -1
- package/dist/transport.d.ts +1 -0
- package/dist/transport.js +7 -0
- package/dist/transport.js.map +1 -1
- package/package.json +3 -3
- package/scripts/mcp-stdio-shim.cjs +218 -84
- package/templates/recipes/incident-to-pr.yaml +187 -0
|
@@ -1,30 +1,29 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Per-recipe
|
|
2
|
+
* Per-recipe budget — PR2b (tokens) + cost-routing Phase 3 (USD).
|
|
3
3
|
*
|
|
4
|
-
* Built on PR2a's `AgentResult.usage` plumbing. The runner constructs
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* audit trail).
|
|
4
|
+
* Built on PR2a's `AgentResult.usage` plumbing. The runner constructs one
|
|
5
|
+
* `RunBudget` per recipe execution; agent steps consult it before dispatch
|
|
6
|
+
* (admission) and reconcile actual consumption after the call returns. On
|
|
7
|
+
* breach the run halts at the *next* admission check (we never retroactively
|
|
8
|
+
* fail a step that succeeded — the user's tokens/dollars are already spent;
|
|
9
|
+
* halting after-the-fact would just confuse the audit trail).
|
|
11
10
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
11
|
+
* Two independent caps, either or both:
|
|
12
|
+
* - `tokensMax` — cumulative input + output tokens.
|
|
13
|
+
* - `usdMax` — cumulative USD, priced from token usage via the Phase 2
|
|
14
|
+
* price table (`costUsd`).
|
|
16
15
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* - `wallClockMs` (orthogonal to tokens, separate accounting)
|
|
16
|
+
* Subscription drivers (Claude CLI, provider subprocess CLIs) don't surface
|
|
17
|
+
* per-call token counts, and a model with no price-table entry can't be
|
|
18
|
+
* costed. Both cases FAIL OPEN: `RunBudget` records a deduped one-time
|
|
19
|
+
* warning and never blocks. So a USD cap enforces for *measured + priced*
|
|
20
|
+
* (API) drivers and is a no-op-with-notice for everything else — never a
|
|
21
|
+
* silent or a surprise halt on the token-blind default driver. (The opt-in
|
|
22
|
+
* estimate-the-unmeasured path is a planned follow-up; absent it, unmeasured
|
|
23
|
+
* = fail-open, which is the conservative default the design calls for.)
|
|
26
24
|
*/
|
|
27
|
-
import type
|
|
25
|
+
import { type AgentUsage } from "./agentExecutor.js";
|
|
26
|
+
import { type PriceTable } from "./pricing/priceTable.js";
|
|
28
27
|
import type { BudgetPolicy } from "./schema.js";
|
|
29
28
|
export interface BudgetAdmission {
|
|
30
29
|
/** True = step may proceed. False = budget exhausted. */
|
|
@@ -36,35 +35,86 @@ export interface BudgetTotals {
|
|
|
36
35
|
inputTokens: number;
|
|
37
36
|
outputTokens: number;
|
|
38
37
|
total: number;
|
|
39
|
-
/** Remaining tokens before breach. Undefined when no limit is set. */
|
|
38
|
+
/** Remaining tokens before breach. Undefined when no token limit is set. */
|
|
40
39
|
remaining?: number;
|
|
41
|
-
/**
|
|
40
|
+
/** Cumulative measured USD. Undefined when no USD limit is set. */
|
|
41
|
+
usd?: number;
|
|
42
|
+
/** Remaining USD before breach. Undefined when no USD limit is set. */
|
|
43
|
+
usdRemaining?: number;
|
|
44
|
+
/**
|
|
45
|
+
* Notional list-price USD estimated for unmeasured (subscription) calls when
|
|
46
|
+
* `estimateUnmeasured` is on. A label ONLY — never counted toward `usd` or
|
|
47
|
+
* `usdMax`, never halts. Present only when > 0.
|
|
48
|
+
*/
|
|
49
|
+
usdEstimated?: number;
|
|
50
|
+
/** True once total tokens >= tokensMax (false when no token limit). */
|
|
42
51
|
breached: boolean;
|
|
52
|
+
/** True once measured usd >= usdMax (false when no USD limit). */
|
|
53
|
+
usdBreached: boolean;
|
|
43
54
|
/** Whether the configured policy halts on breach (vs warn). */
|
|
44
55
|
haltOnBreach: boolean;
|
|
45
56
|
}
|
|
46
57
|
export declare class RunBudget {
|
|
47
58
|
private readonly tokensMax?;
|
|
59
|
+
private readonly usdMax?;
|
|
48
60
|
private readonly haltOnBreach;
|
|
61
|
+
private readonly estimateUnmeasured;
|
|
62
|
+
private readonly priceTable?;
|
|
49
63
|
private inputTokens;
|
|
50
64
|
private outputTokens;
|
|
51
|
-
|
|
52
|
-
|
|
65
|
+
private usdSpent;
|
|
66
|
+
/** Notional list-price USD for unmeasured calls (warn-only; never enforced). */
|
|
67
|
+
private usdEstimated;
|
|
68
|
+
/** Dedup keys for one-time warnings (unmeasured driver, breach, etc.). */
|
|
69
|
+
private readonly warnedKeys;
|
|
53
70
|
/** Free-form warnings surfaced via the run log. */
|
|
54
71
|
private readonly warningList;
|
|
55
|
-
|
|
72
|
+
/**
|
|
73
|
+
* @param policy recipe budget block.
|
|
74
|
+
* @param priceTable optional injected table (tests); otherwise loaded once
|
|
75
|
+
* here when a USD cap is set (file/env override aware).
|
|
76
|
+
*/
|
|
77
|
+
constructor(policy?: BudgetPolicy, priceTable?: PriceTable);
|
|
78
|
+
/** True when neither cap is configured — reconcile/admit are no-ops. */
|
|
79
|
+
private get hasBudget();
|
|
56
80
|
/** Cheap admission check before dispatching an agent step. */
|
|
57
81
|
admit(): BudgetAdmission;
|
|
58
82
|
/**
|
|
59
|
-
* Record the actual usage reported by an agent call. `usage`
|
|
60
|
-
*
|
|
61
|
-
*
|
|
83
|
+
* Record the actual usage reported by an agent call. `usage` is undefined
|
|
84
|
+
* when the driver doesn't surface token counts — record the driver once and
|
|
85
|
+
* continue (fail-open). `model` is the resolved model (from `servedBy`) used
|
|
86
|
+
* to price USD; an unpriced model fails open with a one-time notice.
|
|
62
87
|
*/
|
|
63
|
-
reconcile(driver: string, usage: AgentUsage | undefined
|
|
88
|
+
reconcile(driver: string, usage: AgentUsage | undefined, model?: string, estimate?: {
|
|
89
|
+
inputChars: number;
|
|
90
|
+
outputChars: number;
|
|
91
|
+
}): void;
|
|
64
92
|
/** Snapshot of current totals + breach state. */
|
|
65
93
|
totals(): BudgetTotals;
|
|
66
94
|
/** Warnings collected so the runner can surface them in the run log. */
|
|
67
95
|
warnings(): string[];
|
|
96
|
+
/**
|
|
97
|
+
* Run-completion warnings: the live `warnings()` plus a one-line ≈$ summary
|
|
98
|
+
* of the notional cost estimated for unmeasured calls (only when
|
|
99
|
+
* `estimateUnmeasured` produced one). Use at run end; `warnings()` is the
|
|
100
|
+
* live per-driver list without the (end-of-run-only) summary.
|
|
101
|
+
*/
|
|
102
|
+
finalWarnings(): string[];
|
|
103
|
+
/**
|
|
104
|
+
* USD remaining before the cap, or undefined when no USD cap is set.
|
|
105
|
+
* Cost-aware routing (Phase 4) uses this to decide when to downshift.
|
|
106
|
+
*/
|
|
107
|
+
remainingUsd(): number | undefined;
|
|
108
|
+
/**
|
|
109
|
+
* Pre-dispatch USD estimate for a prospective (driver, model) call, or
|
|
110
|
+
* undefined when it would NOT be USD-enforced: no USD cap, a non-billable
|
|
111
|
+
* driver (local / subscription), or an unpriced model. Mirrors exactly the
|
|
112
|
+
* enforcement rule in `reconcile`, so a candidate the router treats as
|
|
113
|
+
* "free" is precisely one `reconcile` would not charge.
|
|
114
|
+
*/
|
|
115
|
+
quoteUsd(driver: string | undefined, model: string | undefined, inputTokens: number, outputTokens: number): number | undefined;
|
|
116
|
+
private pushOnce;
|
|
68
117
|
private totalTokens;
|
|
69
|
-
private
|
|
118
|
+
private tokenBreached;
|
|
119
|
+
private usdMaxBreached;
|
|
70
120
|
}
|
|
@@ -1,83 +1,149 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Per-recipe
|
|
2
|
+
* Per-recipe budget — PR2b (tokens) + cost-routing Phase 3 (USD).
|
|
3
3
|
*
|
|
4
|
-
* Built on PR2a's `AgentResult.usage` plumbing. The runner constructs
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* audit trail).
|
|
4
|
+
* Built on PR2a's `AgentResult.usage` plumbing. The runner constructs one
|
|
5
|
+
* `RunBudget` per recipe execution; agent steps consult it before dispatch
|
|
6
|
+
* (admission) and reconcile actual consumption after the call returns. On
|
|
7
|
+
* breach the run halts at the *next* admission check (we never retroactively
|
|
8
|
+
* fail a step that succeeded — the user's tokens/dollars are already spent;
|
|
9
|
+
* halting after-the-fact would just confuse the audit trail).
|
|
11
10
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
11
|
+
* Two independent caps, either or both:
|
|
12
|
+
* - `tokensMax` — cumulative input + output tokens.
|
|
13
|
+
* - `usdMax` — cumulative USD, priced from token usage via the Phase 2
|
|
14
|
+
* price table (`costUsd`).
|
|
16
15
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
|
|
16
|
+
* Subscription drivers (Claude CLI, provider subprocess CLIs) don't surface
|
|
17
|
+
* per-call token counts, and a model with no price-table entry can't be
|
|
18
|
+
* costed. Both cases FAIL OPEN: `RunBudget` records a deduped one-time
|
|
19
|
+
* warning and never blocks. So a USD cap enforces for *measured + priced*
|
|
20
|
+
* (API) drivers and is a no-op-with-notice for everything else — never a
|
|
21
|
+
* silent or a surprise halt on the token-blind default driver. (The opt-in
|
|
22
|
+
* estimate-the-unmeasured path is a planned follow-up; absent it, unmeasured
|
|
23
|
+
* = fail-open, which is the conservative default the design calls for.)
|
|
24
|
+
*/
|
|
25
|
+
import { DEFAULT_MODEL } from "./agentExecutor.js";
|
|
26
|
+
import { costUsd, loadPriceTable, } from "./pricing/priceTable.js";
|
|
27
|
+
/**
|
|
28
|
+
* Drivers that incur real, metered, per-token API billing — the ONLY ones a
|
|
29
|
+
* USD cap is enforced against. Includes the metered Gemini API driver
|
|
30
|
+
* (stamped "gemini"); the subscription/CLI subprocess drivers report no usage
|
|
31
|
+
* and never reach the pricing path; `local`
|
|
32
|
+
* (self-hosted Ollama / LM Studio) DOES report usage but costs no real money,
|
|
33
|
+
* so it must not be priced at notional API rates and halted on spend that
|
|
34
|
+
* never happened. Anything not in this set fails open with a one-time notice.
|
|
26
35
|
*/
|
|
36
|
+
const BILLABLE_DRIVERS = new Set(["anthropic", "openai", "grok", "gemini"]);
|
|
37
|
+
/** Rough chars-per-token used for the opt-in unmeasured-driver USD estimate. */
|
|
38
|
+
const ESTIMATE_CHARS_PER_TOKEN = 4;
|
|
27
39
|
export class RunBudget {
|
|
28
40
|
tokensMax;
|
|
41
|
+
usdMax;
|
|
29
42
|
haltOnBreach;
|
|
43
|
+
estimateUnmeasured;
|
|
44
|
+
priceTable;
|
|
30
45
|
inputTokens = 0;
|
|
31
46
|
outputTokens = 0;
|
|
32
|
-
|
|
33
|
-
|
|
47
|
+
usdSpent = 0;
|
|
48
|
+
/** Notional list-price USD for unmeasured calls (warn-only; never enforced). */
|
|
49
|
+
usdEstimated = 0;
|
|
50
|
+
/** Dedup keys for one-time warnings (unmeasured driver, breach, etc.). */
|
|
51
|
+
warnedKeys = new Set();
|
|
34
52
|
/** Free-form warnings surfaced via the run log. */
|
|
35
53
|
warningList = [];
|
|
36
|
-
|
|
54
|
+
/**
|
|
55
|
+
* @param policy recipe budget block.
|
|
56
|
+
* @param priceTable optional injected table (tests); otherwise loaded once
|
|
57
|
+
* here when a USD cap is set (file/env override aware).
|
|
58
|
+
*/
|
|
59
|
+
constructor(policy, priceTable) {
|
|
37
60
|
this.tokensMax = policy?.tokensMax;
|
|
61
|
+
this.usdMax = policy?.usdMax;
|
|
38
62
|
this.haltOnBreach = (policy?.onBreach ?? "halt") === "halt";
|
|
63
|
+
this.estimateUnmeasured = policy?.estimateUnmeasured ?? false;
|
|
64
|
+
if (this.usdMax !== undefined) {
|
|
65
|
+
this.priceTable = priceTable ?? loadPriceTable();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/** True when neither cap is configured — reconcile/admit are no-ops. */
|
|
69
|
+
get hasBudget() {
|
|
70
|
+
return this.tokensMax !== undefined || this.usdMax !== undefined;
|
|
39
71
|
}
|
|
40
72
|
/** Cheap admission check before dispatching an agent step. */
|
|
41
73
|
admit() {
|
|
42
|
-
|
|
74
|
+
// warn-mode never blocks; the breach warning was emitted at reconcile.
|
|
75
|
+
if (!this.haltOnBreach)
|
|
43
76
|
return { admitted: true };
|
|
44
|
-
if (this.
|
|
45
|
-
if (!this.haltOnBreach) {
|
|
46
|
-
// warn-mode: never block, just keep going. The warning was
|
|
47
|
-
// already emitted on the post-call reconcile that breached.
|
|
48
|
-
return { admitted: true };
|
|
49
|
-
}
|
|
77
|
+
if (this.tokenBreached()) {
|
|
50
78
|
return {
|
|
51
79
|
admitted: false,
|
|
52
|
-
reason: `Run exceeded its token budget — budget_exceeded: total=${this.totalTokens()}
|
|
80
|
+
reason: `Run exceeded its token budget — budget_exceeded: total=${this.totalTokens()} >= tokensMax=${this.tokensMax}.`,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
if (this.usdMaxBreached()) {
|
|
84
|
+
return {
|
|
85
|
+
admitted: false,
|
|
86
|
+
reason: `Run exceeded its USD budget — budget_exceeded: usd=$${this.usdSpent.toFixed(4)} >= usdMax=$${this.usdMax}.`,
|
|
53
87
|
};
|
|
54
88
|
}
|
|
55
89
|
return { admitted: true };
|
|
56
90
|
}
|
|
57
91
|
/**
|
|
58
|
-
* Record the actual usage reported by an agent call. `usage`
|
|
59
|
-
*
|
|
60
|
-
*
|
|
92
|
+
* Record the actual usage reported by an agent call. `usage` is undefined
|
|
93
|
+
* when the driver doesn't surface token counts — record the driver once and
|
|
94
|
+
* continue (fail-open). `model` is the resolved model (from `servedBy`) used
|
|
95
|
+
* to price USD; an unpriced model fails open with a one-time notice.
|
|
61
96
|
*/
|
|
62
|
-
reconcile(driver, usage) {
|
|
63
|
-
if (this.
|
|
97
|
+
reconcile(driver, usage, model, estimate) {
|
|
98
|
+
if (!this.hasBudget)
|
|
64
99
|
return;
|
|
65
100
|
if (!usage) {
|
|
66
|
-
if (
|
|
67
|
-
|
|
68
|
-
|
|
101
|
+
if (this.usdMax !== undefined && this.estimateUnmeasured && estimate) {
|
|
102
|
+
// OPT-IN notional estimate for an unmeasured (subscription) driver.
|
|
103
|
+
// WARN-ONLY: accumulates into usdEstimated, NEVER usdSpent, and never
|
|
104
|
+
// affects admit() — a flat-rate subscription call is not real money out,
|
|
105
|
+
// and halting on a ~4-chars/token guess would be wrong.
|
|
106
|
+
const estModel = model ?? DEFAULT_MODEL;
|
|
107
|
+
const estCost = costUsd(estModel, {
|
|
108
|
+
inputTokens: Math.ceil(estimate.inputChars / ESTIMATE_CHARS_PER_TOKEN),
|
|
109
|
+
outputTokens: Math.ceil(estimate.outputChars / ESTIMATE_CHARS_PER_TOKEN),
|
|
110
|
+
}, this.priceTable);
|
|
111
|
+
if (estCost !== undefined && Number.isFinite(estCost)) {
|
|
112
|
+
this.usdEstimated += estCost;
|
|
113
|
+
this.pushOnce(`estimate:${driver}`, `Driver "${driver}" reports no usage — estimating notional list-price cost (≈, never enforced; see usdEstimated).`);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
69
116
|
}
|
|
117
|
+
this.pushOnce(`unmeasured:${driver}`, `Driver "${driver}" does not report token usage — budget enforcement skipped for its calls. Set recipe.budget.onBreach="warn" or move to an API driver to fix.`);
|
|
70
118
|
return;
|
|
71
119
|
}
|
|
72
120
|
this.inputTokens += usage.inputTokens;
|
|
73
121
|
this.outputTokens += usage.outputTokens;
|
|
74
|
-
if (this.
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
122
|
+
if (this.usdMax !== undefined) {
|
|
123
|
+
if (!BILLABLE_DRIVERS.has(driver)) {
|
|
124
|
+
// Not a metered-API driver (local / subscription) — a USD cap here
|
|
125
|
+
// would be notional, not real money out. Fail open with a notice.
|
|
126
|
+
this.pushOnce(`notbilled:${driver}`, `Driver "${driver}" does not incur metered API cost — usdMax is not enforced for its calls.`);
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
const cost = costUsd(model ?? "", usage, this.priceTable);
|
|
130
|
+
// `cost === undefined` (unpriced model) OR a non-finite cost (defends
|
|
131
|
+
// against a malformed price entry) → fail open, never poison usdSpent.
|
|
132
|
+
if (cost === undefined || !Number.isFinite(cost)) {
|
|
133
|
+
this.pushOnce(`unpriced:${model ?? "(no model)"}`, `Model "${model ?? "(unspecified)"}" is not in the price table — USD budget enforcement skipped for its calls. Add it to ~/.patchwork/prices.json or set the agent step's model.`);
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
this.usdSpent += cost;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
// warn-mode: emit a single in-band notice the first time we cross a cap.
|
|
141
|
+
if (!this.haltOnBreach) {
|
|
142
|
+
if (this.tokenBreached()) {
|
|
143
|
+
this.pushOnce("warn-token-breach", `Token budget exceeded (total=${this.totalTokens()}, tokensMax=${this.tokensMax}) but onBreach="warn" — continuing.`);
|
|
144
|
+
}
|
|
145
|
+
if (this.usdMaxBreached()) {
|
|
146
|
+
this.pushOnce("warn-usd-breach", `USD budget exceeded (usd=$${this.usdSpent.toFixed(4)}, usdMax=$${this.usdMax}) but onBreach="warn" — continuing.`);
|
|
81
147
|
}
|
|
82
148
|
}
|
|
83
149
|
}
|
|
@@ -91,7 +157,13 @@ export class RunBudget {
|
|
|
91
157
|
...(this.tokensMax !== undefined && {
|
|
92
158
|
remaining: Math.max(0, this.tokensMax - total),
|
|
93
159
|
}),
|
|
94
|
-
|
|
160
|
+
...(this.usdMax !== undefined && {
|
|
161
|
+
usd: this.usdSpent,
|
|
162
|
+
usdRemaining: Math.max(0, this.usdMax - this.usdSpent),
|
|
163
|
+
}),
|
|
164
|
+
...(this.usdEstimated > 0 && { usdEstimated: this.usdEstimated }),
|
|
165
|
+
breached: this.tokenBreached(),
|
|
166
|
+
usdBreached: this.usdMaxBreached(),
|
|
95
167
|
haltOnBreach: this.haltOnBreach,
|
|
96
168
|
};
|
|
97
169
|
}
|
|
@@ -99,11 +171,72 @@ export class RunBudget {
|
|
|
99
171
|
warnings() {
|
|
100
172
|
return [...this.warningList];
|
|
101
173
|
}
|
|
174
|
+
/**
|
|
175
|
+
* Run-completion warnings: the live `warnings()` plus a one-line ≈$ summary
|
|
176
|
+
* of the notional cost estimated for unmeasured calls (only when
|
|
177
|
+
* `estimateUnmeasured` produced one). Use at run end; `warnings()` is the
|
|
178
|
+
* live per-driver list without the (end-of-run-only) summary.
|
|
179
|
+
*/
|
|
180
|
+
finalWarnings() {
|
|
181
|
+
const out = [...this.warningList];
|
|
182
|
+
if (this.usdEstimated > 0) {
|
|
183
|
+
out.push(`Unmeasured (subscription) calls would cost ≈$${this.usdEstimated.toFixed(4)} at list prices — estimate only, never enforced.`);
|
|
184
|
+
}
|
|
185
|
+
return out;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* USD remaining before the cap, or undefined when no USD cap is set.
|
|
189
|
+
* Cost-aware routing (Phase 4) uses this to decide when to downshift.
|
|
190
|
+
*/
|
|
191
|
+
remainingUsd() {
|
|
192
|
+
if (this.usdMax === undefined)
|
|
193
|
+
return undefined;
|
|
194
|
+
return Math.max(0, this.usdMax - this.usdSpent);
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Pre-dispatch USD estimate for a prospective (driver, model) call, or
|
|
198
|
+
* undefined when it would NOT be USD-enforced: no USD cap, a non-billable
|
|
199
|
+
* driver (local / subscription), or an unpriced model. Mirrors exactly the
|
|
200
|
+
* enforcement rule in `reconcile`, so a candidate the router treats as
|
|
201
|
+
* "free" is precisely one `reconcile` would not charge.
|
|
202
|
+
*/
|
|
203
|
+
quoteUsd(driver, model, inputTokens, outputTokens) {
|
|
204
|
+
if (this.usdMax === undefined)
|
|
205
|
+
return undefined;
|
|
206
|
+
// Mirror executeAgent/reconcile resolution so a candidate the router calls
|
|
207
|
+
// "free" is EXACTLY one reconcile would not charge:
|
|
208
|
+
// - "api"/"claude" select the billable Anthropic path (→ "anthropic").
|
|
209
|
+
// - undefined optimistically assumes auto-detect lands on a metered API
|
|
210
|
+
// driver (usually anthropic); if it actually resolves to a subscription
|
|
211
|
+
// driver the call is free and the cap simply isn't enforced there.
|
|
212
|
+
const resolvedDriver = driver === "api" || driver === "claude" ? "anthropic" : driver;
|
|
213
|
+
if (resolvedDriver !== undefined && !BILLABLE_DRIVERS.has(resolvedDriver)) {
|
|
214
|
+
return undefined;
|
|
215
|
+
}
|
|
216
|
+
// The anthropic path bills DEFAULT_MODEL when the step omits `model`.
|
|
217
|
+
// (Provider drivers default internally to a model unknowable pre-dispatch,
|
|
218
|
+
// so an omitted model there stays unpriced → not routed on.)
|
|
219
|
+
const onAnthropicPath = resolvedDriver === "anthropic" || resolvedDriver === undefined;
|
|
220
|
+
const resolvedModel = model ?? (onAnthropicPath ? DEFAULT_MODEL : undefined);
|
|
221
|
+
if (!resolvedModel)
|
|
222
|
+
return undefined;
|
|
223
|
+
const cost = costUsd(resolvedModel, { inputTokens, outputTokens }, this.priceTable);
|
|
224
|
+
return cost !== undefined && Number.isFinite(cost) ? cost : undefined;
|
|
225
|
+
}
|
|
226
|
+
pushOnce(key, msg) {
|
|
227
|
+
if (this.warnedKeys.has(key))
|
|
228
|
+
return;
|
|
229
|
+
this.warnedKeys.add(key);
|
|
230
|
+
this.warningList.push(msg);
|
|
231
|
+
}
|
|
102
232
|
totalTokens() {
|
|
103
233
|
return this.inputTokens + this.outputTokens;
|
|
104
234
|
}
|
|
105
|
-
|
|
235
|
+
tokenBreached() {
|
|
106
236
|
return this.tokensMax !== undefined && this.totalTokens() >= this.tokensMax;
|
|
107
237
|
}
|
|
238
|
+
usdMaxBreached() {
|
|
239
|
+
return this.usdMax !== undefined && this.usdSpent >= this.usdMax;
|
|
240
|
+
}
|
|
108
241
|
}
|
|
109
242
|
//# sourceMappingURL=runBudget.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runBudget.js","sourceRoot":"","sources":["../../src/recipes/runBudget.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"runBudget.js","sourceRoot":"","sources":["../../src/recipes/runBudget.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAmB,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACpE,OAAO,EACL,OAAO,EACP,cAAc,GAEf,MAAM,yBAAyB,CAAC;AAGjC;;;;;;;;GAQG;AACH,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAAC,CAAC,WAAW,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;AAE5E,gFAAgF;AAChF,MAAM,wBAAwB,GAAG,CAAC,CAAC;AAiCnC,MAAM,OAAO,SAAS;IACH,SAAS,CAAU;IACnB,MAAM,CAAU;IAChB,YAAY,CAAU;IACtB,kBAAkB,CAAU;IAC5B,UAAU,CAAc;IACjC,WAAW,GAAG,CAAC,CAAC;IAChB,YAAY,GAAG,CAAC,CAAC;IACjB,QAAQ,GAAG,CAAC,CAAC;IACrB,gFAAgF;IACxE,YAAY,GAAG,CAAC,CAAC;IACzB,0EAA0E;IACzD,UAAU,GAAG,IAAI,GAAG,EAAU,CAAC;IAChD,mDAAmD;IAClC,WAAW,GAAa,EAAE,CAAC;IAE5C;;;;OAIG;IACH,YAAY,MAAqB,EAAE,UAAuB;QACxD,IAAI,CAAC,SAAS,GAAG,MAAM,EAAE,SAAS,CAAC;QACnC,IAAI,CAAC,MAAM,GAAG,MAAM,EAAE,MAAM,CAAC;QAC7B,IAAI,CAAC,YAAY,GAAG,CAAC,MAAM,EAAE,QAAQ,IAAI,MAAM,CAAC,KAAK,MAAM,CAAC;QAC5D,IAAI,CAAC,kBAAkB,GAAG,MAAM,EAAE,kBAAkB,IAAI,KAAK,CAAC;QAC9D,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;YAC9B,IAAI,CAAC,UAAU,GAAG,UAAU,IAAI,cAAc,EAAE,CAAC;QACnD,CAAC;IACH,CAAC;IAED,wEAAwE;IACxE,IAAY,SAAS;QACnB,OAAO,IAAI,CAAC,SAAS,KAAK,SAAS,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS,CAAC;IACnE,CAAC;IAED,8DAA8D;IAC9D,KAAK;QACH,uEAAuE;QACvE,IAAI,CAAC,IAAI,CAAC,YAAY;YAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;QAClD,IAAI,IAAI,CAAC,aAAa,EAAE,EAAE,CAAC;YACzB,OAAO;gBACL,QAAQ,EAAE,KAAK;gBACf,MAAM,EAAE,0DAA0D,IAAI,CAAC,WAAW,EAAE,iBAAiB,IAAI,CAAC,SAAS,GAAG;aACvH,CAAC;QACJ,CAAC;QACD,IAAI,IAAI,CAAC,cAAc,EAAE,EAAE,CAAC;YAC1B,OAAO;gBACL,QAAQ,EAAE,KAAK;gBACf,MAAM,EAAE,uDAAuD,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,IAAI,CAAC,MAAM,GAAG;aACrH,CAAC;QACJ,CAAC;QACD,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC5B,CAAC;IAED;;;;;OAKG;IACH,SAAS,CACP,MAAc,EACd,KAA6B,EAC7B,KAAc,EACd,QAAsD;QAEtD,IAAI,CAAC,IAAI,CAAC,SAAS;YAAE,OAAO;QAC5B,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS,IAAI,IAAI,CAAC,kBAAkB,IAAI,QAAQ,EAAE,CAAC;gBACrE,oEAAoE;gBACpE,sEAAsE;gBACtE,yEAAyE;gBACzE,wDAAwD;gBACxD,MAAM,QAAQ,GAAG,KAAK,IAAI,aAAa,CAAC;gBACxC,MAAM,OAAO,GAAG,OAAO,CACrB,QAAQ,EACR;oBACE,WAAW,EAAE,IAAI,CAAC,IAAI,CACpB,QAAQ,CAAC,UAAU,GAAG,wBAAwB,CAC/C;oBACD,YAAY,EAAE,IAAI,CAAC,IAAI,CACrB,QAAQ,CAAC,WAAW,GAAG,wBAAwB,CAChD;iBACF,EACD,IAAI,CAAC,UAAU,CAChB,CAAC;gBACF,IAAI,OAAO,KAAK,SAAS,IAAI,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;oBACtD,IAAI,CAAC,YAAY,IAAI,OAAO,CAAC;oBAC7B,IAAI,CAAC,QAAQ,CACX,YAAY,MAAM,EAAE,EACpB,WAAW,MAAM,iGAAiG,CACnH,CAAC;oBACF,OAAO;gBACT,CAAC;YACH,CAAC;YACD,IAAI,CAAC,QAAQ,CACX,cAAc,MAAM,EAAE,EACtB,WAAW,MAAM,8IAA8I,CAChK,CAAC;YACF,OAAO;QACT,CAAC;QAED,IAAI,CAAC,WAAW,IAAI,KAAK,CAAC,WAAW,CAAC;QACtC,IAAI,CAAC,YAAY,IAAI,KAAK,CAAC,YAAY,CAAC;QAExC,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;YAC9B,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;gBAClC,mEAAmE;gBACnE,kEAAkE;gBAClE,IAAI,CAAC,QAAQ,CACX,aAAa,MAAM,EAAE,EACrB,WAAW,MAAM,2EAA2E,CAC7F,CAAC;YACJ,CAAC;iBAAM,CAAC;gBACN,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;gBAC1D,sEAAsE;gBACtE,uEAAuE;gBACvE,IAAI,IAAI,KAAK,SAAS,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;oBACjD,IAAI,CAAC,QAAQ,CACX,YAAY,KAAK,IAAI,YAAY,EAAE,EACnC,UAAU,KAAK,IAAI,eAAe,+IAA+I,CAClL,CAAC;gBACJ,CAAC;qBAAM,CAAC;oBACN,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC;gBACxB,CAAC;YACH,CAAC;QACH,CAAC;QAED,yEAAyE;QACzE,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,CAAC;YACvB,IAAI,IAAI,CAAC,aAAa,EAAE,EAAE,CAAC;gBACzB,IAAI,CAAC,QAAQ,CACX,mBAAmB,EACnB,gCAAgC,IAAI,CAAC,WAAW,EAAE,eAAe,IAAI,CAAC,SAAS,qCAAqC,CACrH,CAAC;YACJ,CAAC;YACD,IAAI,IAAI,CAAC,cAAc,EAAE,EAAE,CAAC;gBAC1B,IAAI,CAAC,QAAQ,CACX,iBAAiB,EACjB,6BAA6B,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,aAAa,IAAI,CAAC,MAAM,qCAAqC,CACnH,CAAC;YACJ,CAAC;QACH,CAAC;IACH,CAAC;IAED,iDAAiD;IACjD,MAAM;QACJ,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;QACjC,OAAO;YACL,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,YAAY,EAAE,IAAI,CAAC,YAAY;YAC/B,KAAK;YACL,GAAG,CAAC,IAAI,CAAC,SAAS,KAAK,SAAS,IAAI;gBAClC,SAAS,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC;aAC/C,CAAC;YACF,GAAG,CAAC,IAAI,CAAC,MAAM,KAAK,SAAS,IAAI;gBAC/B,GAAG,EAAE,IAAI,CAAC,QAAQ;gBAClB,YAAY,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC;aACvD,CAAC;YACF,GAAG,CAAC,IAAI,CAAC,YAAY,GAAG,CAAC,IAAI,EAAE,YAAY,EAAE,IAAI,CAAC,YAAY,EAAE,CAAC;YACjE,QAAQ,EAAE,IAAI,CAAC,aAAa,EAAE;YAC9B,WAAW,EAAE,IAAI,CAAC,cAAc,EAAE;YAClC,YAAY,EAAE,IAAI,CAAC,YAAY;SAChC,CAAC;IACJ,CAAC;IAED,wEAAwE;IACxE,QAAQ;QACN,OAAO,CAAC,GAAG,IAAI,CAAC,WAAW,CAAC,CAAC;IAC/B,CAAC;IAED;;;;;OAKG;IACH,aAAa;QACX,MAAM,GAAG,GAAG,CAAC,GAAG,IAAI,CAAC,WAAW,CAAC,CAAC;QAClC,IAAI,IAAI,CAAC,YAAY,GAAG,CAAC,EAAE,CAAC;YAC1B,GAAG,CAAC,IAAI,CACN,gDAAgD,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,kDAAkD,CAC/H,CAAC;QACJ,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IAED;;;OAGG;IACH,YAAY;QACV,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS;YAAE,OAAO,SAAS,CAAC;QAChD,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC;IAClD,CAAC;IAED;;;;;;OAMG;IACH,QAAQ,CACN,MAA0B,EAC1B,KAAyB,EACzB,WAAmB,EACnB,YAAoB;QAEpB,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS;YAAE,OAAO,SAAS,CAAC;QAChD,2EAA2E;QAC3E,oDAAoD;QACpD,yEAAyE;QACzE,0EAA0E;QAC1E,4EAA4E;QAC5E,uEAAuE;QACvE,MAAM,cAAc,GAClB,MAAM,KAAK,KAAK,IAAI,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC;QACjE,IAAI,cAAc,KAAK,SAAS,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,cAAc,CAAC,EAAE,CAAC;YAC1E,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,sEAAsE;QACtE,2EAA2E;QAC3E,6DAA6D;QAC7D,MAAM,eAAe,GACnB,cAAc,KAAK,WAAW,IAAI,cAAc,KAAK,SAAS,CAAC;QACjE,MAAM,aAAa,GACjB,KAAK,IAAI,CAAC,eAAe,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;QACzD,IAAI,CAAC,aAAa;YAAE,OAAO,SAAS,CAAC;QACrC,MAAM,IAAI,GAAG,OAAO,CAClB,aAAa,EACb,EAAE,WAAW,EAAE,YAAY,EAAE,EAC7B,IAAI,CAAC,UAAU,CAChB,CAAC;QACF,OAAO,IAAI,KAAK,SAAS,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;IACxE,CAAC;IAEO,QAAQ,CAAC,GAAW,EAAE,GAAW;QACvC,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,OAAO;QACrC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACzB,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC7B,CAAC;IAEO,WAAW;QACjB,OAAO,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,YAAY,CAAC;IAC9C,CAAC;IAEO,aAAa;QACnB,OAAO,IAAI,CAAC,SAAS,KAAK,SAAS,IAAI,IAAI,CAAC,WAAW,EAAE,IAAI,IAAI,CAAC,SAAS,CAAC;IAC9E,CAAC;IAEO,cAAc;QACpB,OAAO,IAAI,CAAC,MAAM,KAAK,SAAS,IAAI,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,MAAM,CAAC;IACnE,CAAC;CACF"}
|
package/dist/recipes/schema.d.ts
CHANGED
|
@@ -115,20 +115,41 @@ export interface ErrorPolicy {
|
|
|
115
115
|
* drivers (Anthropic API, OpenAI/Gemini/Grok subprocess that surfaces
|
|
116
116
|
* usage, local LLM adapters) get full enforcement.
|
|
117
117
|
*
|
|
118
|
-
* Nested object (not flat `tokensMax`) so
|
|
119
|
-
* `wallClockMs
|
|
118
|
+
* Nested object (not flat `tokensMax`) so siblings — `usdMax` (added in
|
|
119
|
+
* cost-routing Phase 3), and future `wallClockMs` / `stepsMax` — don't churn
|
|
120
|
+
* the schema again.
|
|
120
121
|
*/
|
|
121
122
|
export interface BudgetPolicy {
|
|
122
123
|
/** Cumulative input + output tokens allowed across the whole run. */
|
|
123
124
|
tokensMax?: number;
|
|
124
125
|
/**
|
|
125
|
-
*
|
|
126
|
+
* Cumulative USD allowed across the whole run, priced from token usage via
|
|
127
|
+
* the model price table (`src/recipes/pricing`). Enforced for measured +
|
|
128
|
+
* priced (API) drivers; a subscription driver that reports no tokens, or a
|
|
129
|
+
* model with no price-table entry, FAILS OPEN with a one-time warning and
|
|
130
|
+
* never halts on it. Same `onBreach` semantics as `tokensMax`. A USD cap on
|
|
131
|
+
* a subscription driver would be notional, not real money out — so it is
|
|
132
|
+
* deliberately not enforced there.
|
|
133
|
+
*/
|
|
134
|
+
usdMax?: number;
|
|
135
|
+
/**
|
|
136
|
+
* What to do when a budget is breached. `halt` (default) stops the
|
|
126
137
|
* run on the next admission check with a `budget_exceeded` halt
|
|
127
138
|
* reason. `warn` continues the run but emits a warning + records the
|
|
128
139
|
* breach in the run log; useful for tuning budgets without breaking
|
|
129
|
-
* production cron recipes.
|
|
140
|
+
* production cron recipes. Applies to both `tokensMax` and `usdMax`.
|
|
130
141
|
*/
|
|
131
142
|
onBreach?: "halt" | "warn";
|
|
143
|
+
/**
|
|
144
|
+
* OPT-IN, default **false**. When true, the runner ESTIMATES the notional USD
|
|
145
|
+
* a subscription/unmeasured driver call would have cost at list prices (from
|
|
146
|
+
* the prompt + output length) and surfaces a `≈$X` figure. This is a label
|
|
147
|
+
* ONLY — estimated spend is tracked separately from `usdMax` and can NEVER
|
|
148
|
+
* halt a run (subscription spend is flat-rate, not real money out; halting on
|
|
149
|
+
* a ~4-chars/token guess would be wrong). Requires `usdMax` to be set (it
|
|
150
|
+
* reuses the price table). Default false preserves the fail-open invariant.
|
|
151
|
+
*/
|
|
152
|
+
estimateUnmeasured?: boolean;
|
|
132
153
|
}
|
|
133
154
|
export interface Recipe {
|
|
134
155
|
name: string;
|
|
@@ -22,6 +22,15 @@ export declare function generateSchemaSet(): SchemaSet;
|
|
|
22
22
|
* should pin on `schemaVersion` and validate against this.
|
|
23
23
|
*/
|
|
24
24
|
export declare function generateDryRunPlanSchema(): unknown;
|
|
25
|
+
/**
|
|
26
|
+
* JSON Schema for the What-If Preview `RecipeSimulationReport`
|
|
27
|
+
* (`src/recipes/simulation/types.ts`), the output of
|
|
28
|
+
* `GET /recipes/:name/simulate` and `patchwork recipe simulate`. A superset of
|
|
29
|
+
* the dry-run plan; consumers pin on `schemaVersion`. Standalone (not part of
|
|
30
|
+
* `generateSchemaSet`/`writeSchemas`) — served on demand to consumers that want
|
|
31
|
+
* to validate the wire contract.
|
|
32
|
+
*/
|
|
33
|
+
export declare function generateSimulationSchema(): unknown;
|
|
25
34
|
/**
|
|
26
35
|
* Write schema files to disk.
|
|
27
36
|
*/
|