diff --git a/docs/concepts/oauth.md b/docs/concepts/oauth.md index 784e54271698..a25ee5b0144e 100644 --- a/docs/concepts/oauth.md +++ b/docs/concepts/oauth.md @@ -3,7 +3,7 @@ summary: "OAuth in OpenClaw: token exchange, storage, and multi-account patterns read_when: - You want to understand OpenClaw OAuth end-to-end - You hit token invalidation / logout issues - - You want setup-token or OAuth auth flows + - You want setup-token, Claude CLI, or OAuth auth flows - You want multiple accounts or profile routing title: "OAuth" --- @@ -153,10 +153,14 @@ Claude CLI path: 3. store no new auth profile; switch model selection to `claude-cli/...` 4. keep existing Anthropic auth profiles for rollback -Wizard paths: +Interactive assistant path: -- `openclaw onboard` → auth choice `anthropic-cli` -- `openclaw onboard` → auth choice `setup-token` (Anthropic) +- `openclaw onboard` / `openclaw configure` → auth choice `anthropic-cli` + +Manual setup-token path: + +- `openclaw models auth setup-token --provider anthropic` +- `openclaw models auth paste-token --provider anthropic` ### OpenAI Codex (ChatGPT OAuth) diff --git a/docs/gateway/authentication.md b/docs/gateway/authentication.md index ad055ee4b9b7..a542b31e5008 100644 --- a/docs/gateway/authentication.md +++ b/docs/gateway/authentication.md @@ -158,6 +158,10 @@ Onboarding shortcut: openclaw onboard --auth-choice anthropic-cli ``` +Interactive `openclaw onboard` and `openclaw configure` prefer Claude CLI for +Anthropic and do not show setup-token in the assistant picker. Setup-token +remains supported through the manual commands above. + ## Checking model auth status ```bash diff --git a/docs/help/faq.md b/docs/help/faq.md index ade10109b30c..ecb7381cc55a 100644 --- a/docs/help/faq.md +++ b/docs/help/faq.md @@ -590,7 +590,7 @@ Quick answers plus deeper troubleshooting for real-world setups (local dev, VPS, - `claude setup-token` generates a **token string** via the Claude Code CLI (it is not available in the web console). You can run it on **any machine**. Choose **Anthropic token (paste setup-token)** in onboarding or paste it with `openclaw models auth paste-token --provider anthropic`. The token is stored as an auth profile for the **anthropic** provider and used like an API key (no auto-refresh). More detail: [OAuth](/concepts/oauth). + `claude setup-token` generates a **token string** via the Claude Code CLI (it is not available in the web console). You can run it on **any machine**. Interactive onboarding/configure no longer shows setup-token as an assistant choice; use `openclaw models auth setup-token --provider anthropic` or paste an existing token with `openclaw models auth paste-token --provider anthropic`. The token is stored as an auth profile for the **anthropic** provider and used like an API key (no auto-refresh). More detail: [OAuth](/concepts/oauth). @@ -600,17 +600,17 @@ Quick answers plus deeper troubleshooting for real-world setups (local dev, VPS, claude setup-token ``` - Copy the token it prints, then choose **Anthropic token (paste setup-token)** in onboarding. If you want to run it on the gateway host, use `openclaw models auth setup-token --provider anthropic`. If you ran `claude setup-token` elsewhere, paste it on the gateway host with `openclaw models auth paste-token --provider anthropic`. See [Anthropic](/providers/anthropic). + Copy the token it prints, then either run `openclaw models auth setup-token --provider anthropic` on the gateway host or paste it there with `openclaw models auth paste-token --provider anthropic`. See [Anthropic](/providers/anthropic). Yes. You can either: - - use a **setup-token** - reuse a local **Claude CLI** login on the gateway host with `openclaw models auth login --provider anthropic --method cli --set-default` + - use a **setup-token** manually with `openclaw models auth setup-token --provider anthropic` - Setup-token is still supported. Claude CLI migration is simpler when the gateway host already runs Claude Code. See [Anthropic](/providers/anthropic) and [OAuth](/concepts/oauth). + Claude CLI is the preferred interactive Anthropic path. Setup-token is still supported for manual config. See [Anthropic](/providers/anthropic) and [OAuth](/concepts/oauth). Important: Anthropic changed third-party harness billing on **April 4, 2026 at 12:00 PM PT / 8:00 PM BST**. Anthropic says Claude subscription limits no diff --git a/docs/providers/anthropic.md b/docs/providers/anthropic.md index fdf8ae1730cf..fe80339c3023 100644 --- a/docs/providers/anthropic.md +++ b/docs/providers/anthropic.md @@ -305,6 +305,11 @@ Or in onboarding: openclaw onboard --auth-choice anthropic-cli ``` +Interactive `openclaw onboard` and `openclaw configure` now prefer **Anthropic +Claude CLI** first and **Anthropic API key** second. The setup-token flow +remains supported through manual auth commands, but is not shown in the +assistant picker. + What this does: - verifies Claude CLI is already signed in on the gateway host @@ -339,7 +344,7 @@ you need to. More details: [/gateway/cli-backends](/gateway/cli-backends) -## Option C: Claude setup-token +## Option C: Claude setup-token (manual) **Best for:** using your Claude subscription with Anthropic **Extra Usage** enabled, or while transitioning to API-key billing. @@ -352,25 +357,18 @@ Setup-tokens are created by the **Claude Code CLI**, not the Anthropic Console. claude setup-token ``` -Paste the token into OpenClaw (wizard: **Anthropic token (paste setup-token)**), or run it on the gateway host: +Then either run it on the gateway host: ```bash openclaw models auth setup-token --provider anthropic ``` -If you generated the token on a different machine, paste it: +Or if you generated the token on a different machine, paste it: ```bash openclaw models auth paste-token --provider anthropic ``` -### CLI setup (setup-token) - -```bash -# Paste a setup-token during setup -openclaw onboard --auth-choice setup-token -``` - ### Config snippet (setup-token) ```json5 diff --git a/docs/reference/wizard.md b/docs/reference/wizard.md index 6854d092009b..356919470ab4 100644 --- a/docs/reference/wizard.md +++ b/docs/reference/wizard.md @@ -31,8 +31,8 @@ For a high-level overview, see [Onboarding (CLI)](/start/wizard). - **Anthropic API key**: uses `ANTHROPIC_API_KEY` if present or prompts for a key, then saves it for daemon use. - - **Anthropic Claude CLI**: on macOS onboarding checks Keychain item "Claude Code-credentials" (choose "Always Allow" so launchd starts don't block); on Linux/Windows it reuses `~/.claude/.credentials.json` if present and switches model selection to `claude-cli/...`. - - **Anthropic token (paste setup-token)**: run `claude setup-token` on any machine, then paste the token (you can name it; blank = default). + - **Anthropic Claude CLI**: preferred Anthropic assistant choice in onboarding/configure. On macOS onboarding checks Keychain item "Claude Code-credentials" (choose "Always Allow" so launchd starts don't block); on Linux/Windows it reuses `~/.claude/.credentials.json` if present and switches model selection to `claude-cli/...`. + - **Anthropic setup-token**: still supported as a manual auth flow with `openclaw models auth setup-token --provider anthropic` or `openclaw models auth paste-token --provider anthropic`, but no longer shown in the interactive assistant picker. - **OpenAI Code (Codex) subscription (Codex CLI)**: if `~/.codex/auth.json` exists, onboarding can reuse it. Reused Codex CLI credentials stay managed by Codex CLI; OpenClaw re-reads that source on expiry instead of rotating the copied refresh token itself. - **OpenAI Code (Codex) subscription (OAuth)**: browser flow; paste the `code#state`. - Sets `agents.defaults.model` to `openai-codex/gpt-5.2` when model is unset or `openai/*`. diff --git a/docs/start/wizard-cli-automation.md b/docs/start/wizard-cli-automation.md index f373f3d4bc60..04ac02763910 100644 --- a/docs/start/wizard-cli-automation.md +++ b/docs/start/wizard-cli-automation.md @@ -51,6 +51,19 @@ openclaw onboard --non-interactive \ ## Provider-specific examples + + ```bash + openclaw onboard --non-interactive \ + --mode local \ + --auth-choice anthropic-cli \ + --gateway-port 18789 \ + --gateway-bind loopback + ``` + + Requires Claude CLI already installed and signed in on the same gateway + host. + + ```bash openclaw onboard --non-interactive \ @@ -182,6 +195,11 @@ openclaw onboard --non-interactive \ +Anthropic setup-token remains supported for manual flows, but interactive +onboarding/configure no longer offers it as an assistant choice. Use +`openclaw models auth setup-token --provider anthropic` or +`openclaw models auth paste-token --provider anthropic` when you need it. + ## Add another agent Use `openclaw agents add ` to create a separate agent with its own workspace, diff --git a/docs/start/wizard-cli-reference.md b/docs/start/wizard-cli-reference.md index 016553721fd3..b75b6861b40e 100644 --- a/docs/start/wizard-cli-reference.md +++ b/docs/start/wizard-cli-reference.md @@ -16,7 +16,7 @@ For the short guide, see [Onboarding (CLI)](/start/wizard). Local mode (default) walks you through: -- Model and auth setup (OpenAI Code subscription OAuth, Anthropic API key or setup token, plus MiniMax, GLM, Ollama, Moonshot, StepFun, and AI Gateway options) +- Model and auth setup (OpenAI Code subscription OAuth, Anthropic Claude CLI or API key, plus MiniMax, GLM, Ollama, Moonshot, StepFun, and AI Gateway options) - Workspace location and bootstrap files - Gateway settings (port, bind, auth, tailscale) - Channels and providers (Telegram, WhatsApp, Discord, Google Chat, Mattermost plugin, Signal) @@ -130,15 +130,22 @@ What you set: Reuses a local Claude CLI login on the gateway host and switches model selection to `claude-cli/...`. + This is the preferred interactive Anthropic path in `openclaw onboard` and + `openclaw configure`. + - macOS: checks Keychain item "Claude Code-credentials" - Linux and Windows: reuses `~/.claude/.credentials.json` if present On macOS, choose "Always Allow" so launchd starts do not block. - - Run `claude setup-token` on any machine, then paste the token. - You can name it; blank uses default. + + Supported for manual config, not shown as an interactive assistant choice. + + - Generate on any machine: `claude setup-token` + - Run on the gateway host: `openclaw models auth setup-token --provider anthropic` + - Or paste an existing token: `openclaw models auth paste-token --provider anthropic` + If `~/.codex/auth.json` exists, the wizard can reuse it. diff --git a/docs/start/wizard.md b/docs/start/wizard.md index a7db4d9b0e93..9a6799382e6e 100644 --- a/docs/start/wizard.md +++ b/docs/start/wizard.md @@ -66,12 +66,13 @@ Onboarding starts with **QuickStart** (defaults) vs **Advanced** (full control). **Local mode (default)** walks you through these steps: -1. **Model/Auth** — choose any supported provider/auth flow (API key, OAuth, or setup-token), including Custom Provider +1. **Model/Auth** — choose any supported provider/auth flow (API key, OAuth, or provider-specific manual auth), including Custom Provider (OpenAI-compatible, Anthropic-compatible, or Unknown auto-detect). Pick a default model. Security note: if this agent will run tools or process webhook/hooks content, prefer the strongest latest-generation model available and keep tool policy strict. Weaker/older tiers are easier to prompt-inject. For non-interactive runs, `--secret-input-mode ref` stores env-backed refs in auth profiles instead of plaintext API key values. In non-interactive `ref` mode, the provider env var must be set; passing inline key flags without that env var fails fast. In interactive runs, choosing secret reference mode lets you point at either an environment variable or a configured provider ref (`file` or `exec`), with a fast preflight validation before saving. + For Anthropic, interactive onboarding/configure prefers **Anthropic Claude CLI** first, then **Anthropic API key**. The **setup-token** flow remains supported through manual auth commands. 2. **Workspace** — Location for agent files (default `~/.openclaw/workspace`). Seeds bootstrap files. 3. **Gateway** — Port, bind address, auth mode, Tailscale exposure. In interactive token mode, choose default plaintext token storage or opt into SecretRef. diff --git a/extensions/anthropic/index.ts b/extensions/anthropic/index.ts index 3d645bc38ebd..741f51f90c56 100644 --- a/extensions/anthropic/index.ts +++ b/extensions/anthropic/index.ts @@ -382,6 +382,7 @@ export default definePluginEntry({ choiceId: "anthropic-cli", choiceLabel: "Anthropic Claude CLI", choiceHint: "Reuse a local Claude CLI login on this host", + assistantPriority: -20, groupId: "anthropic", groupLabel: "Anthropic", groupHint: "Claude CLI + setup-token + API key", @@ -409,6 +410,7 @@ export default definePluginEntry({ choiceId: "token", choiceLabel: "Anthropic token (paste setup-token)", choiceHint: "Run `claude setup-token` elsewhere, then paste the token here", + assistantVisibility: "manual-only", groupId: "anthropic", groupLabel: "Anthropic", groupHint: "Claude CLI + setup-token + API key", diff --git a/extensions/anthropic/openclaw.plugin.json b/extensions/anthropic/openclaw.plugin.json index 21af2607d39b..85b919986928 100644 --- a/extensions/anthropic/openclaw.plugin.json +++ b/extensions/anthropic/openclaw.plugin.json @@ -17,6 +17,7 @@ "deprecatedChoiceIds": ["claude-cli"], "choiceLabel": "Anthropic Claude CLI", "choiceHint": "Reuse a local Claude CLI login on this host", + "assistantPriority": -20, "groupId": "anthropic", "groupLabel": "Anthropic", "groupHint": "Claude CLI + setup-token + API key" @@ -27,6 +28,7 @@ "choiceId": "token", "choiceLabel": "Anthropic token (paste setup-token)", "choiceHint": "Run `claude setup-token` elsewhere, then paste the token here", + "assistantVisibility": "manual-only", "groupId": "anthropic", "groupLabel": "Anthropic", "groupHint": "Claude CLI + setup-token + API key" diff --git a/src/commands/auth-choice-options.static.ts b/src/commands/auth-choice-options.static.ts index 73bf6d48d6e8..e4980811b9b7 100644 --- a/src/commands/auth-choice-options.static.ts +++ b/src/commands/auth-choice-options.static.ts @@ -1,5 +1,5 @@ -import { resolveLegacyAuthChoiceAliasesForCli } from "./auth-choice-legacy.js"; import type { AuthChoice, AuthChoiceGroupId } from "./onboard-types.js"; +import { resolveLegacyAuthChoiceAliasesForCli } from "./auth-choice-legacy.js"; export type { AuthChoiceGroupId }; @@ -10,6 +10,8 @@ export type AuthChoiceOption = { groupId?: AuthChoiceGroupId; groupLabel?: string; groupHint?: string; + assistantPriority?: number; + assistantVisibility?: "visible" | "manual-only"; }; export type AuthChoiceGroup = { diff --git a/src/commands/auth-choice-options.test.ts b/src/commands/auth-choice-options.test.ts index 0cc771f19e10..453344cccf2d 100644 --- a/src/commands/auth-choice-options.test.ts +++ b/src/commands/auth-choice-options.test.ts @@ -378,6 +378,51 @@ describe("buildAuthChoiceOptions", () => { expect(litellmGroup?.options.some((opt) => opt.value === "litellm-api-key")).toBe(true); }); + it("prefers Anthropic Claude CLI over API key and hides manual-only setup-token in grouped selection", () => { + resolveManifestProviderAuthChoices.mockReturnValue([ + { + pluginId: "anthropic", + providerId: "anthropic", + methodId: "api-key", + choiceId: "apiKey", + choiceLabel: "Anthropic API key", + groupId: "anthropic", + groupLabel: "Anthropic", + }, + { + pluginId: "anthropic", + providerId: "anthropic", + methodId: "cli", + choiceId: "anthropic-cli", + choiceLabel: "Anthropic Claude CLI", + assistantPriority: -20, + groupId: "anthropic", + groupLabel: "Anthropic", + }, + { + pluginId: "anthropic", + providerId: "anthropic", + methodId: "setup-token", + choiceId: "token", + choiceLabel: "Anthropic token (paste setup-token)", + assistantVisibility: "manual-only", + groupId: "anthropic", + groupLabel: "Anthropic", + }, + ]); + const { groups } = buildAuthChoiceGroups({ + store: EMPTY_STORE, + includeSkip: false, + }); + const anthropicGroup = groups.find((group) => group.value === "anthropic"); + + expect(anthropicGroup).toBeDefined(); + expect(anthropicGroup?.options.map((option) => option.value)).toEqual([ + "anthropic-cli", + "apiKey", + ]); + }); + it("groups OpenCode Zen and Go under one OpenCode entry", () => { resolveManifestProviderAuthChoices.mockReturnValue([ { diff --git a/src/commands/auth-choice-options.ts b/src/commands/auth-choice-options.ts index 4fd6c47b0775..8bee688cc4f4 100644 --- a/src/commands/auth-choice-options.ts +++ b/src/commands/auth-choice-options.ts @@ -1,5 +1,6 @@ import type { AuthProfileStore } from "../agents/auth-profiles.js"; import type { OpenClawConfig } from "../config/config.js"; +import type { AuthChoice, AuthChoiceGroupId } from "./onboard-types.js"; import { resolveManifestProviderSetupFlowContributions, resolveProviderSetupFlowContributions, @@ -10,12 +11,17 @@ import { type AuthChoiceOption, formatStaticAuthChoiceChoicesForCli, } from "./auth-choice-options.static.js"; -import type { AuthChoice, AuthChoiceGroupId } from "./onboard-types.js"; function compareOptionLabels(a: AuthChoiceOption, b: AuthChoiceOption): number { return a.label.localeCompare(b.label); } +function compareAssistantOptions(a: AuthChoiceOption, b: AuthChoiceOption): number { + const priorityA = a.assistantPriority ?? 0; + const priorityB = b.assistantPriority ?? 0; + return priorityA - priorityB || compareOptionLabels(a, b); +} + function compareGroupLabels(a: AuthChoiceGroup, b: AuthChoiceGroup): number { return a.label.localeCompare(b.label); } @@ -63,6 +69,7 @@ export function formatAuthChoiceChoicesForCli(params?: { export function buildAuthChoiceOptions(params: { store: AuthProfileStore; includeSkip: boolean; + assistantVisibleOnly?: boolean; config?: OpenClawConfig; workspaceDir?: string; env?: NodeJS.ProcessEnv; @@ -80,9 +87,11 @@ export function buildAuthChoiceOptions(params: { optionByValue.set(option.value, option); } - const options: AuthChoiceOption[] = Array.from(optionByValue.values()).toSorted( - compareOptionLabels, - ); + const options: AuthChoiceOption[] = Array.from(optionByValue.values()) + .toSorted(compareOptionLabels) + .filter((option) => + params.assistantVisibleOnly ? option.assistantVisibility !== "manual-only" : true, + ); if (params.includeSkip) { options.push({ value: "skip", label: "Skip for now" }); @@ -104,6 +113,7 @@ export function buildAuthChoiceGroups(params: { const options = buildAuthChoiceOptions({ ...params, includeSkip: false, + assistantVisibleOnly: true, }); const groupsById = new Map(); @@ -126,7 +136,7 @@ export function buildAuthChoiceGroups(params: { const groups = Array.from(groupsById.values()) .map((group) => ({ ...group, - options: [...group.options].toSorted(compareOptionLabels), + options: [...group.options].toSorted(compareAssistantOptions), })) .toSorted(compareGroupLabels); diff --git a/src/flows/provider-flow.ts b/src/flows/provider-flow.ts index 676d61f52e17..31724a4f33d0 100644 --- a/src/flows/provider-flow.ts +++ b/src/flows/provider-flow.ts @@ -1,12 +1,12 @@ import type { OpenClawConfig } from "../config/config.js"; +import type { ProviderPlugin } from "../plugins/types.js"; +import type { FlowContribution, FlowOption } from "./types.js"; import { resolveManifestProviderAuthChoices } from "../plugins/provider-auth-choices.js"; import { resolveProviderModelPickerEntries, resolveProviderWizardOptions, } from "../plugins/provider-wizard.js"; import { resolvePluginProviders } from "../plugins/providers.runtime.js"; -import type { ProviderPlugin } from "../plugins/types.js"; -import type { FlowContribution, FlowOption } from "./types.js"; import { mergeFlowContributions, sortFlowContributionsByLabel } from "./types.js"; export type ProviderFlowScope = "text-inference" | "image-generation"; @@ -95,6 +95,10 @@ export function resolveManifestProviderSetupFlowContributions(params?: { value: choice.choiceId, label: choice.choiceLabel, ...(choice.choiceHint ? { hint: choice.choiceHint } : {}), + ...(choice.assistantPriority !== undefined + ? { assistantPriority: choice.assistantPriority } + : {}), + ...(choice.assistantVisibility ? { assistantVisibility: choice.assistantVisibility } : {}), ...(choice.groupId && choice.groupLabel ? { group: { @@ -142,6 +146,10 @@ export function resolveRuntimeFallbackProviderSetupFlowContributions(params?: { value: option.value, label: option.label, ...(option.hint ? { hint: option.hint } : {}), + ...(option.assistantPriority !== undefined + ? { assistantPriority: option.assistantPriority } + : {}), + ...(option.assistantVisibility ? { assistantVisibility: option.assistantVisibility } : {}), group: { id: option.groupId, label: option.groupLabel, diff --git a/src/flows/types.ts b/src/flows/types.ts index dd36792d7960..30f373a0aad2 100644 --- a/src/flows/types.ts +++ b/src/flows/types.ts @@ -19,6 +19,8 @@ export type FlowOption = { hint?: string; group?: FlowOptionGroup; docs?: FlowDocsLink; + assistantPriority?: number; + assistantVisibility?: "visible" | "manual-only"; }; export type FlowContribution = { diff --git a/src/plugins/manifest-registry.test.ts b/src/plugins/manifest-registry.test.ts index f9a81a6bcd1c..b6a0350bf858 100644 --- a/src/plugins/manifest-registry.test.ts +++ b/src/plugins/manifest-registry.test.ts @@ -2,11 +2,11 @@ import fs from "node:fs"; import path from "node:path"; import { afterEach, describe, expect, it, vi } from "vitest"; import type { PluginCandidate } from "./discovery.js"; +import type { OpenClawPackageManifest } from "./manifest.js"; import { clearPluginManifestRegistryCache, loadPluginManifestRegistry, } from "./manifest-registry.js"; -import type { OpenClawPackageManifest } from "./manifest.js"; import { cleanupTrackedTempDirs, makeTrackedTempDir } from "./test-helpers/fs-fixtures.js"; vi.unmock("../version.js"); @@ -389,6 +389,8 @@ describe("loadPluginManifestRegistry", () => { method: "api-key", choiceId: "openai-api-key", choiceLabel: "OpenAI API key", + assistantPriority: 10, + assistantVisibility: "visible", }, ], configSchema: { type: "object" }, @@ -411,6 +413,8 @@ describe("loadPluginManifestRegistry", () => { method: "api-key", choiceId: "openai-api-key", choiceLabel: "OpenAI API key", + assistantPriority: 10, + assistantVisibility: "visible", }, ]); }); diff --git a/src/plugins/manifest.ts b/src/plugins/manifest.ts index 33ef17e3aac8..ee949972b98f 100644 --- a/src/plugins/manifest.ts +++ b/src/plugins/manifest.ts @@ -1,11 +1,11 @@ +import JSON5 from "json5"; import fs from "node:fs"; import path from "node:path"; -import JSON5 from "json5"; import type { ChannelConfigRuntimeSchema } from "../channels/plugins/types.plugin.js"; +import type { PluginConfigUiHint, PluginKind } from "./types.js"; import { MANIFEST_KEY } from "../compat/legacy-names.js"; import { matchBoundaryFileOpenFailure, openBoundaryFileSync } from "../infra/boundary-file-read.js"; import { isRecord } from "../utils.js"; -import type { PluginConfigUiHint, PluginKind } from "./types.js"; export const PLUGIN_MANIFEST_FILENAME = "openclaw.plugin.json"; export const PLUGIN_MANIFEST_FILENAMES = [PLUGIN_MANIFEST_FILENAME] as const; @@ -91,6 +91,10 @@ export type PluginManifestProviderAuthChoice = { /** Optional user-facing choice label/hint for grouped onboarding UI. */ choiceLabel?: string; choiceHint?: string; + /** Lower values sort earlier in interactive assistant pickers. */ + assistantPriority?: number; + /** Keep the choice out of interactive assistant pickers while preserving manual CLI support. */ + assistantVisibility?: "visible" | "manual-only"; /** Legacy choice ids that should point users at this replacement choice. */ deprecatedChoiceIds?: string[]; /** Optional grouping metadata for auth-choice pickers. */ @@ -202,6 +206,14 @@ function normalizeProviderAuthChoices( } const choiceLabel = typeof entry.choiceLabel === "string" ? entry.choiceLabel.trim() : ""; const choiceHint = typeof entry.choiceHint === "string" ? entry.choiceHint.trim() : ""; + const assistantPriority = + typeof entry.assistantPriority === "number" && Number.isFinite(entry.assistantPriority) + ? entry.assistantPriority + : undefined; + const assistantVisibility = + entry.assistantVisibility === "manual-only" || entry.assistantVisibility === "visible" + ? entry.assistantVisibility + : undefined; const deprecatedChoiceIds = normalizeStringList(entry.deprecatedChoiceIds); const groupId = typeof entry.groupId === "string" ? entry.groupId.trim() : ""; const groupLabel = typeof entry.groupLabel === "string" ? entry.groupLabel.trim() : ""; @@ -221,6 +233,8 @@ function normalizeProviderAuthChoices( choiceId, ...(choiceLabel ? { choiceLabel } : {}), ...(choiceHint ? { choiceHint } : {}), + ...(assistantPriority !== undefined ? { assistantPriority } : {}), + ...(assistantVisibility ? { assistantVisibility } : {}), ...(deprecatedChoiceIds.length > 0 ? { deprecatedChoiceIds } : {}), ...(groupId ? { groupId } : {}), ...(groupLabel ? { groupLabel } : {}), @@ -320,7 +334,7 @@ export function loadPluginManifest( } let raw: unknown; try { - raw = JSON5.parse(fs.readFileSync(opened.fd, "utf-8")) as unknown; + raw = JSON5.parse(fs.readFileSync(opened.fd, "utf-8")); } catch (err) { return { ok: false, diff --git a/src/plugins/provider-auth-choices.test.ts b/src/plugins/provider-auth-choices.test.ts index 066017905ddb..a25f00285621 100644 --- a/src/plugins/provider-auth-choices.test.ts +++ b/src/plugins/provider-auth-choices.test.ts @@ -59,6 +59,8 @@ describe("provider auth choice manifest helpers", () => { method: "api-key", choiceId: "openai-api-key", choiceLabel: "OpenAI API key", + assistantPriority: 10, + assistantVisibility: "visible", onboardingScopes: ["text-inference"], optionKey: "openaiApiKey", cliFlag: "--openai-api-key", @@ -74,6 +76,8 @@ describe("provider auth choice manifest helpers", () => { methodId: "api-key", choiceId: "openai-api-key", choiceLabel: "OpenAI API key", + assistantPriority: 10, + assistantVisibility: "visible", onboardingScopes: ["text-inference"], optionKey: "openaiApiKey", cliFlag: "--openai-api-key", diff --git a/src/plugins/provider-auth-choices.ts b/src/plugins/provider-auth-choices.ts index 58f5ad25daa3..c751acb2032f 100644 --- a/src/plugins/provider-auth-choices.ts +++ b/src/plugins/provider-auth-choices.ts @@ -1,5 +1,5 @@ -import { normalizeProviderIdForAuth } from "../agents/model-selection.js"; import type { OpenClawConfig } from "../config/config.js"; +import { normalizeProviderIdForAuth } from "../agents/model-selection.js"; import { normalizePluginsConfig, resolveEffectiveEnableState } from "./config-state.js"; import { loadPluginManifestRegistry } from "./manifest-registry.js"; @@ -10,6 +10,8 @@ export type ProviderAuthChoiceMetadata = { choiceId: string; choiceLabel: string; choiceHint?: string; + assistantPriority?: number; + assistantVisibility?: "visible" | "manual-only"; deprecatedChoiceIds?: string[]; groupId?: string; groupLabel?: string; @@ -59,6 +61,12 @@ export function resolveManifestProviderAuthChoices(params?: { choiceId: choice.choiceId, choiceLabel: choice.choiceLabel ?? choice.choiceId, ...(choice.choiceHint ? { choiceHint: choice.choiceHint } : {}), + ...(choice.assistantPriority !== undefined + ? { assistantPriority: choice.assistantPriority } + : {}), + ...(choice.assistantVisibility + ? { assistantVisibility: choice.assistantVisibility } + : {}), ...(choice.deprecatedChoiceIds ? { deprecatedChoiceIds: choice.deprecatedChoiceIds } : {}), diff --git a/src/plugins/provider-validation.ts b/src/plugins/provider-validation.ts index 28f4e1671bef..b5f15bb16ac3 100644 --- a/src/plugins/provider-validation.ts +++ b/src/plugins/provider-validation.ts @@ -105,6 +105,14 @@ function normalizeProviderWizardSetup(params: { ...(normalizeText(params.setup.choiceHint) ? { choiceHint: normalizeText(params.setup.choiceHint) } : {}), + ...(typeof params.setup.assistantPriority === "number" && + Number.isFinite(params.setup.assistantPriority) + ? { assistantPriority: params.setup.assistantPriority } + : {}), + ...(params.setup.assistantVisibility === "manual-only" || + params.setup.assistantVisibility === "visible" + ? { assistantVisibility: params.setup.assistantVisibility } + : {}), ...(normalizeText(params.setup.groupId) ? { groupId: normalizeText(params.setup.groupId) } : {}), diff --git a/src/plugins/provider-wizard.ts b/src/plugins/provider-wizard.ts index 07b78bc44faa..a4271e6455c9 100644 --- a/src/plugins/provider-wizard.ts +++ b/src/plugins/provider-wizard.ts @@ -1,19 +1,19 @@ -import { DEFAULT_PROVIDER } from "../agents/defaults.js"; -import { normalizeProviderId } from "../agents/model-selection.js"; import type { OpenClawConfig } from "../config/config.js"; import type { WizardPrompter } from "../wizard/prompts.js"; -import { - buildPluginSnapshotCacheEnvKey, - resolvePluginSnapshotCacheTtlMs, - shouldUsePluginSnapshotCache, -} from "./cache-controls.js"; -import { resolvePluginProviders } from "./providers.runtime.js"; import type { ProviderAuthMethod, ProviderPlugin, ProviderPluginWizardModelPicker, ProviderPluginWizardSetup, } from "./types.js"; +import { DEFAULT_PROVIDER } from "../agents/defaults.js"; +import { normalizeProviderId } from "../agents/model-selection.js"; +import { + buildPluginSnapshotCacheEnvKey, + resolvePluginSnapshotCacheTtlMs, + shouldUsePluginSnapshotCache, +} from "./cache-controls.js"; +import { resolvePluginProviders } from "./providers.runtime.js"; export const PROVIDER_PLUGIN_CHOICE_PREFIX = "provider-plugin:"; type ProviderWizardCacheEntry = { @@ -45,6 +45,8 @@ export type ProviderWizardOption = { groupLabel: string; groupHint?: string; onboardingScopes?: Array<"text-inference" | "image-generation">; + assistantPriority?: number; + assistantVisibility?: "visible" | "manual-only"; }; export type ProviderModelPickerEntry = { @@ -114,6 +116,13 @@ function buildSetupOptionForMethod(params: { groupLabel: params.wizard.groupLabel?.trim() || params.provider.label, groupHint: params.wizard.groupHint?.trim(), ...(params.wizard.onboardingScopes ? { onboardingScopes: params.wizard.onboardingScopes } : {}), + ...(typeof params.wizard.assistantPriority === "number" && + Number.isFinite(params.wizard.assistantPriority) + ? { assistantPriority: params.wizard.assistantPriority } + : {}), + ...(params.wizard.assistantVisibility + ? { assistantVisibility: params.wizard.assistantVisibility } + : {}), }; } diff --git a/src/plugins/types.ts b/src/plugins/types.ts index b7776c92c0c3..8b760cd753bb 100644 --- a/src/plugins/types.ts +++ b/src/plugins/types.ts @@ -1,9 +1,9 @@ -import type { IncomingMessage, ServerResponse } from "node:http"; import type { AgentMessage } from "@mariozechner/pi-agent-core"; import type { StreamFn } from "@mariozechner/pi-agent-core"; import type { Api, Model } from "@mariozechner/pi-ai"; import type { ModelRegistry } from "@mariozechner/pi-coding-agent"; import type { Command } from "commander"; +import type { IncomingMessage, ServerResponse } from "node:http"; import type { ApiKeyCredential, AuthProfileCredential, @@ -925,6 +925,8 @@ export type ProviderPluginWizardSetup = { choiceId?: string; choiceLabel?: string; choiceHint?: string; + assistantPriority?: number; + assistantVisibility?: "visible" | "manual-only"; groupId?: string; groupLabel?: string; groupHint?: string;