import { type Query, type SDKMessage, type SDKUserMessage } from "@anthropic-ai/claude-agent-sdk";
import type { MessageParam } from "@anthropic-ai/sdk/resources";
import type { SessionMode } from "../../mailbox.js";
import type { ScreenshotInjector } from "../../screenshot-injector.js";
import type { AgentSessionConstructorArgs, AgentSessionLike, AgentStreamMessage, InjectMessageOptions, SendAndStreamOptions, UsageSnapshot } from "../session-types.js";
export type { SDKMessage, SDKUserMessage };
export type { SystemPromptConfig } from "../session-types.js";
/**
 * Used-token threshold (fraction of context window) at which we trigger
 * compaction for **large** context windows (>= {@link COMPACT_MAX_CONTEXT_WINDOW}).
 *
 * Smaller windows scale up to {@link COMPACT_SMALL_WINDOW_USED_PCT_THRESHOLD}
 * via {@link getCompactUsedPctThreshold}.
 */
export declare const COMPACT_LARGE_WINDOW_USED_PCT_THRESHOLD = 0.7;
export declare const MAX_BASE64_IMAGE_BLOCKS = 10;
/**
 * Safety-net threshold: trigger compaction when remaining type:"image" blocks
 * exceed this count, to stay under Claude API's 100-image hard limit.
 *
 * Since #328, pruned images become text blocks ("[Screenshot: archived=...]")
 * and no longer count toward the API image limit. This threshold now primarily
 * guards against edge cases: legacy placeholder PNGs from older transcripts,
 * sessions with many unpruned screenshots accumulating before the pruner runs,
 * or rapid screenshot bursts between prune cycles.
 */
export declare const IMAGE_COMPACT_THRESHOLD = 80;
/**
 * Wraps the V1 query() API with a long-lived async generator for streaming
 * input. This allows injecting additional user messages while the agent is
 * processing (interrupt messages), instead of waiting for the query to
 * finish before sending the next message.
 */
