import { StreamOptions, Message, Tool, ToolResultContent, ServerToolDefinition, Usage, StopReason, AssistantMessage } from '@kenkaiiii/gg-ai'; import { z } from 'zod'; interface StructuredToolResult { content: ToolResultContent; details?: unknown; } type ToolExecuteResult = string | StructuredToolResult; interface ToolContext { signal: AbortSignal; toolCallId: string; onUpdate?: (update: unknown) => void; } type ToolExecutionMode = "parallel" | "sequential"; interface AgentTool extends Tool { parameters: T; /** * Per-tool execution hint for batches of tool calls from one assistant turn. * Tools default to parallel. If any requested tool is sequential, the whole * batch runs in source order so stateful mutations cannot race each other. */ executionMode?: ToolExecutionMode; /** * Overrides the loop's default per-tool timeout. A tool that owns a longer * internal budget than the default must declare it here, or the loop cancels * it first and the tool's own timeout — with its specific, actionable error * message — becomes unreachable. */ timeoutMs?: number; execute: (args: z.infer, context: ToolContext) => ToolExecuteResult | Promise; } interface AgentTextDeltaEvent { type: "text_delta"; text: string; } interface AgentThinkingDeltaEvent { type: "thinking_delta"; text: string; } interface AgentToolCallStartEvent { type: "tool_call_start"; toolCallId: string; name: string; args: Record; } interface AgentToolCallUpdateEvent { type: "tool_call_update"; toolCallId: string; update: unknown; } interface AgentToolCallEndEvent { type: "tool_call_end"; toolCallId: string; result: string; details?: unknown; isError: boolean; durationMs: number; /** * Set only when the call failed schema validation: how many consecutive * times this tool produced this same validation error. 1 means the model * still has room to self-correct; 3 is the threshold that ends the turn. * Logged so a retry loop shows up as a count instead of identical lines. */ invalidArgAttempt?: number; } interface AgentTurnTiming { /** Logical turn start, before context transforms or provider retries. Unix epoch milliseconds. */ startedAt: number; /** First provider event, or full-response arrival for non-streaming fallback. */ firstProviderEventAt?: number; /** Successful provider response completion. Unix epoch milliseconds. */ completedAt: number; /** Time spent awaiting provider attempts, including failed attempts but excluding retry backoff. */ providerDurationMs: number; /** Time from logical turn start to the first provider event. */ ttftMs?: number; /** Output tokens divided by total provider duration. Omitted when no rate is measurable. */ outputTokensPerSecond?: number; } interface AgentTurnEndEvent { type: "turn_end"; turn: number; stopReason: StopReason; usage: Usage; timing: AgentTurnTiming; } /** * A safe point between steps: the assistant message and every tool result for * this turn are now in the message array, and no provider call is in flight. * * Hosts that persist a transcript flush here. Without it a crash mid-run loses * the WHOLE turn — including tool results whose side effects already landed on * disk — because the only flush happens after the loop returns. * * Yielded immediately after tool results are appended, so it pairs with * `turn_end` (which covers the assistant half) to cover every message. */ interface AgentCheckpointEvent { type: "checkpoint"; turn: number; } interface AgentDoneEvent { type: "agent_done"; totalTurns: number; totalUsage: Usage; } /** * Terminal signal emitted when the loop stops because it exhausted its turn * budget (`maxTurns`) mid-task — i.e. the model still wanted to run tools but * ran out of turns. Distinguishes a hard cut-off from a clean completion so * callers (e.g. the subagent spawner) can tell the parent the output may be * incomplete. Yielded immediately before the final `agent_done`. */ interface AgentMaxTurnsEvent { type: "max_turns"; totalTurns: number; maxTurns: number; } /** * Emitted when the loop was about to stop on an exhausted turn budget but the * host granted an extension instead. The effective budget is raised and the * loop continues with a continuation prompt, so this is NOT terminal — unlike * `max_turns`, which still fires if the extended budget is also spent. */ interface AgentTurnBudgetExtendedEvent { type: "turn_budget_extended"; /** Turn number at which the budget was exhausted. */ turn: number; /** New effective `maxTurns` after the extension. */ grantedTurns: number; /** 1-based extension count for this run. */ extension: number; } /** * Warning signal emitted when a turn ended on a non-clean stop reason — * `max_tokens` (output clipped at the model's output-token limit), `refusal`, * or a provider-reported `error` stop. Distinguishes a truncated/degraded * completion from a clean one so hosts can warn the user instead of silently * presenting incomplete output as done. */ interface AgentTruncatedEvent { type: "truncated"; reason: "max_tokens" | "refusal" | "provider_error" | "empty_response"; /** True when the loop injected a continuation and will keep going. */ continued: boolean; } interface AgentRetryEvent { type: "retry"; reason: "overloaded" | "rate_limit" | "provider_error" | "empty_response" | "stream_stall" | "overflow_compact" | "tool_argument_glitch" | "runaway_toolcall"; attempt: number; maxAttempts: number; delayMs: number; /** Provider-reported prompt/context token count, when present in an overflow error. */ observedTokens?: number; /** Provider-reported context/token limit, when present in an overflow error. */ observedLimit?: number; /** When true, the retry should not be shown to the user (hidden retry). */ silent?: boolean; /** * Chars of streamed text preserved in message history across this retry * (transport failures only). When > 0 the retry CONTINUES from the partial * instead of replaying — UIs must keep the streamed text on screen rather * than rolling it back. */ preservedChars?: number; } interface AgentToolCallDeltaEvent { type: "toolcall_delta"; chars: number; } interface AgentErrorEvent { type: "error"; error: Error; } interface AgentServerToolCallEvent { type: "server_tool_call"; id: string; name: string; input: unknown; } interface AgentServerToolResultEvent { type: "server_tool_result"; toolUseId: string; resultType: string; data: unknown; } interface AgentSteeringMessageEvent { type: "steering_message"; content: Message["content"]; } interface AgentFollowUpMessageEvent { type: "follow_up_message"; content: Message["content"]; } type AgentEvent = AgentTextDeltaEvent | AgentThinkingDeltaEvent | AgentToolCallStartEvent | AgentToolCallUpdateEvent | AgentToolCallEndEvent | AgentToolCallDeltaEvent | AgentServerToolCallEvent | AgentServerToolResultEvent | AgentSteeringMessageEvent | AgentFollowUpMessageEvent | AgentRetryEvent | AgentTurnEndEvent | AgentCheckpointEvent | AgentDoneEvent | AgentMaxTurnsEvent | AgentTurnBudgetExtendedEvent | AgentTruncatedEvent | AgentErrorEvent; interface TransformContextOptions { /** Force a transform after the provider reports context overflow. */ force?: boolean; /** Latest successful provider usage, anchored at its assistant message. */ usage?: Usage; /** Messages appended after that usage sample and not yet seen by the provider. */ pendingMessages: Message[]; } interface AgentOptions { provider: StreamOptions["provider"]; model: string; system?: string; /** Prior conversation messages (excluding system) to hydrate the Agent on construction. Used for session resume. */ priorMessages?: Message[]; tools?: AgentTool[]; serverTools?: ServerToolDefinition[]; /** Control whether tools may/must be called, or select a named tool when supported. */ toolChoice?: StreamOptions["toolChoice"]; maxTurns?: number; /** * How many times `onTurnBudgetExhausted` may grant extra turns in one run. * Each grant raises the effective budget by the original `maxTurns`. * Default: 2. Set 0 to disable extensions entirely. */ maxTurnExtensions?: number; maxTokens?: number; temperature?: number; thinking?: StreamOptions["thinking"]; apiKey?: string; /** * Re-resolve the credential at the start of every turn. A run can span many * minutes, and an OAuth grant refreshed by any process (another app window, a * CLI session, the usage poller) invalidates the access token captured when * the run began — so a pinned `apiKey` goes dead mid-run and every remaining * turn fails with an authentication error. Returning the current credential * here keeps a long run alive across rotations. * * Falls back to `apiKey`/`accountId`/`projectId` when omitted or when the * resolver throws (the provider call then surfaces the real auth error). */ resolveCredentials?: () => Promise<{ apiKey: string; accountId?: string; projectId?: string; }>; baseUrl?: string; signal?: AbortSignal; accountId?: string; transportSessionId?: StreamOptions["transportSessionId"]; projectId?: StreamOptions["projectId"]; cacheRetention?: StreamOptions["cacheRetention"]; /** Stable per-session cache routing key for providers that support it. */ promptCacheKey?: StreamOptions["promptCacheKey"]; /** Override the User-Agent sent with OAuth-authenticated Anthropic requests. */ userAgent?: StreamOptions["userAgent"]; /** Extra HTTP headers attached to every model request (e.g. Kimi For Coding * client-identity headers). Merged into the underlying SDK default headers. */ defaultHeaders?: StreamOptions["defaultHeaders"]; /** OpenAI service tier for latency-sensitive first-party API requests. */ serviceTier?: StreamOptions["serviceTier"]; /** Whether the target model supports image input. When false, image blocks * in messages/tool_results are downgraded to text placeholders. Default: true. */ supportsImages?: boolean; /** Whether the target model supports video input. When false, video blocks * in messages are downgraded to text placeholders. Default: false. */ supportsVideo?: boolean; /** Enable provider-native web search. */ webSearch?: boolean; /** Enable server-side compaction (Anthropic only, beta). */ compaction?: boolean; /** Enable server-side clearing of old tool use/result pairs (Anthropic only, beta). */ clearToolUses?: boolean; /** Max characters for a single tool result. Results exceeding this are truncated with a notice. */ maxToolResultChars?: number; /** Aggregate budget for ALL tool results in one assistant turn. Protects * against parallel fan-outs injecting huge uncached context in one turn; * the largest results are trimmed (water-filling) with a re-run notice. */ maxTurnToolResultChars?: number; /** Max consecutive pause_turn continuations before stopping (default: 5). * Prevents infinite loops when server-side tools keep pausing. */ maxContinuations?: number; /** * Called before each LLM call. Allows the caller to inspect and transform * the messages array (e.g. compaction, truncation). Return the same array * for no-op, or a new array to replace the conversation context. * * The latest provider usage is authoritative for the history through its * assistant response. `pendingMessages` contains context appended afterward. * When `options.force` is true, the caller should compact unconditionally * (e.g. after a context overflow error from the API). */ transformContext?: (messages: Message[], options: TransformContextOptions) => Message[] | Promise; /** * Polled after tool execution completes each turn. Returns user messages * to inject into the conversation before the next LLM call (steering). * Return null/empty to inject nothing. Messages are consumed (cleared) * on read. */ getSteeringMessages?: () => Promise | Message[] | null; /** * Polled when the agent would otherwise stop (no tool calls, no steering). * Returns messages to inject and continue the loop. Lower priority than * steering — only checked after getSteeringMessages returns empty. * Return null/empty to inject nothing. Messages are consumed (cleared) * on read. */ getFollowUpMessages?: () => Promise | Message[] | null; /** * Consulted when a tool-running turn exhausts the turn budget mid-task, * before the loop emits the terminal `max_turns` event. Return true to grant * another `maxTurns` worth of turns; false (the default when unset) keeps * today's hard cut-off. Hosts should only grant on evidence of progress — * extending a spinning agent just buys it more tokens to spin with. */ onTurnBudgetExhausted?: (ctx: { turn: number; maxTurns: number; extension: number; }) => Promise | boolean; } interface AgentResult { message: AssistantMessage; totalTurns: number; totalUsage: Usage; } /** * Dual-nature result: async iterable for streaming events, * thenable for awaiting the final AgentResult. * * ```ts * // Stream events * for await (const event of agent.prompt("hello")) { ... } * * // Or just await the result * const result = await agent.prompt("hello"); * ``` */ declare class AgentStream implements AsyncIterable { private events; private resultPromise; private resolveResult; private rejectResult; private hasConsumer; constructor(generator: AsyncGenerator, onDone: () => void); private pump; [Symbol.asyncIterator](): AsyncIterator; then(onfulfilled?: ((value: AgentResult) => TResult1 | PromiseLike) | null, onrejected?: ((reason: unknown) => TResult2 | PromiseLike) | null): Promise; private drainEvents; } declare class Agent { private messages; private _running; private options; private steeringQueue; private followUpQueue; constructor(options: AgentOptions); /** Snapshot of the current message history. Used for session persistence. */ getMessages(): Message[]; /** * Swap the abort signal used for subsequent prompts. Call this after * `controller.abort()` so the next prompt() call gets a fresh, unaborted * signal — without losing message history. */ setSignal(signal: AbortSignal | undefined): void; get running(): boolean; /** Queue a steering message for injection after current tool execution completes. */ steer(msg: Message): void; /** Queue a follow-up message for injection when the agent would otherwise stop. */ followUp(msg: Message): void; prompt(content: string): AgentStream; } /** * Lightweight stream diagnostic callback. When set, the agent loop calls this * at every phase boundary with timing and state info. This lets the hosting * app (ggcoder, come-alive, etc.) log stall diagnostics without the agent * package needing fs/process dependencies. */ type StreamDiagnosticFn = (phase: string, data?: Record) => void; /** Register a diagnostic callback for stream stall tracing. */ declare function setStreamDiagnostic(fn: StreamDiagnosticFn | null): void; /** * Detect abort errors — user-initiated cancellation or AbortSignal. * These should be caught and handled gracefully, not re-thrown. */ declare function isAbortError(err: unknown): boolean; /** * Detect context window overflow errors from LLM providers. * * Patterns drawn from observed errors across Anthropic, OpenAI, OpenAI Codex, * Bedrock, Ollama, and OpenAI-compatible Chinese providers (GLM, Kimi, MiniMax). */ declare function isContextOverflow(err: unknown): boolean; /** * Detect billing/quota errors — these should NOT be retried. * GLM returns HTTP 429 with "Insufficient balance" for quota exhaustion. */ declare function isBillingError(err: unknown): boolean; /** * Detect subscription/plan usage-window exhaustion (e.g. an Anthropic OAuth * plan running out of usage). Unlike a transient per-minute 429, this does NOT * clear with a quick retry — the user must wait for the window to reset — so the * loop surfaces it immediately instead of retrying for minutes. Matches the * canonical message gg-ai stamps onto the provider error. */ declare function isUsageLimitError(err: unknown): boolean; declare function agentLoop(messages: Message[], options: AgentOptions): AsyncGenerator; /** * A tool call that never reached its tool. Nothing ran, so nothing changed. */ declare function cancelledBeforeStartText(name: string): string; /** * A tool call that started running and was cut off before reporting back. * * The distinction from {@link cancelledBeforeStartText} is the whole point: a * dispatched `git push`, deploy or MCP call may well have COMPLETED before the * abort landed. Telling the model it was "interrupted" reads as "it did not * happen", so the model repeats the side effect — or reports to the user that * something never ran when it did. */ declare function indeterminateOutcomeText(name: string): string; /** * A local inference server (llama.cpp, vLLM, Ollama, LM Studio) can spend * minutes prefilling a large prompt before it emits its first token. The * first-event watchdog that protects hosted streams turns that into an abort * → retry → cold-prefill loop that never converges, so it is disabled for * loopback backends. */ declare function isLocalBackendUrl(baseUrl?: string): boolean; export { Agent, type AgentDoneEvent, type AgentErrorEvent, type AgentEvent, type AgentFollowUpMessageEvent, type AgentOptions, type AgentResult, type AgentRetryEvent, type AgentServerToolCallEvent, type AgentServerToolResultEvent, type AgentSteeringMessageEvent, AgentStream, type AgentTextDeltaEvent, type AgentThinkingDeltaEvent, type AgentTool, type AgentToolCallDeltaEvent, type AgentToolCallEndEvent, type AgentToolCallStartEvent, type AgentToolCallUpdateEvent, type AgentTurnEndEvent, type AgentTurnTiming, type StreamDiagnosticFn, type StructuredToolResult, type ToolContext, type ToolExecuteResult, type ToolExecutionMode, type TransformContextOptions, agentLoop, cancelledBeforeStartText, indeterminateOutcomeText, isAbortError, isBillingError, isContextOverflow, isLocalBackendUrl, isUsageLimitError, setStreamDiagnostic };