"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.CopilotTokenManagerFromGitHubToken = exports.CopilotTokenManagerFromDeviceId = exports.RefreshableCopilotTokenManager = exports.StaticExtendedTokenInfoCopilotTokenManager = exports.FixedCopilotTokenManager = exports.BaseCopilotTokenManager = exports.tokenErrorString = void 0;
exports.createStaticGitHubTokenProvider = createStaticGitHubTokenProvider;
exports.getOrCreateTestingCopilotTokenManager = getOrCreateTestingCopilotTokenManager;
const copilot_api_1 = require("@vscode/copilot-api");
const event_1 = require("../../../util/vs/base/common/event");
const lifecycle_1 = require("../../../util/vs/base/common/lifecycle");
const descriptors_1 = require("../../../util/vs/platform/instantiation/common/descriptors");
const configurationService_1 = require("../../configuration/common/configurationService");
const capiClient_1 = require("../../endpoint/common/capiClient");
const domainService_1 = require("../../endpoint/common/domainService");
const envService_1 = require("../../env/common/envService");
const nullOctokitServiceImpl_1 = require("../../github/common/nullOctokitServiceImpl");
const logService_1 = require("../../log/common/logService");
const fetcherService_1 = require("../../networking/common/fetcherService");
const telemetry_1 = require("../../telemetry/common/telemetry");
const telemetryData_1 = require("../../telemetry/common/telemetryData");
const copilotToken_1 = require("../common/copilotToken");
const copilotTokenManager_1 = require("../common/copilotTokenManager");
exports.tokenErrorString = `Tests: either GITHUB_PAT, GITHUB_OAUTH_TOKEN, or GITHUB_OAUTH_TOKEN+VSCODE_COPILOT_CHAT_TOKEN must be set unless running from an IS_SCENARIO_AUTOMATION environment. Run "npm run get_token" to get credentials.`;
function createStaticGitHubTokenProvider() {
    const pat = process.env.GITHUB_PAT;
    const oauthToken = process.env.GITHUB_OAUTH_TOKEN;
    // In automation scenarios, NoAuth/BYOK-only scenarios are expected to not have any tokens set.
    if (envService_1.isScenarioAutomation && !pat && !oauthToken) {
        return undefined;
    }
    return () => {
        if (pat) {
            return pat;
        }
        if (oauthToken) {
            return oauthToken;
        }
        throw new Error(exports.tokenErrorString);
    };
}
function getOrCreateTestingCopilotTokenManager(deviceId) {
    if (process.env.VSCODE_COPILOT_CHAT_TOKEN) {
        return new descriptors_1.SyncDescriptor(StaticExtendedTokenInfoCopilotTokenManager, [process.env.VSCODE_COPILOT_CHAT_TOKEN]);
    }
    if (process.env.GITHUB_OAUTH_TOKEN) {
        return new descriptors_1.SyncDescriptor(CopilotTokenManagerFromGitHubToken, [process.env.GITHUB_OAUTH_TOKEN, 'unknown']);
    }
    if (process.env.GITHUB_PAT) {
        return new descriptors_1.SyncDescriptor(FixedCopilotTokenManager, [process.env.GITHUB_PAT]);
    }
    // In automation scenarios, NoAuth/BYOK-only scenarios are expected to not have any tokens set.
    if (envService_1.isScenarioAutomation) {
        return new descriptors_1.SyncDescriptor(CopilotTokenManagerFromDeviceId, [deviceId]);
    }
    throw new Error(exports.tokenErrorString);
}
//TODO: Move this to common
class BaseCopilotTokenManager extends lifecycle_1.Disposable {
    //#endregion
    constructor(_baseOctokitservice, _logService, _telemetryService, _domainService, _capiClientService, _fetcherService, _envService) {
        super();
        this._baseOctokitservice = _baseOctokitservice;
        this._logService = _logService;
        this._telemetryService = _telemetryService;
        this._domainService = _domainService;
        this._capiClientService = _capiClientService;
        this._fetcherService = _fetcherService;
        this._envService = _envService;
        this._isDisposed = false;
        //#region Events
        this._copilotTokenRefreshEmitter = this._register(new event_1.Emitter());
        this.onDidCopilotTokenRefresh = this._copilotTokenRefreshEmitter.event;
        this._register((0, lifecycle_1.toDisposable)(() => this._isDisposed = true));
    }
    get copilotToken() {
        return this._copilotToken;
    }
    set copilotToken(token) {
        if (token !== this._copilotToken) {
            this._copilotToken = token;
            this._copilotTokenRefreshEmitter.fire();
        }
    }
    //#endregion
    //#region Public methods
    resetCopilotToken(httpError) {
        if (httpError !== undefined) {
            this._telemetryService.sendGHTelemetryEvent('auth.reset_token_' + httpError);
        }
        this._logService.debug(`Resetting copilot token on HTTP error ${httpError || 'unknown'}`);
        this.copilotToken = undefined;
    }
    /**
     * Fetches a Copilot token from the GitHub token.
     * @param githubToken A GitHub token to mint a Copilot token from.
     * @returns A Copilot token info or an error.
     * @todo this should be not be public, but it is for now to allow testing.
     */
    async authFromGitHubToken(githubToken, ghUsername) {
        return this.doAuthFromGitHubTokenOrDevDeviceId({ githubToken, ghUsername });
    }
    /**
     * Fetches a Copilot token from the devDeviceId.
     * @param devDeviceId A device ID to mint a Copilot token from.
     * @returns A Copilot token info or an error.
     * @todo this should be not be public, but it is for now to allow testing.
     */
    async authFromDevDeviceId(devDeviceId) {
        return this.doAuthFromGitHubTokenOrDevDeviceId({ devDeviceId });
    }
    async doAuthFromGitHubTokenOrDevDeviceId(context) {
        this._telemetryService.sendGHTelemetryEvent('auth.new_login');
        let result;
        let userInfo;
        let ghUsername;
        try {
            if ('githubToken' in context) {
                ghUsername = context.ghUsername;
                [result, userInfo] = (await Promise.all([
                    this.fetchCopilotTokenFromGitHubToken(context.githubToken),
                    this.fetchCopilotUserInfo(context.githubToken)
                ]));
            }
            else {
                result = await this.fetchCopilotTokenFromDevDeviceId(context.devDeviceId);
            }
        }
        catch (e) {
            this._logService.warn('Failed to get copilot token due to fetch throwing: ' + (e.message || String(e)));
            return { kind: 'failure', reason: 'RequestFailed', message: e.message || String(e) };
        }
        // Handle HTTP errors
        if (!result.ok) {
            this._logService.warn(`Failed to get copilot token due to status ${result.status} ${result.statusText}`);
            const data = telemetryData_1.TelemetryData.createAndMarkAsIssued({
                status: result.status.toString(),
                status_text: result.statusText,
            });
            this._telemetryService.sendGHTelemetryErrorEvent('auth.invalid_token', data.properties, data.measurements);
            // TODO: Look at telemetry to see if this even happens
            // because looking at the backend code, 401s aren't expected here
            if (result.status === 401) {
                this._logService.warn('Failed to get copilot token due to 401 status');
                this._telemetryService.sendGHTelemetryErrorEvent('auth.unknown_401');
                return { kind: 'failure', reason: 'HTTP401' };
            }
        }
        // Copilot Errors
        if (result.kind === 'error-envelope') {
            this._logService.warn(`Failed to get copilot token due to: ${result.body.error_details.message}`);
            this._telemetryService.sendGHTelemetryErrorEvent('auth.request_read_failed');
            return { kind: 'failure', reason: 'NotAuthorized', ...result.body.error_details };
        }
        // Standard Errors like rate limiting
        if (result.kind === 'error') {
            if (result.body.message?.startsWith('API rate limit exceeded')) {
                this._logService.warn('Failed to get copilot token due to exceeding API rate limit');
                this._telemetryService.sendGHTelemetryErrorEvent('auth.rate_limited');
                return { kind: 'failure', reason: 'RateLimited' };
            }
            this._logService.warn(`Failed to get copilot token due to: ${result.body.message}`);
            return { kind: 'failure', reason: 'NotAuthorized' };
        }
        // Parse errors
        if (result.kind === 'parse-failed') {
            this._logService.warn(`Failed to get copilot token due to: ${result.parseError}`);
            this._telemetryService.sendGHTelemetryErrorEvent('auth.request_read_failed');
            return { kind: 'failure', reason: 'ParseFailed', message: result.parseError };
        }
        // Success - we have a validated TokenEnvelope
        const tokenInfo = result.body;
        const expires_at = tokenInfo.expires_at;
        // some users have clocks adjusted ahead, expires_at will immediately be less than current clock time;
        // adjust expires_at to the refresh time + a buffer to avoid expiring the token before the refresh can fire.
        tokenInfo.expires_at = (0, copilotTokenManager_1.nowSeconds)() + tokenInfo.refresh_in + 60; // extra buffer to allow refresh to happen successfully
        // extend the token envelope
        const login = ghUsername ?? 'unknown';
        const extendedInfo = {
            ...tokenInfo,
            copilot_plan: userInfo?.copilot_plan ?? tokenInfo.sku ?? '',
            quota_snapshots: userInfo?.quota_snapshots,
            quota_reset_date: userInfo?.quota_reset_date,
            codex_agent_enabled: userInfo?.codex_agent_enabled,
            token_based_billing: userInfo?.token_based_billing,
            organization_login_list: userInfo?.organization_login_list ?? [],
            username: login,
            isVscodeTeamMember: (0, copilotToken_1.containsVSCodeOrg)(tokenInfo.organization_list ?? []),
        };
        const telemetryData = telemetryData_1.TelemetryData.createAndMarkAsIssued({}, {
            adjusted_expires_at: tokenInfo.expires_at,
            expires_at: expires_at, // track original expires_at
            current_time: (0, copilotTokenManager_1.nowSeconds)(),
        });
        this._telemetryService.sendGHTelemetryEvent('auth.new_token', telemetryData.properties, telemetryData.measurements);
        return { kind: 'success', ...extendedInfo };
    }
    //#endregion
    //#region Private methods
    async fetchCopilotTokenFromGitHubToken(githubToken) {
        const options = {
            callSite: 'copilot-token-github',
            headers: {
                Authorization: `token ${githubToken}`,
                'X-GitHub-Api-Version': '2025-04-01'
            },
            retryFallbacks: true,
            expectJSON: true,
        };
        const response = await this._capiClientService.makeRequest(options, { type: copilot_api_1.RequestType.CopilotToken });
        return this.parseTokenResponse(response);
    }
    async fetchCopilotTokenFromDevDeviceId(devDeviceId) {
        const options = {
            callSite: 'copilot-token-device',
            headers: {
                'X-GitHub-Api-Version': '2025-04-01',
                'Editor-Device-Id': `${devDeviceId}`
            },
            retryFallbacks: true,
            expectJSON: true,
        };
        const response = await this._capiClientService.makeRequest(options, { type: copilot_api_1.RequestType.CopilotNLToken });
        return this.parseTokenResponse(response);
    }
    /**
     * Parses and validates a token endpoint response.
     * Returns a structured result with HTTP status and validated body.
     */
    async parseTokenResponse(response) {
        const httpInfo = { ok: response.ok, status: response.status, statusText: response.statusText };
        let parsed;
        try {
            parsed = await (0, fetcherService_1.jsonVerboseError)(response);
        }
        catch (err) {
            return { ...httpInfo, body: undefined, kind: 'parse-failed', parseError: err.message || String(err) };
        }
        const validationResult = (0, copilotToken_1.validateTokenEnvelope)(parsed);
        if (validationResult.valid) {
            this.sendTokenValidationTelemetry(validationResult);
            return { ...httpInfo, body: validationResult.envelope, kind: 'token' };
        }
        if ((0, copilotToken_1.isErrorEnvelope)(parsed)) {
            return { ...httpInfo, body: parsed, kind: 'error-envelope' };
        }
        if ((0, copilotToken_1.isStandardErrorEnvelope)(parsed)) {
            return { ...httpInfo, body: parsed, kind: 'error' };
        }
        // Token validation failed entirely - send telemetry for the failed case
        this.sendTokenValidationTelemetry(validationResult);
        return { ...httpInfo, body: undefined, kind: 'parse-failed', parseError: 'Response is not valid: ' + JSON.stringify(parsed) };
    }
    /**
     * Sends telemetry when token validation uses fallback strategy or fails entirely.
     * This helps track server schema drift over time.
     */
    sendTokenValidationTelemetry(validationResult) {
        if (validationResult.strategy === 'strict') {
            // We were able to validate strictly as expected - no telemetry needed
            return;
        }
        /* __GDPR__
            "copilotTokenFetching.validation" : {
                "owner": "TylerLeonhardt",
                "comment": "Track token envelope validation strategy to detect server schema drift.",
                "strategy": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "comment": "The validation strategy used: 'fallback' or 'failed'" },
                "strictError": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "comment": "The error from strict validation, if any" },
                "fallbackError": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "comment": "The error from fallback validation, if failed" }
            }
        */
        this._telemetryService.sendMSFTTelemetryEvent('copilotTokenFetching.validation', {
            strategy: validationResult.strategy,
            strictError: validationResult.strictError,
            fallbackError: validationResult.fallbackError,
        });
    }
    async fetchCopilotUserInfo(githubToken) {
        const options = {
            callSite: 'copilot-token-user-info',
            headers: {
                Authorization: `token ${githubToken}`,
                'X-GitHub-Api-Version': '2025-04-01',
            },
            retryFallbacks: true,
            expectJSON: true,
        };
        const response = await this._capiClientService.makeRequest(options, { type: copilot_api_1.RequestType.CopilotUserInfo });
        const data = await response.json();
        return data;
    }
}
exports.BaseCopilotTokenManager = BaseCopilotTokenManager;
//#region FixedCopilotTokenManager
/**
 * A `CopilotTokenManager` that always returns the same token.
 * Mostly only useful for short periods, e.g. tests or single completion requests,
 * as these tokens typically expire after a few hours.
 * @todo Move this to a test layer
 */
