/** * agent-runner.ts — Enterprise Agent Execution Engine * * Core execution engine that creates sessions, runs agents, collects results. * Enhanced with: * - Resource quotas (token budgets, time limits, tool limits) * - Circuit breaker for model calls * - Structured error classification * - Graceful degradation strategies * - Comprehensive telemetry and metrics */ import type { Api, Model } from "@earendil-works/pi-ai"; import type { ExtensionContext } from "@earendil-works/pi-coding-agent"; import { type AgentSession, type ExtensionAPI } from "@earendil-works/pi-coding-agent"; import { type EffectiveConfig } from "./agent-types.js"; import { type CompactionSnapshot } from "./compaction-snapshot.js"; import { type AgentHandoff } from "./handoff.js"; import { type HookRegistry } from "./hooks.js"; import { type AgentAbortReason, type AgentOutcome, type AgentRunnerErrorCode } from "./spend.js"; import type { SubagentType, ThinkingLevel, ValidationResult } from "./types.js"; export declare class AgentRunnerError extends Error { readonly code: AgentRunnerErrorCode; readonly context?: Record | undefined; constructor(message: string, code: AgentRunnerErrorCode, context?: Record | undefined); } export declare function normalizeMaxTurns(n: number | undefined): number | undefined; export declare function getDefaultMaxTurns(): number | undefined; export declare function setDefaultMaxTurns(n: number | undefined): void; export declare function getGraceTurns(): number; export declare function setGraceTurns(n: number): void; export declare function getMaxEndHookRevisions(): number; /** Clamp end-hook revision budget to a safe finite 0..10 range (NaN/Inf → 0). */ export declare function clampMaxEndHookRevisions(n: number): number; export declare function setMaxEndHookRevisions(n: number): void; /** * True when a thrown error or soft assistant errorMessage looks like a model / * provider transport failure (auth, rate limit, unavailable). Used so the * circuit breaker trips on 401s during session.prompt — not on bad cwd / config * failures from createAgentSession (CHE-17). */ export declare function isModelTransportFailure(err: unknown): boolean; export declare function isModelFailureMessage(message: string): boolean; /** * Auth-shaped model failures that justify one automatic retry on the * session-default (parent) model — e.g. Explore's pinned haiku returning * PAID_MODEL_AUTH_REQUIRED / 401 while the parent model still works (CHE-15). */ export declare function isModelAuthFailure(message: string): boolean; /** True when a pi-ai Model has zero cost on every dimension (free tier). */ export declare function isFreeModel(model: Model): boolean; declare class ModelCircuitBreaker { private failures; private lastFailureAt; private state; /** Throw if the breaker is open and the recovery window has not elapsed. */ assertAllow(): void; recordSuccess(): void; recordFailure(): void; /** * Run `fn` under the breaker. By default every rejection counts; pass * `countFailure` to ignore non-model errors (e.g. AbortError). */ call(fn: () => Promise, options?: { countFailure?: (err: unknown) => boolean; }): Promise; getState(): { state: string; failures: number; lastFailureAt: number; }; /** Test helper — clear breaker state between cases. */ reset(): void; } declare const globalCircuitBreaker: ModelCircuitBreaker; /** * Resolve the effective configured model string for a spawned agent. * * A `subagentModel` setting overrides the agent's own configured model: * - `"inherit"` → `undefined`, so `resolveDefaultModel` falls back to the * session-default (parent) model. This is the escape hatch when a built-in * read-only agent's pinned model is unreachable (e.g. provider 401). * - any other non-empty string → used verbatim as a `provider/modelId`. * - `undefined` → the agent's own configured model is used (current behavior). */ export declare function resolveConfiguredModel(subagentModelSetting: string | undefined, agentModel: string | undefined): string | undefined; export interface ToolActivity { type: "start" | "end"; toolName: string; } export interface ResourceQuotas { /** Max total tokens (input + output) before hard stop. */ maxTokens?: number; /** Max execution duration in ms. */ maxDurationMs?: number; /** Max number of tool calls. */ maxToolCalls?: number; } export interface RunOptions { pi: ExtensionAPI; agentId?: string; model?: Model; maxTurns?: number; signal?: AbortSignal; isolated?: boolean; inheritContext?: boolean; thinkingLevel?: ThinkingLevel; cwd?: string; onToolActivity?: (activity: ToolActivity) => void; onTextDelta?: (delta: string, fullText: string) => void; onSessionCreated?: (session: AgentSession) => void; onTurnEnd?: (turnCount: number) => void; onAssistantUsage?: (usage: { input: number; output: number; cacheWrite: number; }) => void; onCompaction?: (info: CompactionSnapshot) => void; skipValidators?: boolean; onValidationComplete?: (results: ValidationResult[]) => void; currentLevel?: number; levelLimit?: number; parentConfig?: EffectiveConfig; partitions?: readonly string[]; /** Short correlation id (8 hex chars). Set by AgentManager at spawn. */ correlationId?: string; hooks?: HookRegistry; spawnedAt?: number; onContextBuilt?: (timestamp: number) => void; /** Resource quotas for this run. */ quotas?: ResourceQuotas; /** * Override the module-level `maxEndHookRevisions` setting for this run. * `0` = fail closed on `subagent:end` block with no revision turn. */ maxEndHookRevisions?: number; } export interface RunResult { responseText: string; session: AgentSession; aborted: boolean; /** True when the run was aborted because it exceeded its duration quota. */ timedOut?: boolean; steered: boolean; validationResults?: ValidationResult[]; validated?: boolean; handoff?: AgentHandoff; /** Execution metrics. */ metrics: RunMetrics; /** * Set when the run ended because the model/provider errored on its final * turn (no thrown exception). Callers that key off status should treat a * non-empty `error` as a failure even though `session.prompt` resolved. */ error?: string; /** * Structured reason when an internal budget gate (token/tool/duration/turn * quota) stopped the run. Absent for normal completion and external stops, * so a `blocked_budget` outcome is derivable rather than guessed from * error strings (R4 / AE2). */ abortReason?: AgentAbortReason; /** Explicit outcome contract (R4): executed | blocked_budget | not_executed. */ outcome?: AgentOutcome; /** Reason for the outcome — the structured abort message or a partial-progress note. */ outcomeReason?: string; } export interface RunMetrics { durationMs: number; turns: number; toolCalls: number; tokensIn: number; tokensOut: number; tokensCacheWrite: number; contextBuiltAt?: number; latencyToFirstTokenMs?: number; } export declare function buildEffectivePrompt(ctx: ExtensionContext, prompt: string, options: RunOptions): string; export declare function runAgent(ctx: ExtensionContext, type: SubagentType, prompt: string, options: RunOptions): Promise; export declare function resumeAgent(session: AgentSession, prompt: string, options?: { agentId?: string; hooks?: HookRegistry; onToolActivity?: (activity: ToolActivity) => void; onAssistantUsage?: (usage: { input: number; output: number; cacheWrite: number; }) => void; onCompaction?: (info: CompactionSnapshot) => void; signal?: AbortSignal; inheritContext?: boolean; ctx?: ExtensionContext; }): Promise; export declare function steerAgent(session: AgentSession, message: string): Promise; export declare function getAgentConversation(session: AgentSession): string; export { globalCircuitBreaker };