"use strict";
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
    return function (target, key) { decorator(target, key, paramIndex); }
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AnthropicMessagesProcessor = void 0;
exports.buildToolInputSchema = buildToolInputSchema;
exports.createMessagesRequestBody = createMessagesRequestBody;
exports.rawMessagesToMessagesAPI = rawMessagesToMessagesAPI;
exports.clearAllCacheControl = clearAllCacheControl;
exports.addToolsAndSystemCacheControl = addToolsAndSystemCacheControl;
exports.addMessagesApiCacheControl = addMessagesApiCacheControl;
exports.processResponseFromMessagesEndpoint = processResponseFromMessagesEndpoint;
exports.processNonStreamingResponseFromMessagesEndpoint = processNonStreamingResponseFromMessagesEndpoint;
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 uuid_1 = require("../../../util/vs/base/common/uuid");
const commonTypes_1 = require("../../chat/common/commonTypes");
const configurationService_1 = require("../../configuration/common/configurationService");
const logService_1 = require("../../log/common/logService");
const anthropic_1 = require("../../networking/common/anthropic");
const fetch_1 = require("../../networking/common/fetch");
const openai_1 = require("../../networking/common/openai");
const toolDeferralService_1 = require("../../networking/common/toolDeferralService");
const chatStream_1 = require("../../networking/node/chatStream");
const nullExperimentationService_1 = require("../../telemetry/common/nullExperimentationService");
const telemetry_1 = require("../../telemetry/common/telemetry");
/**
 * Build the `input_schema` for an Anthropic tool from an arbitrary JSON Schema
 * object. Ensures `type: 'object'` and `properties` default, preserves extra
 * keys like `$defs` and `additionalProperties`, and strips `$schema` which the
 * Anthropic API rejects.
 */