let FixedCopilotTokenManager = class FixedCopilotTokenManager extends BaseCopilotTokenManager {
    constructor(_completionsToken, logService, telemetryService, capiClientService, domainService, fetcherService, envService) {
        super(new nullOctokitServiceImpl_1.NullBaseOctoKitService(capiClientService, fetcherService, logService, telemetryService), logService, telemetryService, domainService, capiClientService, fetcherService, envService);
        this._completionsToken = _completionsToken;
        this.copilotToken = (0, copilotToken_1.createTestExtendedTokenInfo)({ token: _completionsToken, username: 'fixedTokenManager', copilot_plan: 'unknown' });
    }
    set completionsToken(token) {
        this._completionsToken = token;
        this.copilotToken = (0, copilotToken_1.createTestExtendedTokenInfo)({ token, username: 'fixedTokenManager', copilot_plan: 'unknown' });
    }
    get completionsToken() {
        return this._completionsToken;
    }
    async getCopilotToken() {
        return new copilotToken_1.CopilotToken(this.copilotToken);
    }
    async checkCopilotToken() {
        // assume it's valid
        return { status: 'OK' };
    }
};
exports.FixedCopilotTokenManager = FixedCopilotTokenManager;
exports.FixedCopilotTokenManager = FixedCopilotTokenManager = __decorate([
    __param(1, logService_1.ILogService),
    __param(2, telemetry_1.ITelemetryService),
    __param(3, capiClient_1.ICAPIClientService),
    __param(4, domainService_1.IDomainService),
    __param(5, fetcherService_1.IFetcherService),
    __param(6, envService_1.IEnvService)
], FixedCopilotTokenManager);
//#endregion
//#region StaticExtendedTokenInfoCopilotTokenManager
/**
 * Use the `StaticExtendedTokenInfoCopilotTokenManager` when you have a base64, JSON-encoded `ExtendedTokenInfo`
 * in an automation scenario.
 */
