mirror of
https://github.com/Gitlawb/openclaude.git
synced 2026-08-24 02:34:15 -05:00
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:
@@ -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 folder’s [README](vscode-extension/openclaude-vscode/README.md).
|
||||
|
||||
## Security
|
||||
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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)
|
||||
@@ -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
|
||||
~~~
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
@@ -6,8 +6,7 @@
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"args": ["--extensionDevelopmentPath=${workspaceFolder}"],
|
||||
"outFiles": ["${workspaceFolder}/out/**/*.js"],
|
||||
"preLaunchTask": "${defaultBuildTask}"
|
||||
"outFiles": ["${workspaceFolder}/out/**/*.js"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user