Add Azure / Foundry launch support to VS Code extension (#1365)

* Enhance OpenClaude VS Code extension with Microsoft Foundry / Azure OpenAI support. Added configuration options for Azure API key, endpoint, and deployment settings. Updated README and documentation for new features, including a setup wizard for Azure integration. Improved terminal launch environment handling for Azure compatibility.

* Fix packaged Windows helper runtime references

* Use installed CLI from Windows helper aliases

* Scope Windows helper env overrides to invocation

* Align Windows alias docs with shipped helper
This commit is contained in:
JoneSSL
2026-06-09 06:27:18 +08:00
committed by GitHub
parent 0d4e247905
commit 07c1c56b4f
11 changed files with 704 additions and 19 deletions
+1 -1
View File
@@ -369,7 +369,7 @@ Coverage output is written to `coverage/lcov.info`, and OpenClaude also generate
## VS Code Extension
The repo includes a VS Code extension in [`vscode-extension/openclaude-vscode`](vscode-extension/openclaude-vscode) for OpenClaude launch integration, provider-aware control-center UI, and theme support.
The repo includes a VS Code extension in [`vscode-extension/openclaude-vscode`](vscode-extension/openclaude-vscode) for OpenClaude launch integration, provider-aware Control Center, in-editor chat, theme support, and optional **Microsoft Foundry / Azure OpenAI** configuration (endpoint, API version, deployment, API key via Secret Storage) injected into launched terminals. See that folders [README](vscode-extension/openclaude-vscode/README.md).
## Security
+24
View File
@@ -256,6 +256,30 @@ export OPENAI_BASE_URL=https://your-resource.openai.azure.com/openai/deployments
export OPENAI_MODEL=gpt-4o
```
### Microsoft Foundry / Azure OpenAI (resource URL + deployment)
When your endpoint is the **resource base URL** (not the full `.../deployments/.../v1` path), set `OPENAI_MODEL` to the **deployment name** and `AZURE_OPENAI_API_VERSION` to your API version. The OpenAI shim builds:
`{base}/openai/deployments/{OPENAI_MODEL}/chat/completions?api-version={AZURE_OPENAI_API_VERSION}`
and sends the key in the `api-key` header for Azure hosts.
```bash
export CLAUDE_CODE_USE_OPENAI=1
export OPENAI_API_KEY=your-azure-key
export OPENAI_BASE_URL=https://your-resource.openai.azure.com
export OPENAI_MODEL=your-deployment-name
export AZURE_OPENAI_API_VERSION=2024-12-01-preview
```
If your hostname is not detected as Azure (for example some inference endpoints), force Azure URL and header behavior:
```bash
export OPENAI_AZURE_STYLE=1
```
The **OpenClaude VS Code extension** can store the key in Secret Storage and set these variables for you when you launch from the Control Center. See `vscode-extension/openclaude-vscode/README.md`.
## Environment Variables
| Variable | Required | Description |
+7 -3
View File
@@ -155,9 +155,13 @@ npm install -g @gitlawb/openclaude@latest
npm uninstall -g @gitlawb/openclaude
```
## Need Advanced Setup?
Use:
For advanced provider setup, custom endpoints, environment variables, and enterprise launch workflows, see the advanced setup guide:
- [Advanced Setup](advanced-setup.md)
For Codex, Gemini, Mistral, LiteLLM, provider profiles, and runtime diagnostics.
- [Advanced setup](advanced-setup.md)
For Windows helper aliases and launcher shortcuts such as `oc`, `oc-init`, `oc-local`, `oc-provider`, and `oc-check`, see:
- [Windows aliases and launchers](windows-aliases-and-launchers.md)
+163
View File
@@ -0,0 +1,163 @@
# Windows aliases and launchers
This page documents optional PowerShell helper functions for launching OpenClaude on Windows after a global npm install.
These helpers are designed for the installed package workflow:
~~~powershell
npm install -g @gitlawb/openclaude
~~~
The helpers use the installed `openclaude` CLI command. They do not require a source checkout and do not call source-only `bun run scripts/*.ts` entrypoints.
## One-time setup
Run this once in PowerShell:
~~~powershell
$packageRoot = Join-Path (npm root -g) "@gitlawb/openclaude"
$aliases = Join-Path $packageRoot "scripts\windows\openclaude-aliases.ps1"
if (-not (Test-Path $aliases)) {
throw "Alias script not found at $aliases. Update or reinstall @gitlawb/openclaude."
}
if (-not (Test-Path $PROFILE)) {
New-Item -ItemType File -Path $PROFILE -Force | Out-Null
}
$profileLine = ". `"$aliases`""
if (-not (Select-String -Path $PROFILE -Pattern ([regex]::Escape($profileLine)) -Quiet)) {
Add-Content -Path $PROFILE -Value "`n$profileLine"
}
. $aliases
oc-help
~~~
Open a new PowerShell window after setup, or dot-source the profile:
~~~powershell
. $PROFILE
~~~
## Daily commands
### Launch OpenClaude using the installed CLI
~~~powershell
oc
~~~
You can pass normal CLI arguments through `oc`:
~~~powershell
oc --version
oc --help
~~~
### Launch with local Ollama/OpenAI-compatible environment hints
~~~powershell
oc-local
~~~
By default, this uses local Ollama through the OpenAI-compatible API:
~~~text
CLAUDE_CODE_USE_OPENAI=1
OPENAI_BASE_URL=http://localhost:11434/v1
OPENAI_MODEL=llama3.1:8b
~~~
To use a different local model for that invocation:
~~~powershell
oc-local -Model "qwen2.5-coder:7b"
~~~
The environment overrides are scoped to that single `openclaude` invocation. A later plain `oc` call returns to normal installed CLI behavior and saved-provider-profile behavior.
### Launch with low-latency local defaults
~~~powershell
oc-fast
~~~
To use a different model:
~~~powershell
oc-fast -Model "qwen2.5-coder:7b"
~~~
Like `oc-local`, the environment overrides are scoped to that single invocation.
### Open the provider manager
~~~powershell
oc-provider
~~~
This opens the provider manager through the installed OpenClaude CLI.
### Check local Ollama state
~~~powershell
oc-check
~~~
To check a specific model:
~~~powershell
oc-check -Model "qwen2.5-coder:7b"
~~~
### Pull/check a local model, then launch local mode
~~~powershell
oc-init
~~~
To choose a model:
~~~powershell
oc-init -Model "qwen2.5-coder:7b"
~~~
To skip pulling the model and only check/launch:
~~~powershell
oc-init -Model "qwen2.5-coder:7b" -SkipModelPull
~~~
`oc-init` does not save a provider profile. It pulls/checks the local Ollama model and then launches `oc-local`.
### Show quick help
~~~powershell
oc-help
~~~
## Command summary
| Command | Purpose |
| --- | --- |
| `oc` | Launch OpenClaude using the installed CLI and saved/default behavior |
| `oc-local` | Launch once with local Ollama/OpenAI-compatible environment hints |
| `oc-fast` | Launch once with local Ollama/OpenAI-compatible low-latency hints |
| `oc-provider` | Open the provider manager |
| `oc-check` | Show local Ollama install/listening/model state |
| `oc-init` | Pull/check a local Ollama model, then launch local mode |
| `oc-help` | Show quick command help |
## Notes
These helpers are intentionally global-install oriented. They use the installed CLI instead of source-checkout development scripts.
For advanced provider setup, use the built-in provider manager:
~~~powershell
oc-provider
~~~
+2
View File
@@ -20,6 +20,8 @@
"dist/sdk.mjs",
"src/entrypoints/sdk.d.ts",
"src/entrypoints/sdk/coreTypes.generated.ts",
"scripts/windows/openclaude-aliases.ps1",
"docs/windows-aliases-and-launchers.md",
"README.md"
],
"scripts": {
+206
View File
@@ -0,0 +1,206 @@
Set-StrictMode -Version Latest
function Test-OpenClaudeCommand {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string]$Name
)
return [bool](Get-Command -Name $Name -ErrorAction SilentlyContinue)
}
function Assert-OpenClaudeCommand {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string]$Name,
[Parameter(Mandatory = $true)]
[string]$InstallHint
)
if (-not (Test-OpenClaudeCommand -Name $Name)) {
throw "Required command '$Name' was not found. $InstallHint"
}
}
function Invoke-OpenClaude {
[CmdletBinding()]
param(
[Parameter(ValueFromRemainingArguments = $true)]
[string[]]$OpenClaudeArgs
)
Assert-OpenClaudeCommand -Name "openclaude" -InstallHint "Install with: npm install -g @gitlawb/openclaude"
& openclaude @OpenClaudeArgs
if ($LASTEXITCODE -ne 0) {
throw "openclaude failed with exit code $LASTEXITCODE."
}
}
function Invoke-OpenClaudeWithEnvironment {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[hashtable]$Environment,
[Parameter(ValueFromRemainingArguments = $true)]
[string[]]$OpenClaudeArgs
)
$previousValues = @{}
foreach ($name in $Environment.Keys) {
$previousValues[$name] = [Environment]::GetEnvironmentVariable($name, "Process")
Set-Item -Path "Env:$name" -Value $Environment[$name]
}
try {
Invoke-OpenClaude @OpenClaudeArgs
}
finally {
foreach ($name in $Environment.Keys) {
if ($null -eq $previousValues[$name]) {
Remove-Item -Path "Env:$name" -ErrorAction SilentlyContinue
}
else {
Set-Item -Path "Env:$name" -Value $previousValues[$name]
}
}
}
}
function Get-OpenClaudeQuickHelp {
[CmdletBinding()]
param()
@(
"OpenClaude quick commands:",
" oc [args...] -> launch OpenClaude using the installed CLI",
" oc-local [args...] -> launch OpenClaude with local/Ollama OpenAI-compatible environment hints for this invocation only",
" oc-fast [args...] -> launch OpenClaude with low-latency local defaults for this invocation only",
" oc-provider -> open the provider manager in OpenClaude",
" oc-check -> show Ollama install/listening/model state",
" oc-init -> pull/check the local model, then launch local/Ollama mode",
" oc-help -> show this help"
) -join [Environment]::NewLine
}
function oc {
[CmdletBinding()]
param(
[Parameter(ValueFromRemainingArguments = $true)]
[string[]]$OpenClaudeArgs
)
Invoke-OpenClaude @OpenClaudeArgs
}
function oc-local {
[CmdletBinding()]
param(
[string]$Model = "llama3.1:8b",
[Parameter(ValueFromRemainingArguments = $true)]
[string[]]$OpenClaudeArgs
)
Invoke-OpenClaudeWithEnvironment `
-Environment @{
CLAUDE_CODE_USE_OPENAI = "1"
OPENAI_BASE_URL = "http://localhost:11434/v1"
OPENAI_MODEL = $Model
} `
@OpenClaudeArgs
}
function oc-fast {
[CmdletBinding()]
param(
[string]$Model = "llama3.1:8b",
[Parameter(ValueFromRemainingArguments = $true)]
[string[]]$OpenClaudeArgs
)
Invoke-OpenClaudeWithEnvironment `
-Environment @{
CLAUDE_CODE_USE_OPENAI = "1"
OPENAI_BASE_URL = "http://localhost:11434/v1"
OPENAI_MODEL = $Model
OPENCLAUDE_FAST_MODE = "1"
} `
@OpenClaudeArgs
}
function oc-provider {
[CmdletBinding()]
param()
Invoke-OpenClaude "/provider"
}
function oc-check {
[CmdletBinding()]
param(
[string]$Model = "llama3.1:8b"
)
Assert-OpenClaudeCommand -Name "ollama" -InstallHint "Install Ollama from https://ollama.com/download/windows."
$version = & ollama --version 2>$null
$modelNames = (& ollama list 2>$null | Select-Object -Skip 1 | ForEach-Object {
($_ -split "\s+")[0]
}) | Where-Object { $_ }
$isModelAvailable = $modelNames -contains $Model
$probeSucceeded = $false
try {
$response = Invoke-RestMethod -Uri "http://localhost:11434/api/tags" -Method Get -TimeoutSec 3
if ($response.models) {
$probeSucceeded = $true
}
}
catch {
$probeSucceeded = $false
}
[PSCustomObject]@{
OllamaInstalled = $true
OllamaVersion = $version
OllamaListening = $probeSucceeded
Model = $Model
ModelAvailable = $isModelAvailable
}
}
function oc-init {
[CmdletBinding()]
param(
[string]$Model = "llama3.1:8b",
[switch]$SkipModelPull
)
Assert-OpenClaudeCommand -Name "ollama" -InstallHint "Install Ollama from https://ollama.com/download/windows."
if (-not $SkipModelPull) {
& ollama pull $Model
if ($LASTEXITCODE -ne 0) {
throw "ollama pull $Model failed with exit code $LASTEXITCODE."
}
}
$health = oc-check -Model $Model
if (-not $health.OllamaListening) {
Write-Warning "Ollama is installed but API probe to localhost:11434 did not succeed. Start Ollama and retry."
}
oc-local -Model $Model
}
function oc-help {
[CmdletBinding()]
param()
Get-OpenClaudeQuickHelp
}
+19 -6
View File
@@ -23,6 +23,11 @@
* CLAUDE_CODE_USE_GITHUB=1 — enable GitHub inference (no need for USE_OPENAI)
* GITHUB_TOKEN or GH_TOKEN — Copilot API token (mapped to Bearer auth)
* OPENAI_MODEL — optional; use github:copilot or openai/gpt-4.1 style IDs
*
* Azure OpenAI / Microsoft Foundry (OpenAI-compatible chat):
* AZURE_OPENAI_API_VERSION — query param for chat/completions (default: 2024-12-01-preview)
* OPENAI_AZURE_STYLE=1 — force Azure deployment URL + api-key header when the hostname
* would not otherwise match (for example inference.ml.azure.com)
*/
import { APIError } from '@anthropic-ai/sdk'
@@ -2441,12 +2446,20 @@ class OpenAIShimMessages {
: apiKey
// Detect Azure endpoints by hostname (not raw URL) to prevent bypass via
// path segments like https://evil.com/cognitiveservices.azure.com/
let isAzure = false
try {
const { hostname } = new URL(request.baseUrl)
isAzure = hostname.endsWith('.azure.com') &&
(hostname.includes('cognitiveservices') || hostname.includes('openai') || hostname.includes('services.ai'))
} catch { /* malformed URL — not Azure */ }
let isAzure = isEnvTruthy(process.env.OPENAI_AZURE_STYLE)
if (!isAzure) {
try {
const { hostname } = new URL(request.baseUrl)
isAzure =
hostname.endsWith('.azure.com') &&
(hostname.includes('cognitiveservices') ||
hostname.includes('openai') ||
hostname.includes('services.ai') ||
hostname.includes('inference.ml'))
} catch {
/* malformed URL — not Azure */
}
}
let isBankr = false
try {
+1 -2
View File
@@ -6,8 +6,7 @@
"type": "extensionHost",
"request": "launch",
"args": ["--extensionDevelopmentPath=${workspaceFolder}"],
"outFiles": ["${workspaceFolder}/out/**/*.js"],
"preLaunchTask": "${defaultBuildTask}"
"outFiles": ["${workspaceFolder}/out/**/*.js"]
}
]
}
+17 -1
View File
@@ -24,6 +24,7 @@ A practical VS Code companion for OpenClaude with a project-aware **Control Cent
- Open Setup Guide
- Open Command Palette
- **Built-in dark theme**: `OpenClaude Terminal Black`
- **Microsoft Foundry / Azure OpenAI**: optional wizard and settings store endpoint, API version, deployment name, and API key (Secret Storage); launch injects `OPENAI_*` and `AZURE_OPENAI_API_VERSION` into the OpenClaude terminal (see `docs/advanced-setup.md` on the repo).
## Requirements
@@ -38,14 +39,29 @@ A practical VS Code companion for OpenClaude with a project-aware **Control Cent
- `OpenClaude: Open Repository`
- `OpenClaude: Open Setup Guide`
- `OpenClaude: Open Workspace Profile`
- `OpenClaude: New Chat` / `OpenClaude: Open Chat Panel` / `OpenClaude: Resume Session` / `OpenClaude: Abort Generation`
- `OpenClaude: Configure Azure / Foundry Chat (wizard)`
- `OpenClaude: Set Azure / Foundry API Key (Secret Storage)`
- `OpenClaude: Clear Azure / Foundry API Key`
- `OpenClaude: Open Azure / Foundry Settings`
## Microsoft Foundry / Azure OpenAI (terminal chat)
1. Command Palette → **OpenClaude: Configure Azure / Foundry Chat (wizard)** and enter endpoint, API version, deployment name, and API key; or set `openclaude.azure.*` in Settings and use **OpenClaude: Set Azure / Foundry API Key**.
2. Enable **OpenClaude: Azure: Enabled** (the wizard turns this on).
3. **OpenClaude: Launch in Terminal** — the extension merges env vars the OpenAI shim expects (`CLAUDE_CODE_USE_OPENAI`, `OPENAI_BASE_URL`, `OPENAI_API_KEY`, `OPENAI_MODEL`, `AZURE_OPENAI_API_VERSION`, and `OPENAI_AZURE_STYLE` when forced).
If you use `.openclaude-profile.json` for the same workspace, leave Azure injection off to avoid conflicting provider configuration.
## Settings
- `openclaude.launchCommand` (default: `openclaude`)
- `openclaude.terminalName` (default: `OpenClaude`)
- `openclaude.useOpenAIShim` (default: `false`)
- `openclaude.azure.*` — Foundry / Azure OpenAI terminal injection (see Settings UI)
- `openclaude.permissionMode` — chat permission mode
`openclaude.useOpenAIShim` only injects `CLAUDE_CODE_USE_OPENAI=1` into terminals launched by the extension. It does not guess or configure a provider by itself.
`openclaude.useOpenAIShim` only injects `CLAUDE_CODE_USE_OPENAI=1` when Azure injection did not already set it. It does not configure endpoints or keys by itself.
## Notes on Status Detection
@@ -1,7 +1,7 @@
{
"name": "openclaude-vscode",
"displayName": "OpenClaude",
"description": "Practical VS Code companion for OpenClaude with project-aware launch behavior and a real Control Center.",
"description": "Practical VS Code companion for OpenClaude with project-aware launch, Control Center, optional Microsoft Foundry / Azure OpenAI terminal env, and chat.",
"version": "0.2.0",
"publisher": "devnull-bootloader",
"engines": {
@@ -23,6 +23,10 @@
"onCommand:openclaude.openChat",
"onCommand:openclaude.resumeSession",
"onCommand:openclaude.abortChat",
"onCommand:openclaude.setAzureApiKey",
"onCommand:openclaude.clearAzureApiKey",
"onCommand:openclaude.configureAzureChat",
"onCommand:openclaude.openAzureSettings",
"onView:openclaude.controlCenter",
"onView:openclaude.chat"
],
@@ -87,6 +91,26 @@
"command": "openclaude.abortChat",
"title": "OpenClaude: Abort Generation",
"category": "OpenClaude"
},
{
"command": "openclaude.setAzureApiKey",
"title": "OpenClaude: Set Azure / Foundry API Key (Secret Storage)",
"category": "OpenClaude"
},
{
"command": "openclaude.clearAzureApiKey",
"title": "OpenClaude: Clear Azure / Foundry API Key",
"category": "OpenClaude"
},
{
"command": "openclaude.configureAzureChat",
"title": "OpenClaude: Configure Azure / Foundry Chat (wizard)",
"category": "OpenClaude"
},
{
"command": "openclaude.openAzureSettings",
"title": "OpenClaude: Open Azure / Foundry Settings",
"category": "OpenClaude"
}
],
"viewsContainers": {
@@ -135,7 +159,37 @@
"openclaude.useOpenAIShim": {
"type": "boolean",
"default": false,
"description": "Optionally set CLAUDE_CODE_USE_OPENAI=1 in launched OpenClaude terminals."
"description": "Optionally set CLAUDE_CODE_USE_OPENAI=1 in launched OpenClaude terminals when Azure injection is off or incomplete."
},
"openclaude.azure.enabled": {
"type": "boolean",
"default": false,
"description": "When true, launch injects Microsoft Foundry / Azure OpenAI-compatible chat env (OPENAI_* + AZURE_OPENAI_API_VERSION) into the OpenClaude terminal."
},
"openclaude.azure.endpoint": {
"type": "string",
"default": "",
"markdownDescription": "Azure resource base URL for OpenAI-compatible chat (example: `https://YOUR_RESOURCE.openai.azure.com`). Do not put `api-version` here; use **OpenClaude: Azure API Version**."
},
"openclaude.azure.apiVersion": {
"type": "string",
"default": "2024-12-01-preview",
"description": "Azure API version for chat completions (AZURE_OPENAI_API_VERSION)."
},
"openclaude.azure.deployment": {
"type": "string",
"default": "",
"markdownDescription": "Azure deployment name (maps to `OPENAI_MODEL` for the OpenAI shim)."
},
"openclaude.azure.forceAzureUrlStyle": {
"type": "boolean",
"default": true,
"description": "Sets OPENAI_AZURE_STYLE=1 so deployment URLs and api-key header work on Foundry and non-standard Azure hosts."
},
"openclaude.azure.apiKey": {
"type": "string",
"default": "",
"markdownDescription": "Optional API key in settings (not recommended). Prefer **OpenClaude: Set Azure / Foundry API Key**. Secret wins if both are set."
},
"openclaude.permissionMode": {
"type": "string",
@@ -169,7 +223,9 @@
"terminal",
"theme",
"cli",
"llm"
"llm",
"azure",
"foundry"
],
"repository": {
"type": "git",
@@ -19,6 +19,10 @@ const { DiffContentProvider, SCHEME: DIFF_SCHEME } = require('./chat/diffControl
const OPENCLAUDE_REPO_URL = 'https://github.com/Gitlawb/openclaude';
const OPENCLAUDE_SETUP_URL = 'https://github.com/Gitlawb/openclaude/blob/main/README.md#quick-start';
const PROFILE_FILE_NAME = '.openclaude-profile.json';
const SECRET_AZURE_API_KEY = 'openclaude.azure.apiKey';
/** @type {vscode.ExtensionContext | null} */
let extensionContext = null;
function escapeHtml(value) {
return String(value)
@@ -52,6 +56,172 @@ function getExecutableFromCommand(command) {
return normalized.split(/\s+/)[0];
}
/**
* @param {string} raw
*/
function normalizeAzureEndpoint(raw) {
const t = (raw || '').trim();
if (!t) {
return '';
}
try {
const u = new URL(t);
const pathname = u.pathname.replace(/\/+$/, '');
return `${u.origin}${pathname}`;
} catch {
return t.replace(/\/+$/, '');
}
}
/**
* @param {vscode.ExtensionContext | null} context
* @param {vscode.WorkspaceConfiguration} configured
*/
async function resolveAzureApiKey(context, configured) {
if (!context) {
return '';
}
const fromSecret = await context.secrets.get(SECRET_AZURE_API_KEY);
if (fromSecret) {
return fromSecret;
}
return (configured.get('azure.apiKey', '') || '').trim();
}
/**
* @param {vscode.WorkspaceConfiguration} configured
*/
async function buildLaunchAzureEnv(configured) {
const env = {};
const ctx = extensionContext;
if (!ctx) {
return env;
}
const azureEnabled = configured.get('azure.enabled', false);
const endpoint = normalizeAzureEndpoint(configured.get('azure.endpoint', ''));
const apiVersion = (configured.get('azure.apiVersion', '2024-12-01-preview') || '').trim();
const deployment = (configured.get('azure.deployment', '') || '').trim();
const forceStyle = configured.get('azure.forceAzureUrlStyle', true);
if (!azureEnabled) {
return env;
}
const apiKey = await resolveAzureApiKey(ctx, configured);
if (!endpoint || !deployment) {
void vscode.window.showWarningMessage(
'OpenClaude Azure chat is enabled but endpoint or deployment is missing. Run "OpenClaude: Configure Azure / Foundry Chat" or set openclaude.azure.* in settings.',
);
return env;
}
if (!apiKey) {
void vscode.window.showWarningMessage(
'OpenClaude Azure chat is enabled but no API key is set. Use "OpenClaude: Set Azure / Foundry API Key" or openclaude.azure.apiKey (not recommended).',
);
return env;
}
env.CLAUDE_CODE_USE_OPENAI = '1';
env.OPENAI_BASE_URL = endpoint;
env.OPENAI_API_KEY = apiKey;
env.OPENAI_MODEL = deployment;
env.AZURE_OPENAI_API_VERSION = apiVersion || '2024-12-01-preview';
if (forceStyle) {
env.OPENAI_AZURE_STYLE = '1';
}
return env;
}
/**
* @param {vscode.ExtensionContext} context
*/
async function setAzureApiKey(context) {
const key = await vscode.window.showInputBox({
title: 'OpenClaude — Azure / Foundry API key',
prompt: 'Stored in VS Code Secret Storage (not committed to the repo).',
password: true,
ignoreFocusOut: true,
validateInput: v => (v && v.trim() ? null : 'Enter a non-empty key'),
});
if (key == null) {
return;
}
await context.secrets.store(SECRET_AZURE_API_KEY, key.trim());
void vscode.window.showInformationMessage('OpenClaude Azure / Foundry API key saved to Secret Storage.');
}
/**
* @param {vscode.ExtensionContext} context
*/
async function clearAzureApiKey(context) {
await context.secrets.delete(SECRET_AZURE_API_KEY);
void vscode.window.showInformationMessage('OpenClaude Azure / Foundry API key removed from Secret Storage.');
}
/**
* @param {vscode.ExtensionContext} context
*/
async function configureAzureChat(context) {
const cfg = vscode.workspace.getConfiguration('openclaude');
const target = vscode.ConfigurationTarget.Global;
const endpoint = await vscode.window.showInputBox({
title: 'OpenClaude — Azure / Foundry API endpoint',
prompt: 'Resource base URL only (no api-version query). Example: https://YOUR_RESOURCE.openai.azure.com',
ignoreFocusOut: true,
value: cfg.get('azure.endpoint', ''),
validateInput: v => (v && v.trim() ? null : 'Required'),
});
if (endpoint == null) {
return;
}
const apiVersion = await vscode.window.showInputBox({
title: 'OpenClaude — Azure API version',
prompt: 'Matches the api-version used by your deployment (e.g. 2024-12-01-preview).',
value: (cfg.get('azure.apiVersion', '2024-12-01-preview') || '').trim(),
ignoreFocusOut: true,
validateInput: v => (v && v.trim() ? null : 'Required'),
});
if (apiVersion == null) {
return;
}
const deployment = await vscode.window.showInputBox({
title: 'OpenClaude — Azure deployment / model',
prompt: 'Deployment name in Azure (this becomes OPENAI_MODEL for the OpenAI shim).',
value: cfg.get('azure.deployment', ''),
ignoreFocusOut: true,
validateInput: v => (v && v.trim() ? null : 'Required'),
});
if (deployment == null) {
return;
}
const key = await vscode.window.showInputBox({
title: 'OpenClaude — Azure / Foundry API key',
prompt: 'Stored in VS Code Secret Storage.',
password: true,
ignoreFocusOut: true,
validateInput: v => (v && v.trim() ? null : 'Required'),
});
if (key == null) {
return;
}
await cfg.update('azure.endpoint', normalizeAzureEndpoint(endpoint), target);
await cfg.update('azure.apiVersion', apiVersion.trim(), target);
await cfg.update('azure.deployment', deployment.trim(), target);
await cfg.update('azure.forceAzureUrlStyle', true, target);
await cfg.update('azure.enabled', true, target);
await context.secrets.store(SECRET_AZURE_API_KEY, key.trim());
void vscode.window.showInformationMessage(
'OpenClaude Azure / Foundry chat saved. Launch OpenClaude from the Control Center or command palette.',
);
}
function getWorkspacePaths() {
return (vscode.workspace.workspaceFolders || []).map(folder => folder.uri.fsPath);
}
@@ -305,8 +475,8 @@ async function launchOpenClaude(options = {}) {
return;
}
const env = {};
if (shimEnabled) {
const env = await buildLaunchAzureEnv(configured);
if (shimEnabled && !env.CLAUDE_CODE_USE_OPENAI) {
env.CLAUDE_CODE_USE_OPENAI = '1';
}
@@ -887,6 +1057,10 @@ function renderControlCenterHtml(status, options = {}) {
<span class="support-link-label">Open Command Palette</span>
<span class="summary-detail">Access VS Code and OpenClaude commands quickly.</span>
</button>
<button class="support-link" id="azureFoundry" type="button">
<span class="support-link-label">Azure / Foundry settings</span>
<span class="summary-detail">Configure endpoint, API version, deployment, and API key for chat.</span>
</button>
</div>
</section>
</section>
@@ -904,6 +1078,7 @@ function renderControlCenterHtml(status, options = {}) {
document.getElementById('repo').addEventListener('click', () => vscode.postMessage({ type: 'repo' }));
document.getElementById('setup').addEventListener('click', () => vscode.postMessage({ type: 'setup' }));
document.getElementById('commands').addEventListener('click', () => vscode.postMessage({ type: 'commands' }));
document.getElementById('azureFoundry').addEventListener('click', () => vscode.postMessage({ type: 'azureSettings' }));
document.getElementById('refresh').addEventListener('click', () => vscode.postMessage({ type: 'refresh' }));
const profileButton = document.getElementById('openProfile');
@@ -950,6 +1125,9 @@ class OpenClaudeControlCenterProvider {
case 'commands':
await vscode.commands.executeCommand('workbench.action.showCommands');
break;
case 'azureSettings':
await vscode.commands.executeCommand('workbench.action.openSettings', 'openclaude.azure');
break;
case 'refresh':
default:
break;
@@ -1044,6 +1222,8 @@ class OpenClaudeControlCenterProvider {
* @param {vscode.ExtensionContext} context
*/
function activate(context) {
extensionContext = context;
// ── Control Center (existing) ──
const provider = new OpenClaudeControlCenterProvider();
const refreshProvider = () => {
@@ -1165,6 +1345,22 @@ function activate(context) {
chatController.abort();
});
const setAzureApiKeyCommand = vscode.commands.registerCommand('openclaude.setAzureApiKey', async () => {
await setAzureApiKey(context);
});
const clearAzureApiKeyCommand = vscode.commands.registerCommand('openclaude.clearAzureApiKey', async () => {
await clearAzureApiKey(context);
});
const configureAzureChatCommand = vscode.commands.registerCommand('openclaude.configureAzureChat', async () => {
await configureAzureChat(context);
});
const openAzureSettingsCommand = vscode.commands.registerCommand('openclaude.openAzureSettings', async () => {
await vscode.commands.executeCommand('workbench.action.openSettings', 'openclaude.azure');
});
// ── Register providers ──
const controlCenterProviderReg = vscode.window.registerWebviewViewProvider(
'openclaude.controlCenter',
@@ -1193,6 +1389,10 @@ function activate(context) {
openChatCommand,
resumeSessionCommand,
abortChatCommand,
setAzureApiKeyCommand,
clearAzureApiKeyCommand,
configureAzureChatCommand,
openAzureSettingsCommand,
chatViewProviderReg,
diffProviderReg,
statusBarItem,
@@ -1221,7 +1421,9 @@ function activate(context) {
);
}
function deactivate() {}
function deactivate() {
extensionContext = null;
}
module.exports = {
activate,