"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 __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
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 __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
var __param = (this && this.__param) || function (paramIndex, decorator) {
    return function (target, key) { decorator(target, key, paramIndex); }
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.XtabNextCursorPredictor = void 0;
const copilot_api_1 = require("@vscode/copilot-api");
const commonTypes_1 = require("../../../platform/chat/common/commonTypes");
const configurationService_1 = require("../../../platform/configuration/common/configurationService");
const endpointProvider_1 = require("../../../platform/endpoint/common/endpointProvider");
const chatEndpoint_1 = require("../../../platform/endpoint/node/chatEndpoint");
const nextCursorLinePrediction_1 = require("../../../platform/inlineEdits/common/dataTypes/nextCursorLinePrediction");
const xtabPromptOptions = __importStar(require("../../../platform/inlineEdits/common/dataTypes/xtabPromptOptions"));
const xtabPromptOptions_1 = require("../../../platform/inlineEdits/common/dataTypes/xtabPromptOptions");
const languageDiagnosticsService_1 = require("../../../platform/languages/common/languageDiagnosticsService");
const proxyModelsService_1 = require("../../../platform/proxyModels/common/proxyModelsService");
const nullExperimentationService_1 = require("../../../platform/telemetry/common/nullExperimentationService");
const backwardCompatSetting_1 = require("../../../util/common/backwardCompatSetting");
const errors_1 = require("../../../util/common/errors");
const result_1 = require("../../../util/common/result");
const tokenizer_1 = require("../../../util/common/tokenizer");
const assert_1 = require("../../../util/vs/base/common/assert");
const instantiation_1 = require("../../../util/vs/platform/instantiation/common/instantiation");
const lintErrors_1 = require("../common/lintErrors");
const promptCrafting_1 = require("../common/promptCrafting");
const xtabUtils_1 = require("./xtabUtils");
const DEFAULT_CURSOR_JUMP_MODEL_NAME = 'copilot-suggestions-himalia-001';
let XtabNextCursorPredictor = class XtabNextCursorPredictor {
    constructor(computeTokens, instaService, configService, expService, langDiagService, endpointProvider, proxyModelsService) {
        this.computeTokens = computeTokens;
        this.instaService = instaService;
        this.configService = configService;
        this.expService = expService;
        this.langDiagService = langDiagService;
        this.endpointProvider = endpointProvider;
        this.proxyModelsService = proxyModelsService;
        this.isDisabled = false;
    }
    determineEnablement(supportsNextCursorLinePrediction) {
        if (this.isDisabled) {
            return undefined;
        }
        if (supportsNextCursorLinePrediction === false) {
            return undefined;
        }
        // the cast is for backward compatibility with older experiments
        const originalNextCursorLinePrediction = this.configService.getExperimentBasedConfig(configurationService_1.ConfigKey.InlineEditsNextCursorPredictionEnabled, this.expService);
        switch (originalNextCursorLinePrediction) {
            case true:
                return nextCursorLinePrediction_1.NextCursorLinePrediction.OnlyWithEdit;
            case false:
            case undefined:
                return undefined;
            // for backward compatibility
            case nextCursorLinePrediction_1.NextCursorLinePrediction.OnlyWithEdit:
            case nextCursorLinePrediction_1.NextCursorLinePrediction.Jump:
                return nextCursorLinePrediction_1.NextCursorLinePrediction.OnlyWithEdit;
            default:
                (0, assert_1.assertNever)(originalNextCursorLinePrediction);
        }
    }
    async predictNextCursorPosition(promptPieces, parentTracer, telemetryBuilder, cancellationToken) {
        const tracer = parentTracer.createSubLogger('predictNextCursorPosition');
        const systemMessage = `Your task is to predict the line number where the developer is most likely to make their next edit. If you jump in the current file, just output the line number. If you want to jump to another file, output the filepath (relative to workspace root), colon, then line number. If you don't think anywhere is a good next line jump target, just output the current line number of the cursor. Make sure to output no explanation, reasoning, extra spaces, etc.`;
        const maxTokens = this.configService.getExperimentBasedConfig(configurationService_1.ConfigKey.Advanced.InlineEditsNextCursorPredictionCurrentFileMaxTokens, this.expService);
        const currentFileContentR = (0, promptCrafting_1.constructTaggedFile)(promptPieces.currentDocument, promptPieces.editWindowLinesRange, promptPieces.areaAroundEditWindowLinesRange, {
            ...promptPieces.opts,
            currentFile: {
                ...promptPieces.opts.currentFile,
                maxTokens,
                includeTags: false,
            }
        }, this.computeTokens, {
            includeLineNumbers: {
                areaAroundCodeToEdit: xtabPromptOptions.IncludeLineNumbersOption.None,
                currentFileContent: xtabPromptOptions.IncludeLineNumbersOption.WithSpaceAfter
            }
        });
        if (currentFileContentR.isError()) {
            tracer.trace(`Failed to construct tagged file: ${currentFileContentR.err}`);
            return result_1.Result.fromString(currentFileContentR.err);
        }
        const { clippedTaggedCurrentDoc, areaAroundCodeToEdit } = currentFileContentR.val;
        // Get lint diagnostics if enabled for cursor prediction
        const lintOptions = this.determineLintOptions();
        const lintErrors = new lintErrors_1.LintErrors(promptPieces.activeDoc.id, promptPieces.currentDocument, this.langDiagService, promptPieces.xtabHistory);
        const includeLineNumbersInRecentSnippets = (0, backwardCompatSetting_1.backwardCompatSetting)(this.configService.getExperimentBasedConfig(configurationService_1.ConfigKey.TeamInternal.InlineEditsNextCursorPredictionRecentSnippetsIncludeLineNumbers, this.expService), (oldValue) => {
            if (typeof oldValue === 'boolean') {
                return oldValue ? xtabPromptOptions.IncludeLineNumbersOption.WithSpaceAfter : xtabPromptOptions.IncludeLineNumbersOption.None;
            }
            return oldValue;
        });
        const newPromptPieces = new promptCrafting_1.PromptPieces(promptPieces.currentDocument, promptPieces.editWindowLinesRange, promptPieces.areaAroundEditWindowLinesRange, promptPieces.activeDoc, promptPieces.xtabHistory, clippedTaggedCurrentDoc.lines, areaAroundCodeToEdit, promptPieces.langCtx, promptPieces.aggressivenessLevel, lintErrors, this.computeTokens, {
            ...promptPieces.opts,
            includePostScript: false,
            lintOptions,
            recentlyViewedDocuments: {
                ...promptPieces.opts.recentlyViewedDocuments,
                includeLineNumbers: includeLineNumbersInRecentSnippets,
            },
        });
        const { prompt: userMessage } = (0, promptCrafting_1.getUserPrompt)(newPromptPieces);
        const messages = (0, xtabUtils_1.constructMessages)({
            systemMsg: systemMessage,
            userMsg: userMessage
        });
        telemetryBuilder?.setCursorJumpPrompt(messages);
        const modelName = this.determineModelName();
        telemetryBuilder?.setCursorJumpModelName(modelName);
        const resolvedEndpoint = await this.resolveEndpoint(modelName, tracer);
        if (!resolvedEndpoint) {
            return result_1.Result.fromString('endpointNotResolved');
        }
        const { endpoint, usesResponsesApi } = resolvedEndpoint;
        const secretKey = this.configService.getConfig(configurationService_1.ConfigKey.TeamInternal.InlineEditsNextCursorPredictionApiKey);
        const maxResponseTokens = this.configService.getExperimentBasedConfig(configurationService_1.ConfigKey.TeamInternal.InlineEditsNextCursorPredictionMaxResponseTokens, this.expService);
        let requestOptions = {
            // Responses API models include reasoning tokens in max_output_tokens,
            // so we need a larger budget to leave room for actual output.
            max_tokens: usesResponsesApi ? Math.max(maxResponseTokens, 2048) : maxResponseTokens,
        };
        if (secretKey) {
            requestOptions = { ...requestOptions, secretKey };
        }
        const response = await endpoint.makeChatRequest2({
            messages,
            debugName: 'nes.nextCursorPosition',
            finishedCb: undefined,
            location: commonTypes_1.ChatLocation.Other,
            requestOptions,
        }, cancellationToken);
        if (response.type !== commonTypes_1.ChatFetchResponseType.Success) {
            if (response.type === commonTypes_1.ChatFetchResponseType.NotFound) {
                tracer.trace('Next cursor position prediction endpoint not found; disabling predictor for current session.');
                this.isDisabled = true;
            }
            return result_1.Result.fromString(`fetchError:${response.type}`);
        }
        try {
            telemetryBuilder?.setCursorJumpResponse(response.value);
            const trimmed = response.value.trim();
            return this.parseResponse(trimmed, clippedTaggedCurrentDoc.keptRange);
        }
        catch (err) {
            tracer.trace(`Failed to parse predicted line number from response '${response.value}': ${err}`);
            return result_1.Result.fromString(`failedToParseLine:"${response.value}". Error ${errors_1.ErrorUtils.fromUnknown(err).message}`);
        }
    }
    async resolveEndpoint(modelName, tracer) {
        const useEndpointProvider = this.configService.getConfig(configurationService_1.ConfigKey.TeamInternal.InlineEditsNextCursorPredictionUseEndpointProvider);
        if (useEndpointProvider) {
            const allEndpoints = await this.endpointProvider.getAllChatEndpoints();
            const endpoint = allEndpoints.find(e => e.model === modelName || e.family === modelName);
            if (!endpoint) {
                tracer.trace(`Could not find endpoint for model '${modelName}' via endpoint provider`);
                return undefined;
            }
            const usesResponsesApi = endpoint.apiType === 'responses';
            return { endpoint, usesResponsesApi };
        }
        const url = this.configService.getConfig(configurationService_1.ConfigKey.TeamInternal.InlineEditsNextCursorPredictionUrl);
        return {
            endpoint: this.instaService.createInstance(chatEndpoint_1.ChatEndpoint, {
                id: modelName,
                name: 'nes.nextCursorPosition',
                vendor: modelName,
                urlOrRequestMetadata: url ? url : { type: copilot_api_1.RequestType.ProxyChatCompletions },
                model_picker_enabled: false,
                is_chat_default: false,
                is_chat_fallback: false,
                version: '',
                capabilities: {
                    type: 'chat',
                    family: '',
                    tokenizer: tokenizer_1.TokenizerType.CL100K,
                    limits: undefined,
                    supports: {
                        parallel_tool_calls: false,
                        tool_calls: false,
                        streaming: true,
                        vision: false,
                        prediction: false,
                        thinking: false
                    }
                },
            }),
            usesResponsesApi: false,
        };
    }
    determineModelName() {
        // Priority: experiment-configured model name, then the first `CursorJumpChat`
        // model advertised by the `/models` endpoint, then a hard-coded fallback.
        return this.configService.getExperimentBasedConfig(configurationService_1.ConfigKey.TeamInternal.InlineEditsNextCursorPredictionModelName, this.expService)
            ?? this.proxyModelsService.cursorJumpModels?.[0]?.name
            ?? DEFAULT_CURSOR_JUMP_MODEL_NAME;
    }
    determineLintOptions() {
        const localLintOptions = this.configService.getConfig(configurationService_1.ConfigKey.TeamInternal.InlineEditsNextCursorPredictionLintOptions);
        if (localLintOptions) {
            return { ...xtabPromptOptions_1.DEFAULT_CURSOR_PREDICTION_LINT_OPTIONS, ...localLintOptions };
        }
        const expLintOptions = this.configService.getExperimentBasedConfig(configurationService_1.ConfigKey.TeamInternal.InlineEditsNextCursorPredictionLintOptionsString, this.expService);
        if (expLintOptions) {
            return (0, xtabPromptOptions_1.parseLintOptionString)(expLintOptions, xtabPromptOptions_1.DEFAULT_CURSOR_PREDICTION_LINT_OPTIONS);
        }
        return xtabPromptOptions_1.DEFAULT_CURSOR_PREDICTION_LINT_OPTIONS;
    }
    parseResponse(rawResponse, keptRange) {
        const trimmed = stripThinkTags(rawResponse);
        // Try parsing as a plain line number (same-file jump)
        const lineNumber = parseInt(trimmed, 10);
        if (!isNaN(lineNumber) && String(lineNumber) === trimmed) {
            return this.parseSameFileLineNumber(lineNumber, keptRange);
        }
        // Try parsing as filepath:lineNumber (cross-file jump)
        const lastColonIdx = trimmed.lastIndexOf(':');
        if (lastColonIdx <= 0) {
            return result_1.Result.fromString(`gotNaN`);
        }
        const filePath = trimmed.substring(0, lastColonIdx);
        const lineNumberStr = trimmed.substring(lastColonIdx + 1);
        const crossFileLineNumber = parseInt(lineNumberStr, 10);
        if (isNaN(crossFileLineNumber) || crossFileLineNumber < 0) {
            return result_1.Result.fromString(`crossFileInvalidLineNumber`);
        }
        if (filePath.trim().length === 0) {
            return result_1.Result.fromString(`crossFileEmptyFilePath`);
        }
        return result_1.Result.ok({ kind: 'differentFile', filePath: filePath.trim(), lineNumber: crossFileLineNumber });
    }
    parseSameFileLineNumber(lineNumber, keptRange) {
        if (lineNumber < 0) {
            return result_1.Result.fromString(`negativeLineNumber`);
        }
        if (lineNumber < keptRange.start || keptRange.endExclusive <= lineNumber) {
            return result_1.Result.fromString(`modelNotSeenLineNumber`);
        }
        return result_1.Result.ok({ kind: 'sameFile', lineNumber });
    }
};
exports.XtabNextCursorPredictor = XtabNextCursorPredictor;
exports.XtabNextCursorPredictor = XtabNextCursorPredictor = __decorate([
    __param(1, instantiation_1.IInstantiationService),
    __param(2, configurationService_1.IConfigurationService),
    __param(3, nullExperimentationService_1.IExperimentationService),
    __param(4, languageDiagnosticsService_1.ILanguageDiagnosticsService),
    __param(5, endpointProvider_1.IEndpointProvider),
    __param(6, proxyModelsService_1.IProxyModelsService)
], XtabNextCursorPredictor);
/**
 * Strip `<think>...</think>` reasoning blocks emitted by thinking models.
 * Mirrors the post-processing logic specified by the model owners: remove
 * any complete think blocks, and drop any unterminated leading `<think>`
 * (which can happen when generation hits the max tokens limit).
 */
function stripThinkTags(text) {
    let result = text.replace(/<think>[\s\S]*?<\/think>\s*/g, '');
    if (result.trimStart().startsWith('<think>')) {
        result = '';
    }
    return result.trim();
}
//# sourceMappingURL=xtabNextCursorPredictor.js.map