Skip to content
| Marketplace
Sign in
Visual Studio Code>Other>AI CoreNew to Visual Studio Code? Get it now.
AI Core

AI Core

23J1633

|
12 installs
| (1) | Free
Unified AI API configuration and credential management for VS Code extensions
Installation
Launch VS Code Quick Open (Ctrl+P), paste the following command, and press enter.
Copied to clipboard
More Info

AI Core

English | 中文

English

Unified AI API configuration and credential management for VS Code extensions.

Features

  • Centralized Configuration: Manage all AI providers, API keys, and models in one place
  • Secure Storage: API keys are stored using VS Code's SecretStorage API
  • Multi-Provider Support: Configure multiple AI providers (OpenAI, Claude, Gemini, etc.)
  • Load Balancing: Automatically distribute requests across multiple API keys for the same model
  • Graphical UI: Easy-to-use Webview interface for managing configurations
  • Cross-Extension API: Simple API for other extensions to retrieve credentials
  • Config Sync: Export/import configurations to sync with other extensions
  • Access Control: Whitelist-based access control for sensitive API methods
  • Connectivity Testing: Test model API connectivity directly from the configuration UI with latency display
  • Credential Naming: Custom names for each API credential (e.g., "Production Key", "Test Key") for easier management
  • Auto-Save: Configuration changes are automatically saved with 500ms debounce
  • Status Bar Icon: Optional gear icon in the status bar for quick access to configuration
  • Bilingual UI: Webview interface supports Chinese/English language switching

Installation

  1. Install the extension from VS Code marketplace
  2. Open Command Palette (Ctrl+Shift+P / Cmd+Shift+P)
  3. Run AI Core: Open Configuration

Extension Settings

Setting Type Default Description
aiCore.allowedExtensions string[] ["23J1633.ghost-annotations", "23J1633.bcos", "23J1633.hilbert-go"] Whitelist of extension IDs allowed to access sensitive API methods
aiCore.showStatusBarIcon boolean true Show/hide the AI Core gear icon in the status bar

Configuration Structure