export declare class ClaudeAgentSession implements AgentSessionLike {
    private config;
    private buildSystemPrompt;
    private runtimeSurface;
    private getDualSessionEnabled;
    private getAndroidUseEnabled;
    private getCardAvailable;
    /** Working directory for the SDK subprocess. Defaults to profile config dir. */
    private readonly sessionCwd;
    private nativeTools;
    /** Resolved external MCP servers (playwright, serpapi, etc.) for this runtime. */
    private externalMcpServers;
    /** Dynamic MCP servers added at runtime via manage_mcp_servers tool. */
    private dynamicMcpServers;
    private currentQuery;
    private warmQuery;
    private warmUpPromise;
    private warmReadyAtMs;
    private warmGeneration;
    private sessionId;
    private transcriptPath;
    readonly mode: SessionMode;
    /**
     * Pending message queue for the long-lived generator.
     * When a message is injected, it's pushed here and the waiting
     * generator is woken up via the resolver.
     */
    private pendingMessages;
    private messageResolver;
    private generatorClosed;
    private _orphanedInjections;
    private lastCompactRequestAtMs;
    private compactInFlight;
    /**
     * Number of consecutive compaction attempts that hit the timeout without a
     * successful `compact_boundary`. Incremented when the compaction timer
     * fires; reset to 0 on any successful compaction. The wake loop reads this
     * to decide when to force an auto-refresh — see the auto-refresh constants in
     * `agent/compaction-policy.ts`.
     */
    private consecutiveCompactionTimeouts;
    private stopRequested;
    /**
     * Tracks whether we've seeded `<profile>/.claude/settings.local.json`
     * with our auto-memory directory override this process-lifetime. The seed
     * helper is itself idempotent, but this flag keeps hot paths (every
     * `consume()` / `warmUp()`) from hitting the filesystem repeatedly.
     */
    private autoMemorySeedDone;
    /** Optional: auto-injects screenshots after Playwright tool calls. */
    private _screenshotInjector;
    /** AbortController passed to the SDK; aborting it cleanly stops the running query. */
    private abortController;
    private warmAbortController;
    /**
     * Timer that proactively replaces the pre-warmed subprocess at
     * `WARM_REFRESH_TTL_MS` intervals. Cleared when the warm is consumed,
     * on stop, or before arming a new one.
     */
    private warmRefreshTimer;
    private lastUsageSnapshot;
    private playwriterConnectionsResetThisTurn;
    constructor({ config, buildSystemPrompt, runtimeSurface, sessionContext, }: AgentSessionConstructorArgs);
    set screenshotInjector(injector: ScreenshotInjector | null);
    /**
     * Seed `<profile>/.claude/settings.local.json` once per process so the
     * CLI's auto-memory lands inside the active profile instead of the
     * default `~/.claude/projects/...` tree. Called from every CLI-spawn
     * path (warm + cold); the underlying helper is itself idempotent.
     *
     * Gated on the `autoMemorySeedDone` flag so the happy path doesn't
     * re-read/re-write the file every turn. The flag is set **only on
     * success** — if the first attempt fails (permissions, disk full, etc.)
     * the next warm/cold spawn will retry until it lands, instead of
     * silently leaving the CLI on its default memory path for the rest of
     * the session.
     */
    private ensureAutoMemorySeeded;
    /**
     * Build a fresh mcpServers record with a new visionclaw tool server instance.
     * This avoids the "Already connected to a transport" error when the same
     * McpServer instance is reused across concurrent/sequential query() calls.
     */
    private buildMcpServers;
    private buildNativeToolOptions;
    private buildHooks;
    /**
     * Build the shared Claude query options used for both pre-warm and live runs.
     */
    private buildQueryOptions;
    /**
     * Cancel any pending proactive-refresh timer. Idempotent.
     */
    private clearWarmRefreshTimer;
    /**
     * Arm the proactive-refresh timer. Caller must ensure any previous timer
     * is cleared. Uses `.unref()` so the process can exit while a warm
     * subprocess is idle — the refresh is purely a background optimization
     * and should never hold the event loop alive on its own.
     */
    private scheduleWarmRefresh;
    /**
     * Handler for the refresh timer. Covers two cases:
     *  1. Normal TTL refresh: `warmQuery` is non-null → close the stale
     *     subprocess and start a fresh one.
     *  2. Retry after a previous failure: `warmQuery` is null → just
     *     attempt warmUp again (no teardown needed).
     *
     * If the warm was already consumed between timer arming and firing,
     * the consume path would have cleared the timer, so we shouldn't
     * reach here in that case.
     */
    private performWarmRefresh;
    /**
     * Post-warmUp follow-up for performWarmRefresh. Kept in its own method so
     * TS's control-flow narrowing in performWarmRefresh doesn't treat these
     * fields as having their pre-await values.
     */
    private handleWarmRefreshOutcome;
    /**
     * Send the initial user message and return the query generator to stream
     * responses. The underlying async generator stays open so that additional
     * messages can be injected via injectMessage().
     */
    warmUp(): Promise<void>;
    /**
     * Tear down any idle pre-warmed subprocess and cancel its refresh timer.
     *
     * Called when something in `config` that was baked into the warm options
     * changed (provider, env, model). The warm subprocess inlines provider
     * env via `buildAgentEnv(config)` at warm time and `sendAndStream()`
     * deliberately reuses those baked options on the warm path — so without
     * this, a provider switch can be silently undone by consuming a stale
     * warm child for one more turn.
     *
     * Idempotent and best-effort: safe to call when no warm exists, and
     * does not throw on close failures (the child will be reaped on
     * process exit if abort/close misfires).
     */
    discardWarmQuery(): void;
    sendAndStream(content: MessageParam["content"], options?: SendAndStreamOptions): AsyncIterable<AgentStreamMessage>;
    /**
     * Inject a new user message into the running query stream.
     * The agent will see this as a follow-up user message in the conversation.
     * Only works while a query is active (between sendAndStream and closeInput).
     *
     * @returns true if the message was injected, false if the generator is closed.
     */
    injectMessage(content: MessageParam["content"], options?: InjectMessageOptions): boolean;
    /**
     * Signal the input generator to close.
     * Any injected messages still in the pending queue are counted as
     * orphaned — they were accepted by injectMessage but never delivered
     * to the SDK.
     */
    closeInput(): void;
    /**
     * Returns true if injected messages were lost when the generator closed.
     */
    get hasOrphanedInjections(): boolean;
    get isInputClosed(): boolean;
    /**
     * Request that the current agent turn be stopped.
     * Uses the SDK's AbortController to cleanly signal the running query to
     * stop, then closes the input stream so the loop returns to idle.
     */
    requestStop(): void;
    isStopRequested(): boolean;
    clearStop(): void;
    /**
     * Update the persisted session ID.
     *
     * Only saves on first capture (null → id). Once set, a different ID from
     * the SDK is ignored — it means the resume failed and the SDK created a
     * throwaway conversation. Persisting it would corrupt session.json and
     * orphan the real transcript.
     */
    captureSessionId(id: string | undefined): void;
    captureTranscriptPath(p: string | undefined | null): void;
    getSessionId(): string | null;
    private pendingResetSummary;
    resetForNewSession(summary?: string): void;
    injectSummary(summary: string): void;
    /**
     * If a reset summary is pending, consume and return it (clears the pending state).
     */
    consumeResetSummary(): string | null;
    /**
     * Proactively request compaction by running the /compact slash command as a
     * separate one-turn query resumed from the current session.
     *
     * This is more reliable than injecting "/compact" into the streaming prompt
     * because the running query may treat it as ordinary text.
     */
    requestCompaction(): Promise<void>;
    captureUsageSnapshot(snapshot: UsageSnapshot): void;
    /**
     * Update the usage snapshot after the SDK performs mid-query auto-compaction.
     * Uses `pre_tokens` (the post-compaction token count) and the last known
     * context window to compute an accurate `usedPct`, preventing the post-query
     * `maybeCompact` from triggering a redundant compaction.
     */
    capturePostCompactionSnapshot(postCompactionTokens: number): void;
    /**
     * Unified between-turn compaction gate.  Triggers compaction when **either**:
     *  1. Token usage exceeds the context-window threshold, OR
     *  2. Total image blocks (real + placeholder) in the transcript reach
     *     `imageThreshold`, approaching Claude API's 100-image limit.
     *
     * Both triggers share the same cooldown and in-flight guard so we never
     * run two back-to-back compactions in the same between-turn pass.
     */
    maybeCompact(options?: {
        /**
         * Optional explicit override. When set, takes precedence over the
         * context-window-aware auto-scaling done by
         * {@link getCompactUsedPctThreshold}.
         */
        usedPctThreshold?: number;
        cooldownMs?: number;
        imageThreshold?: number;
    }): Promise<void>;
    getTranscriptPath(): string | null;
    getTranscriptInfo(): {
        kind: "claude";
        sessionFilePath?: string;
        sessionDir: string;
    };
    /**
     * Derive the transcript path from the session ID and config dir, even before
     * the SDK subprocess has started and reported it via hooks.
     */
    private deriveTranscriptPath;
    getUsageSnapshot(): UsageSnapshot | null;
    getConsecutiveCompactionTimeouts(): number;
    /** Returns the live Query object, or null if no query is active. */
    getCurrentQuery(): Query | null;
    /**
     * Validation adapter around `Query.getContextUsage()` (SDK 0.2.114+).
     *
     * The SDK's `Query` interface exposes `getContextUsage()` as a control
     * request over the streaming bridge. It returns a rich breakdown:
     *
     * ```ts
     * {
     *   categories: { name: string; tokens: number; color: string; isDeferred?: boolean }[];
     *   totalTokens: number;
     *   maxTokens: number;       // context window for the *active* model
     *   rawMaxTokens: number;    // nominal max before any reductions
     *   percentage: number;      // UI percentage (0..100)
     *   gridRows: ...[];         // TUI-only
     *   model: string;
     *   memoryFiles: { path: string; type: string; tokens: number }[];
     *   mcpTools: { name: string; serverName: string; tokens: number }[];
     * }
     * ```
     *
     * We map it onto our existing {@link UsageSnapshot} shape:
     *   - `usedInputTokens` ← `totalTokens`
     *   - `contextWindow`   ← `maxTokens` (post-reduction; matches what the
     *                         model actually has to work with right now)
     *   - `usedPct`         ← computed from the two above, NOT from the
     *                         SDK's `percentage` field. The SDK's value is
     *                         UI-scale (0..100); our consumers expect 0..1.
     *                         Computing locally keeps both fields internally
     *                         consistent.
     *   - `allImageBlocks`  — intentionally preserved from any existing
     *                         snapshot; `getContextUsage` doesn't know about
     *                         raw image-block counts in the transcript, so
     *                         we don't clobber the value that
     *                         {@link captureUsageSnapshot} maintains.
     *
     * This is purely a read adapter — no callers in compaction or the
     * wake loop should depend on it yet. It exists to let us sanity-check
     * the live SDK response before wiring any behavior changes around it.
     * Returns null when no live query is active, when the method is absent
     * on the active Query instance (e.g. an older CLI), or when the call
     * errors transiently.
     */
    getLiveContextUsage(): Promise<UsageSnapshot | null>;
    /** Replace the full set of dynamic MCP servers (used when loading from disk). */
    setDynamicMcpServers(servers: Record<string, Record<string, unknown>>): void;
    /** Get the current dynamic MCP servers record. */
    getDynamicMcpServers(): Record<string, Record<string, unknown>>;
}
//# sourceMappingURL=session.d.ts.map