feat: add Azure Claude (AI Foundry) onboarding path#47181
feat: add Azure Claude (AI Foundry) onboarding path#47181LazarusStack wants to merge 1 commit intoopenclaw:mainfrom
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 371ae59ce5
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
src/plugins/provider-wizard.ts
Outdated
| const manifestRegistry = loadPluginManifestRegistry({ | ||
| config: cfg, | ||
| workspaceDir: params.workspaceDir, | ||
| env, | ||
| }); |
There was a problem hiding this comment.
Derive onboarding choices from loaded providers
resolveProviderWizardOptions now builds auth choices directly from manifest records, so it no longer verifies that the corresponding provider actually loads. In this state, plugins with valid openclaw.plugin.json but load/runtime failures are still shown, and plugins that only expose wizard metadata via registerProvider(...).wizard are omitted; both cases desynchronize the prompt from applyAuthChoiceLoadedPluginProvider, which resolves against resolvePluginProviders and can return null (silent no-op) for a selected choice.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
This file is no longer modified in this PR — no longer applicable.
src/plugins/provider-wizard.ts
Outdated
| const manifestRegistry = loadPluginManifestRegistry({ | ||
| config: cfg, | ||
| workspaceDir: params.workspaceDir, | ||
| env, | ||
| }); |
There was a problem hiding this comment.
Derive model-picker setup entries from loaded providers
resolveProviderModelPickerEntries has the same manifest-only source of truth, so model-setup options can be presented for providers that are not actually loadable at runtime. When the user selects one of these entries, promptDefaultModel resolves choices via resolvePluginProviders/resolveProviderPluginChoice and can fail to resolve the selection, causing the setup action to do nothing despite the option being offered.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
This file is no longer modified in this PR — no longer applicable.
Greptile SummaryThis PR adds Azure AI Foundry (Azure Claude) as a new auth option alongside the existing Anthropic choices, and improves gateway startup time by replacing the TypeScript-compiled plugin-loading path with a JSON-manifest fast path for resolving onboarding wizard entries. Key changes:
Issues found:
Confidence Score: 3/5
|
| const normalizedResource = raw.toLowerCase().replace(/[^a-z0-9-]/g, ""); | ||
| if (!normalizedResource) { | ||
| throw new Error("Azure Claude resource name must contain alphanumeric characters or hyphens."); | ||
| } | ||
| return `https://${normalizedResource}${ANTHROPIC_AZURE_HOST_SUFFIX}${ANTHROPIC_AZURE_API_SUFFIX}`; |
There was a problem hiding this comment.
Silent character stripping produces a wrong endpoint
Invalid characters in a resource name are silently dropped rather than rejected. For example, a user providing fabric_hub (underscore) — a very common typo for fabric-hub — gets transformed to fabrichub, which resolves to the completely different endpoint fabrichub.services.ai.azure.com. The user receives no warning and will get confusing auth errors instead of a clear input-validation message.
A safer approach is to reject the input when non-[a-z0-9-] characters remain after lowercasing:
| const normalizedResource = raw.toLowerCase().replace(/[^a-z0-9-]/g, ""); | |
| if (!normalizedResource) { | |
| throw new Error("Azure Claude resource name must contain alphanumeric characters or hyphens."); | |
| } | |
| return `https://${normalizedResource}${ANTHROPIC_AZURE_HOST_SUFFIX}${ANTHROPIC_AZURE_API_SUFFIX}`; | |
| const normalizedResource = raw.toLowerCase(); | |
| if (/[^a-z0-9-]/.test(normalizedResource)) { | |
| throw new Error( | |
| `Azure Claude resource name "${raw}" contains invalid characters. ` + | |
| "Only lowercase letters, numbers, and hyphens are allowed (e.g. fabric-hub).", | |
| ); | |
| } | |
| if (!normalizedResource) { | |
| throw new Error("Azure Claude resource name must contain alphanumeric characters or hyphens."); | |
| } | |
| return `https://${normalizedResource}${ANTHROPIC_AZURE_HOST_SUFFIX}${ANTHROPIC_AZURE_API_SUFFIX}`; |
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/commands/anthropic-azure-utils.ts
Line: 66-70
Comment:
**Silent character stripping produces a wrong endpoint**
Invalid characters in a resource name are silently dropped rather than rejected. For example, a user providing `fabric_hub` (underscore) — a very common typo for `fabric-hub` — gets transformed to `fabrichub`, which resolves to the completely different endpoint `fabrichub.services.ai.azure.com`. The user receives no warning and will get confusing auth errors instead of a clear input-validation message.
A safer approach is to reject the input when non-`[a-z0-9-]` characters remain after lowercasing:
```suggestion
const normalizedResource = raw.toLowerCase();
if (/[^a-z0-9-]/.test(normalizedResource)) {
throw new Error(
`Azure Claude resource name "${raw}" contains invalid characters. ` +
"Only lowercase letters, numbers, and hyphens are allowed (e.g. fabric-hub).",
);
}
if (!normalizedResource) {
throw new Error("Azure Claude resource name must contain alphanumeric characters or hyphens.");
}
return `https://${normalizedResource}${ANTHROPIC_AZURE_HOST_SUFFIX}${ANTHROPIC_AZURE_API_SUFFIX}`;
```
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
Fixed — resource names with invalid characters are now rejected with a descriptive error message instead of being silently stripped. Added a regression test covering fabric_hub and space-containing inputs.
src/plugins/provider-wizard.ts
Outdated
| workspaceDir?: string; | ||
| env?: NodeJS.ProcessEnv; | ||
| }): ProviderWizardOption[] { | ||
| const providers = resolvePluginProviders(params); | ||
| // Fast path: read wizard metadata from manifests (JSON only, no jiti compilation). | ||
| // Plugins that want to appear in the auth choice must declare wizard.onboarding in their | ||
| // openclaw.plugin.json manifest. This avoids blocking synchronous TypeScript compilation | ||
| // via jiti on every onboarding auth choice prompt render. | ||
| const env = params.env ?? process.env; | ||
| const cfg = params.config ?? {}; | ||
| const normalized = normalizePluginsConfig(cfg.plugins); | ||
| const manifestRegistry = loadPluginManifestRegistry({ | ||
| config: cfg, | ||
| workspaceDir: params.workspaceDir, | ||
| env, | ||
| }); | ||
| const options: ProviderWizardOption[] = []; | ||
|
|
||
| for (const provider of providers) { | ||
| const wizard = provider.wizard?.onboarding; | ||
| if (!wizard) { | ||
| for (const record of manifestRegistry.plugins) { | ||
| const onboarding = record.wizard?.onboarding; | ||
| if (!onboarding) { | ||
| continue; | ||
| } | ||
| const explicitMethod = resolveMethodById(provider, wizard.methodId); | ||
| if (explicitMethod) { | ||
| options.push( | ||
| buildOnboardingOptionForMethod({ | ||
| provider, | ||
| wizard, | ||
| method: explicitMethod, | ||
| value: resolveWizardOnboardingChoiceId(provider, wizard), | ||
| }), | ||
| ); | ||
| const enableState = resolveEffectiveEnableState({ | ||
| id: record.id, | ||
| origin: record.origin, | ||
| config: normalized, | ||
| rootConfig: cfg, | ||
| }); | ||
| if (!enableState.enabled) { | ||
| continue; | ||
| } | ||
|
|
||
| for (const method of provider.auth) { | ||
| options.push( | ||
| buildOnboardingOptionForMethod({ | ||
| provider, | ||
| wizard, | ||
| method, | ||
| value: buildProviderPluginMethodChoice(provider.id, method.id), | ||
| }), | ||
| ); | ||
| } | ||
| const choiceId = onboarding.choiceId?.trim(); | ||
| const methodId = onboarding.methodId?.trim(); | ||
| const value = choiceId | ||
| ? normalizeChoiceId(choiceId) | ||
| : methodId | ||
| ? buildProviderPluginMethodChoice(record.id, methodId) | ||
| : record.id; | ||
| const groupId = onboarding.groupId?.trim() || record.id; | ||
| options.push({ | ||
| value, |
There was a problem hiding this comment.
Breaking change for plugins that declare wizard.onboarding only in TypeScript
The old implementation called resolvePluginProviders(), which compiled and evaluated each plugin's TypeScript entry point so that provider.wizard?.onboarding could be read from the runtime object. The new fast path reads record.wizard?.onboarding exclusively from the JSON manifest, bypassing TypeScript entirely.
Any third-party (or first-party) plugin that declared wizard.onboarding only in its TypeScript entry and did not mirror it in openclaw.plugin.json will now silently disappear from the auth choice list. The bundled plugins (vllm, sglang, ollama) are updated in this very PR, but custom/third-party plugins have no migration path and no warning.
Consider adding a diagnostic entry when a plugin has a TypeScript-defined wizard.onboarding but lacks the JSON wizard field, or at minimum documenting this as a breaking change in the PR description and migration guide.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/plugins/provider-wizard.ts
Line: 71-110
Comment:
**Breaking change for plugins that declare `wizard.onboarding` only in TypeScript**
The old implementation called `resolvePluginProviders()`, which compiled and evaluated each plugin's TypeScript entry point so that `provider.wizard?.onboarding` could be read from the runtime object. The new fast path reads `record.wizard?.onboarding` exclusively from the JSON manifest, bypassing TypeScript entirely.
Any third-party (or first-party) plugin that declared `wizard.onboarding` only in its TypeScript entry and did **not** mirror it in `openclaw.plugin.json` will now silently disappear from the auth choice list. The bundled plugins (vllm, sglang, ollama) are updated in this very PR, but custom/third-party plugins have no migration path and no warning.
Consider adding a diagnostic entry when a plugin has a TypeScript-defined `wizard.onboarding` but lacks the JSON `wizard` field, or at minimum documenting this as a breaking change in the PR description and migration guide.
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
This file is no longer modified in this PR — the setup → onboarding rename was from merge commits in the old branch history, not part of this feature. The rebased branch only touches Azure-specific files.
84cc578 to
cdecee6
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: cdecee6e9f
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| }; | ||
|
|
||
| await ensureApiKeyFromOptionEnvOrPrompt({ | ||
| token: params.opts?.anthropicAzureApiKey, |
There was a problem hiding this comment.
Use generic --token input for apiKey-mapped Azure auth
When authChoice=apiKey with tokenProvider=anthropic-azure, normalizeApiKeyTokenProviderAuthChoice routes into this Azure branch, but the API key source is hardcoded to opts.anthropicAzureApiKey instead of opts.token. As a result, flows like --auth-choice apiKey --token-provider anthropic-azure --token ... ignore the supplied token and fall back to env/prompt, which breaks scripted onboarding and is inconsistent with other apiKey-mapped providers.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ddc21e48d3
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| if (params.setDefaultModel) { | ||
| nextConfig = applyAgentDefaultModelPrimary(nextConfig, `anthropic-azure/${resolvedModelId}`); | ||
| } |
There was a problem hiding this comment.
Set agent override when Azure auth skips global default
In applyAuthChoiceApiProviders, the new anthropic-azure-api-key path only applies applyAgentDefaultModelPrimary(...) when params.setDefaultModel is true and never assigns agentModelOverride otherwise. Callers that intentionally pass setDefaultModel: false (for example agent-scoped setup flows) depend on agentModelOverride to set that agent’s model, so choosing Azure Claude can silently leave the agent on its previous provider/model despite writing Azure auth/config, leading to model/auth mismatches at runtime.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed — replaced the manual applyAgentDefaultModelPrimary call with applyProviderDefaultModel() (the shared helper from createAuthChoiceDefaultModelApplierForMutableState). This correctly sets agentModelOverride for agent-scoped flows when setDefaultModel is false, matching the pattern used by other providers (e.g. LiteLLM).
CI Status UpdateAll failing CI checks match current
Changes made after review feedback
|
81568e5 to
4c78be2
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4c78be2473
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| const normalizedPath = basePath.endsWith(ANTHROPIC_AZURE_API_SUFFIX) | ||
| ? basePath | ||
| : `${basePath}${ANTHROPIC_AZURE_API_SUFFIX}`; |
There was a problem hiding this comment.
Reject invalid Azure endpoint paths during URL normalization
normalizeAnthropicAzureBaseUrl currently appends "/anthropic" to any existing path that does not already end with that suffix, so inputs like https://<resource>.services.ai.azure.com/anthropic/v1 are normalized to .../anthropic/v1/anthropic and then saved into provider config. That produces a malformed base URL which will make subsequent Anthropic requests fail at runtime instead of failing fast during onboarding; the normalizer should reject unexpected path segments (or explicitly canonicalize known variants) rather than concatenating blindly.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 548fb1c1ef
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| if (!url.hostname.toLowerCase().endsWith(ANTHROPIC_AZURE_HOST_SUFFIX)) { | ||
| throw new Error( | ||
| `Azure Claude endpoint host must end with "${ANTHROPIC_AZURE_HOST_SUFFIX}". Received ${url.hostname}.`, |
There was a problem hiding this comment.
Reject invalid Azure resource hosts in URL input
When users pass a full URL, normalization only checks that the hostname ends with .services.ai.azure.com, so invalid resource labels like fabric_hub.services.ai.azure.com are accepted even though the resource-name path rejects underscores. This stores an endpoint that cannot be resolved by Azure and pushes the failure to runtime requests instead of failing fast during onboarding; the URL branch should validate the resource label with the same character rules used for bare resource names.
Useful? React with 👍 / 👎.
Add Azure Claude as a provider option in onboarding, supporting: - Interactive and non-interactive auth flows with API key, base URL, and model selection - Resource name validation that rejects invalid characters with clear error messages - Lazy-loaded Azure utils to avoid startup memory impact - CLI flags: --anthropic-azure-api-key, --anthropic-azure-base-url, --anthropic-azure-model-id - Environment variable support: ANTHROPIC_FOUNDRY_API_KEY, AZURE_CLAUDE_API_KEY, ANTHROPIC_FOUNDRY_BASE_URL, AZURE_CLAUDE_BASE_URL, ANTHROPIC_FOUNDRY_RESOURCE, AZURE_CLAUDE_RESOURCE Also fixes ChannelMessageCapability import in contracts/suites.ts (pre-existing upstream type error).
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9b06ad46bb
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| const normalizedPath = basePath.endsWith(ANTHROPIC_AZURE_API_SUFFIX) | ||
| ? basePath | ||
| : `${basePath}${ANTHROPIC_AZURE_API_SUFFIX}`; | ||
| const finalPath = normalizedPath || ANTHROPIC_AZURE_API_SUFFIX; |
There was a problem hiding this comment.
Reject unsupported Azure endpoint paths
When the input is a full URL, the normalizer appends "/anthropic" to any pathname that does not already end with that suffix, so values like https://<resource>.services.ai.azure.com/anthropic/v1 become .../anthropic/v1/anthropic. That produces a malformed provider baseUrl and shifts the failure to runtime requests instead of failing fast during onboarding.
Useful? React with 👍 / 👎.
| if (!url.hostname.toLowerCase().endsWith(ANTHROPIC_AZURE_HOST_SUFFIX)) { | ||
| throw new Error( | ||
| `Azure Claude endpoint host must end with "${ANTHROPIC_AZURE_HOST_SUFFIX}". Received ${url.hostname}.`, | ||
| ); |
There was a problem hiding this comment.
Validate Azure resource label in URL host
The URL branch only checks endsWith(".services.ai.azure.com"), so hosts with invalid resource labels (for example underscores) pass validation and are persisted as endpoints. This allows clearly invalid Azure resource hosts to be accepted in onboarding and fail later at request time rather than being rejected immediately.
Useful? React with 👍 / 👎.
Summary
anthropic-azure-api-keyas a new auth choice with interactive and non-interactive flows (resource/URL prompt, model picker, API key storage, config wiring).Change Type (select all)
Scope (select all touched areas)
User-visible / Behavior Changes
anthropic-azure-api-keyappears in the onboarding provider picker under the Anthropic group--anthropic-azure-api-key,--anthropic-azure-base-url,--anthropic-azure-model-idANTHROPIC_FOUNDRY_API_KEY,AZURE_CLAUDE_API_KEY,ANTHROPIC_FOUNDRY_BASE_URL,AZURE_CLAUDE_BASE_URL,ANTHROPIC_FOUNDRY_RESOURCE,AZURE_CLAUDE_RESOURCEfabric_hubwith underscores) are rejected with a clear error instead of silently strippedSecurity Impact (required)
NoYes— newsetAnthropicAzureApiKeystores Azure API keys using the existing auth profile credential infrastructure (same pattern as all other providers)NoNoNoSecretInputandAuthProfileStoreinfrastructure as existing providers; no new storage mechanisms.Repro + Verification
Steps
Evidence
src/commands/anthropic-azure-utils.test.ts)src/commands/onboard-non-interactive.provider-auth.test.ts)Human Verification (required)
Review Conversations
Compatibility / Migration
YesYes— new optional env vars and config keys (additive only)NoFailure Recovery (if this breaks)
Risks and Mitigations
anthropic-azure)