"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.Features = void 0;
const logService_1 = require("../../../../../../platform/log/common/logService");
const nullExperimentationService_1 = require("../../../../../../platform/telemetry/common/nullExperimentationService");
const instantiation_1 = require("../../../../../../util/vs/platform/instantiation/common/instantiation");
const prompt_1 = require("../../../prompt/src/prompt");
const copilotTokenManager_1 = require("../auth/copilotTokenManager");
const telemetry_1 = require("../telemetry");
const defaultExpFilters_1 = require("./defaultExpFilters");
const expConfig_1 = require("./expConfig");
const filters_1 = require("./filters");
/** General-purpose API for accessing ExP variable values. */
let Features = class Features {
    constructor(instantiationService, experimentationService, copilotTokenManager) {
        this.instantiationService = instantiationService;
        this.experimentationService = experimentationService;
        this.copilotTokenManager = copilotTokenManager;
        this.includeNeighboringFilesDefault = new Map();
        this.excludeRelatedFilesDefault = new Map();
    }
    /**
     * Central logic for obtaining the assignments of treatment groups
     * for a given set of filters (i.e. descriptors of who is getting the treatment).
     * Also gets the values of variables controlled by experiment.
     *
     * This function should be called **exactly once** at the start of every
     * 'completion request' in the client (e.g. ghostText, panel request or chat conversation).
     *
     * It is called with an initial set of filters, (FeaturesFilterArgs)
     * but it adds many of its own.
     * At first the general background filters like extension version.
     * Then it will check ExP assignments for the first time, to find out
     * whether there are any assignments of a special granularity
     * (i.e. the concept that we want to redraw assignments based on
     * time bucket, or checksum of time, etc).
     *
     * On most calls to this function, the assignment fetches will be the
     * assignments from previously used filters, so they will be cached and return fast.
     *
     * @param telemetryData The base telemetry object to which the experimental filters, ExP
     * variable values, and experimental assignments will be added. All properties and measurements
     * of the input telemetryData will be present in the output TelemetryWithExp object.
     * Every telemetry data used to generate ExP scorecards (e.g. ghostText events) must
     * include the correct experiment assignments in order to properly create those
     * scorecards.
     */
    async updateExPValuesAndAssignments(filtersInfo, telemetryData = telemetry_1.TelemetryData.createAndMarkAsIssued()) {
        // We should not allow accidentally overwriting existing ExP vals/assignments.
        // This doesn't stop all misuse cases, but should prevent some trivial ones.
        if (telemetryData instanceof telemetry_1.TelemetryWithExp) {
            throw new Error('updateExPValuesAndAssignments should not be called with TelemetryWithExp');
        }
        const token = this.copilotTokenManager.token ?? await this.copilotTokenManager.getToken();
        const { filters, exp } = this.createExpConfigAndFilters(token);
        return new telemetry_1.TelemetryWithExp(telemetryData.properties, telemetryData.measurements, telemetryData.issuedTime, {
            filters,
            exp: exp,
        });
    }
    /**
     * Request a Copilot token and use that token to call updateExPValuesAndAssignments. Do NOT call this at startup.
     * Instead, register a onCopilotToken handler and use that token with updateExPValuesAndAssignments directly.
     */
    async fetchTokenAndUpdateExPValuesAndAssignments(filtersInfo, telemetryData) {
        return await this.updateExPValuesAndAssignments(filtersInfo, telemetryData);
    }
    createExpConfigAndFilters(token) {
        const exp2 = {};
        for (const varName of Object.values(expConfig_1.ExpTreatmentVariables)) {
            const value = this.experimentationService.getTreatmentVariable(varName);
            if (value !== undefined) {
                exp2[varName] = value;
            }
        }
        const features = Object.entries(exp2).map(([name, value]) => {
            // Based on what tas-client does in https://github.com/microsoft/tas-client/blob/2bd24c976273b671892aad99139af2c7c7dc3b26/tas-client/src/tas-client/FeatureProvider/TasApiFeatureProvider.ts#L59
            return name + (value ? '' : 'cf');
        });
        const exp = new expConfig_1.ExpConfig(exp2, features.join(';'));
        const filterMap = this.instantiationService.invokeFunction(defaultExpFilters_1.createCompletionsFilters, token);
        const filterRecord = {};
        for (const [key, value] of filterMap.entries()) {
            filterRecord[key] = value;
        }
        const filters = new filters_1.FilterSettings(filterRecord);
        return { filters, exp };
    }
    /** Get the entries from this.assignments corresponding to given settings. */
    async getFallbackExpAndFilters() {
        const token = this.copilotTokenManager.token ?? await this.copilotTokenManager.getToken();
        return this.createExpConfigAndFilters(token);
    }
    /** Override for BlockMode to send in the request. */
    overrideBlockMode(telemetryWithExp) {
        return (telemetryWithExp.filtersAndExp.exp.variables[expConfig_1.ExpTreatmentVariables.OverrideBlockMode] ||
            undefined);
    }
    /** Functions with arguments, passed via object destructuring */
    /** @returns the string for copilotcustomengine, or "" if none is set. */
    customEngine(telemetryWithExp) {
        return telemetryWithExp.filtersAndExp.exp.variables[expConfig_1.ExpTreatmentVariables.CustomEngine] ?? '';
    }
    /** @returns the string for copilotcustomenginetargetengine, or undefined if none is set. */
    customEngineTargetEngine(telemetryWithExp) {
        return telemetryWithExp.filtersAndExp.exp.variables[expConfig_1.ExpTreatmentVariables.CustomEngineTargetEngine];
    }
    /** @returns the percent of prompt tokens to be allocated to the suffix */
    suffixPercent(telemetryWithExp) {
        return (telemetryWithExp.filtersAndExp.exp.variables[expConfig_1.ExpTreatmentVariables.SuffixPercent] ??
            prompt_1.DEFAULT_PROMPT_ALLOCATION_PERCENT.suffix);
    }
    /** @returns the percentage match threshold for using the cached suffix */
    suffixMatchThreshold(telemetryWithExp) {
        return (telemetryWithExp.filtersAndExp.exp.variables[expConfig_1.ExpTreatmentVariables.SuffixMatchThreshold] ??
            prompt_1.DEFAULT_SUFFIX_MATCH_THRESHOLD);
    }
    /** @returns whether to enable the inclusion of C++ headers as neighbor files. */
    cppHeadersEnableSwitch(telemetryWithExp) {
        return (telemetryWithExp.filtersAndExp.exp.variables[expConfig_1.ExpTreatmentVariables.CppHeadersEnableSwitch] ??
            false);
    }
    /** @returns whether to use included related files as neighbor files for C# (vscode experiment). */
    relatedFilesVSCodeCSharp(telemetryWithExp) {
        return (telemetryWithExp.filtersAndExp.exp.variables[expConfig_1.ExpTreatmentVariables.RelatedFilesVSCodeCSharp] ??
            false);
    }
    /** @returns whether to use included related files as neighbor files for TS/JS (vscode experiment). */
    relatedFilesVSCodeTypeScript(telemetryWithExp) {
        return (telemetryWithExp.filtersAndExp.exp.variables[expConfig_1.ExpTreatmentVariables.RelatedFilesVSCodeTypeScript] ?? false);
    }
    /** @returns whether to use included related files as neighbor files (vscode experiment). */
    relatedFilesVSCode(telemetryWithExp) {
        return (telemetryWithExp.filtersAndExp.exp.variables[expConfig_1.ExpTreatmentVariables.RelatedFilesVSCode] ?? false);
    }
    /** @returns the list of context providers IDs to enable. The special value `*` enables all context providers. */
    contextProviders(telemetryWithExp) {
        const providers = (telemetryWithExp.filtersAndExp.exp.variables[expConfig_1.ExpTreatmentVariables.ContextProviders] ??
            '');
        if (!providers) {
            return [];
        }
        return providers.split(',').map(provider => provider.trim());
    }
    contextProviderTimeBudget(languageId, telemetryWithExp) {
        const client = (telemetryWithExp.filtersAndExp.exp.variables[expConfig_1.ExpTreatmentVariables.ContextProviderTimeBudget] ??
            150);
        if (client) {
            return client;
        }
        const chat = this.getContextProviderExpSettings(languageId);
        return chat?.timeBudget ?? 150;
    }
    setIncludeNeighboringFilesDefault(languageId, include) {
        this.includeNeighboringFilesDefault.set(languageId, include);
    }
    includeNeighboringFiles(languageId, telemetryWithExp) {
        const client = (telemetryWithExp.filtersAndExp.exp.variables[expConfig_1.ExpTreatmentVariables.IncludeNeighboringFiles] ??
            false);
        if (client) {
            return true;
        }
        const chat = this.getContextProviderExpSettings(languageId);
        return chat?.includeNeighboringFiles ?? this.includeNeighboringFilesDefault.get(languageId) ?? false;
    }
    setExcludeRelatedFilesDefault(languageId, exclude) {
        this.excludeRelatedFilesDefault.set(languageId, exclude);
    }
    excludeRelatedFiles(languageId, telemetryWithExp) {
        const client = (telemetryWithExp.filtersAndExp.exp.variables[expConfig_1.ExpTreatmentVariables.ExcludeRelatedFiles] ??
            false);
        if (client) {
            return true;
        }
        const chat = this.getContextProviderExpSettings(languageId);
        return chat?.excludeRelatedFiles ?? this.excludeRelatedFilesDefault.get(languageId) ?? false;
    }
    getContextProviderExpSettings(languageId) {
        const value = this.experimentationService.getTreatmentVariable(`config.github.copilot.chat.contextprovider.${languageId}`);
        if (typeof value === 'string') {
            try {
                const parsed = JSON.parse(value);
                const ids = this.getProviderIDs(parsed);
                delete parsed.id;
                delete parsed.ids;
                return Object.assign({ ids }, { includeNeighboringFiles: false, excludeRelatedFiles: false, timeBudget: 150 }, parsed);
            }
            catch (err) {
                this.instantiationService.invokeFunction((accessor) => {
                    const logService = accessor.get(logService_1.ILogService);
                    logService.error(`Failed to parse context provider exp settings for language ${languageId}`);
                });
                return undefined;
            }
        }
        else {
            return undefined;
        }
    }
    getProviderIDs(json) {
        const result = [];
        if (typeof json.id === 'string' && json.id.length > 0) {
            result.push(json.id);
        }
        if (Array.isArray(json.ids)) {
            for (const id of json.ids) {
                if (typeof id === 'string' && id.length > 0) {
                    result.push(id);
                }
            }
        }
        return result;
    }
    /** @returns the maximal number of tokens of prompt AND completion */
    maxPromptCompletionTokens(telemetryWithExp) {
        return (telemetryWithExp.filtersAndExp.exp.variables[expConfig_1.ExpTreatmentVariables.MaxPromptCompletionTokens] ??
            prompt_1.DEFAULT_MAX_PROMPT_LENGTH + prompt_1.DEFAULT_MAX_COMPLETION_LENGTH);
    }
    stableContextPercent(telemetryWithExp) {
        return (telemetryWithExp.filtersAndExp.exp.variables[expConfig_1.ExpTreatmentVariables.StableContextPercent] ??
            prompt_1.DEFAULT_PROMPT_ALLOCATION_PERCENT.stableContext);
    }
    volatileContextPercent(telemetryWithExp) {
        return (telemetryWithExp.filtersAndExp.exp.variables[expConfig_1.ExpTreatmentVariables.VolatileContextPercent] ??
            prompt_1.DEFAULT_PROMPT_ALLOCATION_PERCENT.volatileContext);
    }
    /** Custom parameters for language specific Context Providers. */
    cppContextProviderParams(telemetryWithExp) {
        const cppContextProviderParams = telemetryWithExp.filtersAndExp.exp.variables[expConfig_1.ExpTreatmentVariables.CppContextProviderParams];
        return cppContextProviderParams;
    }
    csharpContextProviderParams(telemetryWithExp) {
        const csharpContextProviderParams = telemetryWithExp.filtersAndExp.exp.variables[expConfig_1.ExpTreatmentVariables.CSharpContextProviderParams];
        return csharpContextProviderParams;
    }
    javaContextProviderParams(telemetryWithExp) {
        const javaContextProviderParams = telemetryWithExp.filtersAndExp.exp.variables[expConfig_1.ExpTreatmentVariables.JavaContextProviderParams];
        return javaContextProviderParams;
    }
    multiLanguageContextProviderParams(telemetryWithExp) {
        const multiLanguageContextProviderParams = telemetryWithExp.filtersAndExp.exp.variables[expConfig_1.ExpTreatmentVariables.MultiLanguageContextProviderParams];
        return multiLanguageContextProviderParams;
    }
    tsContextProviderParams(telemetryWithExp) {
        const tsContextProviderParams = telemetryWithExp.filtersAndExp.exp.variables[expConfig_1.ExpTreatmentVariables.TsContextProviderParams];
        return tsContextProviderParams;
    }
    completionsDebounce(telemetryWithExp) {
        return telemetryWithExp.filtersAndExp.exp.variables[expConfig_1.ExpTreatmentVariables.CompletionsDebounce];
    }
    enableElectronFetcher(telemetryWithExp) {
        return (telemetryWithExp.filtersAndExp.exp.variables[expConfig_1.ExpTreatmentVariables.ElectronFetcher] ?? false);
    }
    enableFetchFetcher(telemetryWithExp) {
        return telemetryWithExp.filtersAndExp.exp.variables[expConfig_1.ExpTreatmentVariables.FetchFetcher] ?? false;
    }
    asyncCompletionsTimeout(telemetryWithExp) {
        return (telemetryWithExp.filtersAndExp.exp.variables[expConfig_1.ExpTreatmentVariables.AsyncCompletionsTimeout] ??
            200);
    }
    enableProgressiveReveal(telemetryWithExp) {
        return (telemetryWithExp.filtersAndExp.exp.variables[expConfig_1.ExpTreatmentVariables.ProgressiveReveal] ?? false);
    }
    modelAlwaysTerminatesSingleline(telemetryWithExp) {
        return (telemetryWithExp.filtersAndExp.exp.variables[expConfig_1.ExpTreatmentVariables.ModelAlwaysTerminatesSingleline] ?? true);
    }
    longLookaheadSize(telemetryWithExp) {
        return (telemetryWithExp.filtersAndExp.exp.variables[expConfig_1.ExpTreatmentVariables.ProgressiveRevealLongLookaheadSize] ?? 9);
    }
    shortLookaheadSize(telemetryWithExp) {
        return (telemetryWithExp.filtersAndExp.exp.variables[expConfig_1.ExpTreatmentVariables.ProgressiveRevealShortLookaheadSize] ?? 3);
    }
    maxMultilineTokens(telemetryWithExp) {
        // p50 line length is 19 characters (p95 is 73)
        // average token length is around 4 characters
        // the below value has quite a bit of buffer while bringing the limit in significantly from 500
        return (telemetryWithExp.filtersAndExp.exp.variables[expConfig_1.ExpTreatmentVariables.MaxMultilineTokens] ?? 200);
    }
    multilineAfterAcceptLines(telemetryWithExp) {
        return (telemetryWithExp.filtersAndExp.exp.variables[expConfig_1.ExpTreatmentVariables.MultilineAfterAcceptLines] ??
            1);
    }
    completionsDelay(telemetryWithExp) {
        return telemetryWithExp.filtersAndExp.exp.variables[expConfig_1.ExpTreatmentVariables.CompletionsDelay] ?? 200;
    }
    singleLineUnlessAccepted(telemetryWithExp) {
        return (telemetryWithExp.filtersAndExp.exp.variables[expConfig_1.ExpTreatmentVariables.SingleLineUnlessAccepted] ??
            false);
    }
};
exports.Features = Features;
exports.Features = Features = __decorate([
    __param(0, instantiation_1.IInstantiationService),
    __param(1, nullExperimentationService_1.IExperimentationService),
    __param(2, copilotTokenManager_1.ICompletionsCopilotTokenManager)
], Features);
//# sourceMappingURL=features.js.map