function buildToolInputSchema(schema) {
    if (!schema) {
        return { type: 'object', properties: {} };
    }
    const { $schema: _, ...rest } = schema;
    return { type: 'object', properties: {}, ...rest };
}
function createMessagesRequestBody(accessor, options, model, endpoint) {
    const configurationService = accessor.get(configurationService_1.IConfigurationService);
    const experimentationService = accessor.get(nullExperimentationService_1.IExperimentationService);
    const toolDeferralService = accessor.get(toolDeferralService_1.IToolDeferralService);
    const toolSearchEnabled = !!endpoint.supportsToolSearch
        && !!options.requestOptions?.tools?.some(t => t.function.name === anthropic_1.CUSTOM_TOOL_SEARCH_NAME);
    // Split tools into non-deferred and deferred up front so we can build finalTools
    // with non-deferred first. This ensures the cache_control breakpoint on the last
    // non-deferred tool caches the maximum stable prefix.
    const nonDeferredTools = [];
    const deferredTools = [];
    if (options.requestOptions?.tools) {
        for (const tool of options.requestOptions.tools) {
            if (!tool.function.name || tool.function.name.length === 0) {
                continue;
            }
            const isDeferred = options.modelCapabilities?.enableToolSearch && toolSearchEnabled && !toolDeferralService.isNonDeferredTool(tool.function.name);
            const anthropicTool = {
                name: tool.function.name,
                description: tool.function.description || '',
                input_schema: buildToolInputSchema(tool.function.parameters),
                ...(isDeferred ? { defer_loading: true } : {}),
            };
            (isDeferred ? deferredTools : nonDeferredTools).push(anthropicTool);
        }
    }
    // Build final tools array. The client-side search_tools tool is already in the
    // anthropicTools array (registered as a model-specific VS Code tool) and will handle
    // tool search client-side. Deferred tools still have defer_loading: true so the model
    // knows to use the search tool to discover them.
    const finalTools = [...nonDeferredTools, ...deferredTools];
    // Thinking is enabled only when options.modelCapabilities?.enableThinking is true, a non-zero thinking budget
    // is configured for the model, and the model supports thinking. reasoningEffort (if present)
    // is used only to configure the effort level when thinking is enabled, not to gate it.
    const reasoningEffort = options.modelCapabilities?.reasoningEffort;
    let thinkingConfig;
    if (options.modelCapabilities?.enableThinking) {
        const hardcodedBudget = 16000;
        if (endpoint.supportsAdaptiveThinking) {
            thinkingConfig = { type: 'adaptive', display: 'summarized' };
        }
        else if (endpoint.maxThinkingBudget && endpoint.minThinkingBudget) {
            const maxTokens = options.postOptions.max_tokens ?? 1024;
            const minBudget = endpoint.minThinkingBudget ?? 1024;
            const normalizedBudget = hardcodedBudget < minBudget ? minBudget : hardcodedBudget;
            const maxBudget = endpoint.maxThinkingBudget ?? 32000;
            const thinkingBudget = Math.min(maxBudget, maxTokens - 1, normalizedBudget);
            if (thinkingBudget) {
                thinkingConfig = { type: 'enabled', budget_tokens: thinkingBudget };
            }
        }
    }
    const thinkingEnabled = !!thinkingConfig;
    let effort;
    if (thinkingConfig && endpoint.supportsReasoningEffort?.length) {
        const candidateEffort = configurationService.getConfig(configurationService_1.ConfigKey.Advanced.ReasoningEffortOverride)
            ?? reasoningEffort
            ?? (endpoint.supportsReasoningEffort.length === 1 ? endpoint.supportsReasoningEffort[0] : 'medium');
        if (candidateEffort === 'low' || candidateEffort === 'medium' || candidateEffort === 'high') {
            effort = candidateEffort;
        }
    }
    // Build context management configuration
    const contextManagement = options.modelCapabilities?.enableContextEditing && (0, anthropic_1.isAnthropicContextEditingEnabled)(endpoint, configurationService, experimentationService)
        ? (0, anthropic_1.getContextManagementFromConfig)(configurationService, experimentationService, thinkingEnabled)
        : undefined;
    const logService = accessor.get(logService_1.ILogService);
    const telemetryService = accessor.get(telemetry_1.ITelemetryService);
    // TODO: Ideally the custom tool_search tool should filter results itself, but it doesn't
    // have access to the enabled tools for the request. For now, filter tool_reference blocks
    // here against the actual tools sent to Anthropic to avoid 400 errors from unknown tool names.
    const validToolNames = finalTools.length > 0 ? new Set(finalTools.map(t => t.name)) : undefined;
    const messagesResult = rawMessagesToMessagesAPI(options.messages, toolSearchEnabled ? validToolNames : undefined);
    // Subagent requests are out of scope for the extended cache TTL — their
    // context is short-lived. The three subagent call sites (search loop,
    // execution loop, Task-tool-spawned agent) all set
    // `interactionTypeOverride: 'conversation-subagent'`, which is also the
    // source of truth for the `X-Interaction-Type` wire header.
    //
    // The rolling message breakpoints default to the 5m TTL and only upgrade to
    // 1h when the `extendedTtlMessages` sub-toggle is on (which itself requires
    // the parent `extendedTtl` to be on — see `isExtendedCacheTtlMessagesEnabled`).
    const isSubagent = options.interactionTypeOverride === 'conversation-subagent';
    const useExtendedCacheTtl = (0, anthropic_1.isExtendedCacheTtlEnabled)(endpoint, configurationService, experimentationService, options.location, isSubagent);
    const cacheTtl = useExtendedCacheTtl ? '1h' : undefined;
    const useExtendedCacheTtlMessages = (0, anthropic_1.isExtendedCacheTtlMessagesEnabled)(useExtendedCacheTtl, configurationService, experimentationService);
    const messageCacheTtl = useExtendedCacheTtlMessages ? '1h' : undefined;
    clearAllCacheControl(messagesResult);
    addMessagesApiCacheControl(messagesResult, messageCacheTtl);
    addToolsAndSystemCacheControl(finalTools, messagesResult, cacheTtl);
    // Guard: The Anthropic Messages API requires the conversation to end with a user message.
    // A trailing assistant message is treated as a prefill request, which is not supported
    // and will return a 400 error. This catches upstream edge cases where isContinuation
    // skips the UserMessage or validateToolMessages drops trailing tool messages.
    const lastMessage = messagesResult.messages.at(-1);
    if (lastMessage && lastMessage.role === 'assistant') {
        logService.warn(`[messagesAPI] Trailing assistant message detected — appending synthetic user message to prevent prefill error. Total messages: ${messagesResult.messages.length}`);
        /* __GDPR__
            "messagesApi.trailingAssistantGuard" : {
                "owner": "bhavyaus",
                "comment": "Tracks when a trailing assistant message is detected and a synthetic user message is appended to prevent prefill errors",
                "model": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The model being used" },
                "location": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The chat location (agent, panel, etc)" },
                "messageCount": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "comment": "Total number of messages in the conversation" }
            }
        */
        telemetryService.sendMSFTTelemetryEvent('messagesApi.trailingAssistantGuard', { model, location: commonTypes_1.ChatLocation.toString(options.location) }, { messageCount: messagesResult.messages.length });
        messagesResult.messages.push({
            role: 'user',
            content: [{ type: 'text', text: 'Please continue.' }],
        });
    }
    return {
        model,
        ...messagesResult,
        stream: options.requestOptions?.stream ?? true,
        tools: finalTools.length > 0 ? finalTools : undefined,
        max_tokens: options.postOptions.max_tokens,
        thinking: thinkingConfig,
        ...(effort ? { output_config: { effort } } : {}),
        ...(contextManagement ? { context_management: contextManagement } : {}),
    };
}
function rawMessagesToMessagesAPI(messages, validToolNames) {
    const unmergedMessages = [];
    const systemBlocks = [];
    const toolCallIdToName = new Map();
    for (const message of messages) {
        switch (message.role) {
            case prompt_tsx_1.Raw.ChatRole.System: {
                systemBlocks.push(...rawContentToAnthropicContent(message.content).filter((c) => c.type === 'text'));
                break;
            }
            case prompt_tsx_1.Raw.ChatRole.User: {
                const content = rawContentToAnthropicContent(message.content);
                if (content.length > 0) {
                    unmergedMessages.push({
                        role: 'user',
                        content,
                    });
                }
                break;
            }
            case prompt_tsx_1.Raw.ChatRole.Assistant: {
                const content = rawContentToAnthropicContent(message.content);
                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,
                        });
                        toolCallIdToName.set(toolCall.id, toolCall.function.name);
                    }
                }
                if (content.length > 0) {
                    unmergedMessages.push({
                        role: 'assistant',
                        content,
                    });
                }
                break;
            }
            case prompt_tsx_1.Raw.ChatRole.Tool: {
                if (message.toolCallId) {
                    const toolContent = rawContentToAnthropicContent(message.content);
                    // Extract cache_control from content blocks - it belongs on the tool_result block, not inner content
                    let hasCacheControl = false;
                    for (const block of toolContent) {
                        if (contentBlockSupportsCacheControl(block) && block.cache_control) {
                            hasCacheControl = true;
                            delete block.cache_control;
                        }
                    }
                    // If this is a custom tool search result and validToolNames is provided,
                    // attempt to convert the text content into tool_reference blocks per the
                    // Anthropic custom tool search spec. When validToolNames is undefined
                    // (i.e. tool_reference conversion is disabled/unsupported for this
                    // request), fall through to the regular text/image filter to avoid
                    // sending unsupported content types.
                    const isCustomToolSearch = validToolNames && toolCallIdToName.get(message.toolCallId) === anthropic_1.CUSTOM_TOOL_SEARCH_NAME;
                    const toolReferenceContent = isCustomToolSearch
                        ? tryParseToolReferences(toolContent, validToolNames)
                        : undefined;
                    const validContent = toolReferenceContent
                        ?? toolContent.filter((c) => (c.type === 'text' || c.type === 'image' || c.type === 'document') && !(c.type === 'text' && c.text.trim() === ''));
                    const toolResultBlock = {
                        type: 'tool_result',
                        tool_use_id: message.toolCallId,
                        content: validContent.length > 0 ? validContent : undefined,
                    };
                    if (hasCacheControl) {
                        toolResultBlock.cache_control = { type: 'ephemeral' };
                    }
                    unmergedMessages.push({
                        role: 'user',
                        content: [toolResultBlock],
                    });
                }
                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);
        }
    }
    return {
        messages: mergedMessages,
        ...(systemBlocks.length ? { system: systemBlocks } : {}),
    };
}
/**
 * Parses tool result content from the custom tool search tool into
 * tool_reference content blocks that the Anthropic API understands.
 * Expects a single text block containing a JSON array of tool name strings.
 * @see https://platform.claude.com/docs/en/agents-and-tools/tool-use/tool-search-tool#custom-tool-search-implementation
 */