let StaticExtendedTokenInfoCopilotTokenManager = class StaticExtendedTokenInfoCopilotTokenManager extends BaseCopilotTokenManager {
    constructor(serializedToken, logService, telemetryService, capiClientService, domainService, fetcherService, envService) {
        super(new nullOctokitServiceImpl_1.NullBaseOctoKitService(capiClientService, fetcherService, logService, telemetryService), logService, telemetryService, domainService, capiClientService, fetcherService, envService);
        const data = Buffer.from(serializedToken, 'base64').toString('utf8');
        this._initialToken = JSON.parse(data);
    }
    async getCopilotToken() {
        if (!this.copilotToken) {
            this.copilotToken = { ...this._initialToken };
        }
        return new copilotToken_1.CopilotToken(this._initialToken);
    }
    async checkCopilotToken() {
        return { status: 'OK' };
    }
};
exports.StaticExtendedTokenInfoCopilotTokenManager = StaticExtendedTokenInfoCopilotTokenManager;
exports.StaticExtendedTokenInfoCopilotTokenManager = StaticExtendedTokenInfoCopilotTokenManager = __decorate([
    __param(1, logService_1.ILogService),
    __param(2, telemetry_1.ITelemetryService),
    __param(3, capiClient_1.ICAPIClientService),
    __param(4, domainService_1.IDomainService),
    __param(5, fetcherService_1.IFetcherService),
    __param(6, envService_1.IEnvService)
], StaticExtendedTokenInfoCopilotTokenManager);
//#endregion
//#region RefreshableCopilotTokenManager
/**
 * Generic token manager that handles token caching and refresh logic.
 * Takes an authentication function to fetch new tokens.
 */
