import { GoogleGenAI } from "@google/genai";
export class GeminiProvider {
    name = "gemini";
    client;
    constructor(opts = {}) {
        // GoogleGenAI doesn't accept a custom fetch; relies on undici globally,
        // which proxy-bootstrap.ts has already configured. baseURL is supported
        // via httpOptions.
        this.client = new GoogleGenAI({
            apiKey: opts.apiKey,
            ...(opts.baseURL ? { httpOptions: { baseUrl: opts.baseURL } } : {}),
        });
    }
    async runTurn(opts) {
        const contents = anthropicToGemini(opts.messages);
        const tools = opts.tools.length > 0
            ? [
                {
                    functionDeclarations: opts.tools.map((t) => ({
                        name: t.name,
                        description: t.description,
                        parametersJsonSchema: t.inputSchema,
                    })),
                },
            ]
            : undefined;
        const stream = await this.client.models.generateContentStream({
            model: opts.model,
            contents,
            config: {
                systemInstruction: opts.systemPrompt,
                tools,
                maxOutputTokens: opts.maxTokens ?? 16_000,
            },
        });
        let text = "";
        const toolCalls = [];
        let finish = "STOP";
        let inputTokens = 0;
        let outputTokens = 0;
        for await (const chunk of stream) {
            const cand = chunk.candidates?.[0];
            if (cand?.finishReason)
                finish = cand.finishReason;
            const parts = cand?.content?.parts ?? [];
            for (const p of parts) {
                if (typeof p.text === "string" && p.text.length > 0) {
                    text += p.text;
                    opts.handlers?.onTextDelta?.(p.text);
                }
                if (p.functionCall) {
                    toolCalls.push({
                        id: p.functionCall.id ?? `call_${p.functionCall.name}_${Math.random().toString(36).slice(2)}`,
                        name: p.functionCall.name ?? "",
                        args: (p.functionCall.args ?? {}),
                    });
                }
            }
            // usageMetadata is on the final chunk
            const usage = chunk.usageMetadata;
            if (usage) {
                inputTokens = usage.promptTokenCount ?? 0;
                outputTokens = usage.candidatesTokenCount ?? 0;
            }
        }
        const content = [];
        if (text) {
            content.push({ type: "text", text, citations: null });
        }
        for (const tc of toolCalls) {
            content.push({
                type: "tool_use",
                id: tc.id,
                name: tc.name,
                input: tc.args,
            });
        }
        // Gemini finish reasons: STOP, MAX_TOKENS, SAFETY, RECITATION, OTHER, BLOCKLIST,
        // PROHIBITED_CONTENT, SPII, MALFORMED_FUNCTION_CALL, IMAGE_SAFETY.
        // Tool calls are signaled by the presence of functionCall parts, NOT a
        // distinct finishReason — so we infer from toolCalls.length.
        const stopReason = toolCalls.length > 0 ? "tool_use" : "end_turn";
        return {
            content,
            stopReason,
            usage: {
                inputTokens,
                outputTokens,
                cacheReadTokens: 0,
                cacheWriteTokens: 0,
            },
        };
    }
}
/**
 * Translate Lisa's Anthropic-style message history into Gemini's Content[]
 * shape. Handles text, tool_use (assistant), and tool_result (user) blocks.
 *
 * Edge cases handled:
 *   - Empty user text + only tool_results → produces user message with
 *     functionResponse parts (Gemini accepts this).
 *   - Multi-modal images (Anthropic image blocks) → mapped to inlineData
 *     parts when base64 source is present; URL sources skipped (Gemini
 *     wants base64 or fileData).
 */
function anthropicToGemini(messages) {
    const out = [];
    for (const msg of messages) {
        const isUser = msg.role === "user";
        const parts = [];
        const content = msg.content;
        if (typeof content === "string") {
            parts.push({ text: content });
        }
        else {
            for (const block of content) {
                if (block.type === "text") {
                    if (block.text)
                        parts.push({ text: block.text });
                }
                else if (block.type === "tool_use") {
                    // Assistant tool_use → Gemini functionCall
                    parts.push({
                        functionCall: {
                            id: block.id,
                            name: block.name,
                            args: (block.input ?? {}),
                        },
                    });
                }
                else if (block.type === "tool_result") {
                    // User tool_result → Gemini functionResponse
                    // The tool_result content can be a string or an array of blocks.
                    // Gemini's response is an object, so we wrap text in {output: text}
                    // or {error: text}.
                    const resultText = typeof block.content === "string"
                        ? block.content
                        : Array.isArray(block.content)
                            ? block.content
                                .map((b) => (b.type === "text" ? b.text : ""))
                                .join("\n")
                            : "";
                    parts.push({
                        functionResponse: {
                            id: block.tool_use_id,
                            name: extractToolNameFromHistory(messages, block.tool_use_id) ?? "unknown",
                            response: block.is_error
                                ? { error: resultText }
                                : { output: resultText },
                        },
                    });
                }
                else if (block.type === "image" && "source" in block) {
                    // Best-effort image translation. Skip if not base64.
                    const src = block.source;
                    if (src?.type === "base64" && src.data && src.media_type) {
                        parts.push({
                            inlineData: { mimeType: src.media_type, data: src.data },
                        });
                    }
                }
            }
        }
        if (parts.length === 0)
            continue;
        out.push({ role: isUser ? "user" : "model", parts });
    }
    return out;
}
/**
 * Walk back through history to find the tool_use block whose id matches
 * `toolUseId`, return its name. Gemini's functionResponse needs the name,
 * not just the id — Anthropic's tool_result only carries the id.
 */
function extractToolNameFromHistory(messages, toolUseId) {
    for (const msg of messages) {
        if (msg.role !== "assistant")
            continue;
        const content = msg.content;
        if (typeof content === "string")
            continue;
        for (const block of content) {
            if (block.type === "tool_use" && block.id === toolUseId) {
                return block.name;
            }
        }
    }
    return null;
}
//# sourceMappingURL=gemini.js.map