/**
 * Chat message role. We intentionally support only the three canonical
 * roles needed by introspection callers; tool / function roles arrive in
 * Stage 2 when we add the real agent loop.
 */
export type LlmMessageRole = 'system' | 'user' | 'assistant' | 'tool';
/**
 * Single chat message as sent to / received from the LLM provider.
 *
 * Roles + valid combinations of fields:
 *
 *   { role: 'system'    | 'user', content }
 *     plain text turn.
 *
 *   { role: 'assistant', content, toolCalls? }
 *     model output. content may be the empty string when the model
 *     emitted only tool calls. toolCalls echoes ChatResult.toolCalls
 *     verbatim from a prior turn — required for history so the next
 *     tool-result message can reference call IDs.
 *
 *   { role: 'tool',      content, toolCallId }
 *     reply from agent code to a previous assistant tool call. content
 *     is the stringified tool result. toolCallId MUST match an id from
 *     the assistant turn's toolCalls (else providers reject the batch).
 */
export interface LlmMessage {
    role: LlmMessageRole;
    content: string;
    /** Required when role='tool'; ignored otherwise. */
    toolCallId?: string;
    /** Required to round-trip assistant turns that contained tool calls;
     *  ignored on user/system/tool messages. Empty/undefined for plain
     *  assistant text turns. */
    toolCalls?: ToolCallRequest[];
    /** DeepSeek / thinking-mode providers echo `reasoning_content` on
     *  assistant turns; MUST round-trip on subsequent API calls. */
    reasoningContent?: string;
    /** Optional multimodal attachments (typically images). Providers with
     *  `supportsVision() === true` encode these as image_url / image
     *  content blocks; others ignore the field and only see `content`. The
     *  text path is preserved verbatim so non-vision providers still know
     *  an attachment was referenced. */
    media?: Array<{
        /** Absolute path to the file on disk. agim only attaches files
         *  inside the per-user media dir (`~/.agim/media/...`); providers
         *  read + inline-base64 the bytes. */
        path: string;
        /** Optional MIME hint (e.g. 'image/jpeg'). Provider may sniff from
         *  the extension if absent. */
        mime?: string;
    }>;
}
/**
 * Single tool exposed to the LLM. Equivalent to OpenAI's
 * {type:'function', function:{...}} wrapper and Anthropic's {name,
 * description, input_schema} entry; providers do the dialect adapt.
 *
 * `parameters` MUST be a JSON Schema object (typically
 * `{type:'object', properties:{...}, required:[...]}`). We don't
 * validate the schema here — providers usually pass it through verbatim
 * and the LLM itself enforces the contract.
 */
export interface ToolDef {
    name: string;
    description: string;
    parameters: Record<string, unknown>;
    /** v1.2.118 — optional per-tool wall timeout (ms). The agent loop
     *  races the dispatch promise against this and surfaces a structured
     *  "timed out" tool-result on overrun, so a wedged tool can't eat
     *  the whole turn budget. Leave undefined to use the agent-loop
     *  default (60s, env IMHUB_NATIVE_TOOL_TIMEOUT_MS). 0 disables for
     *  this specific tool (e.g. `long_task` which is explicitly slow). */
    timeoutMs?: number;
}
/**
 * One tool call requested by the model. The Stage-2 agent loop iterates
 * over `ChatResult.toolCalls`, executes each one, then appends matching
 * `{role:'tool'}` messages and re-invokes the provider. The `id` is
 * provider-assigned (OpenAI: 'call_...', Anthropic: 'toolu_...') and
 * MUST round-trip on the tool-result message — providers reject batches
 * where ids don't match.
 *
 * `arguments` is the already-JSON-parsed input. OpenAI returns a JSON
 * string in `function.arguments`; we parse it client-side so every
 * caller sees a normal object. When the LLM emits malformed JSON the
 * provider sets `argumentsRaw` and leaves `arguments` as `{}`; tool
 * implementations should validate the shape themselves.
 *
 * `raw` is reserved for provider-specific metadata that must round-trip
 * across turns (e.g. Anthropic cache_control hints, future Gemini
 * thought signatures). The agent loop preserves it verbatim.
 */
export interface ToolCallRequest {
    id: string;
    name: string;
    arguments: Record<string, unknown>;
    /** When present, JSON parsing of the arguments failed; tools should
     *  validate or refuse. Kept as a separate field so the common case
     *  (`arguments` is the parsed object) stays cheap. */
    argumentsRaw?: string;
    raw?: unknown;
}
/** Tool-choice hint forwarded to providers. Most providers default to
 *  'auto'; a few callers want to force a specific tool ('required' +
 *  name) or block all tool use ('none') for one call. Provider support
 *  varies — unknown values fall back to 'auto'. */