class RefreshableCopilotTokenManager extends BaseCopilotTokenManager {
    async getCopilotToken(force) {
        if (!this.copilotToken || this.copilotToken.expires_at < (0, copilotTokenManager_1.nowSeconds)() + (60 * 5 /* 5min */) || force) {
            const tokenResult = await this.authenticateAndGetToken();
            if (tokenResult.kind === 'failure') {
                throw Error(`Failed to get copilot token: ${tokenResult.reason.toString()} ${tokenResult.message ?? ''}`);
            }
            this.copilotToken = { ...tokenResult };
        }
        return new copilotToken_1.CopilotToken(this.copilotToken);
    }
    async checkCopilotToken() {
        if (!this.copilotToken || this.copilotToken.expires_at < (0, copilotTokenManager_1.nowSeconds)()) {
            const tokenResult = await this.authenticateAndGetToken();
            if (tokenResult.kind === 'failure') {
                return tokenResult;
            }
            this.copilotToken = { ...tokenResult };
        }
        const result = {
            status: 'OK',
        };
        return result;
    }
}
exports.RefreshableCopilotTokenManager = RefreshableCopilotTokenManager;
//#endregion
//#region CopilotTokenManagerFromDeviceId
let CopilotTokenManagerFromDeviceId = class CopilotTokenManagerFromDeviceId extends RefreshableCopilotTokenManager {
    constructor(deviceId, logService, telemetryService, domainService, capiClientService, fetcherService, envService, configurationService) {
        super(new nullOctokitServiceImpl_1.NullBaseOctoKitService(capiClientService, fetcherService, logService, telemetryService), logService, telemetryService, domainService, capiClientService, fetcherService, envService);
        this.deviceId = deviceId;
        this.configurationService = configurationService;
    }
    async authenticateAndGetToken() {
        return this.authFromDevDeviceId(this.deviceId);
    }
};
exports.CopilotTokenManagerFromDeviceId = CopilotTokenManagerFromDeviceId;
exports.CopilotTokenManagerFromDeviceId = CopilotTokenManagerFromDeviceId = __decorate([
    __param(1, logService_1.ILogService),
    __param(2, telemetry_1.ITelemetryService),
    __param(3, domainService_1.IDomainService),
    __param(4, capiClient_1.ICAPIClientService),
    __param(5, fetcherService_1.IFetcherService),
    __param(6, envService_1.IEnvService),
    __param(7, configurationService_1.IConfigurationService)
], CopilotTokenManagerFromDeviceId);
//#endregion
//#region CopilotTokenManagerFromGitHubToken
/**
 * Given a GitHub token, return a Copilot token, refreshing it as needed.
 * The caller that initializes the object is responsible for checking telemetry consent before
 * using the object.
 */