Provider (e.g., OpenAI)
├── Base URL (e.g., https://api.openai.com/v1)
├── API Format (OpenAI / Claude / Gemini)
└── Credentials[]
    ├── Name (optional, e.g., "Production Key")
    ├── API Key
    └── Models[]
        ├── Model ID (e.g., gpt-4)
        └── Display Name (e.g., GPT-4)

API Reference for Extension Developers

Getting the API

import * as vscode from 'vscode';

const coreApi = vscode.extensions.getExtension('23J1633-AI-core')?.exports;

Access Control

Sensitive API methods (getCredential, exportConfig, importConfig) require the caller to pass its extension ID as the first parameter. The ID is checked against the aiCore.allowedExtensions whitelist in VS Code settings.

Setup for your extension:

  1. Add your extension ID to the whitelist in VS Code settings (settings.json):
{
    "aiCore.allowedExtensions": [
        "23J1633.ghost-extensions",
        "23J1633.bcos"
    ]
}
  1. Pass your extension ID when calling sensitive methods:
const MY_ID = '23J1633.my-extension';
const cred = await coreApi.getCredential(MY_ID, 'OpenAI', 'gpt-4');

Non-sensitive methods (openConfigUI, getModelList, onConfigChange) do not require callerId.

Interface Definition

type ApiFormat = 'OpenAI' | 'Claude' | 'Gemini';
type ImportMode = 'merge' | 'replace';

interface AiModelConfig {
    id: string;
    name: string;
}

interface ApiCredential {
    id: string;
    name?: string;       // Optional display name for the credential
    apiKey: string;
    models: AiModelConfig[];
}

interface AiProvider {
    id: string;
    name: string;
    baseUrl: string;
    format: ApiFormat;
    credentials: ApiCredential[];
}

type RootConfig = AiProvider[];

interface CredentialResult {
    apiKey: string;      // The API key
    baseUrl: string;     // The provider's base URL
    format: ApiFormat;   // 'OpenAI' | 'Claude' | 'Gemini'
}

interface AiCoreApi {
    openConfigUI(): void;
    getCredential(callerId: string, providerName: string, modelName: string): Promise<CredentialResult>;
    exportConfig(callerId: string): Promise<RootConfig>;
    importConfig(callerId: string, config: RootConfig, mode?: ImportMode): Promise<void>;
    onConfigChange: vscode.Event<RootConfig>;
    getModelList(): Promise<string[]>;
}

Methods

openConfigUI(): void

Opens the AI Core configuration panel in the editor.

coreApi.openConfigUI();

getCredential(callerId: string, providerName: string, modelName: string): Promise<CredentialResult>

Retrieves credentials for a specific provider and model. Requires caller to be in the whitelist.

Parameters:

  • callerId: Your extension ID (checked against aiCore.allowedExtensions)
  • providerName: The name of the AI provider (must match the configured name exactly)
  • modelName: The model ID (must match a model ID configured under the provider)

Returns: CredentialResult containing apiKey, baseUrl, and format

Throws: Error if caller not in whitelist, or provider/model not found

Load Balancing: If multiple credentials are configured for the same model, one is randomly selected.

exportConfig(callerId: string): Promise<RootConfig>

Exports the full configuration from AI Core. Returns all providers, credentials (with real API keys), and models. Requires caller to be in the whitelist.

Parameters:

  • callerId: Your extension ID (checked against aiCore.allowedExtensions)

Returns: RootConfig — the complete configuration array

const config = await coreApi.exportConfig('23J1633.my-extension');

importConfig(callerId: string, config: RootConfig, mode?: ImportMode): Promise<void>

Imports an external configuration into AI Core. Requires caller to be in the whitelist.

Parameters:

  • callerId: Your extension ID (checked against aiCore.allowedExtensions)
  • config: The configuration to import (same structure as RootConfig)
  • mode: Optional. 'merge' (default) merges with existing config; 'replace' overwrites entirely

Merge behavior:

  • Providers are matched by name — if a provider with the same name exists, its credentials are merged
  • Credentials within a provider are matched by id — existing entries with the same id are updated
  • New providers or credentials are appended
const MY_ID = '23J1633.my-extension';

// Merge: add/update providers and credentials
await coreApi.importConfig(MY_ID, externalConfig);

// Replace: overwrite entire configuration
await coreApi.importConfig(MY_ID, externalConfig, 'replace');

onConfigChange: vscode.Event<RootConfig>

Event that fires whenever the configuration changes (after save, import, etc.).

Callback parameter: The new RootConfig after the change

coreApi.onConfigChange((newConfig) => {
    console.log('AI Core config changed, syncing...');
    syncToMyExtension(newConfig);
});

getModelList(): Promise<string[]>

Returns a list of all configured models in "provider|model" format.

Returns: Array of strings like "OpenAI|gpt-4", "Anthropic|claude-3-sonnet", etc. Each provider-model combination appears only once.

const MY_ID = '23J1633.my-extension';

const models = await coreApi.getModelList();
console.log(models);
// ["OpenAI|gpt-4", "OpenAI|gpt-3.5-turbo", "Anthropic|claude-3-sonnet"]

// Use in a QuickPick
const selected = await vscode.window.showQuickPick(models, {
    placeHolder: 'Select an AI model'
});
if (selected) {
    const [providerName, modelName] = selected.split('|');
    const cred = await coreApi.getCredential(MY_ID, providerName, modelName);
    // ...
}

Config Sync

Sync from External Extension to AI Core

const MY_ID = '23J1633.my-extension';

async function syncToAiCore(myConfig: RootConfig) {
    const coreApi = vscode.extensions.getExtension('23J1633-AI-core')?.exports;
    if (!coreApi) { return; }

    // Import external config, merging with existing
    await coreApi.importConfig(MY_ID, myConfig, 'merge');
}

Sync from AI Core to External Extension

function watchAiCoreChanges() {
    const coreApi = vscode.extensions.getExtension('23J1633-AI-core')?.exports;
    if (!coreApi) { return; }

    // Listen for config changes
    coreApi.onConfigChange(async (newConfig) => {
        // Adapt to your extension's format and save
        const adapted = adaptConfigForMyExtension(newConfig);
        await saveMyConfig(adapted);
    });
}

// Initial sync on activation
async function initialSync() {
    const coreApi = vscode.extensions.getExtension('23J1633-AI-core')?.exports;
    if (!coreApi) { return; }

    const config = await coreApi.exportConfig();
    const adapted = adaptConfigForMyExtension(config);
    await saveMyConfig(adapted);
}

Integration Examples

Basic Usage

const MY_ID = '23J1633.my-extension';

async function callAI() {
    const coreApi = vscode.extensions.getExtension('23J1633-AI-core')?.exports;

    try {
        const cred = await coreApi.getCredential(MY_ID, 'OpenAI', 'gpt-4');

        const response = await fetch(`${cred.baseUrl}/chat/completions`, {
            method: 'POST',
            headers: {
                'Authorization': `Bearer ${cred.apiKey}`,
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({
                model: 'gpt-4',
                messages: [{ role: 'user', content: 'Hello!' }]
            })
        });

        return await response.json();
    } catch (error) {
        vscode.window.showErrorMessage(`AI Error: ${error.message}`, 'Configure').then(action => {
            if (action === 'Configure') {
                coreApi.openConfigUI();
            }
        });
    }
}

Multi-Format Support

const MY_ID = '23J1633.my-extension';

async function callAI(providerName: string, modelName: string) {
    const coreApi = vscode.extensions.getExtension('23J1633-AI-core')?.exports;
    const cred = await coreApi.getCredential(MY_ID, providerName, modelName);
    
    let requestBody: any;
    let headers: Record<string, string>;
    
    switch (cred.format) {
        case 'OpenAI':
            headers = {
                'Authorization': `Bearer ${cred.apiKey}`,
                'Content-Type': 'application/json'
            };
            requestBody = {
                model: modelName,
                messages: [{ role: 'user', content: 'Hello!' }]
            };
            break;
            
        case 'Claude':
            headers = {
                'x-api-key': cred.apiKey,
                'anthropic-version': '2023-06-01',
                'Content-Type': 'application/json'
            };
            requestBody = {
                model: modelName,
                max_tokens: 1024,
                messages: [{ role: 'user', content: 'Hello!' }]
            };
            break;
            
        case 'Gemini':
            headers = {
                'Content-Type': 'application/json'
            };
            requestBody = {
                contents: [{ parts: [{ text: 'Hello!' }] }]
            };
            break;
    }
    
    const url = cred.format === 'Gemini' 
        ? `${cred.baseUrl}/models/${modelName}:generateContent?key=${cred.apiKey}`
        : `${cred.baseUrl}/chat/completions`;
        
    const response = await fetch(url, {
        method: 'POST',
        headers,
        body: JSON.stringify(requestBody)
    });
    
    return await response.json();
}

Error Handling with Configuration Prompt

const MY_ID = '23J1633.my-extension';

async function handleAIRequest() {
    const coreApi = vscode.extensions.getExtension('23J1633-AI-core')?.exports;

    if (!coreApi) {
        const action = await vscode.window.showErrorMessage(
            'AI Core extension is not installed',
            'Install'
        );
        if (action === 'Install') {
            vscode.commands.executeCommand('extension.open', '23J1633-AI-core');
        }

        return;
    }

    try {
        const cred = await coreApi.getCredential(MY_ID, 'Anthropic', 'claude-3-sonnet');
        // ... make API call
    } catch (error) {
        if (error.message.includes('not in the AI Core access whitelist')) {
            const action = await vscode.window.showWarningMessage(
                `Access denied: ${error.message}`,
                'Open Settings',
                'Cancel'
            );
            if (action === 'Open Settings') {
                coreApi.openConfigUI();
            }
        } else if (error.message.includes('not found')) {
            const action = await vscode.window.showWarningMessage(
                `Configuration missing for requested model: ${error.message}`,
                'Open Settings',
                'Cancel'
            );
            if (action === 'Open Settings') {
                coreApi.openConfigUI();
            }
        } else {
            throw error;
        }
    }
}

Security Notes

  • API keys are stored in VS Code's SecretStorage (encrypted by the OS)
  • Keys are masked (e.g., sk-****1234) when displayed in the UI
  • The configuration UI never receives full API keys in the DOM
  • Keys are only transmitted in memory between extensions
  • Access to sensitive APIs is controlled by the aiCore.allowedExtensions whitelist

Extension Dependencies

Add to your extension's package.json:

{
    "extensionDependencies": [
        "23J1633-AI-core"
    ]
}

This ensures AI Core is installed before your extension activates.

License

MIT


中文

为 VS Code 扩展提供统一的 AI API 配置和凭据管理。

功能特性

  • 集中配置:在一个地方管理所有 AI 服务商、API 密钥和模型
  • 安全存储:使用 VS Code 的 SecretStorage API 存储 API 密钥
  • 多服务商支持:配置多个 AI 服务商(OpenAI、Claude、Gemini 等)
  • 负载均衡:自动在同一模型的多个 API 密钥间分配请求
  • 图形界面:易于使用的 Webview 界面管理配置
  • 跨扩展 API:简单的 API 供其他扩展获取凭据
  • 配置同步:导出/导入配置,与其他扩展保持同步
  • 访问控制:基于白名单的敏感 API 访问控制
  • 连通性测试:在配置界面中直接测试模型 API 可用性,显示延迟
  • 凭证命名:为每个 API 凭证自定义名称(如"生产Key"、"测试Key"),便于区分管理
  • 自动保存:配置修改后 500ms 防抖自动保存,无需手动操作
  • 状态栏图标:可选的齿轮图标显示在状态栏,一键打开配置
  • 中英文界面:Webview 界面支持中英文一键切换

安装使用

  1. 从 VS Code 应用商店安装扩展
  2. 打开命令面板(Ctrl+Shift+P / Cmd+Shift+P)
  3. 运行 AI Core: Open Configuration

扩展设置

设置项 类型 默认值 说明
aiCore.allowedExtensions string[] ["23J1633.ghost-annotations", "23J1633.bcos", "23J1633.hilbert-go"] 允许访问敏感 API 方法的扩展 ID 白名单
aiCore.showStatusBarIcon boolean true 是否在状态栏显示 AI Core 齿轮图标

配置结构

服务商 (例如 OpenAI)
├── 基础 URL (例如 https://api.openai.com/v1)
├── API 格式 (OpenAI / Claude / Gemini)
└── 凭据[]
    ├── 名称 (可选,例如"生产Key")
    ├── API 密钥
    └── 模型[]
        ├── 模型 ID (例如 gpt-4)
        └── 显示名称 (例如 GPT-4)

扩展开发者 API 参考

获取 API

import * as vscode from 'vscode';

const coreApi = vscode.extensions.getExtension('23J1633-AI-core')?.exports;

访问控制

敏感 API 方法(getCredential、exportConfig、importConfig)要求调用方传入其扩展 ID 作为第一个参数。该 ID 会在 VS Code 设置的 aiCore.allowedExtensions 白名单中进行校验。

为你的扩展做配置:

  1. 在 VS Code 设置(settings.json)中将你的扩展 ID 添加到白名单:
{
    "aiCore.allowedExtensions": [
        "23J1633.ghost-extensions",
        "23J1633.bcos"
    ]
}
  1. 调用敏感方法时传入你的扩展 ID:
const MY_ID = '23J1633.my-extension';
const cred = await coreApi.getCredential(MY_ID, 'OpenAI', 'gpt-4');

非敏感方法(openConfigUI、getModelList、onConfigChange)不需要 callerId。

接口定义

type ApiFormat = 'OpenAI' | 'Claude' | 'Gemini';
type ImportMode = 'merge' | 'replace';

interface AiModelConfig {
    id: string;
    name: string;
}

interface ApiCredential {
    id: string;
    name?: string;       // 可选的凭据显示名称
    apiKey: string;
    models: AiModelConfig[];
}

interface AiProvider {
    id: string;
    name: string;
    baseUrl: string;
    format: ApiFormat;
    credentials: ApiCredential[];
}

type RootConfig = AiProvider[];

interface CredentialResult {
    apiKey: string;      // API 密钥
    baseUrl: string;     // 服务商的基础 URL
    format: ApiFormat;   // 'OpenAI' | 'Claude' | 'Gemini'
}

interface AiCoreApi {
    openConfigUI(): void;
    getCredential(callerId: string, providerName: string, modelName: string): Promise<CredentialResult>;
    exportConfig(callerId: string): Promise<RootConfig>;
    importConfig(callerId: string, config: RootConfig, mode?: ImportMode): Promise<void>;
    onConfigChange: vscode.Event<RootConfig>;
    getModelList(): Promise<string[]>;
}

方法说明

openConfigUI(): void

在编辑器中打开 AI Core 配置面板。

coreApi.openConfigUI();

getCredential(callerId: string, providerName: string, modelName: string): Promise<CredentialResult>

获取特定服务商和模型的凭据。需要调用方在白名单中。

参数:

  • callerId:你的扩展 ID(在 aiCore.allowedExtensions 中校验)
  • providerName:AI 服务商的名称(必须与配置的名称完全匹配)
  • modelName:模型 ID(必须与服务商下配置的模型 ID 匹配)

返回: 包含 apiKey、baseUrl 和 format 的 CredentialResult

异常: 如果调用方不在白名单中,或找不到服务商/模型会抛出错误

负载均衡: 如果同一模型配置了多个凭据,会随机选择一个。

exportConfig(callerId: string): Promise<RootConfig>

从 AI Core 导出完整配置。返回所有服务商、凭据(包含真实 API 密钥)和模型。需要调用方在白名单中。

参数:

  • callerId:你的扩展 ID(在 aiCore.allowedExtensions 中校验)

返回: RootConfig — 完整的配置数组

const config = await coreApi.exportConfig('23J1633.my-extension');

importConfig(callerId: string, config: RootConfig, mode?: ImportMode): Promise<void>

将外部配置导入到 AI Core。需要调用方在白名单中。

参数:

  • callerId:你的扩展 ID(在 aiCore.allowedExtensions 中校验)
  • config:要导入的配置(与 RootConfig 结构相同)
  • mode:可选。'merge'(默认)与现有配置合并;'replace' 完全覆盖

合并行为:

  • 服务商按 name 匹配 — 如果同名服务商已存在,则合并其凭据
  • 服务商内的凭据按 id 匹配 — 相同 id 的条目会被更新
  • 新的服务商或凭据会被追加
const MY_ID = '23J1633.my-extension';

// 合并:添加/更新服务商和凭据
await coreApi.importConfig(MY_ID, externalConfig);

// 替换:覆盖整个配置
await coreApi.importConfig(MY_ID, externalConfig, 'replace');

onConfigChange: vscode.Event<RootConfig>

配置变更时触发的事件(保存、导入等操作后)。

回调参数: 变更后的新的 RootConfig

coreApi.onConfigChange((newConfig) => {
    console.log('AI Core 配置已变更,开始同步...');
    syncToMyExtension(newConfig);
});

getModelList(): Promise<string[]>

返回所有已配置模型的列表,格式为 "服务商|模型"。

返回: 字符串数组,如 ["OpenAI|gpt-4", "Anthropic|claude-3-sonnet"]。每个服务商-模型组合仅出现一次。

const MY_ID = '23J1633.my-extension';

const models = await coreApi.getModelList();
console.log(models);
// ["OpenAI|gpt-4", "OpenAI|gpt-3.5-turbo", "Anthropic|claude-3-sonnet"]

// 用于 QuickPick 选择
const selected = await vscode.window.showQuickPick(models, {
    placeHolder: '选择一个 AI 模型'
});
if (selected) {
    const [providerName, modelName] = selected.split('|');
    const cred = await coreApi.getCredential(MY_ID, providerName, modelName);
    // ...
}

配置同步

从外部扩展同步到 AI Core

const MY_ID = '23J1633.my-extension';

async function syncToAiCore(myConfig: RootConfig) {
    const coreApi = vscode.extensions.getExtension('23J1633-AI-core')?.exports;
    if (!coreApi) { return; }

    // 导入外部配置,与现有配置合并
    await coreApi.importConfig(MY_ID, myConfig, 'merge');
}

从 AI Core 同步到外部扩展

function watchAiCoreChanges() {
    const coreApi = vscode.extensions.getExtension('23J1633-AI-core')?.exports;
    if (!coreApi) { return; }

    // 监听配置变更
    coreApi.onConfigChange(async (newConfig) => {
        // 转换为你的扩展格式并保存
        const adapted = adaptConfigForMyExtension(newConfig);
        await saveMyConfig(adapted);
    });
}

// 激活时初始同步
async function initialSync() {
    const coreApi = vscode.extensions.getExtension('23J1633-AI-core')?.exports;
    if (!coreApi) { return; }

    const config = await coreApi.exportConfig(MY_ID);
    const adapted = adaptConfigForMyExtension(config);
    await saveMyConfig(adapted);
}

集成示例

基础用法

const MY_ID = '23J1633.my-extension';

async function callAI() {
    const coreApi = vscode.extensions.getExtension('23J1633-AI-core')?.exports;

    try {
        const cred = await coreApi.getCredential(MY_ID, 'OpenAI', 'gpt-4');

        const response = await fetch(`${cred.baseUrl}/chat/completions`, {
            method: 'POST',
            headers: {
                'Authorization': `Bearer ${cred.apiKey}`,
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({
                model: 'gpt-4',
                messages: [{ role: 'user', content: '你好!' }]
            })
        });

        return await response.json();
    } catch (error) {
        vscode.window.showErrorMessage(`AI 错误: ${error.message}`, '去配置').then(action => {
            if (action === '去配置') {
                coreApi.openConfigUI();
            }
        });
    }
}

多格式支持

const MY_ID = '23J1633.my-extension';

async function callAI(providerName: string, modelName: string) {
    const coreApi = vscode.extensions.getExtension('23J1633-AI-core')?.exports;
    const cred = await coreApi.getCredential(MY_ID, providerName, modelName);
    
    let requestBody: any;
    let headers: Record<string, string>;
    
    switch (cred.format) {
        case 'OpenAI':
            headers = {
                'Authorization': `Bearer ${cred.apiKey}`,
                'Content-Type': 'application/json'
            };
            requestBody = {
                model: modelName,
                messages: [{ role: 'user', content: '你好!' }]
            };
            break;
            
        case 'Claude':
            headers = {
                'x-api-key': cred.apiKey,
                'anthropic-version': '2023-06-01',
                'Content-Type': 'application/json'
            };
            requestBody = {
                model: modelName,
                max_tokens: 1024,
                messages: [{ role: 'user', content: '你好!' }]
            };
            break;
            
        case 'Gemini':
            headers = {
                'Content-Type': 'application/json'
            };
            requestBody = {
                contents: [{ parts: [{ text: '你好!' }] }]
            };
            break;
    }
    
    const url = cred.format === 'Gemini' 
        ? `${cred.baseUrl}/models/${modelName}:generateContent?key=${cred.apiKey}`
        : `${cred.baseUrl}/chat/completions`;
        
    const response = await fetch(url, {
        method: 'POST',
        headers,
        body: JSON.stringify(requestBody)
    });
    
    return await response.json();
}

带配置引导的错误处理

const MY_ID = '23J1633.my-extension';

async function handleAIRequest() {
    const coreApi = vscode.extensions.getExtension('23J1633-AI-core')?.exports;

    if (!coreApi) {
        const action = await vscode.window.showErrorMessage(
            'AI Core 扩展未安装',
            '去安装'
        );
        if (action === '去安装') {
            vscode.commands.executeCommand('extension.open', '23J1633-AI-core');
        }
        return;
    }

    try {
        const cred = await coreApi.getCredential(MY_ID, 'Anthropic', 'claude-3-sonnet');
        // ... 发起 API 调用
    } catch (error) {
        if (error.message.includes('not in the AI Core access whitelist')) {
            const action = await vscode.window.showWarningMessage(
                `访问被拒绝: ${error.message}`,
                '打开设置',
                '取消'
            );
            if (action === '打开设置') {
                coreApi.openConfigUI();
            }
        } else if (error.message.includes('not found')) {
            const action = await vscode.window.showWarningMessage(
                `请求的模型配置缺失: ${error.message}`,
                '打开设置',
                '取消'
            );
            if (action === '打开设置') {
                coreApi.openConfigUI();
            }
        } else {
            throw error;
        }
    }
}

安全说明

  • API 密钥存储在 VS Code 的 SecretStorage 中(由操作系统加密)
  • 密钥在 UI 中显示时会被脱敏(例如 sk-****1234)
  • 配置界面的 DOM 中永远不会接收到完整的 API 密钥
  • 密钥仅在扩展间的内存中传输
  • 敏感 API 的访问由 aiCore.allowedExtensions 白名单控制

扩展依赖

在你的扩展的 package.json 中添加:

{
    "extensionDependencies": [
        "23J1633-AI-core"
    ]
}

这确保 AI Core 在你的扩展激活之前已安装。

许可证

MIT

  • Contact us
  • Jobs
  • Privacy
  • Manage cookies
  • Terms of use
  • Trademarks
© 2026 Microsoft