Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
8c1330c
fix(cli): refresh static header on model switch
pomelo-nwu Apr 27, 2026
1102d96
feat(cli): simplify api key provider registry
pomelo-nwu Apr 28, 2026
918f1a9
refactor(cli): split Alibaba auth providers
pomelo-nwu Apr 30, 2026
40a4620
polish(cli): refine auth provider onboarding
pomelo-nwu May 6, 2026
7fdf5a5
fix(cli): update OpenRouter free defaults
pomelo-nwu May 6, 2026
16ee88f
fix(cli): restrict token plan models
pomelo-nwu May 6, 2026
75f6f26
chore(cli): remove unused third-party providers
pomelo-nwu May 6, 2026
ee7809a
feat(cli): add regional third-party providers
pomelo-nwu May 6, 2026
6c306cc
refactor(cli): simplify api key provider endpoints
pomelo-nwu May 6, 2026
12a867f
refactor(cli): split auth dialog flows
pomelo-nwu May 6, 2026
fd1ff48
refactor(cli): unify auth around declarative provider config
pomelo-nwu May 6, 2026
17c779a
refactor(cli): simplify provider setup input flow
pomelo-nwu May 6, 2026
9b8d7c9
refactor(cli): remove toLlmProvider and legacy auth wrappers
pomelo-nwu May 6, 2026
d99513b
refactor(cli): flatten auth flow files and simplify ProviderSetupStep…
pomelo-nwu May 6, 2026
61d3c62
feat(cli): prefill API key from existing env settings in provider set…
pomelo-nwu May 6, 2026
69343d7
fix(cli): correct third-party provider context windows
pomelo-nwu May 7, 2026
b8e243e
fix(cli): harden provider auth setup
pomelo-nwu May 7, 2026
d4dc11e
feat(cli): support provider modality and context settings
pomelo-nwu May 7, 2026
ae28533
feat: eable modelsEditable for coding plan
pomelo-nwu May 7, 2026
d446229
refactor(cli): auto-derive provider metadata key and state
pomelo-nwu May 7, 2026
e6cfb52
refactor(cli): namespace provider metadata under providerMetadata key
pomelo-nwu May 7, 2026
f838397
fix(cli): polish ProviderUpdatePrompt styling and test coverage [skip…
pomelo-nwu May 7, 2026
4e51cbe
Merge branch main into feat/api-key-provider-registry
pomelo-nwu May 7, 2026
0ae8282
refactor(auth): simplify auth flows around provider abstraction [skip…
pomelo-nwu May 7, 2026
50b71ad
refactor(auth): update provider models and refine auth infrastructure
pomelo-nwu May 7, 2026
1de1f46
fix(i18n): add missing zh-TW translations for token plan and subscrip…
pomelo-nwu May 7, 2026
f977be6
refactor(auth): improve provider install error recovery and AuthDialo…
pomelo-nwu May 7, 2026
4f935b3
fix(auth): i18n for step labels, lazy preview JSON, and accurate head…
pomelo-nwu May 7, 2026
91fa309
fix(auth): address round-3 review blockers
pomelo-nwu May 7, 2026
679175c
fix(cli): stabilize AuthDialog tests for slower CI environments
pomelo-nwu May 7, 2026
4c4ebb8
fix(core): use id+baseUrl composite key for model identity
pomelo-nwu May 7, 2026
2b3a8c0
fix(cli): update ModelDialog tests for composite-key model identity
pomelo-nwu May 7, 2026
8558c49
fix(cli): skip flaky TUI input tests on all CI environments
pomelo-nwu May 7, 2026
7760d9f
fix(cli): improve auth/provider edge cases and UX
pomelo-nwu May 8, 2026
aacb025
test(cli): update tests for empty model validation and skip auth refresh
pomelo-nwu May 8, 2026
ebe7afb
fix(cli): skip remaining flaky TUI input AuthDialog tests on CI
pomelo-nwu May 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 111 additions & 0 deletions docs/design/auth/motivation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# Auth Provider Registry Motivation

The auth module used to model each setup path as a separate flow: API key,
OAuth, subscription plans, and custom providers. In practice, all of these paths
produce the same kind of output: updates to the user's provider configuration in
`~/.qwen/settings.json`.

This refactor makes provider setup the shared abstraction. A provider describes
how it is shown, how credentials are collected, which models it installs, and
which settings patch should be applied. API keys, OAuth, coding plans, token
plans, and custom wizards are setup methods for a provider, not separate auth
architectures.

## Goals

- Keep `/auth` user-facing flows easy to understand:
- Alibaba ModelStudio for first-party Qwen setup.
- Third-party providers for common built-in integrations such as DeepSeek,
MiniMax, and Z.AI.
- OAuth providers such as OpenRouter.
- Custom providers for local servers, proxies, or providers that are not built
in.
- Move provider-specific data into small declarative provider configs.
- Make third-party provider contributions simple: adding a common provider
should usually mean adding one provider config plus tests.
- Centralize settings writes through `ProviderInstallPlan` and
`applyProviderInstallPlan`.
- Keep UI grouping separate from install behavior. Groups help users navigate
`/auth`; they should not drive settings logic.
- Preserve a path for model list ownership and provider metadata so provider
model updates can be detected and applied safely.

## Architecture

The new structure separates provider definitions, install logic, and UI state:

```text
packages/cli/src/auth/
├── allProviders.ts
├── providerConfig.ts
├── types.ts
├── install/
│ └── applyProviderInstallPlan.ts
└── providers/
├── alibaba/
├── custom/
├── oauth/
└── thirdParty/
```

`ProviderConfig` is the declarative contract for built-in providers. It contains
the provider label, protocol, base URL options, environment key, model list,
model metadata, UI grouping, and setup behavior.

`buildInstallPlan` converts a provider config and collected setup inputs into a
`ProviderInstallPlan`. The install plan is the only object the settings writer
needs to understand.

`applyProviderInstallPlan` applies that plan by updating environment settings,
`modelProviders`, selected auth type, optional model selection, and provider
metadata. This keeps settings persistence independent from the UI flow that
collected the inputs.

## User flows

`/auth` can still present different entry points, but they should all converge on
the same provider install path:

1. **Alibaba ModelStudio**
- Coding Plan
- Token Plan
- Standard API key

2. **Third-party Providers**
- Common providers with built-in defaults.
- Each provider should own its base URL, env key, default models, and model
metadata.
- Z.AI must use the setup-specific base URL:
- Coding Plan: `https://api.z.ai/api/coding/paas/v4`
- Standard API key: `https://api.z.ai/api/paas/v4`

3. **OAuth**
- Browser-based authorization for routing platforms such as OpenRouter.
- OAuth-specific mechanics can live in the provider implementation, but the
final result should still be a provider install plan.

4. **Custom Provider**
- Manual setup for local servers, proxies, or unsupported providers.
- The wizard collects protocol, base URL, API key, model IDs, and advanced
model options such as thinking, multimodal input, context window, and max
tokens.

## Model ownership and updates

Static built-in providers can persist provider metadata under
`providerMetadata.<providerId>`, including the model list version and base URL.
This lets Qwen Code detect when a provider's built-in model list changes and
prompt the user to update owned models without overwriting unrelated custom
models.

Custom providers are different: their model list is user-authored and should not
be treated as an auto-updatable built-in model list.

## Non-goals

- Do not make API key, OAuth, coding plan, or token plan the top-level settings
architecture.
- Do not couple settings writes to React components or CLI command handlers.
- Do not make UI groups a business-logic axis.
- Do not require contributors to understand the full auth UI to add a simple
third-party provider.
4 changes: 2 additions & 2 deletions docs/users/configuration/model-providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ Use `modelProviders` to declare curated model lists per auth type that the `/mod
>
> Only the `/model` command exposes non-default auth types. Anthropic, Gemini, etc., must be defined via `modelProviders`. The `/auth` command lists Qwen OAuth, Alibaba Cloud Coding Plan, and API Key as the built-in authentication options.

> [!warning]
> [!note]
>
> **Duplicate model IDs within the same authType:** Defining multiple models with the same `id` under a single `authType` (e.g., two entries with `"id": "gpt-4o"` in `openai`) is currently not supported. If duplicates exist, **the first occurrence wins** and subsequent duplicates are skipped with a warning. Note that the `id` field is used both as the configuration identifier and as the actual model name sent to the API, so using unique IDs (e.g., `gpt-4o-creative`, `gpt-4o-balanced`) is not a viable workaround. This is a known limitation that we plan to address in a future release.
> **Model uniqueness:** Models within the same `authType` are uniquely identified by the combination of `id` + `baseUrl`. This means you can define the same model ID (e.g., `"gpt-4o"`) multiple times under a single `authType` as long as each entry has a different `baseUrl` — for example, one pointing to OpenAI directly and another to a proxy endpoint. If two entries share both the same `id` and the same `baseUrl` (or both omit `baseUrl`), the first occurrence wins and subsequent duplicates are skipped with a warning.

## Configuration Examples by Auth Type

Expand Down
96 changes: 96 additions & 0 deletions packages/cli/src/auth/allProviders.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/**
* @license
* Copyright 2026 Qwen Team
* SPDX-License-Identifier: Apache-2.0
*
* Provider registry — imports all provider definitions and assembles the
* lookup tables used by the UI and CLI commands.
*/

import {
providerMatchesCredentials,
type ProviderConfig,
} from './providerConfig.js';
import { codingPlanProvider } from './providers/alibaba/codingPlan.js';
import { tokenPlanProvider } from './providers/alibaba/tokenPlan.js';
import { alibabaStandardProvider } from './providers/alibaba/alibabaStandard.js';
import { openRouterProvider } from './providers/oauth/openrouter.js';
import { deepseekProvider } from './providers/thirdParty/deepseek.js';
import { minimaxProvider } from './providers/thirdParty/minimax.js';
import { zaiProvider } from './providers/thirdParty/zai.js';
import { customProvider } from './providers/custom/customProvider.js';

// Re-export all providers
export {
codingPlanProvider,
tokenPlanProvider,
alibabaStandardProvider,
openRouterProvider,
deepseekProvider,
minimaxProvider,
zaiProvider,
customProvider,
};
export {
CUSTOM_API_KEY_ENV_PREFIX,
generateCustomEnvKey,
} from './providers/custom/customProvider.js';

// ---------------------------------------------------------------------------
// Provider Registry
// ---------------------------------------------------------------------------

/** All known providers, in display order. */
export const ALL_PROVIDERS: readonly ProviderConfig[] = [
codingPlanProvider,
tokenPlanProvider,
alibabaStandardProvider,
openRouterProvider,
deepseekProvider,
minimaxProvider,
zaiProvider,
customProvider,
];

/** Providers grouped by uiGroup. */
export const ALIBABA_PROVIDERS = ALL_PROVIDERS.filter(
(p) => p.uiGroup === 'alibaba',
);
export const THIRD_PARTY_PROVIDERS = ALL_PROVIDERS.filter(
(p) => p.uiGroup === 'third-party',
);
export const OAUTH_PROVIDERS = ALL_PROVIDERS.filter(
(p) => p.uiGroup === 'oauth',
);

export function findProviderById(id: string): ProviderConfig | undefined {
return ALL_PROVIDERS.find((p) => p.id === id);
}

/** Find a provider by model credentials (baseUrl + envKey). */
export function findProviderByCredentials(
baseUrl: string | undefined,
envKey: string | undefined,
): ProviderConfig | undefined {
return ALL_PROVIDERS.find((p) =>
providerMatchesCredentials(p, baseUrl, envKey),
);
}

/** All known provider base URLs (for preconnect, validation, etc.). */
export function getAllProviderBaseUrls(): string[] {
return ALL_PROVIDERS.flatMap((p) => {
if (typeof p.baseUrl === 'string') return [p.baseUrl];
if (Array.isArray(p.baseUrl)) return p.baseUrl.map((o) => o.url);
return [];
});
}

// Re-export providerConfig utilities for convenience
export {
buildInstallPlan,
resolveBaseUrl,
getDefaultModelIds,
shouldShowStep,
computeModelListVersion,
} from './providerConfig.js';
Loading
Loading