let CopilotTokenManagerFromGitHubToken = class CopilotTokenManagerFromGitHubToken extends RefreshableCopilotTokenManager {
    constructor(githubToken, githubUsername, logService, telemetryService, domainService, capiClientService, fetcherService, envService, configurationService) {
        super(new nullOctokitServiceImpl_1.NullBaseOctoKitService(capiClientService, fetcherService, logService, telemetryService), logService, telemetryService, domainService, capiClientService, fetcherService, envService);
        this.githubToken = githubToken;
        this.githubUsername = githubUsername;
        this.configurationService = configurationService;
    }
    async authenticateAndGetToken() {
        return this.authFromGitHubToken(this.githubToken, this.githubUsername);
    }
};
exports.CopilotTokenManagerFromGitHubToken = CopilotTokenManagerFromGitHubToken;
exports.CopilotTokenManagerFromGitHubToken = CopilotTokenManagerFromGitHubToken = __decorate([
    __param(2, logService_1.ILogService),
    __param(3, telemetry_1.ITelemetryService),
    __param(4, domainService_1.IDomainService),
    __param(5, capiClient_1.ICAPIClientService),
    __param(6, fetcherService_1.IFetcherService),
    __param(7, envService_1.IEnvService),
    __param(8, configurationService_1.IConfigurationService)
], CopilotTokenManagerFromGitHubToken);
//# sourceMappingURL=copilotTokenManager.js.map