Files
oh-my-pi/packages/coding-agent/src/eval/budget-bridge.ts
T
can1357 2ddc9c5bc9 feat(coding-agent): added turn-budget parsing, multipliers and hard caps
- Added +Nk/+Nm turn-budget parsing with whitespace-boundary matching, multipliers, and hard `!` indicator.
- Added per-turn budget lifecycle plus APIs (`getTurnBudget`, `recordEvalSubagentUsage`) and hard-cap checks in eval runs.
- Added hard budget observability in eval preludes and docs by exposing `budget.hard` and documenting ceiling modes.
- Fixed streaming preview stutter with max-row tracking and padding, with tests for preview height and budget parsing.
2026-05-31 08:03:45 +02:00

49 lines
1.7 KiB
TypeScript

/**
* Host-side handler for the eval `budget` helper.
*
* Reports the active token ceiling and amount spent so kernel helpers can
* compute remaining budget. Precedence: a `+Nk`/`+Nk!` per-turn directive (the
* user's immediate intent) wins; otherwise an active Goal Mode budget; otherwise
* no ceiling, with `spent` still reflecting this turn's output where available.
*/
import type { ToolSession } from "../tools";
import type { JsStatusEvent } from "./js/shared/types";
/** Synthetic bridge name reserved for the `budget` helper across both runtimes. */
export const EVAL_BUDGET_BRIDGE_NAME = "__budget__";
export interface EvalBudgetBridgeOptions {
session: ToolSession;
signal?: AbortSignal;
emitStatus?: (event: JsStatusEvent) => void;
}
export interface EvalBudgetResult {
total: number | null;
spent: number;
/** Whether the ceiling is enforced (eval `agent()` throws past it) vs advisory. */
hard: boolean;
}
/**
* Resolve the current token budget snapshot for an eval cell's `budget` helper.
* The returned object is JSON-passed verbatim by the bridge transport; kernel
* helpers read `.total`/`.spent`/`.hard` directly.
*/
export async function runEvalBudget(_args: unknown, options: EvalBudgetBridgeOptions): Promise<EvalBudgetResult> {
const turn = options.session.getTurnBudget?.();
if (turn && turn.total !== null) {
return { total: turn.total, spent: turn.spent, hard: turn.hard };
}
const goal = options.session.getGoalModeState?.();
if (goal?.enabled && goal.goal) {
return {
total: goal.goal.tokenBudget ?? null,
spent: goal.goal.tokensUsed ?? 0,
hard: goal.goal.tokenBudget != null,
};
}
const spent = turn?.spent ?? options.session.getUsageStatistics?.()?.output ?? 0;
return { total: null, spent, hard: false };
}