function tryParseToolReferences(content, validToolNames) {
    if (content.length !== 1 || content[0].type !== 'text') {
        return undefined;
    }
    let parsed;
    try {
        parsed = JSON.parse(content[0].text);
    }
    catch {
        return undefined;
    }
    if (!Array.isArray(parsed)) {
        return undefined;
    }
    return parsed
        .filter((name) => typeof name === 'string' && (!validToolNames || validToolNames.has(name)))
        .map((name) => ({ type: 'tool_reference', tool_name: name }));
}
function rawContentToAnthropicContent(content) {
    const convertedContent = [];
    // Track pending cache_control that couldn't be attached to a preceding block
    let pendingCacheControl = false;
    for (const part of content) {
        switch (part.type) {
            case prompt_tsx_1.Raw.ChatCompletionContentPartKind.Text:
                if (part.text.trim()) {
                    convertedContent.push({ type: 'text', text: part.text });
                }
                break;
            case prompt_tsx_1.Raw.ChatCompletionContentPartKind.Image: {
                const url = part.imageUrl.url;
                // Parse data URL: data:image/png;base64,<data>
                const match = url.match(/^data:(image\/(?:jpeg|png|gif|webp));base64,(.+)$/);
                if (match) {
                    convertedContent.push({
                        type: 'image',
                        source: {
                            type: 'base64',
                            media_type: match[1],
                            data: match[2],
                        }
                    });
                }
                else if (url.startsWith('https://')) {
                    // URL image source: https://platform.claude.com/docs/en/api/messages#url_image_source
                    convertedContent.push({
                        type: 'image',
                        source: {
                            type: 'url',
                            url,
                        }
                    });
                }
                break;
            }
            case prompt_tsx_1.Raw.ChatCompletionContentPartKind.CacheBreakpoint: {
                const previousBlock = convertedContent.at(-1);
                if (previousBlock && contentBlockSupportsCacheControl(previousBlock)) {
                    previousBlock.cache_control = { type: 'ephemeral' };
                }
                else {
                    // No preceding block to attach to — defer until the next
                    // cacheable content block is added, or silently drop it.
                    // Previously this created a whitespace-only text block which
                    // the Anthropic API rejects with "text content block must
                    // contain non-whitespace text".
                    pendingCacheControl = true;
                }
                break;
            }
            case prompt_tsx_1.Raw.ChatCompletionContentPartKind.Document: {
                if (part.documentData.mediaType === 'application/pdf') {
                    convertedContent.push({
                        type: 'document',
                        source: {
                            type: 'base64',
                            media_type: 'application/pdf',
                            data: part.documentData.data,
                        }
                    });
                }
                break;
            }
            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) {
                        const thinkingText = Array.isArray(opaqueValue.thinking.text)
                            ? opaqueValue.thinking.text.join('')
                            : opaqueValue.thinking.text;
                        if (thinkingText && opaqueValue.thinking.encrypted) {
                            // Regular thinking block: text is present, encrypted field contains the signature
                            convertedContent.push({
                                type: 'thinking',
                                thinking: thinkingText,
                                signature: opaqueValue.thinking.encrypted,
                            });
                        }
                        else if (opaqueValue.thinking.encrypted && !thinkingText) {
                            // Redacted thinking block: no text, only encrypted data from Claude
                            convertedContent.push({
                                type: 'redacted_thinking',
                                data: opaqueValue.thinking.encrypted,
                            });
                        }
                    }
                }
                break;
            }
        }
        // Attach any pending cache_control to the block we just added
        if (pendingCacheControl && convertedContent.length > 0) {
            const lastBlock = convertedContent.at(-1);
            if (contentBlockSupportsCacheControl(lastBlock)) {
                lastBlock.cache_control = { type: 'ephemeral' };
                pendingCacheControl = false;
            }
        }
    }
    return convertedContent;
}
function contentBlockSupportsCacheControl(block) {
    return block.type !== 'thinking' && block.type !== 'redacted_thinking';
}
/** Removes any cache_control fields from system and message blocks. */
function clearAllCacheControl(messagesResult) {
    if (messagesResult.system) {
        for (const block of messagesResult.system) {
            delete block.cache_control;
        }
    }
    for (const msg of messagesResult.messages) {
        if (Array.isArray(msg.content)) {
            for (const block of msg.content) {
                if (typeof block === 'object' && 'cache_control' in block) {
                    delete block.cache_control;
                }
            }
        }
    }
}
/**
 * Marks the last non-deferred tool and the last system block for caching.
 *
 * When {@link cacheTtl} is `'1h'`, the breakpoints request the extended cache
 * TTL. Sending this requires the `extended-cache-ttl-2025-04-11` Anthropic
 * beta header — see {@link IChatEndpoint.getExtraHeaders}. When omitted, the
 * default 5 minute TTL is used.
 */
