import { OpenCodeAdapter } from './opencode-stdio-adapter.js';
import type { AgentSendOpts, ChatMessage } from '../../../core/types.js';
import { type OpencodeServeManager } from './serve-manager.js';
import { type ApprovalBus } from '../../../core/approval-bus.js';
/** Shape of an event arriving on opencode's /event SSE stream. Every field
 *  is optional because we are deliberately conservative — opencode's bus is
 *  shared across many event types and we touch only the slice we need. */
interface OpencodePart {
    id?: string;
    type?: string;
    text?: string;
    tokens?: {
        input?: number;
        output?: number;
        total?: number;
        reasoning?: number;
    };
    cost?: number;
    time?: {
        start?: number;
        end?: number;
    };
    sessionID?: string;
}
/** Shape returned by POST /session/:id/message on opencode 1.14.45+ where the
 *  /event SSE stream silently drops bus events. The endpoint blocks until the
 *  assistant message is complete and returns the full message body. We use it
 *  as the authoritative source of text + usage when SSE yields nothing. */
interface OpencodeMessageResponse {
    info?: {
        id?: string;
        sessionID?: string;
        cost?: number;
        tokens?: {
            input?: number;
            output?: number;
            total?: number;
            reasoning?: number;
        };
    };
    parts?: OpencodePart[];
}
interface OpencodeEvent {
    type: string;
    properties?: {
        sessionID?: string;
        info?: {
            id?: string;
            status?: {
                type?: string;
            };
            sessionID?: string;
        };
        part?: OpencodePart;
        status?: {
            type?: string;
        };
        error?: unknown;
        /** permission.asked carries an id at properties.id (Permission.Request). */
        id?: string;
        /** Permission name on permission.asked, e.g. "external_directory" / "bash". */
        permission?: string;
        /** Patterns the agent wants to access. */
        patterns?: string[];
        /** Tool-specific metadata that may help the user decide. */
        metadata?: Record<string, unknown>;
    };
}
export interface HttpAdapterOptions {
    /** Override the singleton serve manager. Tests inject a mock. */
    serve?: OpencodeServeManager;
    /** Override fetch. Tests inject a mock. */
    fetchImpl?: typeof fetch;
    /** Override the IM approval bus. Defaults to the process singleton; pass
     *  `null` to disable the bridge entirely (used by tests that want to lock
     *  in the no-bus fallback path). */
    approvalBus?: ApprovalBus | null;
}
export declare class OpenCodeHttpAdapter extends OpenCodeAdapter {
    private readonly serve;
    private readonly fetchImpl;
    /** null when the bridge is intentionally disabled. */
    private readonly approvalBus;
    /** Set of opencode session ids whose ruleset we've already PATCHed in this
     *  process lifetime. Lets us re-apply the gate policy when an agim
     *  restart resurfaces an old session, without duplicating rules every
     *  turn. Cleared only by agim restart — that's fine since the rules
     *  the previous run wrote are still in opencode's session DB. */
    private readonly rulesetApplied;
    constructor(opts?: HttpAdapterOptions);
    /**
     * Translate a single SSE event into adapter-side effects:
     *   - sessionID capture → opts.onAgentSessionId
     *   - step-finish cost/tokens → opts.onUsage
     *   - text-part with finished `time.end` → returned as a chunk (with
     *     part id when known, so the caller can dedupe against the POST-body
     *     fallback for SSE-broken builds — see drainResponseBody).
     * Pure: tests drive this directly without spinning up a daemon.
     */
    inspectHttpEvent(event: OpencodeEvent, sessionID: string, opts: AgentSendOpts): {
        text?: string;
        partId?: string;
    };
    /**
     * `session.status` event with status.type === 'idle' for our session is
     * the canonical "stop" signal in opencode (cf. run.ts:532).
     */
    isIdleEvent(event: OpencodeEvent, sessionID: string): boolean;
    isErrorEvent(event: OpencodeEvent, sessionID: string): {
        error: string;
    } | null;
    buildHttpContextualPrompt(prompt: string, history?: ChatMessage[]): string;
    sendPrompt(_sessionId: string, prompt: string, history?: ChatMessage[], opts?: AgentSendOpts): AsyncGenerator<string>;
    /**
     * Extract assistant text + usage from the POST /session/:id/message response
     * body, skipping any text part already emitted via SSE (matched by part id).
     * Used as a fallback for opencode builds whose /event stream is broken.
     */
    drainResponseBody(body: OpencodeMessageResponse, alreadyYielded: Set<string>, opts: AgentSendOpts): string[];
    /**
     * Decide how to handle a `permission.asked` SSE event:
     *   - If `this.approvalBus` is set AND has a notifier installed AND the
     *     call carries enough IM context (threadId + platform) → register a
     *     synthetic pending so the user gets an IM card; the bus's resolve
     *     callback POSTs the decision back to opencode via /permission/.../reply.
     *   - Otherwise → fall back to `once`. Without an IM channel there's no
     *     human to ask, and a plain `reject` would surface as a tool-call
     *     failure mid-conversation which is worse UX than allowing.
     *
     * Fire-and-forget: the SSE loop must keep draining other events while the
     * bridge waits for the user. The bus enforces its own timeout (5 min by
     * default) so we never deadlock the SSE loop.
     */
    private routeAsk;
    /**
     * Build the session-level permission ruleset to append to opencode's
     * agent defaults. See createSession's docstring for policy rationale.
     *
     * The returned rules are flat `{permission, pattern, action}` objects
     * matching opencode's `Permission.Rule` schema.
     */
    private buildSessionRuleset;
    /**
     * PATCH a resumed session with our gate ruleset so subsequent tool calls
     * surface as `permission.asked` events for the bridge. opencode's update
     * endpoint merges (current + payload) — payload comes last and wins under
     * findLast semantics. Idempotent across agim restarts but NOT within a
     * single process: the caller (sendPrompt) gates on rulesetApplied.
     */
    private applyRuleset;
    private createSession;
    private postPrompt;
    /**
     * Poll `GET /permission` as the fallback channel for permission.asked when
     * opencode's SSE bus is broken (1.14.45+). Every tick lists pending
     * permissions for the daemon, filters to this session, and feeds new
     * entries through routeAsk — the same bridge the SSE path would have used.
     *
     * Local `seen` Set keeps a turn from re-routing the same reqId on every
     * tick. The bus also dedupes by reqId in registerSyntheticPending, so a
     * future opencode build that fixes SSE won't cause double-routing.
     *
     * The first tick fires immediately so a permission already pending at
     * sendPrompt entry (e.g. one persisted from a previous opencode serve
     * lifetime) surfaces without waiting an interval.
     *
     * Returns a stop function. Caller MUST invoke it in finally{} to prevent
     * the interval from leaking once the turn ends.
     */
    private startPermissionPoller;
    private replyPermission;
    private openEventStream;
}
export declare class EventStream implements AsyncIterable<OpencodeEvent> {
    private reader;
    private decoder;
    private buf;
    private closed;
    constructor(body: NonNullable<Response['body']>);
    close(): void;
    [Symbol.asyncIterator](): AsyncIterator<OpencodeEvent>;
}
export type { OpencodeEvent };
//# sourceMappingURL=opencode-http-adapter.d.ts.map