"use strict";
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.AnthropicMessagesProcessor = void 0;
exports.createMessagesRequestBody = createMessagesRequestBody;
exports.processResponseFromMessagesEndpoint = processResponseFromMessagesEndpoint;
const prompt_tsx_1 = require("@vscode/prompt-tsx");
const async_1 = require("../../../util/vs/base/common/async");
const sseParser_1 = require("../../../util/vs/base/common/sseParser");
const types_1 = require("../../../util/vs/base/common/types");
const uuid_1 = require("../../../util/vs/base/common/uuid");
const openai_1 = require("../../networking/common/openai");
function createMessagesRequestBody(accessor, options, model, endpoint) {
    const anthropicTools = options.requestOptions?.tools
        ?.filter(tool => tool.function.name && tool.function.name.length > 0)
        .map((tool) => ({
        name: tool.function.name,
        description: tool.function.description || '',
        input_schema: {
            type: 'object',
            properties: tool.function.parameters?.properties ?? {},
            required: tool.function.parameters?.required ?? [],
        },
    }));
    return {
        model,
        ...rawMessagesToMessagesAPI(options.messages),
        stream: true,
        tools: anthropicTools,
        top_p: options.postOptions.top_p,
        max_tokens: options.postOptions.max_tokens,
        // TODO: Make thinking configuration controllable via settings (enable/disable and budget_tokens)
        thinking: {
            type: 'enabled',
            budget_tokens: 1024,
        },
    };
}
function rawMessagesToMessagesAPI(messages) {
    const unmergedMessages = [];
    const systemParts = [];
    for (const message of messages) {
        switch (message.role) {
            case prompt_tsx_1.Raw.ChatRole.System: {
                const systemText = message.content
                    .filter(c => c.type === prompt_tsx_1.Raw.ChatCompletionContentPartKind.Text)
                    .map(c => c.text)
                    .join('\n');
                if (systemText) {
                    systemParts.push(systemText);
                }
                break;
            }
            case prompt_tsx_1.Raw.ChatRole.User: {
                const content = message.content
                    .map(rawContentToAnthropicContent)
                    .filter(types_1.isDefined);
                if (content.length > 0) {
                    unmergedMessages.push({
                        role: 'user',
                        content,
                    });
                }
                break;
            }
            case prompt_tsx_1.Raw.ChatRole.Assistant: {
                const content = [];
                for (const part of message.content) {
                    const anthropicPart = rawContentToAnthropicContent(part);
                    if (anthropicPart) {
                        content.push(anthropicPart);
                    }
                }
                if (message.toolCalls) {
                    for (const toolCall of message.toolCalls) {
                        let parsedInput = {};
                        try {
                            parsedInput = JSON.parse(toolCall.function.arguments);
                        }
                        catch {
                            // Keep empty object if parse fails
                        }
                        content.push({
                            type: 'tool_use',
                            id: toolCall.id,
                            name: toolCall.function.name,
                            input: parsedInput,
                        });
                    }
                }
                if (content.length > 0) {
                    unmergedMessages.push({
                        role: 'assistant',
                        content,
                    });
                }
                break;
            }
            case prompt_tsx_1.Raw.ChatRole.Tool: {
                if (message.toolCallId) {
                    const toolContent = message.content
                        .map(c => {
                        if (c.type === prompt_tsx_1.Raw.ChatCompletionContentPartKind.Text) {
                            return { type: 'text', text: c.text };
                        }
                        else if (c.type === prompt_tsx_1.Raw.ChatCompletionContentPartKind.Image) {
                            return rawContentToAnthropicContent(c);
                        }
                        return undefined;
                    })
                        .filter(types_1.isDefined);
                    const validToolContent = toolContent.filter((c) => c.type === 'text' || c.type === 'image');
                    unmergedMessages.push({
                        role: 'user',
                        content: [{
                                type: 'tool_result',
                                tool_use_id: message.toolCallId,
                                content: validToolContent,
                            }],
                    });
                }
                break;
            }
        }
    }
    const mergedMessages = [];
    for (const message of unmergedMessages) {
        const lastMessage = mergedMessages[mergedMessages.length - 1];
        if (lastMessage && lastMessage.role === message.role) {
            const prevContent = Array.isArray(lastMessage.content) ? lastMessage.content : [{ type: 'text', text: lastMessage.content }];
            const newContent = Array.isArray(message.content) ? message.content : [{ type: 'text', text: message.content }];
            lastMessage.content = [...prevContent, ...newContent];
        }
        else {
            mergedMessages.push(message);
        }
    }
    const systemText = systemParts.join('\n');
    return {
        messages: mergedMessages,
        ...(systemText ? { system: [{ type: 'text', text: systemText }] } : {}),
    };
}
function rawContentToAnthropicContent(part) {
    switch (part.type) {
        case prompt_tsx_1.Raw.ChatCompletionContentPartKind.Text:
            if (part.text.trim()) {
                return { type: 'text', text: part.text };
            }
            return undefined;
        case prompt_tsx_1.Raw.ChatCompletionContentPartKind.Image:
            // TODO: Add support for image content blocks in Messages API
            return undefined;
        case prompt_tsx_1.Raw.ChatCompletionContentPartKind.Opaque: {
            if (part.value && typeof part.value === 'object' && 'type' in part.value) {
                const opaqueValue = part.value;
                if (opaqueValue.type === 'thinking' && opaqueValue.thinking) {
                    if (opaqueValue.thinking.encrypted) {
                        return {
                            type: 'redacted_thinking',
                            data: opaqueValue.thinking.encrypted,
                        };
                    }
                    else if (opaqueValue.thinking.text) {
                        return {
                            type: 'thinking',
                            thinking: opaqueValue.thinking.text,
                            signature: '',
                        };
                    }
                }
            }
            return undefined;
        }
        default:
            return undefined;
    }
}
async function processResponseFromMessagesEndpoint(instantiationService, telemetryService, logService, response, expectedNumChoices, finishCallback, telemetryData) {
    const body = (await response.body());
    return new async_1.AsyncIterableObject(async (feed) => {
        const requestId = response.headers.get('X-Request-ID') ?? (0, uuid_1.generateUuid)();
        const ghRequestId = response.headers.get('x-github-request-id') ?? '';
        const processor = instantiationService.createInstance(AnthropicMessagesProcessor, telemetryData, requestId, ghRequestId);
        const parser = new sseParser_1.SSEParser((ev) => {
            try {
                const trimmed = ev.data?.trim();
                if (!trimmed || trimmed === '[DONE]') {
                    return;
                }
                logService.trace(`SSE: ${trimmed}`);
                const parsed = JSON.parse(trimmed);
                const type = parsed.type ?? ev.type;
                if (!type) {
                    return;
                }
                const completion = processor.push({ ...parsed, type }, finishCallback);
                if (completion) {
                    feed.emitOne(completion);
                }
            }
            catch (e) {
                feed.reject(e);
            }
        });
        for await (const chunk of body) {
            parser.feed(chunk);
        }
    }, () => {
        body.destroy();
    });
}
class AnthropicMessagesProcessor {
    constructor(telemetryData, requestId, ghRequestId) {
        this.telemetryData = telemetryData;
        this.requestId = requestId;
        this.ghRequestId = ghRequestId;
        this.textAccumulator = '';
        this.toolCallAccumulator = new Map();
        this.thinkingAccumulator = new Map();
        this.completedToolCalls = [];
        this.messageId = '';
        this.model = '';
        this.inputTokens = 0;
        this.outputTokens = 0;
        this.cachedTokens = 0;
    }
    push(chunk, _onProgress) {
        const onProgress = (delta) => {
            this.textAccumulator += delta.text;
            _onProgress(this.textAccumulator, 0, delta);
        };
        switch (chunk.type) {
            case 'message_start':
                if (chunk.message) {
                    this.messageId = chunk.message.id;
                    this.model = chunk.message.model;
                    this.inputTokens = chunk.message.usage.input_tokens;
                    this.outputTokens = chunk.message.usage.output_tokens;
                    if (chunk.message.usage.cache_read_input_tokens) {
                        this.cachedTokens = chunk.message.usage.cache_read_input_tokens;
                    }
                }
                return;
            case 'content_block_start':
                if (chunk.content_block?.type === 'tool_use' && chunk.index !== undefined) {
                    this.toolCallAccumulator.set(chunk.index, {
                        id: chunk.content_block.id || (0, uuid_1.generateUuid)(),
                        name: chunk.content_block.name || '',
                        arguments: '',
                    });
                    onProgress({
                        text: '',
                        beginToolCalls: [{ name: chunk.content_block.name || '' }]
                    });
                }
                else if (chunk.content_block?.type === 'thinking' && chunk.index !== undefined) {
                    this.thinkingAccumulator.set(chunk.index, {
                        thinking: '',
                        signature: '',
                    });
                }
                return;
            case 'content_block_delta':
                if (chunk.delta) {
                    if (chunk.delta.type === 'text_delta' && chunk.delta.text) {
                        return onProgress({ text: chunk.delta.text });
                    }
                    else if (chunk.delta.type === 'thinking_delta' && chunk.delta.thinking && chunk.index !== undefined) {
                        const thinking = this.thinkingAccumulator.get(chunk.index);
                        if (thinking) {
                            thinking.thinking += chunk.delta.thinking;
                        }
                        return onProgress({
                            text: '',
                            thinking: {
                                id: `thinking_${chunk.index}`,
                                text: chunk.delta.thinking,
                            }
                        });
                    }
                    else if (chunk.delta.type === 'signature_delta' && chunk.delta.signature && chunk.index !== undefined) {
                        const thinking = this.thinkingAccumulator.get(chunk.index);
                        if (thinking) {
                            thinking.signature += chunk.delta.signature;
                        }
                        // Don't report signature deltas to the user
                    }
                    else if (chunk.delta.type === 'input_json_delta' && chunk.delta.partial_json && chunk.index !== undefined) {
                        const toolCall = this.toolCallAccumulator.get(chunk.index);
                        if (toolCall) {
                            toolCall.arguments += chunk.delta.partial_json;
                        }
                    }
                }
                return;
            case 'content_block_stop':
                if (chunk.index !== undefined) {
                    const toolCall = this.toolCallAccumulator.get(chunk.index);
                    if (toolCall) {
                        this.completedToolCalls.push(toolCall);
                        onProgress({
                            text: '',
                            copilotToolCalls: [{
                                    id: toolCall.id,
                                    name: toolCall.name,
                                    arguments: toolCall.arguments,
                                }],
                        });
                        this.toolCallAccumulator.delete(chunk.index);
                    }
                    const thinking = this.thinkingAccumulator.get(chunk.index);
                    if (thinking && thinking.signature) {
                        onProgress({
                            text: '',
                            thinking: {
                                id: `thinking_${chunk.index}`,
                                encrypted: thinking.signature,
                            }
                        });
                        this.thinkingAccumulator.delete(chunk.index);
                    }
                }
                return;
            case 'message_delta':
                if (chunk.usage) {
                    this.outputTokens = chunk.usage.output_tokens;
                }
                return;
            case 'message_stop':
                return {
                    blockFinished: true,
                    choiceIndex: 0,
                    model: this.model,
                    tokens: [],
                    telemetryData: this.telemetryData,
                    requestId: {
                        headerRequestId: this.requestId,
                        gitHubRequestId: this.ghRequestId,
                        completionId: this.messageId,
                        created: Date.now(),
                        deploymentId: '',
                        serverExperiments: ''
                    },
                    usage: {
                        prompt_tokens: this.inputTokens,
                        completion_tokens: this.outputTokens,
                        total_tokens: this.inputTokens + this.outputTokens,
                        prompt_tokens_details: {
                            cached_tokens: this.cachedTokens,
                        },
                        completion_tokens_details: {
                            reasoning_tokens: 0,
                            accepted_prediction_tokens: 0,
                            rejected_prediction_tokens: 0,
                        },
                    },
                    finishReason: openai_1.FinishedCompletionReason.Stop,
                    message: {
                        role: prompt_tsx_1.Raw.ChatRole.Assistant,
                        content: this.textAccumulator ? [{
                                type: prompt_tsx_1.Raw.ChatCompletionContentPartKind.Text,
                                text: this.textAccumulator
                            }] : [],
                        ...(this.completedToolCalls.length > 0 ? {
                            toolCalls: this.completedToolCalls.map(tc => ({
                                id: tc.id,
                                type: 'function',
                                function: {
                                    name: tc.name,
                                    arguments: tc.arguments
                                }
                            }))
                        } : {})
                    }
                };
            case 'error': {
                const errorMessage = chunk.error?.message || 'Unknown error';
                return onProgress({
                    text: '',
                    copilotErrors: [{
                            agent: 'anthropic',
                            code: 'unknown',
                            message: errorMessage,
                            type: 'error',
                            identifier: undefined
                        }]
                });
            }
        }
    }
}
exports.AnthropicMessagesProcessor = AnthropicMessagesProcessor;
//# sourceMappingURL=messagesApi.js.map