import fs from 'node:fs';
import path from 'node:path';
import { ensureYagrHomeDir, getYagrPaths } from '../config/yagr-home.js';
export const DEFAULT_COPILOT_API_BASE_URL = 'https://api.individual.githubcopilot.com';
export const GITHUB_COPILOT_DEFAULT_MODEL = 'gpt-4.1';
const GITHUB_CLIENT_ID = 'Iv1.b507a08c87ecfe98';
const GITHUB_DEVICE_CODE_URL = 'https://github.com/login/device/code';
const GITHUB_ACCESS_TOKEN_URL = 'https://github.com/login/oauth/access_token';
const COPILOT_TOKEN_URL = 'https://api.github.com/copilot_internal/v2/token';
const COPILOT_USER_AGENT = 'GitHubCopilotChat/0.26.7';
const COPILOT_EDITOR_VERSION = 'vscode/1.96.2';
const COPILOT_EDITOR_PLUGIN_VERSION = 'copilot-chat/0.26.7';
export function getGitHubCopilotSession() {
    const envToken = readOptionalString(process.env.COPILOT_GITHUB_TOKEN)
        || readOptionalString(process.env.GH_TOKEN)
        || readOptionalString(process.env.GITHUB_TOKEN);
    if (envToken) {
        return {
            githubToken: envToken,
            source: 'env',
        };
    }
    const stored = readStoredGitHubSession();
    if (!stored) {
        return undefined;
    }
    return {
        githubToken: stored.githubToken,
        source: 'yagr',
    };
}
export async function ensureGitHubCopilotSession() {
    const existing = getGitHubCopilotSession();
    if (existing) {
        return existing;
    }
    return undefined;
}
export async function resolveCopilotApiToken(githubToken) {
    const cachePath = getCopilotTokenCachePath();
    const cached = readCachedCopilotToken(cachePath);
    if (cached && isCopilotTokenUsable(cached)) {
        return {
            token: cached.token,
            expiresAt: cached.expiresAt,
            baseUrl: deriveCopilotApiBaseUrlFromToken(cached.token) ?? DEFAULT_COPILOT_API_BASE_URL,
        };
    }
    const response = await fetch(COPILOT_TOKEN_URL, {
        method: 'GET',
        headers: {
            Accept: 'application/json',
            Authorization: `Bearer ${githubToken}`,
            'User-Agent': 'GitHubCopilotChat/0.26.7',
        },
    });
    if (!response.ok) {
        throw new Error(`Copilot token exchange failed: HTTP ${response.status}`);
    }
    const payload = parseCopilotTokenResponse(await response.json());
    writeCachedCopilotToken(cachePath, {
        token: payload.token,
        expiresAt: payload.expiresAt,
        updatedAt: Date.now(),
    });
    return {
        token: payload.token,
        expiresAt: payload.expiresAt,
        baseUrl: deriveCopilotApiBaseUrlFromToken(payload.token) ?? DEFAULT_COPILOT_API_BASE_URL,
    };
}
export async function validateGitHubCopilotRuntime(modelId = GITHUB_COPILOT_DEFAULT_MODEL) {
    if (process.env.YAGR_SKIP_COPILOT_RUNTIME_VALIDATION === '1') {
        return { ok: true, text: 'OK' };
    }
    try {
        const result = await runGitHubCopilotCompletion(modelId, {
            inputFormat: 'prompt',
            mode: { type: 'regular' },
            prompt: [{ role: 'user', content: [{ type: 'text', text: 'Reply with exactly OK.' }] }],
        });
        return {
            ok: result.text.trim().toUpperCase().includes('OK'),
            text: result.text,
        };
    }
    catch (error) {
        return {
            ok: false,
            error: error instanceof Error ? error.message : String(error),
        };
    }
}
export async function fetchGitHubCopilotModels(token, baseUrl = DEFAULT_COPILOT_API_BASE_URL) {
    const response = await fetch(`${baseUrl}/models`, {
        method: 'GET',
        headers: {
            Authorization: `Bearer ${token}`,
            Accept: 'application/json',
            'User-Agent': COPILOT_USER_AGENT,
            'Editor-Version': COPILOT_EDITOR_VERSION,
            'Editor-Plugin-Version': COPILOT_EDITOR_PLUGIN_VERSION,
        },
    });
    if (!response.ok) {
        const body = await response.text().catch(() => '');
        const detail = body.trim();
        throw new Error(detail || `GitHub Copilot model discovery failed: HTTP ${response.status}`);
    }
    const payload = await response.json();
    const models = (payload.data ?? [])
        .map((entry) => entry.id?.trim())
        .filter((entry) => Boolean(entry))
        .sort((a, b) => a.localeCompare(b));
    return [...new Set(models)];
}
export function createGitHubCopilotLanguageModel(modelId) {
    return {
        specificationVersion: 'v1',
        provider: 'copilot-proxy.github',
        modelId,
        defaultObjectGenerationMode: undefined,
        supportsImageUrls: false,
        supportsStructuredOutputs: false,
        async doGenerate(options) {
            const execution = await runGitHubCopilotCompletion(modelId, options);
            return {
                text: execution.text,
                finishReason: execution.finishReason,
                usage: execution.usage,
                ...(execution.toolCalls ? { toolCalls: execution.toolCalls } : {}),
                rawCall: {
                    rawPrompt: options.prompt,
                    rawSettings: { modelId },
                },
                warnings: execution.warnings,
                response: {
                    timestamp: new Date(),
                    modelId,
                },
            };
        },
        async doStream(options) {
            const execution = await runGitHubCopilotCompletion(modelId, options);
            const stream = new ReadableStream({
                start(controller) {
                    if (execution.text) {
                        controller.enqueue({ type: 'text-delta', textDelta: execution.text });
                    }
                    controller.enqueue({
                        type: 'finish',
                        finishReason: execution.finishReason,
                        usage: execution.usage,
                    });
                    controller.close();
                },
            });
            return {
                stream,
                rawCall: {
                    rawPrompt: options.prompt,
                    rawSettings: { modelId },
                },
                warnings: execution.warnings,
            };
        },
    };
}
async function runGitHubCopilotCompletion(modelId, options) {
    const session = await ensureGitHubCopilotSession();
    if (!session) {
        throw new Error('GitHub Copilot OAuth session not found. Run `yagr setup` again.');
    }
    const runtimeAuth = await resolveCopilotApiToken(session.githubToken);
    const regularMode = options.mode.type === 'regular' ? options.mode : undefined;
    const tools = getFunctionTools(options.mode);
    const warnings = buildCopilotWarnings(options, tools);
    const response = await fetch(`${runtimeAuth.baseUrl}/chat/completions`, {
        method: 'POST',
        headers: {
            Authorization: `Bearer ${runtimeAuth.token}`,
            'Content-Type': 'application/json',
            Accept: 'application/json',
            'User-Agent': COPILOT_USER_AGENT,
            'Editor-Version': COPILOT_EDITOR_VERSION,
            'Editor-Plugin-Version': COPILOT_EDITOR_PLUGIN_VERSION,
        },
        body: JSON.stringify({
            model: modelId,
            messages: toOpenAiMessages(options.prompt),
            ...(tools.length > 0 ? { tools: toOpenAiTools(tools), tool_choice: toOpenAiToolChoice(regularMode?.toolChoice) } : {}),
            stream: false,
        }),
    });
    if (!response.ok) {
        const body = await response.text();
        throw new Error(body.trim() || `GitHub Copilot chat completion failed: HTTP ${response.status}`);
    }
    const payload = await response.json();
    const toolCalls = extractOpenAiToolCalls(payload);
    const finishReason = normalizeFinishReason(payload, toolCalls);
    return {
        text: extractCopilotText(payload),
        finishReason,
        usage: extractOpenAiUsage(payload),
        ...(toolCalls.length > 0 ? { toolCalls } : {}),
        warnings,
    };
}
export async function beginGitHubCopilotAuth() {
    const device = await requestDeviceCode();
    return {
        verificationUri: device.verification_uri,
        userCode: device.user_code,
        deviceCode: device.device_code,
        intervalMs: Math.max(1000, device.interval * 1000),
        expiresAt: Date.now() + device.expires_in * 1000,
    };
}
export async function completeGitHubCopilotAuth(challenge) {
    const githubToken = await pollForAccessToken(challenge);
    const stored = {
        provider: 'copilot-proxy',
        githubToken,
        createdAt: new Date().toISOString(),
        updatedAt: new Date().toISOString(),
    };
    writeStoredGitHubSession(stored);
    return {
        githubToken: stored.githubToken,
        source: 'yagr',
    };
}
async function requestDeviceCode() {
    const body = new URLSearchParams({
        client_id: GITHUB_CLIENT_ID,
        scope: 'read:user',
    });
    const response = await fetch(GITHUB_DEVICE_CODE_URL, {
        method: 'POST',
        headers: {
            Accept: 'application/json',
            'Content-Type': 'application/x-www-form-urlencoded',
        },
        body,
    });
    if (!response.ok) {
        throw new Error(`GitHub device code failed: HTTP ${response.status}`);
    }
    return await response.json();
}
async function pollForAccessToken(params) {
    const body = new URLSearchParams({
        client_id: GITHUB_CLIENT_ID,
        device_code: params.deviceCode,
        grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
    });
    while (Date.now() < params.expiresAt) {
        const response = await fetch(GITHUB_ACCESS_TOKEN_URL, {
            method: 'POST',
            headers: {
                Accept: 'application/json',
                'Content-Type': 'application/x-www-form-urlencoded',
            },
            body,
        });
        if (!response.ok) {
            throw new Error(`GitHub access token failed: HTTP ${response.status}`);
        }
        const payload = await response.json();
        if ('access_token' in payload && typeof payload.access_token === 'string') {
            return payload.access_token;
        }
        const error = 'error' in payload ? payload.error : 'unknown';
        if (error === 'authorization_pending') {
            await delay(params.intervalMs);
            continue;
        }
        if (error === 'slow_down') {
            await delay(params.intervalMs + 2000);
            continue;
        }
        if (error === 'access_denied') {
            throw new Error('GitHub login cancelled.');
        }
        if (error === 'expired_token') {
            throw new Error('GitHub device code expired. Retry setup.');
        }
        throw new Error(`GitHub device flow error: ${error}`);
    }
    throw new Error('GitHub device code expired. Retry setup.');
}
function getGitHubSessionPath() {
    const override = process.env.YAGR_COPILOT_SESSION_PATH?.trim();
    if (override) {
        return override;
    }
    ensureYagrHomeDir();
    return path.join(getYagrPaths().accountAuthDir, 'copilot-oauth.json');
}
function readStoredGitHubSession() {
    const sessionPath = getGitHubSessionPath();
    if (!fs.existsSync(sessionPath)) {
        return undefined;
    }
    try {
        const parsed = JSON.parse(fs.readFileSync(sessionPath, 'utf8'));
        return parsed.githubToken ? parsed : undefined;
    }
    catch {
        return undefined;
    }
}
function writeStoredGitHubSession(session) {
    const sessionPath = getGitHubSessionPath();
    fs.mkdirSync(path.dirname(sessionPath), { recursive: true });
    fs.writeFileSync(sessionPath, JSON.stringify(session, null, 2));
}
function getCopilotTokenCachePath() {
    const override = readOptionalString(process.env.YAGR_COPILOT_TOKEN_CACHE_PATH);
    if (override) {
        return override;
    }
    ensureYagrHomeDir();
    return path.join(getYagrPaths().accountAuthDir, 'copilot-runtime-token.json');
}
function readCachedCopilotToken(cachePath) {
    if (!fs.existsSync(cachePath)) {
        return undefined;
    }
    try {
        return JSON.parse(fs.readFileSync(cachePath, 'utf8'));
    }
    catch {
        return undefined;
    }
}
function writeCachedCopilotToken(cachePath, token) {
    fs.mkdirSync(path.dirname(cachePath), { recursive: true });
    fs.writeFileSync(cachePath, JSON.stringify(token, null, 2));
}
function isCopilotTokenUsable(cache, now = Date.now()) {
    return cache.expiresAt - now > 5 * 60 * 1000;
}
function parseCopilotTokenResponse(value) {
    if (!value || typeof value !== 'object') {
        throw new Error('Unexpected response from GitHub Copilot token endpoint');
    }
    const record = value;
    const token = readOptionalString(record.token);
    const expiresAt = normalizeEpochMillis(record.expires_at);
    if (!token || !expiresAt) {
        throw new Error('Invalid Copilot token response.');
    }
    return { token, expiresAt };
}
export function deriveCopilotApiBaseUrlFromToken(token) {
    const match = token.trim().match(/(?:^|;)\s*proxy-ep=([^;\s]+)/i);
    const proxyEndpoint = match?.[1]?.trim();
    if (!proxyEndpoint) {
        return null;
    }
    const host = proxyEndpoint.replace(/^https?:\/\//, '').replace(/^proxy\./i, 'api.');
    return host ? `https://${host}` : null;
}
function buildCopilotWarnings(options, functionTools) {
    if (options.mode.type !== 'regular' || !options.mode.tools || options.mode.tools.length === 0) {
        return [];
    }
    const warnings = [];
    for (const tool of options.mode.tools) {
        if (tool.type === 'provider-defined') {
            warnings.push({
                type: 'unsupported-tool',
                tool,
                details: 'GitHub Copilot OAuth only supports function tools in OpenAI-compatible format.',
            });
        }
    }
    if (functionTools.length > 0) {
        return warnings;
    }
    return options.mode.tools.map((tool) => ({
        type: 'unsupported-tool',
        tool,
        details: 'GitHub Copilot OAuth did not receive any function tools for this call.',
    }));
}
function getFunctionTools(mode) {
    if (mode.type !== 'regular' || !Array.isArray(mode.tools) || mode.tools.length === 0) {
        return [];
    }
    return mode.tools.filter((tool) => tool.type === 'function');
}
function toOpenAiTools(tools) {
    return tools.map((tool) => ({
        type: 'function',
        function: {
            name: tool.name,
            ...(tool.description ? { description: tool.description } : {}),
            parameters: tool.parameters,
        },
    }));
}
function toOpenAiToolChoice(toolChoice) {
    if (!toolChoice || toolChoice.type === 'auto') {
        return 'auto';
    }
    if (toolChoice.type === 'none' || toolChoice.type === 'required') {
        return toolChoice.type;
    }
    if (toolChoice.type === 'tool') {
        return {
            type: 'function',
            function: {
                name: toolChoice.toolName,
            },
        };
    }
    return 'auto';
}
function toOpenAiMessages(prompt) {
    return prompt.map((message) => {
        if (message.role === 'system') {
            return { role: 'system', content: message.content };
        }
        if (message.role === 'user') {
            return {
                role: 'user',
                content: message.content.map((part) => part.type === 'text' ? part.text : `[${part.type}]`).join('\n'),
            };
        }
        if (message.role === 'assistant') {
            const textContent = message.content
                .filter((part) => part.type === 'text' || part.type === 'reasoning')
                .map((part) => part.text)
                .join('\n')
                .trim();
            const toolCalls = message.content
                .filter((part) => part.type === 'tool-call')
                .map((part) => ({
                id: part.toolCallId,
                type: 'function',
                function: {
                    name: part.toolName,
                    arguments: JSON.stringify(part.args ?? {}),
                },
            }));
            return {
                role: 'assistant',
                content: textContent.length > 0 ? textContent : '',
                ...(toolCalls.length > 0 ? { tool_calls: toolCalls } : {}),
            };
        }
        return message.content.map((part) => ({
            role: 'tool',
            tool_call_id: part.toolCallId,
            name: part.toolName,
            content: stringifyToolResult(part.result),
        }));
    }).flat();
}
function extractCopilotText(payload) {
    const choices = payload.choices;
    if (!Array.isArray(choices) || choices.length === 0) {
        return '';
    }
    const firstChoice = choices[0];
    const message = firstChoice.message;
    const content = message?.content;
    if (typeof content === 'string') {
        return content;
    }
    if (Array.isArray(content)) {
        return content.map((part) => typeof part === 'object' && part && typeof part.text === 'string'
            ? part.text
            : '').join('');
    }
    return '';
}
function extractOpenAiUsage(payload) {
    const usage = payload.usage;
    return {
        promptTokens: typeof usage?.prompt_tokens === 'number' ? usage.prompt_tokens : 0,
        completionTokens: typeof usage?.completion_tokens === 'number' ? usage.completion_tokens : 0,
    };
}
function extractOpenAiToolCalls(payload) {
    const choices = payload.choices;
    if (!Array.isArray(choices) || choices.length === 0) {
        return [];
    }
    const message = choices[0]?.message;
    const rawToolCalls = message?.tool_calls;
    if (!Array.isArray(rawToolCalls)) {
        return [];
    }
    return rawToolCalls
        .map((entry, index) => {
        if (!entry || typeof entry !== 'object') {
            return undefined;
        }
        const call = entry;
        const id = readOptionalString(call.id) || `copilot-tool-call-${index + 1}`;
        const fn = call.function;
        const toolName = readOptionalString(fn?.name);
        if (!toolName) {
            return undefined;
        }
        const argsRaw = fn?.arguments;
        const args = typeof argsRaw === 'string' ? argsRaw : JSON.stringify(argsRaw ?? {});
        return {
            toolCallType: 'function',
            toolCallId: id,
            toolName,
            args,
        };
    })
        .filter((entry) => Boolean(entry));
}
function normalizeFinishReason(payload, toolCalls) {
    if (toolCalls.length > 0) {
        return 'tool-calls';
    }
    const choices = payload.choices;
    if (!Array.isArray(choices) || choices.length === 0) {
        return 'unknown';
    }
    const raw = readOptionalString(choices[0]?.finish_reason);
    if (!raw) {
        return 'unknown';
    }
    if (raw === 'stop' || raw === 'length' || raw === 'content-filter' || raw === 'tool-calls' || raw === 'error' || raw === 'other' || raw === 'unknown') {
        return raw;
    }
    if (raw === 'tool_calls') {
        return 'tool-calls';
    }
    if (raw === 'content_filter') {
        return 'content-filter';
    }
    return 'other';
}
function stringifyToolResult(value) {
    if (typeof value === 'string') {
        return value;
    }
    try {
        return JSON.stringify(value ?? null);
    }
    catch {
        return String(value);
    }
}
function readOptionalString(value) {
    return typeof value === 'string' && value.trim().length > 0 ? value.trim() : undefined;
}
function normalizeEpochMillis(value) {
    if (typeof value === 'number' && Number.isFinite(value)) {
        return value > 10_000_000_000 ? value : value * 1000;
    }
    if (typeof value === 'string' && value.trim().length > 0) {
        const parsed = Number.parseInt(value, 10);
        if (Number.isFinite(parsed)) {
            return parsed > 10_000_000_000 ? parsed : parsed * 1000;
        }
    }
    return undefined;
}
function delay(ms) {
    return new Promise((resolve) => setTimeout(resolve, ms));
}
//# sourceMappingURL=copilot-account.js.map