/** * spend.ts — Token/cost accounting for per-subagent budget warnings. * * Pure functions over the pi-ai model `cost` shape (same source as the * free-only gate in agent-runner): rates are $ per million tokens. * Free models (all-zero cost) report "free" and never trip spend warnings. */ /** Subset of the pi-ai Model cost shape this module needs. */ export interface ModelCost { input?: number; output?: number; cacheWrite?: number; cacheRead?: number; } export interface UsageTotals { input: number; output: number; cacheWrite: number; cacheRead?: number; } /** * Estimated USD cost for accumulated usage at the given model's rates. * Missing cost fields count as free; a missing/zero-everything model → 0. */ export declare function estimateCostUsd(cost: ModelCost | undefined, u: UsageTotals): number; /** Compact display: "$1.23" / "<$0.01" / "$0.00". */ export declare function formatSpend(usd: number): string; /** Total billable-ish tokens (input + output; cache tracked separately by callers). */ export declare function totalTokens(u: UsageTotals): number; /** * Utilization percentage of a cap: floor(used / cap * 100). * * Single source of truth for budget-warning percentages (R1): the result is * NEVER clamped, so going over the cap yields >100 (30/25 → 120) and the * percentage always matches the rendered counter ratio. A non-positive cap * yields 0 instead of Infinity/NaN (render sites never pass one: the manager * only fires a warning after checking the cap is positive). */ export declare function utilization(used: number, cap: number): number; /** * "N% used (used/cap)" for budget-warning lines. The percentage and the * counter are derived from the SAME used/cap pair, so they can never * disagree — including at and above the cap (25/25 → "100% used (25/25)", * 30/25 → "120% used (30/25)"). */ export declare function utilizationLabel(used: number, cap: number): string; /** Input for a session-level budget warning (agent count or turn count threshold). */ export interface SessionBudgetWarningInput { kind: "agents" | "turns"; used: number; cap: number; /** True for the 90% thresholds, where enforcement is imminent. */ critical: boolean; } /** * Full text of a session budget warning (R1 + R3): the percentage and the * counter come from the same used/cap pair via utilizationLabel, and the * message names concrete operator actions — raise the limit, restart the * session, or deny further work. The critical (90%) variants lead with the * imminent-stop consequence. */ export declare function sessionBudgetWarningMessage({ kind, used, cap, critical }: SessionBudgetWarningInput): string; /** * Full text of a per-agent token-cap warning (R1 + R3). `thresholdPct` is * the crossed threshold (50/80/100); the rendered percentage routes through * `utilization` so it always matches the counter. The hint names the two * actions that apply to a per-agent cap: raise it via settings, or deny * further work for the agent (it aborts at the cap). */ export declare function spendBudgetWarningMessage(input: { thresholdPct: number; perAgentTokenLimit: number; agentCount: number; }): string; /** * Explicit outcome for a finished subagent run (R4): a run that ended under * budget pressure — or with genuinely empty output — is never presented as a * successful empty completion. * * - `executed`: the agent ran (normal completion, or a cut after real work — * with a partial-progress note as the reason). * - `blocked_budget`: a budget gate stopped the agent before any work. * - `not_executed`: the agent never did observable work (silent no-op * completion — the issue #40 shape — or a stop before any work). */ export type AgentOutcome = "executed" | "blocked_budget" | "not_executed"; /** * Error-code vocabulary of `AgentRunnerError` (`src/agent-runner.ts`). Kept * here as the single source for the outcome mapping so the pure helpers stay * dependency-free; the runner's error class consumes this type. */ export type AgentRunnerErrorCode = "depth_exceeded" | "model_unavailable" | "quota_exceeded" | "aborted" | "timeout" | "unknown"; /** * Structured abort reason kinds. Internal budget gates (token/tool/duration/ * turn quotas, session turn limit) set these at the abort site so the outcome * is derivable rather than guessed from error strings. External stops carry no * abort reason at all; hook gates throw instead of aborting. */ export type AgentAbortKind = "token_quota" | "tool_quota" | "duration_quota" | "turn_budget" | "session_turn_limit" | "hook_gate" | "external_stop"; /** Structured reason attached to a run that an internal gate stopped. */ export interface AgentAbortReason { kind: AgentAbortKind; message: string; } export interface AgentOutcomeInput { /** True when the run was aborted (internal budget gate or external stop). */ aborted: boolean; /** Structured reason when an internal gate aborted the run. */ abortReason?: AgentAbortReason; /** The agent produced non-empty assistant text (before any end-report substitution). */ hasOutput: boolean; /** The agent executed observable work (tool calls) before ending. */ executedWork: boolean; } export interface AgentOutcomeResult { outcome: AgentOutcome; /** Structured abort message, or a partial-progress / no-output note. */ reason?: string; } /** * Derive the explicit outcome (R4) from how a run ended. "Real work" follows * what the fail-loud end report already measures: tool calls and output text. */ export declare function deriveAgentOutcome(input: AgentOutcomeInput): AgentOutcomeResult; /** * Map a thrown `AgentRunnerError.code` to the outcome contract (R4) for runs * that rejected instead of resolving. `quota_exceeded` → blocked_budget; * codes meaning the agent never executed → not_executed; a `subagent:end` * hook block fires after real work, so it maps to executed; `timeout` / * `unknown` carry no budget semantics and stay unmapped (the error status * presentation covers them). */ export declare function outcomeFromRunnerErrorCode(code: AgentRunnerErrorCode, message: string, context?: Record): AgentOutcomeResult | undefined;