function addToolsAndSystemCacheControl(tools, messagesResult, cacheTtl) {
    const cacheControl = cacheTtl
        ? { type: 'ephemeral', ttl: cacheTtl }
        : { type: 'ephemeral' };
    for (let i = tools.length - 1; i >= 0; i--) {
        if (!tools[i].defer_loading) {
            tools[i].cache_control = cacheControl;
            break;
        }
    }
    const lastSystemBlock = messagesResult.system?.at(-1);
    if (lastSystemBlock && !lastSystemBlock.cache_control) {
        lastSystemBlock.cache_control = cacheControl;
    }
}
/**
 * Marks the last cacheable block of the two most recent cacheable messages.
 *
 * Anthropic's prompt cache matches on content prefix, so a single tail anchor
 * is sufficient under steady-state. The second (older) anchor is intended to
 * improve the chance of retaining a useful fallback within Anthropic's
 * lookback window: if the tail anchor misses (TTL expiry on a slow tool call,
 * or rare content drift), the older anchor can still serve a cache hit
 * covering everything up to it, so we typically lose at most one exchange
 * instead of resetting the entire conversation cache.
 *
 * Combined with the tools + system breakpoints, this can produce up to 4
 * cache_control markers, which matches Anthropic's per-request limit.
 */
function addMessagesApiCacheControl(messagesResult, cacheTtl) {
    const messages = messagesResult.messages;
    let marked = 0;
    for (let i = messages.length - 1; i >= 0 && marked < 2; i--) {
        const msg = messages[i];
        if (Array.isArray(msg.content) && msg.content.some(b => typeof b === 'object' && contentBlockSupportsCacheControl(b))) {
            markLastCacheableBlock(msg, cacheTtl);
            marked++;
        }
    }
}
function markLastCacheableBlock(msg, cacheTtl) {
    if (!Array.isArray(msg.content)) {
        return;
    }
    for (let j = msg.content.length - 1; j >= 0; j--) {
        const block = msg.content[j];
        if (typeof block === 'object' && contentBlockSupportsCacheControl(block)) {
            block.cache_control = cacheTtl ? { type: 'ephemeral', ttl: cacheTtl } : { type: 'ephemeral' };
            return;
        }
    }
}
async function processResponseFromMessagesEndpoint(instantiationService, telemetryService, logService, response, finishCallback, telemetryData) {
    // Route based on Content-Type: non-streaming Anthropic responses return
    // application/json, streaming returns text/event-stream.
    const contentType = response.headers.get('content-type') ?? '';
    if (!contentType.includes('text/event-stream')) {
        return processNonStreamingResponseFromMessagesEndpoint(telemetryService, logService, response, finishCallback, telemetryData);
    }
    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 { serverExperiments } = (0, fetch_1.getRequestId)(response.headers);
        const processor = instantiationService.createInstance(AnthropicMessagesProcessor, telemetryData, requestId, ghRequestId, serverExperiments);
        const parser = new sseParser_1.SSEParser((ev) => {
            try {
                logService.trace(`[messagesAPI]SSE: ${ev.data}`);
                const trimmed = ev.data?.trim();
                if (!trimmed || trimmed === '[DONE]') {
                    return;
                }
                const parsed = JSON.parse(trimmed);
                const type = parsed.type ?? ev.type;
                if (!type) {
                    return;
                }
                const completion = processor.push({ ...parsed, type }, finishCallback);
                if (completion) {
                    logService.info(`[messagesAPI] message ${completion.choiceIndex} returned. finish reason: [${completion.finishReason}]`);
                    const dataToSendToTelemetry = telemetryData.extendedBy({
                        completionChoiceFinishReason: completion.finishReason,
                        headerRequestId: completion.requestId.headerRequestId
                    });
                    telemetryService.sendGHTelemetryEvent('completion.finishReason', dataToSendToTelemetry.properties, dataToSendToTelemetry.measurements);
                    const telemetryMessage = (0, openai_1.rawMessageToCAPI)(completion.message);
                    let telemetryDataWithUsage = telemetryData;
                    if (completion.usage) {
                        telemetryDataWithUsage = telemetryData.extendedBy({}, {
                            promptTokens: completion.usage.prompt_tokens,
                            completionTokens: completion.usage.completion_tokens,
                            totalTokens: completion.usage.total_tokens,
                            ...(completion.usage.prompt_tokens_details && { cachedTokens: completion.usage.prompt_tokens_details.cached_tokens }),
                            ...(completion.usage.completion_tokens_details && {
                                reasoningTokens: completion.usage.completion_tokens_details.reasoning_tokens,
                                acceptedPredictionTokens: completion.usage.completion_tokens_details.accepted_prediction_tokens,
                                rejectedPredictionTokens: completion.usage.completion_tokens_details.rejected_prediction_tokens,
                            }),
                        });
                    }
                    (0, chatStream_1.sendEngineMessagesTelemetry)(telemetryService, [telemetryMessage], telemetryDataWithUsage, true, logService);
                    feed.emitOne(completion);
                }
            }
            catch (e) {
                feed.reject(e);
            }
        });
        for await (const chunk of response.body) {
            parser.feed(chunk);
        }
    }, async () => {
        await response.body.destroy();
    });
}
/**
 * Map an Anthropic `stop_reason` string to a {@link FinishedCompletionReason}.
 */
