import { createAnthropic } from '@ai-sdk/anthropic';
import { createOpenAI } from '@ai-sdk/openai';
import { createAnthropicAccountLanguageModel } from './anthropic-account.js';
import { createGitHubCopilotLanguageModel, fetchGitHubCopilotModels } from './copilot-account.js';
import { getOpenAiCompatibleProviderSettingsForCapability, resolveModelCapabilityProfile, } from './model-capabilities.js';
import { createOpenAiAccountLanguageModel, getOpenAiAccountSession } from './openai-account.js';
import { fetchAndCacheProviderMetadata, primeProviderModelMetadata, warmProviderMetadataCacheFromDiscovery } from './provider-metadata.js';
import { getDefaultBaseUrlForProvider, getProviderDefinition, isOAuthAccountProvider, } from './provider-registry.js';
function buildProviderPlugin(provider) {
    const definition = getProviderDefinition(provider);
    const plugin = {
        id: provider,
        definition,
        transport: {
            usesOpenAiCompatibleApi: definition.usesOpenAiCompatibleApi,
            managedProxy: Boolean(definition.managedProxy),
            oauthAccount: isOAuthAccountProvider(provider),
        },
        factory: {
            createLanguageModel: buildModelFactory(provider, definition),
        },
    };
    const discovery = buildProviderDiscovery(provider, definition);
    if (discovery) {
        plugin.discovery = discovery;
    }
    if (provider === 'openrouter') {
        plugin.metadata = {
            warmDiscoveryPayload: (payload) => {
                warmProviderMetadataCacheFromDiscovery(provider, payload);
            },
            primeModelMetadata: async ({ model, apiKey, baseUrl }) => {
                await primeProviderModelMetadata(provider, model, apiKey, baseUrl);
            },
        };
    }
    else if (definition.modelDiscovery) {
        plugin.metadata = {
            warmDiscoveryPayload: (payload) => {
                warmProviderMetadataCacheFromDiscovery(provider, payload);
            },
            primeModelMetadata: async ({ apiKey, baseUrl }) => {
                await fetchAndCacheProviderMetadata(provider, apiKey, baseUrl).catch(() => undefined);
            },
        };
    }
    else if (provider === 'copilot-proxy') {
        plugin.metadata = {
            warmDiscoveryPayload: (payload) => {
                warmProviderMetadataCacheFromDiscovery(provider, payload);
            },
            primeModelMetadata: async ({ model, apiKey, baseUrl }) => {
                await primeProviderModelMetadata(provider, model, apiKey, baseUrl).catch(() => undefined);
            },
        };
    }
    return plugin;
}
function buildModelFactory(provider, definition) {
    if (provider === 'anthropic') {
        return ({ model, apiKey, baseUrl }) => createAnthropic({
            apiKey,
            baseURL: baseUrl,
        })(model);
    }
    if (provider === 'openai') {
        return ({ model, apiKey, baseUrl }) => {
            const headers = apiKey ? { Authorization: `Bearer ${apiKey}` } : undefined;
            return createOpenAI({
                apiKey,
                baseURL: baseUrl || definition.defaultBaseUrl,
                headers,
                name: provider,
                compatibility: 'compatible',
            }).responses(model);
        };
    }
    if (provider === 'openai-proxy') {
        return ({ model, capabilityProfile }) => {
            if (!getOpenAiAccountSession()?.accessToken) {
                throw new Error('OpenAI account session not found. Run `yagr setup` again.');
            }
            return createOpenAiAccountLanguageModel(model, capabilityProfile);
        };
    }
    if (provider === 'anthropic-proxy') {
        return ({ model, apiKey, capabilityProfile }) => createAnthropicAccountLanguageModel(model, apiKey, capabilityProfile);
    }
    if (provider === 'copilot-proxy') {
        return ({ model, capabilityProfile }) => createGitHubCopilotLanguageModel(model, capabilityProfile);
    }
    if (definition.usesOpenAiCompatibleApi) {
        // Hoist once at factory registration time so the thought-signature map
        // survives across all createLanguageModel() calls within a session.
        const persistentFetch = provider === 'google'
            ? createGoogleThoughtSignatureFetch()
            : getOpenAiFetchOverride(provider);
        return ({ model, apiKey, baseUrl, capabilityProfile }) => {
            const headers = apiKey ? { Authorization: `Bearer ${apiKey}` } : undefined;
            const providerClient = createOpenAI({
                apiKey,
                baseURL: baseUrl || definition.defaultBaseUrl,
                headers,
                name: provider,
                compatibility: 'compatible',
                fetch: persistentFetch,
            });
            const providerSettings = getOpenAiCompatibleProviderSettings(provider, capabilityProfile);
            return providerSettings ? providerClient(model, providerSettings) : providerClient(model);
        };
    }
    return ({ model }) => {
        throw new Error(`Provider ${provider} is not yet fully implemented for model ${model}`);
    };
}
function buildProviderDiscovery(provider, definition) {
    const discovery = definition.modelDiscovery;
    if (provider === 'copilot-proxy') {
        return {
            fetchAvailableModels: async ({ apiKey, baseUrl }) => {
                if (!apiKey) {
                    return [];
                }
                const response = await fetch(`${baseUrl || getDefaultBaseUrlForProvider(provider)}/models`, {
                    headers: {
                        Authorization: `Bearer ${apiKey}`,
                        Accept: 'application/json',
                        'User-Agent': 'GitHubCopilotChat/0.26.7',
                        'Editor-Version': 'vscode/1.96.2',
                        'Editor-Plugin-Version': 'copilot-chat/0.26.7',
                    },
                });
                if (!response.ok) {
                    return [];
                }
                const payload = await response.json();
                getProviderPlugin(provider).metadata?.warmDiscoveryPayload?.(payload);
                return fetchGitHubCopilotModels(apiKey, baseUrl || getDefaultBaseUrlForProvider(provider));
            },
        };
    }
    if (!discovery) {
        return undefined;
    }
    return {
        fetchAvailableModels: async ({ apiKey, baseUrl }) => {
            const discoveryUrl = discovery.buildUrl(baseUrl || getDefaultBaseUrlForProvider(provider));
            if (!discoveryUrl) {
                return [];
            }
            const headers = {
                'Content-Type': 'application/json',
            };
            if ((discovery.authMode === 'bearer-optional' || discovery.authMode === 'bearer-required') && apiKey) {
                headers.Authorization = `Bearer ${apiKey}`;
            }
            if (discovery.authMode === 'x-api-key-required' && apiKey) {
                headers['x-api-key'] = apiKey;
                headers['anthropic-version'] = '2023-06-01';
            }
            if (discovery.authMode === 'bearer-required' && !apiKey) {
                return [];
            }
            if (discovery.authMode === 'x-api-key-required' && !apiKey) {
                return [];
            }
            try {
                const response = await fetch(discoveryUrl, { headers });
                if (!response.ok) {
                    throw new Error(`${response.status} ${response.statusText}`);
                }
                const payload = await response.json();
                getProviderPlugin(provider).metadata?.warmDiscoveryPayload?.(payload);
                const models = discovery.mapResponse(payload).sort((left, right) => left.localeCompare(right));
                if (provider === 'openrouter') {
                    return models.filter((model) => resolveModelCapabilityProfile({ provider, model }).toolCalling !== 'none');
                }
                return models;
            }
            catch {
                return [];
            }
        },
    };
}
function getOpenAiCompatibleProviderSettings(provider, capabilityProfile) {
    const baseSettings = getOpenAiCompatibleProviderSettingsForCapability(capabilityProfile);
    if (provider === 'mistral') {
        return {
            ...baseSettings,
            simulateStreaming: true,
        };
    }
    return baseSettings;
}
/**
 * Creates a per-model-instance fetch override for Google that preserves thought_signature
 * values across multi-turn tool use. Newer Gemini models attach a thought_signature to each
 * function call in streaming responses; the OpenAI-compatible SDK strips it when building
 * conversation history, causing subsequent requests to fail with INVALID_ARGUMENT.
 * This interceptor tees the response stream to capture signatures, then re-injects them
 * into the corresponding assistant messages of subsequent requests.
 */
