/** * Media router — one LLM call that picks which image/video model fits * the user's request, with alternatives and cost estimates, so the agent * can show a clean AskUser proposal before spending. * * Principle (matches turn-analyzer): harness orchestrates, free model * decides. No keyword-to-model mapping in TypeScript. Classifier reads * the prompt + the current gateway catalog (pulled dynamically), picks * one recommended model plus a cheaper + a premium alternative, and * explains the choice in one sentence. * * Cost estimates come from `gateway-models.ts` — always dynamic, * margin-adjusted. */ import type { ModelClient } from './llm.js'; export type MediaKind = 'image' | 'video'; export type MediaStyle = 'photoreal' | 'illustration' | 'anime' | 'logo' | 'concept' | 'other'; export type MediaPriority = 'cost' | 'quality' | 'balanced'; export interface MediaChoice { model: string; estimatedCostUsd: number; rationale: string; } export interface MediaProposal { kind: MediaKind; quantity: number; durationSeconds?: number; maxDurationSeconds?: number; recommended: MediaChoice; cheaper?: MediaChoice; premium?: MediaChoice; intent: { style: MediaStyle; priority: MediaPriority; }; /** * A fuller rewrite of the user's prompt, following the 5-slot template * (scene/subject/details/use-case/constraints). Null when the classifier * judged the input already well-specified, or when the env opt-out is set, * or when the rewrite was identical to the raw input. When non-null, the * AskUser layout surfaces it as "Refined:" with a "Use ORIGINAL" option. */ refinedPrompt: string | null; refinementSummary: string; totalCostUsd: number; } export declare function clearMediaRouterCache(): void; /** * Normalize a refined prompt: trim, cap length, reject obvious junk. * Returns null when the value should be treated as absent (missing, * non-string, empty after trim). * * Exported for testability — invariants matter more here than elsewhere * because the output is user-visible and paid for. */ export declare function validateRefined(raw: unknown, maxChars: number): string | null; /** * Whitespace-insensitive, case-insensitive identity check — if the * classifier's "refinement" is just the input with different spacing, * don't bother the user with a "Refined:" block. */ export declare function isEffectivelyIdentical(a: string, b: string): boolean; export declare const REFINED_PROMPT_MAX_CHARS = 500; export declare const REFINEMENT_SUMMARY_LIMIT = 80; export interface AnalyzeMediaOpts { kind: MediaKind; prompt: string; client: ModelClient; quantity?: number; durationSeconds?: number; signal?: AbortSignal; /** * One-shot opt-out — caller stripped a `///` prefix from the user's input * and wants the proposal rendered without a Refined block or Use-original * option. The classifier still runs (for model selection), but the * refinement is discarded at parse time. */ skipRefine?: boolean; } /** * Pick the best model + alternatives for this media request. Returns null * on any failure path (classifier timeout, parse error, empty catalog) so * the caller can fall back to its old hardcoded default rather than * blocking the user. */ export declare function analyzeMediaRequest(opts: AnalyzeMediaOpts): Promise; /** * Render a proposal as the user-facing AskUser question. Layout matches * the spec from v3.8.31 planning: recommended first with • bullet, * alternatives below with ○ bullets, prices include the 5% margin note. */ export declare function renderProposalForAskUser(p: MediaProposal, userPrompt: string): { question: string; options: Array<{ id: string; label: string; }>; };