import type { complete, Api, Model, ProviderHeaders } from "@earendil-works/pi-ai/compat"; import type { SummaryGenerationContext } from "./summary-review.ts"; import { awaitWithAbort } from "./abortable.ts"; import { findModelWithProviderRouting, isModelInScope } from "./summary-model-scope.ts"; async function resolveFirstAvailableModel( ctx: SummaryGenerationContext, candidates: Array<{ provider: string; id: string }>, ): Promise<{ model: Model; apiKey?: string; headers?: ProviderHeaders }> { for (const { provider, id } of candidates) { const model = findModelWithProviderRouting(ctx.modelRegistry, provider, id); if (!model || !isModelInScope(model, ctx.scopedModels)) continue; const auth = await ctx.modelRegistry.getApiKeyAndHeaders(model); if (auth.ok) return { model, apiKey: auth.apiKey, headers: auth.headers }; } throw new Error(`No enabled model available: ${candidates.map(candidate => `${candidate.provider}/${candidate.id}`).join(", ")}`); } export async function rewriteSearchQuery( query: string, ctx: SummaryGenerationContext, signal: AbortSignal, ): Promise { const { model, apiKey, headers } = await resolveFirstAvailableModel(ctx, [ { provider: "anthropic", id: "claude-haiku-4-5" }, { provider: "google", id: "gemini-3.6-flash" }, { provider: "openai", id: "gpt-5-mini" }, ]); const registry = ctx.modelRegistry as typeof ctx.modelRegistry & { complete?: typeof complete }; const usesRegistryComplete = typeof registry.complete === "function"; if (signal.aborted) throw new Error("Aborted"); const completeFn = usesRegistryComplete ? registry.complete!.bind(registry) : (await awaitWithAbort(import("@earendil-works/pi-ai/compat"), signal)).complete; if (signal.aborted) throw new Error("Aborted"); const response = await completeFn( model, { messages: [{ role: "user", content: [{ type: "text", text: `Rewrite this web search query to get better, more specific results. Add relevant year qualifiers, precise technical terms, and specificity. Return ONLY the improved query text, nothing else.\n\nQuery: ${query}` }], timestamp: Date.now(), }], }, usesRegistryComplete ? { signal } : { apiKey, headers, signal }, ); if (response.stopReason === "aborted") throw new Error("Aborted"); const contentParts = Array.isArray(response.content) ? response.content : []; const text = contentParts .map((part: unknown) => part && typeof part === "object" && (part as { type?: unknown }).type === "text" && typeof (part as { text?: unknown }).text === "string" ? (part as { text: string }).text : "") .join("") .trim(); if (!text) throw new Error("Rewrite returned empty response"); return text; }