function createGoogleThoughtSignatureFetch() {
    const thoughtSigs = new Map();
    // Track extraction from the previous turn — must finish before the next request runs
    let extractionPromise = Promise.resolve();
    return async (input, init) => {
        // Wait for any in-progress extraction from the previous response before
        // checking / injecting thought signatures into this request.
        await extractionPromise.catch(() => undefined);
        // Optionally rewrite the request body to inject thought signatures
        const requestInit = (thoughtSigs.size > 0 && init?.body && typeof init.body === 'string')
            ? (() => {
                const injected = injectGoogleThoughtSignatures(init.body, thoughtSigs);
                return injected !== init.body ? { ...init, body: injected } : init;
            })()
            : init;
        const response = await fetch(input, requestInit);
        if (!response.ok || !response.body) {
            return response;
        }
        // Tee the response stream so we can extract thought_signatures for the next turn
        const [sdkStream, captureStream] = response.body.tee();
        extractionPromise = extractGoogleThoughtSignatures(captureStream, thoughtSigs);
        return new Response(sdkStream, {
            status: response.status,
            statusText: response.statusText,
            headers: response.headers,
        });
    };
}
function injectGoogleThoughtSignatures(body, sigs) {
    try {
        const parsed = JSON.parse(body);
        if (!Array.isArray(parsed.messages)) {
            return body;
        }
        let modified = false;
        for (const message of parsed.messages) {
            if (message.role !== 'assistant' || !Array.isArray(message.tool_calls)) {
                continue;
            }
            for (const toolCall of message.tool_calls) {
                if (toolCall.id && sigs.has(toolCall.id) && !toolCall.extra_content) {
                    toolCall.extra_content = { google: { thought_signature: sigs.get(toolCall.id) } };
                    modified = true;
                }
            }
        }
        return modified ? JSON.stringify(parsed) : body;
    }
    catch {
        return body;
    }
}
async function extractGoogleThoughtSignatures(stream, sigs) {
    const reader = stream.getReader();
    const decoder = new TextDecoder();
    let buffer = '';
    try {
        while (true) {
            const { done, value } = await reader.read();
            if (done)
                break;
            buffer += decoder.decode(value, { stream: true });
        }
        buffer += decoder.decode(); // flush
    }
    catch {
        // best effort — stream read failure is non-fatal
        return;
    }
    // Check if the body is a plain JSON response (non-streaming doGenerate)
    // or an SSE stream (streaming doStream with "data: {...}" lines)
    const trimmed = buffer.trimStart();
    if (trimmed.startsWith('{')) {
        // Non-streaming JSON response: choices[i].message.tool_calls[j].extra_content
        try {
            const data = JSON.parse(trimmed);
            for (const choice of data.choices ?? []) {
                for (const toolCall of choice.message?.tool_calls ?? []) {
                    const sig = toolCall.extra_content?.google?.thought_signature;
                    if (sig && toolCall.id) {
                        sigs.set(toolCall.id, sig);
                    }
                }
            }
        }
        catch {
            // ignore parse errors
        }
        return;
    }
    // SSE streaming response: scan "data: {...}" lines
    // Track tool call id by index in case id and extra_content arrive in different chunks
    const indexToId = new Map();
    for (const line of buffer.split('\n')) {
        if (!line.startsWith('data: ') || line === 'data: [DONE]')
            continue;
        try {
            const data = JSON.parse(line.slice(6));
            for (const choice of data.choices ?? []) {
                for (const toolCall of choice.delta?.tool_calls ?? []) {
                    // Accumulate index→id mapping whenever an id arrives
                    if (toolCall.id !== undefined && toolCall.index !== undefined) {
                        indexToId.set(toolCall.index, toolCall.id);
                    }
                    const sig = toolCall.extra_content?.google?.thought_signature;
                    if (sig) {
                        // Resolve id from this chunk or from the accumulated index map
                        const id = toolCall.id ?? (toolCall.index !== undefined ? indexToId.get(toolCall.index) : undefined);
                        if (id) {
                            sigs.set(id, sig);
                        }
                    }
                }
            }
        }
        catch {
            // ignore malformed SSE lines
        }
    }
}
function getOpenAiFetchOverride(provider) {
    if (provider !== 'mistral') {
        return undefined;
    }
    return async (input, init) => {
        const response = await fetch(input, init);
        const contentType = response.headers.get('content-type')?.toLowerCase() || '';
        if (contentType.includes('text/event-stream')) {
            return response;
        }
        const payload = await response.clone().text().catch(() => '');
        if (response.ok) {
            if (payload.trim()) {
                let parsed;
                try {
                    parsed = JSON.parse(payload);
                }
                catch {
                    throw new Error(`Mistral API returned non-JSON payload: ${truncateForError(payload.replace(/\s+/g, ' ').trim(), 280)}`);
                }
                const normalized = normalizeMistralToolCalls(parsed);
                if (normalized !== parsed) {
                    return new Response(JSON.stringify(normalized), {
                        status: response.status,
                        statusText: response.statusText,
                        headers: response.headers,
                    });
                }
            }
            return response;
        }
        const compact = payload.replace(/\s+/g, ' ').trim();
        throw new Error(`Mistral API error ${response.status}: ${truncateForError(compact, 280)}`);
    };
}
function truncateForError(value, maxLength) {
    if (value.length <= maxLength) {
        return value;
    }
    return `${value.slice(0, maxLength - 1)}…`;
}
function normalizeMistralToolCalls(payload) {
    if (!payload || typeof payload !== 'object') {
        return payload;
    }
    const root = payload;
    if (!Array.isArray(root.choices) || root.choices.length === 0) {
        return payload;
    }
    let mutated = false;
    const choices = root.choices.map((choice) => {
        if (!choice || typeof choice !== 'object') {
            return choice;
        }
        const record = choice;
        const toolCalls = record.message?.tool_calls;
        if (!Array.isArray(toolCalls) || toolCalls.length === 0) {
            return choice;
        }
        let choiceMutated = false;
        const normalizedToolCalls = toolCalls.map((toolCall) => {
            if (!toolCall || typeof toolCall !== 'object') {
                return toolCall;
            }
            const callRecord = toolCall;
            if (typeof callRecord.type === 'string' && callRecord.type.length > 0) {
                return toolCall;
            }
            choiceMutated = true;
            return {
                ...callRecord,
                type: 'function',
            };
        });
        if (!choiceMutated) {
            return choice;
        }
        mutated = true;
        return {
            ...choice,
            message: {
                ...record.message,
                tool_calls: normalizedToolCalls,
            },
        };
    });
    if (!mutated) {
        return payload;
    }
    return {
        ...payload,
        choices,
    };
}
const providerPluginCache = new Map();
export function getProviderPlugin(provider) {
    const cached = providerPluginCache.get(provider);
    if (cached) {
        return cached;
    }
    const plugin = buildProviderPlugin(provider);
    providerPluginCache.set(provider, plugin);
    return plugin;
}
//# sourceMappingURL=provider-plugin.js.map