mirror of
https://github.com/microsoft/vscode.git
synced 2026-07-03 13:06:06 +01:00
c04d3b4d48
* Make Copilot sanity tests mint their own token instead of using static copilot-token The Copilot sanity tests resolve their Copilot token via getOrCreateTestingCopilotTokenManager, which prefers VSCODE_COPILOT_CHAT_TOKEN (set here from the `copilot-token` Key Vault secret) over GITHUB_OAUTH_TOKEN. `copilot-token` holds a pre-minted Copilot token that is refreshed out-of-band by a separate scheduled pipeline. When that refresher broke (the GitHub OAuth secret it reads was rotated/removed during a security incident), `copilot-token` silently went stale and expired. The static token manager hands the expired token to CAPI `/models`, which returns 401, leaving no resolved model and surfacing the misleading "server did not mark a chat fallback model" error. Drop the static `copilot-token` so the tests fall through to GITHUB_OAUTH_TOKEN (`capi-oauth-pipeline-token`) and exchange it for a fresh Copilot token on demand. This path self-refreshes and removes the dependency on the external refresher. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Re-enable sanity tests and surface model-fetch error - Re-enable the three Copilot sanity tests that were temporarily skipped in #323684 now that token resolution is fixed. - Surface the underlying model-fetch error (e.g. an expired-token 401) from ModelMetadataFetcher resolve methods instead of the misleading "server did not mark a chat fallback model" message. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Omit reasoning summary instead of sending invalid 'off' value #323639 disabled Responses API reasoning summaries by hard-coding `reasoning.summary = 'off'`, but the Responses API only accepts 'concise', 'detailed', or 'auto' (or omitting the field to disable). Models such as gpt-5.3-codex reject 'off' with HTTP 400 invalid_request_body, which surfaced as failing Copilot sanity tests once they could reach the model. Set summary to undefined so the field is omitted (summaries stay disabled, as intended) and update the unit test accordingly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Decouple reasoning-preserve test from disabled summary The "should preserve reasoning object when thinking is supported" test relied on the reasoning summary always being present to keep body.reasoning defined. Now that the summary is omitted, give the test model reasoning_effort support so body.reasoning is populated via effort, which is what the test actually means to verify. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * signing commit --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
96 lines
4.0 KiB
TypeScript
96 lines
4.0 KiB
TypeScript
/*---------------------------------------------------------------------------------------------
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
*--------------------------------------------------------------------------------------------*/
|
|
|
|
import { AzureCliCredential, ChainedTokenCredential, DeviceCodeCredential, InteractiveBrowserCredential, ManagedIdentityCredential, TokenCredential } from '@azure/identity';
|
|
import { SecretClient } from '@azure/keyvault-secrets';
|
|
import * as fs from 'fs';
|
|
import * as process from 'process';
|
|
|
|
const useColors = (process.stdout.isTTY && typeof process.stdout.hasColors === 'function' && process.stdout.hasColors());
|
|
function red(s: string) { return useColors ? `\x1b[31m${s}\x1b[0m` : s; }
|
|
|
|
async function setupSecretClient(vaultUri: string) {
|
|
const credentialOptions: TokenCredential[] = [];
|
|
|
|
// Only add managed identity credential if the client ID is provided
|
|
if (process.env.AZURE_CLIENT_ID) {
|
|
credentialOptions.push(new ManagedIdentityCredential({ clientId: process.env.AZURE_CLIENT_ID }));
|
|
}
|
|
|
|
// Always add the Azure CLI as an option
|
|
credentialOptions.push(new AzureCliCredential({ tenantId: '72f988bf-86f1-41af-91ab-2d7cd011db47' }));
|
|
|
|
// Check if terminal is interactive, non-interactive environments can't use
|
|
// InteractiveBrowserCredential and don't necessarily have access to a keychain
|
|
// For SSH sessions into Azure VMs, keychain is not available, requires managed identity
|
|
if (process.stdin.isTTY && !process.env.AZURE_CLIENT_ID && !process.env.CODESPACES) {
|
|
credentialOptions.push(new InteractiveBrowserCredential({ tenantId: '72f988bf-86f1-41af-91ab-2d7cd011db47' }));
|
|
}
|
|
|
|
// Use DeviceCodeCredential in Codespaces
|
|
if (process.env.CODESPACES) {
|
|
const deviceCodeCredential = new DeviceCodeCredential({
|
|
tenantId: '72f988bf-86f1-41af-91ab-2d7cd011db47',
|
|
userPromptCallback: (info) => {
|
|
console.log('To authenticate, visit:', info.verificationUri);
|
|
console.log('Enter the code:', info.userCode);
|
|
}
|
|
});
|
|
credentialOptions.push(deviceCodeCredential);
|
|
}
|
|
|
|
const credential = new ChainedTokenCredential(...credentialOptions);
|
|
return new SecretClient(vaultUri, credential);
|
|
}
|
|
|
|
async function fetchSecret(secretClient: SecretClient, secretName: string): Promise<string | undefined> {
|
|
const secret = await secretClient.getSecret(secretName);
|
|
return secret.value;
|
|
}
|
|
|
|
async function fetchSecrets(): Promise<{ [key: string]: string | undefined }> {
|
|
const keyVaultClient = await setupSecretClient('https://copilot-automation.vault.azure.net/');
|
|
|
|
const secrets: { [key: string]: string | undefined } = {};
|
|
secrets['HMAC_SECRET'] = await fetchSecret(keyVaultClient, 'hmac-secret');
|
|
|
|
if (!process.stdin.isTTY) { // only in automation
|
|
secrets['GITHUB_OAUTH_TOKEN'] = await fetchSecret(keyVaultClient, 'capi-oauth-pipeline-token');
|
|
secrets['BLACKBIRD_EMBEDDINGS_KEY'] = await fetchSecret(keyVaultClient, 'vsc-aoai-key');
|
|
secrets['BLACKBIRD_REDIS_CACHE_KEY'] = await fetchSecret(keyVaultClient, 'blackbird-redis-cache-key');
|
|
|
|
try {
|
|
secrets['ANTHROPIC_API_KEY'] = await fetchSecret(keyVaultClient, 'anthropic-key');
|
|
secrets['DEEPSEEK_API_KEY'] = await fetchSecret(keyVaultClient, 'deepseek-key');
|
|
} catch (error) {
|
|
console.log(red(`Failed to fetch optional evaluation tokens. Skipping...`));
|
|
}
|
|
}
|
|
|
|
return secrets;
|
|
}
|
|
|
|
async function main() {
|
|
const env = Object.entries(await fetchSecrets());
|
|
|
|
const raw = fs.existsSync('.env') ? fs.readFileSync('.env', 'utf8') : '';
|
|
const result = raw.split('\n')
|
|
.filter(line => !env.some(([key]) => line.startsWith(`${key}=`)))
|
|
.concat(env.map(([key, value]) => `${key}=${value}`))
|
|
.filter(line => line.trim() !== '') // Remove empty lines
|
|
.join('\n');
|
|
|
|
fs.writeFileSync('.env', result);
|
|
console.log('Wrote secrets to .env');
|
|
process.exit(0);
|
|
}
|
|
|
|
main().catch(error => {
|
|
const msg = `Error when setting up .env file:\n${error}`;
|
|
console.log(msg);
|
|
console.error(red(msg));
|
|
process.exit(1);
|
|
});
|