function mapStopReason(stopReason) {
    switch (stopReason) {
        case 'refusal':
            return openai_1.FinishedCompletionReason.ClientDone;
        case 'max_tokens':
        case 'model_context_window_exceeded':
            return openai_1.FinishedCompletionReason.Length;
        default:
            return openai_1.FinishedCompletionReason.Stop;
    }
}
/**
 * Build a {@link ChatCompletion} from the accumulated Anthropic response
 * state. Shared between the streaming (`message_stop`) and non-streaming
 * response handlers so token accounting, finish-reason mapping, and
 * message construction are defined in exactly one place.
 *
 * @param logService Used for the cache-token consistency warning.
 */
function buildAnthropicCompletion(state, logService) {
    const computedPromptTokens = state.inputTokens + state.cacheCreationTokens + state.cacheReadTokens;
    if (computedPromptTokens < state.cacheReadTokens) {
        logService.warn(`[messagesAPI] Token count inconsistency: computed prompt_tokens (${computedPromptTokens}) < cached_tokens (${state.cacheReadTokens}). Raw values: inputTokens=${state.inputTokens}, cacheCreationTokens=${state.cacheCreationTokens}, cacheReadTokens=${state.cacheReadTokens}`);
    }
    return {
        blockFinished: true,
        choiceIndex: 0,
        model: state.model,
        tokens: [],
        telemetryData: state.telemetryData,
        requestId: {
            headerRequestId: state.requestId,
            gitHubRequestId: state.ghRequestId,
            completionId: state.messageId,
            created: Date.now(),
            deploymentId: '',
            serverExperiments: state.serverExperiments,
        },
        usage: {
            prompt_tokens: computedPromptTokens,
            completion_tokens: state.outputTokens,
            total_tokens: computedPromptTokens + state.outputTokens,
            prompt_tokens_details: {
                cached_tokens: state.cacheReadTokens,
                cache_creation_input_tokens: state.cacheCreationTokens,
            },
            completion_tokens_details: {
                reasoning_tokens: 0,
                accepted_prediction_tokens: 0,
                rejected_prediction_tokens: 0,
            },
            copilot_usage: state.copilotUsage,
        },
        finishReason: mapStopReason(state.stopReason),
        message: {
            role: prompt_tsx_1.Raw.ChatRole.Assistant,
            content: state.textContent ? [{
                    type: prompt_tsx_1.Raw.ChatCompletionContentPartKind.Text,
                    text: state.textContent
                }] : [],
            ...(state.toolCalls.length > 0 ? {
                toolCalls: state.toolCalls.map(tc => ({
                    id: tc.id,
                    type: 'function',
                    function: {
                        name: tc.name,
                        arguments: tc.arguments
                    }
                }))
            } : {})
        }
    };
}
/**
 * Process a non-streaming response from the Anthropic Messages API.
 * Returns the same `ChatCompletion` shape as the streaming path.
 *
 * NOTE: Thinking / redacted_thinking content blocks are intentionally
 * not surfaced. If a future caller needs them, this function must be
 * extended to include them in the `ChatCompletion.message` and in the
 * `finishCallback` delta.
 */