export type ToolChoice = 'auto' | 'required' | 'none' | {
    type: 'tool';
    name: string;
};
/** Token + cost usage surfaced by the provider, when available. */
export interface LlmUsage {
    promptTokens?: number;
    completionTokens?: number;
    totalTokens?: number;
    /** Cost in USD if the provider reports it; otherwise undefined. We
     *  deliberately don't try to compute cost from a hardcoded price table
     *  — Stage 1 just passes through whatever the provider says. */
    costUsd?: number;
}
/** Reason the model stopped generating. */
export type FinishReason = 'stop' | 'tool_use' | 'length' | 'content_filter' | 'error' | 'unknown';
/** Per-call options. All fields are optional with sensible defaults. */
export interface ChatOptions {
    /** Override the provider's default model for this call. */
    model?: string;
    /** Sampling temperature. Default = provider default (usually 0.7). */
    temperature?: number;
    /** Max tokens in the completion. Default = provider default. */
    maxTokens?: number;
    /** Optional system prompt to prepend (a convenience over building a
     *  system message yourself; ignored if messages already starts with
     *  a system message). */
    system?: string;
    /** Abort the request when this signal fires. Required by callers that
     *  manage their own deadlines (memory-consolidate has a 60s cap). */
    signal?: AbortSignal;
    /** Internal hard deadline in ms. If both signal and timeoutMs are
     *  given, the earlier of the two wins. */
    timeoutMs?: number;
    /** Called once per provider response (or once per chunk for stream)
     *  with whatever usage info the provider returned. Best-effort —
     *  callers should not block on this. */
    onUsage?: (usage: LlmUsage) => void;
    /** Extra provider-specific knobs. Each provider documents what it
     *  reads from this bag; unknown keys are ignored. Keeps the public
     *  ChatOptions type stable while letting power users tune
     *  OpenRouter-style headers, DeepSeek-specific thinking mode, etc. */
    providerOptions?: Record<string, unknown>;
    /** Stage 2 — tool definitions advertised to the model. When unset
     *  (or empty) the model has no tool access for this call; this is
     *  the introspection-caller default and matches Stage-1 behaviour. */
    tools?: ToolDef[];
    /** Stage 2 — tool-use hint. 'auto' (default) lets the model decide;
     *  'required' forces some tool call; 'none' suppresses tools even
     *  when `tools` is non-empty; `{type:'tool', name}` forces a
     *  specific tool. Provider support varies; unknown values fall
     *  back to 'auto'. */
    toolChoice?: ToolChoice;
}
/** Result of a chat call. */
export interface ChatResult {
    /** Plain text body. May be the empty string on `finishReason: 'error'`
     *  or when the model emitted only tool_calls. */
    text: string;
    /** Stage 2 — tool calls the model wants the agent loop to execute.
     *  When present, finishReason is typically 'tool_use'. Stage-1
     *  introspection callers can simply ignore this field — non-empty
     *  toolCalls in a chat() call (no agent loop) just means the model
     *  spent budget on a tool the caller never advertised, which is rare
     *  but harmless. */
    toolCalls?: ToolCallRequest[];
    /** Provider-specific thinking trace (e.g. DeepSeek reasoning_content). */
    reasoningContent?: string;
    /** Usage metrics, when the provider reports them. */
    usage: LlmUsage;
    /** Why the model stopped. */
    finishReason: FinishReason;
    /** Model id the provider actually used (handy for diagnostics when
     *  the caller relies on the provider's default rather than an explicit
     *  `opts.model`). */
    modelUsed: string;
}
/** v1.2.112 — partial tool_call landed in a single SSE chunk. The
 *  OpenAI streaming protocol splits a tool_call across many chunks:
 *  first chunk carries id + name, subsequent chunks add `argumentsDelta`
 *  fragments that the consumer concatenates by `index`. Other fields
 *  arrive only on the first chunk for a given index. Consumer
 *  accumulates by index; abort mid-stream may leave some entries with
 *  partial / no name (skip those — too risky to dispatch). */
export interface ToolCallStreamDelta {
    index: number;
    id?: string;
    name?: string;
    argumentsDelta?: string;
}
/** Stream chunk. `textDelta` may be the empty string when the chunk
 *  only carries non-text fields (tool_call delta / reasoning delta /
 *  terminal finishReason). */
export interface ChatStreamChunk {
    textDelta: string;
    /** v1.2.112 — partial tool_call(s) from a streamed turn. */
    toolCallDeltas?: ToolCallStreamDelta[];
    /** v1.2.112 — provider thinking-trace delta (e.g. DeepSeek
     *  reasoning_content). Optional; concatenate by order seen. */
    reasoningDelta?: string;
    /** Only populated on the final chunk. Earlier chunks have null. */
    finishReason?: FinishReason | null;
    /** Only populated on the final chunk, when the provider reports it. */
    usage?: LlmUsage;
}
/**
 * Common configuration every provider needs. Concrete providers may add
 * their own fields (e.g. OpenAICompatProvider adds `baseUrl`).
 */
