/**
 * LLM Client for Franklin
 * Calls BlockRun API directly with x402 payment handling and streaming.
 * Original implementation — not derived from any existing codebase.
 */
import { type Chain } from '../config.js';
import type { Dialogue, CapabilityDefinition, ContentPart, CapabilityInvocation } from './types.js';
/**
 * Anthropic-compatible tool_choice. Forwarded as-is through the proxy and on
 * to the backend (Anthropic / OpenAI / Gemini gateways translate as needed).
 *
 * - `auto`  — model decides (default if omitted)
 * - `any`   — must call SOME tool, model picks which
 * - `tool`  — must call the specifically named tool
 * - `none`  — must not call any tool
 *
 * Used by the grounding-retry path in `loop.ts`: when the evaluator catches
 * an ungrounded answer that should have invoked tools, the next round sets
 * `tool_choice` to force tool use rather than relying on a soft instruction
 * the model can defy by fabricating citations.
 */
export type ToolChoice = {
    type: 'auto';
} | {
    type: 'any';
} | {
    type: 'tool';
    name: string;
} | {
    type: 'none';
};
export interface ModelRequest {
    model: string;
    messages: Dialogue[];
    system?: string;
    tools?: CapabilityDefinition[];
    max_tokens?: number;
    stream?: boolean;
    temperature?: number;
    tool_choice?: ToolChoice;
}
export interface StreamChunk {
    kind: 'content_block_start' | 'content_block_delta' | 'content_block_stop' | 'message_start' | 'message_delta' | 'message_stop' | 'ping' | 'error';
    payload: Record<string, unknown>;
}
export interface CompletionUsage {
    inputTokens: number;
    outputTokens: number;
    /**
     * Anthropic prompt-cache fields. `input_tokens` only counts the base
     * (uncached) portion; the cache-creation and cache-read counts are
     * separate and billed at different rates (1.25× / 0.1× of base input,
     * respectively). Pre-fix, Franklin only read `input_tokens` and
     * silently undercounted every vision / cache-using call's total
     * token spend — verified 2026-05-11 from an Opus 4.7 turn billed
     * $0.567 with audit logging `inputTokens: 3653` (implies ~113K real
     * billed input tokens). Surface all three so audits, stats, and any
     * future estimation paths see the full picture.
     */
    cacheCreationInputTokens?: number;
    cacheReadInputTokens?: number;
}
export interface LLMClientOptions {
    apiUrl: string;
    chain: Chain;
    debug?: boolean;
}
/**
 * Replace Unicode box-drawing characters with their ASCII equivalents.
 *
 * Models occasionally emit U+2502 (`│`) and U+2500 (`─`) in markdown tables
 * — sometimes mixed with ASCII `|` / `-` in the same table. No markdown
 * renderer parses the mix, and the "table" displays as run-on text. Verified
 * 2026-05-06 in a real session: opus-4.7 emitted a CRCL fundamentals table
 * with `│` data rows and `|` separator, ignoring the system-prompt nudge
 * added in 3.15.76. The unconditional swap fixes the rendering at the
 * streaming boundary so every downstream surface (user terminal, conversation
 * history, audit log) gets the corrected version.
 *
 * Trade: the rare case where a user genuinely wants box-drawing in output
 * (e.g. asking what U+2502 looks like) loses fidelity. Acceptable — that
 * case has no real-world frequency, the broken-tables case has weekly.
 */
export declare function sanitizeTableUnicode(s: string): string;
/**
 * Extract the most human-readable message from an error body.
 * Some gateways wrap provider errors multiple times, e.g.
 * `{"error":{"message":"{\"error\":{\"message\":\"...\"}}"}}`.
 * Peel those layers so the UI doesn't show raw nested JSON.
 */
export declare function extractApiErrorMessage(errorBody: string): string;
/**
 * Apply Anthropic prompt caching using the `system_and_3` strategy.
 * Pattern from nousresearch/hermes-agent `agent/prompt_caching.py`.
 *
 * Places 4 cache_control breakpoints (Anthropic's max):
 *   1. System prompt (stable across all turns)
 *   2-4. Last 3 non-system messages (rolling window)
 *
 * Also caches the last tool definition (tools are stable across turns).
 *
 * This keeps the cache warm: each new turn extends the cached prefix rather
 * than invalidating it. Multi-turn conversations see ~75% input token savings
 * on Anthropic models.
 */
/**
 * True if the given Anthropic model accepts the `thinking: { type: 'enabled' }`
 * API flag (so-called *extended thinking*). Models using *adaptive thinking*
 * (Opus 4.7 and later) reject that flag — the behavior is built in and not
 * opt-in via API. Keeping the allowlist explicit, not derived from a regex,
 * so a future model that happens to include "opus" in its name doesn't
 * silently re-enable extended thinking on a model that can't handle it.
 *
 * Exported so tests can pin this decision without a live API.
 */
export declare function modelHasExtendedThinking(model: string): boolean;
/**
 * Classify an unparseable tool-call JSON failure so the user and the model
 * get an actionable message instead of a single generic line. Exported for
 * direct unit testing — the happy path hits it only on stream error.
 */
export declare function classifyToolCallFailure(toolName: string, rawInput: string, signal: AbortSignal | undefined, model: string): string;
export declare function isRoleplayedJsonToolCallText(text: string): boolean;
export declare class ModelClient {
    private apiUrl;
    private chain;
    private debug;
    private walletAddress;
    private cachedBaseWallet;
    private cachedSolanaWallet;
    private walletCacheTime;
    /**
     * USDC actually charged on the most recent x402 settlement, parsed
     * from `details.amount` (micro-USDC → USD). Reset to 0 at the start
     * of every `streamCompletion`, written by `signBasePayment` /
     * `signSolanaPayment`. Callers read it via `getLastPaidUsd()` after
     * the stream completes so franklin-stats.json records the real wallet
     * charge instead of a token-catalog estimate.
     */
    private lastPaidUsd;
    private static WALLET_CACHE_TTL;
    constructor(opts: LLMClientOptions);
    /**
     * Stream a completion from the BlockRun API.
     * Yields parsed SSE chunks as they arrive.
     * Handles x402 payment automatically on 402 responses.
     */
    /**
     * Resolve virtual routing profiles (blockrun/auto, blockrun/free) to
     * concrete models. This is the final safety net — if the router in
     * loop.ts didn't resolve it (e.g. old global install without router),
     * we resolve it here before hitting the API. Legacy blockrun/eco and
     * blockrun/premium fall through the unknown-key path to the same
     * default model.
     */
    private resolveVirtualModel;
    /**
     * USDC actually charged for the most recent stream. 0 if no payment
     * was made (free model / cached / pre-stream error). Callers should
     * read this after the stream finishes — before that it carries the
     * value from a previous call.
     */
    getLastPaidUsd(): number;
    streamCompletion(request: ModelRequest, signal?: AbortSignal): AsyncGenerator<StreamChunk>;
    private parseNonStreamingMessage;
    /**
     * Non-streaming completion for simple requests.
     */
    complete(request: ModelRequest, signal?: AbortSignal, onToolReady?: (tool: CapabilityInvocation) => void, onStreamDelta?: (delta: {
        type: 'text' | 'thinking';
        text: string;
    }) => void): Promise<{
        content: ContentPart[];
        usage: CompletionUsage;
        stopReason: string;
    }>;
    private signPayment;
    private signBasePayment;
    private signSolanaPayment;
    private extractPaymentReq;
    private parseSSEStream;
    private mapEventType;
}