async function processNonStreamingResponseFromMessagesEndpoint(telemetryService, logService, response, finishCallback, telemetryData) {
    return new async_1.AsyncIterableObject(async (feed) => {
        const { headerRequestId, serverExperiments } = (0, fetch_1.getRequestId)(response.headers);
        const requestId = headerRequestId || (0, uuid_1.generateUuid)();
        const ghRequestId = response.headers.get('x-github-request-id') ?? '';
        const bodyText = await response.text();
        let parsed;
        try {
            parsed = JSON.parse(bodyText);
        }
        catch (e) {
            feed.reject(new Error(`Failed to parse non-streaming Anthropic response: ${e instanceof Error ? e.message : String(e)}`));
            return;
        }
        if (parsed.type === 'error') {
            feed.reject(new Error(`Anthropic API error: ${parsed.error?.message ?? 'Unknown error'}`));
            return;
        }
        // Extract content blocks — guard against missing content array
        // (shouldn't happen for type=message, but be defensive).
        let textContent = '';
        const toolCalls = [];
        for (const block of parsed.content ?? []) {
            switch (block.type) {
                case 'text':
                    textContent += block.text;
                    break;
                case 'tool_use':
                    toolCalls.push({
                        id: block.id,
                        name: block.name,
                        arguments: JSON.stringify(block.input),
                    });
                    break;
                case 'thinking':
                case 'redacted_thinking':
                    // Intentionally not surfaced — see function JSDoc.
                    break;
                default: {
                    // Parity with streaming path: log + emit telemetry for unknown block types
                    const unknownType = block.type;
                    logService.warn(`[messagesAPI] non-streaming: unknown content_block type '${unknownType}' for model ${parsed.model}`);
                    /* __GDPR__
                        "messagesApi.unknownContentBlock" : {
                            "owner": "bhavyaus",
                            "comment": "Tracks unknown Anthropic content block types",
                            "requestId": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The request ID for correlation" },
                            "model": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The model that emitted the unknown block" },
                            "blockType": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The unknown content_block.type string" }
                        }
                    */
                    telemetryService.sendMSFTTelemetryEvent('messagesApi.unknownContentBlock', {
                        requestId,
                        model: parsed.model,
                        blockType: unknownType,
                    });
                    break;
                }
            }
        }
        // Report text and tool calls to finishedCb so callers that rely on
        // the callback (e.g. for OTEL tracing, progress, langModelServer SSE
        // forwarding) see the complete response — matching the streaming path.
        const delta = {
            text: textContent,
            ...(toolCalls.length > 0 ? {
                copilotToolCalls: toolCalls.map(tc => ({
                    id: tc.id,
                    name: tc.name,
                    arguments: tc.arguments,
                })),
            } : {}),
        };
        await finishCallback(textContent, 0, delta);
        if (parsed.stop_reason === 'refusal') {
            logService.warn(`[messagesAPI] non-streaming: Refusal received for model ${parsed.model}`);
            /* __GDPR__
                "messagesApi.refusal" : {
                    "owner": "bhavyaus",
                    "comment": "Tracks Anthropic refusal responses including cyber and other policy categories",
                    "requestId": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The request ID for correlation" },
                    "model": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The model that produced the refusal" },
                    "category": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The refusal category (e.g. cyber, content_policy)" }
                }
            */
            telemetryService.sendMSFTTelemetryEvent('messagesApi.refusal', {
                requestId,
                model: parsed.model,
                category: 'unknown',
            });
        }
        const usage = parsed.usage;
        const completion = buildAnthropicCompletion({
            model: parsed.model,
            messageId: parsed.id,
            stopReason: parsed.stop_reason,
            textContent,
            toolCalls,
            inputTokens: usage?.input_tokens ?? 0,
            outputTokens: usage?.output_tokens ?? 0,
            cacheCreationTokens: usage?.cache_creation_input_tokens ?? 0,
            cacheReadTokens: usage?.cache_read_input_tokens ?? 0,
            requestId,
            ghRequestId,
            serverExperiments,
            telemetryData,
        }, logService);
        logService.info(`[messagesAPI] non-streaming message returned. finish reason: [${completion.finishReason}]`);
        const dataToSendToTelemetry = telemetryData.extendedBy({
            completionChoiceFinishReason: completion.finishReason,
            headerRequestId: completion.requestId.headerRequestId
        });
        telemetryService.sendGHTelemetryEvent('completion.finishReason', dataToSendToTelemetry.properties, dataToSendToTelemetry.measurements);
        const telemetryMessage = (0, openai_1.rawMessageToCAPI)(completion.message);
        let telemetryDataWithUsage = telemetryData;
        if (completion.usage) {
            telemetryDataWithUsage = telemetryData.extendedBy({}, {
                promptTokens: completion.usage.prompt_tokens,
                completionTokens: completion.usage.completion_tokens,
                totalTokens: completion.usage.total_tokens,
                ...(completion.usage.prompt_tokens_details && { cachedTokens: completion.usage.prompt_tokens_details.cached_tokens }),
                ...(completion.usage.completion_tokens_details && {
                    reasoningTokens: completion.usage.completion_tokens_details.reasoning_tokens,
                    acceptedPredictionTokens: completion.usage.completion_tokens_details.accepted_prediction_tokens,
                    rejectedPredictionTokens: completion.usage.completion_tokens_details.rejected_prediction_tokens,
                }),
            });
        }
        (0, chatStream_1.sendEngineMessagesTelemetry)(telemetryService, [telemetryMessage], telemetryDataWithUsage, true, logService);
        feed.emitOne(completion);
    }, async () => {
        await response.body.destroy();
    });
}
let AnthropicMessagesProcessor = class AnthropicMessagesProcessor {
    constructor(telemetryData, requestId, ghRequestId, serverExperiments, logService, telemetryService) {
        this.telemetryData = telemetryData;
        this.requestId = requestId;
        this.ghRequestId = ghRequestId;
        this.serverExperiments = serverExperiments;
        this.logService = logService;
        this.telemetryService = telemetryService;
        this.textAccumulator = '';
        this.toolCallAccumulator = new Map();
        this.thinkingAccumulator = new Map();
        this.completedToolCalls = [];
        this.messageId = '';
        this.model = '';
        this.inputTokens = 0;
        this.outputTokens = 0;
        this.cacheCreationTokens = 0;
        this.cacheReadTokens = 0;
    }
    /**
     * Extract IP code citations from copilot_annotations and convert to IIPCodeCitation format
     */
    extractIPCodeCitations(annotations) {
        if (!annotations?.IPCodeCitations?.length) {
            return [];
        }
        // Deduplicate by URL since the same citation can appear multiple times
        const seenUrls = new Set();
        const citations = [];
        for (const citation of annotations.IPCodeCitations) {
            const citationDetails = citation.citations;
            if (!citationDetails) {
                continue;
            }
            const { url, license, snippet } = citationDetails;
            if (typeof url !== 'string' || url.trim() === '') {
                continue;
            }
            if (typeof license !== 'string' || license.trim() === '') {
                continue;
            }
            if (typeof snippet !== 'string' || snippet.trim() === '') {
                continue;
            }
            if (!seenUrls.has(url)) {
                seenUrls.add(url);
                citations.push({
                    citations: {
                        url,
                        license,
                        snippet,
                    }
                });
            }
        }
        if (citations.length > 0) {
            this.logService.trace(`[messagesAPI] IP code citations found: ${citations.length} unique citations`);
        }
        return citations;
    }
    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 ?? 0;
                    this.outputTokens = chunk.message.usage.output_tokens ?? 0;
                    this.cacheCreationTokens = chunk.message.usage.cache_creation_input_tokens ?? 0;
                    this.cacheReadTokens = chunk.message.usage.cache_read_input_tokens ?? 0;
                }
                return;
            case 'content_block_start':
                if (chunk.content_block?.type === 'tool_use' && chunk.index !== undefined) {
                    const toolCallId = chunk.content_block.id || (0, uuid_1.generateUuid)();
                    this.toolCallAccumulator.set(chunk.index, {
                        id: toolCallId,
                        name: chunk.content_block.name || '',
                        arguments: '',
                    });
                    if (this.textAccumulator.length) {
                        onProgress({ text: ' ' });
                    }
                    onProgress({
                        text: '',
                        beginToolCalls: [{ name: chunk.content_block.name || '', id: toolCallId }]
                    });
                }
                else if (chunk.content_block?.type === 'thinking' && chunk.index !== undefined) {
                    this.thinkingAccumulator.set(chunk.index, {
                        thinking: '',
                        signature: '',
                    });
                }
                else if (chunk.content_block?.type === 'redacted_thinking' && chunk.index !== undefined) {
                    const data = chunk.content_block.data;
                    onProgress({
                        text: '',
                        thinking: {
                            id: `thinking_${chunk.index}`,
                            encrypted: data,
                        }
                    });
                }
                return;
            case 'content_block_delta':
                if (chunk.delta) {
                    if (chunk.delta.type === 'text_delta' && chunk.delta.text) {
                        const ipCitations = this.extractIPCodeCitations(chunk.copilot_annotations);
                        if (ipCitations.length > 0) {
                            return onProgress({ text: chunk.delta.text, ipCitations });
                        }
                        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;
                            onProgress({
                                text: '',
                                copilotToolCallStreamUpdates: [{
                                        id: toolCall.id,
                                        name: toolCall.name,
                                        arguments: toolCall.arguments,
                                    }],
                            });
                        }
                    }
                }
                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) {
                    // message_delta provides the most accurate token counts
                    this.outputTokens = chunk.usage.output_tokens;
                    this.inputTokens = chunk.usage.input_tokens ?? this.inputTokens;
                    this.cacheCreationTokens = chunk.usage.cache_creation_input_tokens ?? this.cacheCreationTokens;
                    this.cacheReadTokens = chunk.usage.cache_read_input_tokens ?? this.cacheReadTokens;
                }
                if (chunk.copilot_usage && typeof chunk.copilot_usage.total_nano_aiu === 'number') {
                    this.copilotUsage = chunk.copilot_usage;
                }
                if (chunk.context_management) {
                    this.contextManagementResponse = chunk.context_management;
                    // Report context management via delta so it gets logged to request logger
                    return onProgress({
                        text: '',
                        contextManagement: chunk.context_management
                    });
                }
                // Track stop_reason and stop_details for determining finish reason in message_stop
                if (chunk.delta?.stop_reason) {
                    this.stopReason = chunk.delta.stop_reason;
                }
                if (chunk.delta?.stop_details) {
                    this.stopDetails = chunk.delta.stop_details;
                }
                return;
            case 'message_stop': {
                if (this.contextManagementResponse) {
                    const totalClearedTokens = this.contextManagementResponse.applied_edits.reduce((sum, edit) => sum + (edit.cleared_input_tokens || 0), 0);
                    const totalClearedToolUses = this.contextManagementResponse.applied_edits.reduce((sum, edit) => sum + (edit.cleared_tool_uses || 0), 0);
                    const totalClearedThinkingTurns = this.contextManagementResponse.applied_edits.reduce((sum, edit) => sum + (edit.cleared_thinking_turns || 0), 0);
                    this.logService.trace(`[messagesAPI] Anthropic context editing applied: cleared ${totalClearedTokens} tokens, ${totalClearedToolUses} tool uses.`);
                    /* __GDPR__
                        "contextEditingApplied" : {
                            "owner": "bhavyaus",
                            "comment": "Tracks when Anthropic context editing is applied to manage context window",
                            "requestId": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The request ID for correlation" },
                            "interactionId": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The interaction ID for correlation" },
                            "model": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The model used" },
                            "clearedTokens": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "comment": "Total tokens cleared" },
                            "clearedToolUses": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "comment": "Total tool uses cleared" },
                            "clearedThinkingTurns": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "comment": "Total thinking turns cleared" }
                        }
                    */
                    this.telemetryService.sendMSFTTelemetryEvent('contextEditingApplied', {
                        requestId: this.requestId,
                        interactionId: this.requestId,
                        model: this.model,
                    }, {
                        clearedTokens: totalClearedTokens,
                        clearedToolUses: totalClearedToolUses,
                        clearedThinkingTurns: totalClearedThinkingTurns,
                    });
                }
                if (this.stopReason === 'refusal') {
                    const category = this.stopDetails?.category ?? 'unknown';
                    this.logService.warn(`[messagesAPI] Refusal received: category='${category}' for model ${this.model}`);
                    /* __GDPR__
                        "messagesApi.refusal" : {
                            "owner": "bhavyaus",
                            "comment": "Tracks Anthropic refusal responses including cyber and other policy categories",
                            "requestId": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The request ID for correlation" },
                            "model": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The model that produced the refusal" },
                            "category": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The refusal category (e.g. cyber, content_policy)" }
                        }
                    */
                    this.telemetryService.sendMSFTTelemetryEvent('messagesApi.refusal', {
                        requestId: this.requestId,
                        model: this.model,
                        category,
                    });
                }
                return buildAnthropicCompletion({
                    model: this.model,
                    messageId: this.messageId,
                    stopReason: this.stopReason,
                    textContent: this.textAccumulator,
                    toolCalls: this.completedToolCalls,
                    inputTokens: this.inputTokens,
                    outputTokens: this.outputTokens,
                    cacheCreationTokens: this.cacheCreationTokens,
                    cacheReadTokens: this.cacheReadTokens,
                    requestId: this.requestId,
                    ghRequestId: this.ghRequestId,
                    serverExperiments: this.serverExperiments,
                    telemetryData: this.telemetryData,
                    copilotUsage: this.copilotUsage,
                }, this.logService);
            }
            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;
exports.AnthropicMessagesProcessor = AnthropicMessagesProcessor = __decorate([
    __param(4, logService_1.ILogService),
    __param(5, telemetry_1.ITelemetryService)
], AnthropicMessagesProcessor);
//# sourceMappingURL=messagesApi.js.map