export interface ProviderConfigBase {
    /** Unique identifier within an agim instance — e.g. "deepseek-cheap"
     *  or "gpt-4-1-judge". This is what `llmRoles` references. */
    name: string;
    /** Default model id the provider sends when ChatOptions.model is unset. */
    model: string;
    /** Provider type label, surfaced in logs and the web UI. */
    providerType: string;
    /** API key — provider implementations look this up via secrets.ts; the
     *  base class only uses it for logging length / fingerprinting. */
    apiKey?: string;
    /** Default temperature; falls back to 0.3 for cheap introspection
     *  callers (we want deterministic JSON, not creativity). */
    defaultTemperature?: number;
    /** Default max output tokens. Tight default so a misconfigured prompt
     *  can't burn 16k tokens worth of "Sure! Let me explain…". */
    defaultMaxTokens?: number;
}
/**
 * Base class for every LLM provider. The transport-specific work happens
 * in `_callApi` / `_callApiStream` which subclasses implement. The base
 * adds:
 *   - timeout / AbortSignal merging
 *   - default system prompt injection
 *   - onUsage plumbing (callers shouldn't have to defend against missing
 *     usage info)
 *   - finishReason normalization
 */
export declare abstract class LlmProvider {
    readonly name: string;
    readonly model: string;
    readonly providerType: string;
    protected readonly apiKey: string | undefined;
    protected readonly defaultTemperature: number;
    protected readonly defaultMaxTokens: number;
    constructor(cfg: ProviderConfigBase);
    /** True when the provider has the credentials it needs. False suggests
     *  the operator forgot to set the secret; the registry filters these
     *  out at load time so `getProvider()` callers don't have to check. */
    isConfigured(): boolean;
    /** True when this provider can interpret LlmMessage.media (typically
     *  image attachments). Default false — only the openai-compat
     *  provider currently overrides this (operators opt-in via the
     *  provider's `vision: true` config or a model-name heuristic).
     *  Anthropic / others fall back to text-only. */
    supportsVision(): boolean;
    /**
     * Buffered chat call: send the full message list, await the entire
     * response. Used by all Stage-1 introspection callers because the
     * downstream parsers want a complete JSON string anyway.
     */
    chat(messages: LlmMessage[], opts?: ChatOptions): Promise<ChatResult>;
    /**
     * Streaming chat call: async-iterable of text deltas. Stage-1 callers
     * don't use this yet, but it's in the base API so Stage 2's agent loop
     * (and the future "stream thinking to web UI" feature) can build on it
     * without API churn.
     */
    chatStream(messages: LlmMessage[], opts?: ChatOptions): AsyncIterable<ChatStreamChunk>;
    /** Concrete providers implement this for buffered responses. */
    protected abstract _callApi(messages: LlmMessage[], opts: NormalizedOptions, signal: AbortSignal): Promise<ChatResult>;
    /** Concrete providers implement this for streamed responses. Default
     *  implementation falls back to buffered + single-chunk yield, so
     *  providers that don't support streaming still work (the caller sees
     *  one big chunk on completion). Override for real streaming. */
    protected _callApiStream(messages: LlmMessage[], opts: NormalizedOptions, signal: AbortSignal): AsyncIterable<ChatStreamChunk>;
    /**
     * Prepend `opts.system` as a system message iff the caller didn't
     * already supply one. Saves callers from threading the same conditional
     * everywhere; documented in ChatOptions so behaviour is unsurprising.
     */
    private _prepareMessages;
    private _mergeOptions;
    /**
     * Merge the caller's AbortSignal with the optional timeout into a
     * single composite signal. We deliberately don't use AbortSignal.any
     * because Node 18 doesn't have it; the manual wiring is small enough
     * to keep us portable. Returns a cleanup fn the caller must invoke to
     * clear the timeout (prevents the test runner from keeping the event
     * loop alive past test completion).
     */
    private _buildSignal;
}
/** Internal normalized form of ChatOptions — concrete providers receive
 *  this rather than the raw ChatOptions so they don't have to repeat the
 *  defaulting logic. */
export interface NormalizedOptions {
    model: string;
    temperature: number;
    maxTokens: number;
    timeoutMs?: number;
    providerOptions: Record<string, unknown>;
    /** Stage 2 — tool definitions to advertise. Empty array → undefined
     *  so providers can rely on `opts.tools ? ... : ...` shorthand. */
    tools?: ToolDef[];
    toolChoice?: ToolChoice;
}
/**
 * Helper for providers that want to format an error consistently. Wrapping
 * the underlying fetch / SDK error in LlmProviderError makes it easy for
 * callers (and the registry's "is the provider healthy?" probe) to tell
 * a transport failure apart from a logic bug.
 */
export declare class LlmProviderError extends Error {
    readonly providerName: string;
    readonly httpStatus?: number;
    readonly transient: boolean;
    constructor(providerName: string, message: string, opts?: {
        httpStatus?: number;
        transient?: boolean;
        cause?: unknown;
    });
}
//# sourceMappingURL=provider-base.d.ts.map