Skip to content

refactor(core): extract Protocol enum and decouple model identity from auth type - #5089

Merged
wenshao merged 31 commits into
QwenLM:mainfrom
zzhenyao:model-identity-by-name-v1
Jun 22, 2026
Merged

wenshao merged 31 commits into
QwenLM:mainfrom
zzhenyao:model-identity-by-name-v1

Conversation

@zzhenyao

@zzhenyao zzhenyao commented Jun 13, 2026 •

Copy link
Copy Markdown
Contributor

What this PR does

Extracts a standalone Protocol enum from the auth layer so that model identity (provider ID) and SDK routing (protocol) are no longer coupled.

Concrete changes:

  • AuthType changed from a fixed 5-value enum to type AuthType = string, allowing arbitrary provider IDs (including custom providers).
  • New enum Protocol { OPENAI, QWEN_OAUTH, GEMINI, ANTHROPIC } added to contentGenerator.ts. This controls which SDK handles a request.
  • ContentGeneratorConfig gains a protocol field. createContentGenerator now dispatches on protocol instead of authType.
  • ModelProvidersConfig values changed from ModelConfig[] to ProviderConfig { protocol, models, baseUrl?, envKey? }. Each provider entry now carries its own protocol.
  • ProviderConfig.protocol in providers/types.ts changed from AuthType to Protocol; protocolOptions and envKey function signatures updated accordingly.
  • 10 preset provider files updated to use Protocol.* instead of AuthType.USE_*.
  • Mechanical type/import fixes across CLI (acpAgent.ts, runQwenServe.ts, server.ts, ProviderSetupSteps.tsx, useAuth.ts, useProviderSetupFlow.ts, modelConfigUtils.ts) and VSCode extension (AuthMessageHandler.ts).
  • All affected test files updated to match the new ProviderConfig shape and Protocol enum usage.
  • Added v4→v5 settings migration (packages/cli/src/config/migration/versions/v4-to-v5.ts): auto-converts old modelProviders array format to { protocol, models } on config load.
  • VSCode extension settingsWriter.ts: writeModelProvidersConfig and writeCodingPlanConfig updated to write V5 format; findOpenaiModels updated to read both formats.

Why it's needed

Previously, authType served double duty: it was both the provider identity and the SDK selector. This made it impossible to add custom providers (arbitrary string IDs) without breaking SDK routing. Separating Protocol from authType lets us support custom provider IDs while keeping SDK dispatch stable and explicit.

Reviewer Test Plan

Before / After

Before:

{
  "modelProviders": {
    "openai": [
      { "id": "gpt-4o", "name": "GPT-4o", "baseUrl": "...", "envKey": "..." }
    ]
  }
}

authType was an enum ('openai' | 'gemini' | 'anthropic' | etc.), used for both provider identity and SDK routing.

After:

{
  "modelProviders": {
    "openai": {
      "protocol": "openai",
      "models": [
        { "id": "gpt-4o", "name": "GPT-4o", "baseUrl": "...", "envKey": "..." }
      ]
    }
  }
}

authType is now any string (provider identity). protocol is a separate field that controls SDK routing.

How to verify

  1. npm run build

Scenario 1: v4 to v5 Config Migration

Goal: Old config auto-migrates to new format.

Steps:

  1. Set up v4 format ~/.qwen/settings.json:
    {
      "modelProviders": {
        "openai": [
          {"id": "qwen-model", "name": "qwen-model", "baseUrl": "http://127.0.0.1:8001", "envKey": "OPENAI_API_KEY"}
        ],
        "qwen-oauth": [
          {"id": "qwen-oauth-model", "name": "Qwen OAuth", "baseUrl": "http://127.0.0.1:8001", "envKey": "QWEN_OAUTH_API_KEY"}
        ],
        "gemini": [
          {"id": "gemini-model", "name": "Gemini", "baseUrl": "http://127.0.0.1:8001", "envKey": "GEMINI_API_KEY"}
        ],
        "vertex-ai": [
          {"id": "vertex-model", "name": "Vertex AI", "baseUrl": "http://127.0.0.1:8001", "envKey": "VERTEX_API_KEY"}
        ],
        "anthropic": [
          {"id": "anthropic-model", "name": "Anthropic", "baseUrl": "http://127.0.0.1:8001", "envKey": "ANTHROPIC_API_KEY"}
        ]
      },
      "$version": 4,
      "env": {
        "OPENAI_API_KEY": "sk-openai-xxx",
        "QWEN_OAUTH_API_KEY": "sk-qwen-oauth-xxx",
        "GEMINI_API_KEY": "sk-gemini-xxx",
        "VERTEX_API_KEY": "sk-vertex-xxx",
        "ANTHROPIC_API_KEY": "sk-anthropic-xxx"
      }
    }
  2. Run npm start.
  3. Verify ~/.qwen/settings.json becomes v5 format:
    {
      "modelProviders": {
        "openai": {
          "protocol": "openai",
          "models": [
            {"id": "qwen-model", "name": "qwen-model", "baseUrl": "http://127.0.0.1:8001", "envKey": "OPENAI_API_KEY"}
          ]
        },
        "qwen-oauth": {
          "protocol": "qwen-oauth",
          "models": [
            {"id": "qwen-oauth-model", "name": "Qwen OAuth", "baseUrl": "http://127.0.0.1:8001", "envKey": "QWEN_OAUTH_API_KEY"}
          ]
        },
        "gemini": {
          "protocol": "gemini",
          "models": [
            {"id": "gemini-model", "name": "Gemini", "baseUrl": "http://127.0.0.1:8001", "envKey": "GEMINI_API_KEY"}
          ]
        },
        "vertex-ai": {
          "protocol": "gemini",
          "models": [
            {"id": "vertex-model", "name": "Vertex AI", "baseUrl": "http://127.0.0.1:8001", "envKey": "VERTEX_API_KEY"}
          ]
        },
        "anthropic": {
          "protocol": "anthropic",
          "models": [
            {"id": "anthropic-model", "name": "Anthropic", "baseUrl": "http://127.0.0.1:8001", "envKey": "ANTHROPIC_API_KEY"}
          ]
        }
      },
      "$version": 5,
      "env": {
        "OPENAI_API_KEY": "sk-openai-xxx",
        "QWEN_OAUTH_API_KEY": "sk-qwen-oauth-xxx",
        "GEMINI_API_KEY": "sk-gemini-xxx",
        "VERTEX_API_KEY": "sk-vertex-xxx",
        "ANTHROPIC_API_KEY": "sk-anthropic-xxx"
      }
    }

Expected:

  • $version changes from 4 to 5.
  • Each provider changes from array to object.
  • Objects include protocol field (mapping: openai to openai, qwen-oauth to qwen-oauth, gemini to gemini, vertex-ai to gemini, anthropic to anthropic).
  • models field contains the original array contents.

Scenario 2: Same ID, Same BaseUrl, Different envKey

Goal: Multiple providers sharing the same model ID and baseUrl but different envKeys get correctly distinguished.

Steps:

  1. Set up v4 format ~/.qwen/settings.json:
    {
      "modelProviders": {
        "openai": [
          {"id": "qwen3.7-max", "baseUrl": "http://127.0.0.1:8001", "envKey": "TOKEN_PLAN_KEY"},
          {"id": "qwen3.7-max", "baseUrl": "http://127.0.0.1:8001", "envKey": "IDEALAB_KEY"}
        ]
      },
      "$version": 4,
      "env": {
        "TOKEN_PLAN_KEY": "sk-token-plan-xxx",
        "IDEALAB_KEY": "sk-idealab-xxx"
      }
    }
  2. Run qwen.
  3. Use /model to select a model.

Expected (before PR):

  • /model only shows the first model (TOKEN_PLAN_KEY).
  • Cannot distinguish providers with the same ID.

Expected (after PR):

  • v4 auto-migrates to v5 format.
  • /model shows both models.
  • User can select different providers (openai or qwen3.7-idealab).
  • Selected provider uses the correct envKey.

Scenario 3: /model Command with Provider Specifier

Goal: /model command correctly switches providers when given a provider qualifier.

Steps:

  1. Use the v5 config from scenario 2.
  2. Run this PR's CLI.
  3. Input /model qwen3.7-max.
    Before switch: /status shows Auth: API Key - openai.
  4. Input /model qwen3.7-max(qwen3.7-idealab).
    After switch: /status shows Auth: API Key - qwen3.7-idealab.
    settings.json updates security.auth.selectedType to "qwen3.7-idealab".

Tested on

OS Status
🍏 macOS N/A
🪟 Windows N/A
🐧 Linux ✅

Risk & Scope

  • Scope: 4 core files changed, 19 mechanical type/import propagation files, 10+ test files updated.
  • Runtime risk: Low. createContentGenerator dispatch logic is functionally identical — same branches, just keyed on protocol instead of authType.
  • Config compatibility: The modelProviders structure change from array to { protocol, models } is a breaking format change. A v4→v5 settings migration is included in this PR — old configs are auto-converted on load.
  • No behavior changes: All preset providers, auth flows, and model resolution logic work the same way. Only the internal type structure changed.

Follow-up tasks

  1. ✅ v4 → v5 config migration: Auto-convert old modelProviders array format to { protocol, models } on config load. Done in this PR.
  2. Custom provider UI support: ModelDialog.tsx currently hardcodes the 5 built-in AuthType values. Needs to support arbitrary provider IDs for custom providers. (Separate PR)
  3. model.name → model.id: Rename the settings field for clarity

Linked Issues

Fixes: #5090 #4877 #5080
Closes: #4814
Resolves: #4813 #4722

中文

这个 PR 做了什么

将 Protocol 从认证层中独立出来,使模型身份(提供商 ID)和 SDK 路由(协议)解耦。

具体改动:

  • AuthType 从固定 5 值枚举改为 type AuthType = string,支持任意提供商 ID(包括自定义提供商)。
  • 在 contentGenerator.ts 中新增 enum Protocol { OPENAI, QWEN_OAUTH, GEMINI, ANTHROPIC },用于控制请求走哪个 SDK。
  • ContentGeneratorConfig 新增 protocol 字段,createContentGenerator 按 protocol 分发而非 authType。
  • ModelProvidersConfig 的值从 ModelConfig[] 改为 ProviderConfig { protocol, models, baseUrl?, envKey? }。
  • providers/types.ts 中 ProviderConfig.protocol 从 AuthType 改为 Protocol;protocolOptions 和 envKey 函数签名同步更新。
  • 10 个 preset 提供商文件更新为使用 Protocol.*。
  • CLI 和 VSCode 扩展中的机械性类型/导入修复。
  • 所有相关测试文件已适配新格式。
  • 新增 v4→v5 设置迁移(packages/cli/src/config/migration/versions/v4-to-v5.ts):加载旧配置时自动将 modelProviders 数组格式转换为 { protocol, models }。
  • VSCode 扩展 settingsWriter.ts:writeModelProvidersConfig 和 writeCodingPlanConfig 更新为写入 V5 格式;findOpenaiModels 兼容新旧两种格式。

为什么需要这个改动

之前 authType 同时承担提供商身份和 SDK 选择器两个职责,导致无法支持自定义提供商(任意字符串 ID)。将 Protocol 从 authType 中分离后,可以支持自定义提供商 ID,同时保持 SDK 分发稳定。

Reviewer 测试计划

修改前 / 修改后

修改前:

{
  "modelProviders": {
    "openai": [
      { "id": "gpt-4o", "name": "GPT-4o" }
    ]
  }
}

authType 是枚举,同时用于提供商身份和 SDK 路由。

修改后:

{
  "modelProviders": {
    "openai": {
      "protocol": "openai",
      "models": [
        { "id": "gpt-4o", "name": "GPT-4o" }
      ]
    }
  }
}

authType 可以是任意字符串(提供商身份),protocol 单独控制 SDK 路由。

如何验证

  1. npm run build

场景 1:v4→v5 配置迁移

目标:验证旧配置自动迁移为新格式。

步骤:

  1. 准备 v4 格式的 ~/.qwen/settings.json:
    {
      "modelProviders": {
        "openai": [
          {"id": "qwen-model", "name": "qwen-model", "baseUrl": "http://127.0.0.1:8001", "envKey": "OPENAI_API_KEY"}
        ],
        "qwen-oauth": [
          {"id": "qwen-oauth-model", "name": "Qwen OAuth", "baseUrl": "http://127.0.0.1:8001", "envKey": "QWEN_OAUTH_API_KEY"}
        ],
        "gemini": [
          {"id": "gemini-model", "name": "Gemini", "baseUrl": "http://127.0.0.1:8001", "envKey": "GEMINI_API_KEY"}
        ],
        "vertex-ai": [
          {"id": "vertex-model", "name": "Vertex AI", "baseUrl": "http://127.0.0.1:8001", "envKey": "VERTEX_API_KEY"}
        ],
        "anthropic": [
          {"id": "anthropic-model", "name": "Anthropic", "baseUrl": "http://127.0.0.1:8001", "envKey": "ANTHROPIC_API_KEY"}
        ]
      },
      "$version": 4,
      "env": {
        "OPENAI_API_KEY": "sk-openai-xxx",
        "QWEN_OAUTH_API_KEY": "sk-qwen-oauth-xxx",
        "GEMINI_API_KEY": "sk-gemini-xxx",
        "VERTEX_API_KEY": "sk-vertex-xxx",
        "ANTHROPIC_API_KEY": "sk-anthropic-xxx"
      }
    }
  2. 运行 npm start。
  3. 检查 ~/.qwen/settings.json 是否变为 v5 格式:
    {
      "modelProviders": {
        "openai": {
          "protocol": "openai",
          "models": [
            {"id": "qwen-model", "name": "qwen-model", "baseUrl": "http://127.0.0.1:8001", "envKey": "OPENAI_API_KEY"}
          ]
        },
        "qwen-oauth": {
          "protocol": "qwen-oauth",
          "models": [
            {"id": "qwen-oauth-model", "name": "Qwen OAuth", "baseUrl": "http://127.0.0.1:8001", "envKey": "QWEN_OAUTH_API_KEY"}
          ]
        },
        "gemini": {
          "protocol": "gemini",
          "models": [
            {"id": "gemini-model", "name": "Gemini", "baseUrl": "http://127.0.0.1:8001", "envKey": "GEMINI_API_KEY"}
          ]
        },
        "vertex-ai": {
          "protocol": "gemini",
          "models": [
            {"id": "vertex-model", "name": "Vertex AI", "baseUrl": "http://127.0.0.1:8001", "envKey": "VERTEX_API_KEY"}
          ]
        },
        "anthropic": {
          "protocol": "anthropic",
          "models": [
            {"id": "anthropic-model", "name": "Anthropic", "baseUrl": "http://127.0.0.1:8001", "envKey": "ANTHROPIC_API_KEY"}
          ]
        }
      },
      "$version": 5,
      "env": {
        "OPENAI_API_KEY": "sk-openai-xxx",
        "QWEN_OAUTH_API_KEY": "sk-qwen-oauth-xxx",
        "GEMINI_API_KEY": "sk-gemini-xxx",
        "VERTEX_API_KEY": "sk-vertex-xxx",
        "ANTHROPIC_API_KEY": "sk-anthropic-xxx"
      }
    }

预期结果:

  • $version 从 4 变为 5。
  • 每个提供商从数组变为对象。
  • 对象包含 protocol 字段(映射规则:openai→openai, qwen-oauth→qwen-oauth, gemini→gemini, vertex-ai→gemini, anthropic→anthropic)。
  • models 字段包含原来的数组内容。

场景 2:相同 ID、相同 baseUrl、不同 envKey

目标:验证多个提供商共享同一 model ID 和 baseUrl,但使用不同 envKey 时,系统能正确区分。

步骤:

  1. 准备 v4 格式的 ~/.qwen/settings.json:
    {
      "modelProviders": {
        "openai": [
          {"id": "qwen3.7-max", "baseUrl": "http://127.0.0.1:8001", "envKey": "TOKEN_PLAN_KEY"},
          {"id": "qwen3.7-max", "baseUrl": "http://127.0.0.1:8001", "envKey": "IDEALAB_KEY"}
        ]
      },
      "$version": 4,
      "env": {
        "TOKEN_PLAN_KEY": "sk-token-plan-xxx",
        "IDEALAB_KEY": "sk-idealab-xxx"
      }
    }
  2. 运行 qwen。
  3. 使用 /model 选择模型。

预期结果(此 PR 前):

  • /model 只显示第一个模型(TOKEN_PLAN_KEY)。
  • 无法区分相同 ID 的不同提供商。

预期结果(此 PR 后):

  • v4 自动迁移为 v5 格式。
  • /model 显示两个模型。
  • 用户可以选择不同的提供商(openai 或 qwen3.7-idealab)。
  • 选择后使用对应的 envKey。

场景 3:/model 命令直接指定提供商

目标:验证通过 /model 命令指定提供商和模型时,系统能正确切换。

步骤:

  1. 使用场景 2 的 v5 配置。
  2. 运行此 PR 的 CLI。
  3. 输入 /model qwen3.7-max。
    切换前:/status 显示 Auth: API Key - openai。
  4. 输入 /model qwen3.7-max(qwen3.7-idealab)。
    切换后:/status 显示 Auth: API Key - qwen3.7-idealab。
    settings.json 中 security.auth.selectedType 更新为 "qwen3.7-idealab"。

测试环境

操作系统 状态
🍏 macOS N/A
🪟 Windows N/A
🐧 Linux ✅

风险与范围

  • 范围:4 个核心文件,19 个机械性传播修改文件,10+ 个测试文件。
  • 运行时风险:低。createContentGenerator 分发逻辑功能不变。
  • 配置兼容性:modelProviders 从数组改为 { protocol, models } 是破坏性格式变更。本 PR 已包含 v4→v5 迁移逻辑,旧配置会在加载时自动转换。
  • 无行为变化:所有 preset 提供商、认证流程、模型解析逻辑不变。

后续任务

  1. ✅ v4 → v5 配置迁移:加载旧配置时自动转换 modelProviders 数组格式为 { protocol, models }。已在本 PR 中完成。
  2. 自定义提供商 UI 支持:ModelDialog.tsx 目前硬编码 5 个 AuthType 值,需要支持任意提供商 ID。(单独 PR)
  3. model.name → model.id:重命名 settings 字段以提高语义清晰度

zzhenyao added 5 commits June 13, 2026 19:49
…m auth type

- AuthType: enum -> string (custom provider IDs supported)
- New Protocol enum: OPENAI, QWEN_OAUTH, GEMINI, ANTHROPIC
- ContentGeneratorConfig: add `protocol` field for SDK routing
- ProviderConfig: { protocol, models[], baseUrl?, envKey? }
- ModelProvidersConfig: Record<string, ProviderConfig>
- createContentGenerator: dispatch by `protocol` not `authType`
- v4->v5 migration: authType maps to protocol, models wrapped in ProviderConfig
@zzhenyao
zzhenyao marked this pull request as ready for review June 13, 2026 22:55
@zzhenyao

zzhenyao commented Jun 13, 2026 •

Copy link
Copy Markdown
Contributor Author

#5039 only added fields to CLI settings. External channels (ACP, IDE extensions) still send a single provider/modelId string. This approach couldn't work, so the PR was closed.

#5089 decouples at the core:

  • providerId: string (arbitrary, identity) + protocol: enum
  • Custom providers work natively.
    

Could you take a look? @wenshao

Comment thread packages/core/src/core/contentGenerator.ts
Comment thread packages/core/src/core/contentGenerator.ts
Comment thread packages/core/src/models/types.ts
Comment thread packages/core/src/providers/install.ts Outdated
Comment thread packages/core/src/core/contentGenerator.ts
Comment thread packages/core/src/models/modelConfigResolver.ts Outdated
@zzhenyao

Copy link
Copy Markdown
Contributor Author

Thanks for the review! @wenshao R1 results:

Fixed:

  • protocol never populated on ContentGeneratorConfig: ModelRegistry now exposes getProtocolForAuthType(), getGenerationConfig() includes it automatically. ProviderConfig in config file, preserving the decoupling.
  • Old array format callers: auth.ts, acpAgent.ts, settingsWriter.ts all handle both array and ProviderConfig formats.
  • First-time install defaults to OPENAI: added authTypeToProtocol() mapping in install.ts.
  • Missing tests: added protocol missing, unknown protocol, and ANTHROPIC branch tests.
  • Prototype pollution: Object.hasOwn guard on both AUTH_ENV_MAPPINGS access points.
  • settings.schema.json CI failure: regenerated schema after SETTINGS_VERSION bump from 4 to 5.

Addressed differently:

  • VERTEX_AI in Protocol: by design. vertex-ai uses Gemini SDK → protocol is GEMINI.

@qqqys qqqys left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Critical-only re-review: the prior protocol propagation blocker is still unresolved for non-modelProviders auth paths. The new fix only injects protocol in ModelsConfig.getGenerationConfig() when ModelRegistry has a registered provider for the current authType. For legacy/env/CLI/manual setups such as OPENAI_API_KEY + OPENAI_MODEL + OPENAI_BASE_URL with no modelProviders entry, resolveModelConfig() still returns authType/model/apiKey/baseUrl but no protocol; ModelRegistry only registers qwen-oauth by default, so getGenerationConfig() cannot fill it. refreshAuth() then passes that config into createContentGenerator(), which now throws "ContentGeneratorConfig must have a protocol" before creating any generator. Impact: existing non-registry OpenAI/Gemini/Anthropic/Vertex auth flows fail at startup/auth refresh/model switch. Please derive protocol in the general resolution path as well, not only from provider registry entries.

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test line resolution

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New findings beyond existing 6 inline comments.


[Critical] packages/cli/src/ui/hooks/useProviderUpdates.ts:167 — getInstalledOwnedModelIds casts modelProviders[protocol] to ProviderModelConfig[] and calls .map() / .filter(). After v5 migration, the value is a ProviderConfig object (not an array), so this throws TypeError: allModels.map is not a function at runtime. This file was not updated in the PR. Fix: unwrap like acpAgent.ts does:

const entry = modelProviders[protocol];
const allModels: ProviderModelConfig[] = Array.isArray(entry) ? entry : (entry?.models ?? []);

[Suggestion] packages/cli/src/ui/auth/useProviderSetupFlow.ts:448 — The settings preview JSON emits v4 array format (modelProviders: { [protocol]: models }) instead of v5 ProviderConfig format ({ [protocol]: { protocol, models } }). Display-only, but misleads users who inspect the preview.

— qwen3.7-max via Qwen Code /review

Comment thread packages/core/src/models/modelRegistry.ts
Comment thread packages/core/src/models/constants.ts
Comment thread packages/core/src/models/modelsConfig.ts Outdated

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additional findings not mappable to diff lines:

[Critical] packages/core/src/models/content-generator-config.ts — buildAgentContentGeneratorConfig() spreads parentConfig into nextConfig (inheriting the parent's protocol) but never updates protocol for the target authType. When a subagent targets a different provider (e.g., parent uses OpenAI, subagent targets Anthropic), createContentGenerator dispatches on the parent's protocol, creating the wrong content generator. Fix: after constructing nextConfig, resolve the protocol for authOverrides.authType via modelRegistry.getProtocolForAuthType() and set it on nextConfig.protocol.

[Critical] packages/cli/src/ui/hooks/useProviderUpdates.ts:155-169 — getInstalledOwnedModelIds() was not updated for v5 format. It casts modelProviders as Record<string, ProviderModelConfig[]> (v4 array) and calls .map() / .filter() on the value. After migration to v5, each entry is a {protocol, models} object, so allModels.map(m => m.id) will crash with TypeError: allModels.map is not a function.

— qwen3.7-max via Qwen Code /review

Comment thread packages/cli/src/config/migration/versions/v4-to-v5.ts Outdated
Comment thread packages/cli/src/utils/modelConfigUtils.ts
Comment thread packages/core/src/providers/install.ts Outdated
Comment thread packages/vscode-ide-companion/schemas/settings.schema.json
zzhenyao added 2 commits June 22, 2026 14:07
…havior

  - Update test: any non-empty token in trailing parens is now treated as authType
  - Add test: empty parens fallback returns whole input as modelId
  - Update JSDoc: remove "valid AuthType" since authType is now an arbitrary string

  No logic changes — test and docs only.
@zzhenyao

Copy link
Copy Markdown
Contributor Author

CI fix: ff36dd0
acpModelUtils.test.ts was a leftover from before this PR. It asserted that an "invalid authType" causes the whole string to fall back to modelId, but parseAcpModelOption no longer validates authType.

Changes:

  • acpModelUtils.test.ts — updated the test, added empty-parens fallback case
  • acpModelUtils.ts — updated JSDoc only, no logic change

Other:

  • Merged latest main 660ea26
  • Updated test plan in PR description

Verified the decoupling changes locally against all three test plan scenarios (v4→v5 migration, same ID with different envKeys, /model with provider specifier). Can you take a look when you have time? @wenshao

@zzhenyao
zzhenyao requested a review from wenshao June 22, 2026 06:38

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No review findings. Downgraded from Approve to Comment: CI still running.

— qwen3.7-max via Qwen Code /review

@wenshao

wenshao commented Jun 22, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /triage

@wenshao

wenshao commented Jun 22, 2026

Copy link
Copy Markdown
Collaborator

Verification — Protocol enum + decouple model identity from auth type ✅

This is a large refactor (75 files) plus a v4→v5 settings migration, so I verified two ways: ran the heavily-updated test suites (a refactor must be behavior-preserving) and drove the real dist/cli.js binary for the headline migration and the new protocol routing. Built from HEAD 660ea26d on Linux (npm ci + npm run bundle).

tmux/interactive adds nothing here — the migration runs at startup regardless of mode — so I drove the real binary headless and observed the on-disk transformation directly.

Refactor is behavior-preserving — ~730 tests green across the most-changed suites

Area Suites Tests
migration v4-to-v5, migration/index 19, 26 ✅
settings / config settings (cli), config (core) 133, 234 ✅
models modelsConfig, modelRegistry, content-generator-config 73, 55, 14 ✅
routing contentGenerator (protocol dispatch) 10 ✅
providers install, provider-config, custom-provider 21, 52, 11 ✅
model / auth utils modelConfigUtils, acpModelUtils, useAuth 53, 6, 23 ✅

Real CLI E2E — v4→v5 settings migration (Reviewer Test Plan Scenario 1)

Seeded the PR's exact v4 ~/.qwen/settings.json (5 providers in the old array format, $version: 4), then ran the real binary:

$version: 4 → 5
openai      → { protocol: "openai",      models: […] }
qwen-oauth  → { protocol: "qwen-oauth",  models: […] }
gemini      → { protocol: "gemini",      models: […] }
vertex-ai   → { protocol: "gemini",      models: […] }   ← note: vertex-ai maps to the gemini protocol
anthropic   → { protocol: "anthropic",   models: […] }
  • Every model entry preserved byte-for-byte; unrelated settings (security, privacy) preserved.
  • Idempotent: a second run on the now-v5 file leaves it unchanged (no double-migration / no churn).
  • The -p run then exited on "No auth type is selected" — expected, since the migration runs at settings-load before the prompt; the point is the file transformed cleanly with no parse/crash.

Real CLI E2E — protocol-dispatch routing

createContentGenerator now dispatches on protocol instead of authType. Drove the real binary with openai auth against a mock endpoint → it returned the reply (PROTOCOL_ROUTED_OK, exit 0). So the decoupled routing works end-to-end in the binary, not just in unit tests.

Notes

  • HEAD 660ea26d is a merge of main into the branch; the 75-file scope is mostly mechanical type/import updates plus test updates to the new ProviderConfig { protocol, models } shape — the green suites confirm the shape is consistent across core / cli / providers.
  • Verified on Linux.

No issues found — the refactor is behavior-preserving, the v4→v5 migration is correct (including the vertex-ai → gemini mapping), content-preserving, and idempotent, and protocol routing works in the real binary. LGTM. 🚀

中文版(合并参考)

验证 —— 提取 Protocol 枚举 + 将模型身份与鉴权类型解耦 ✅

这是一个大型重构(75 个文件)外加一个 v4→v5 设置迁移,所以我用两种方式验证:跑了大量被改动的测试套件(重构必须保持行为不变),并端到端驱动真实的 dist/cli.js 二进制来验证头部的迁移和新的 protocol 路由。基于 HEAD 660ea26d,在 Linux 上 npm ci + npm run bundle 构建。

这里 tmux/交互模式没有额外价值 —— 迁移在启动时发生,与模式无关 —— 所以我用 headless 方式驱动真实二进制,直接观察磁盘上的转换。

重构保持行为不变 —— 改动最多的套件 ~730 个用例全绿

范围 套件 用例数
迁移 v4-to-v5、migration/index 19、26 ✅
设置 / 配置 settings(cli)、config(core) 133、234 ✅
模型 modelsConfig、modelRegistry、content-generator-config 73、55、14 ✅
路由 contentGenerator(按 protocol 分发) 10 ✅
providers install、provider-config、custom-provider 21、52、11 ✅
模型 / 鉴权工具 modelConfigUtils、acpModelUtils、useAuth 53、6、23 ✅

真实 CLI 端到端 —— v4→v5 设置迁移(Reviewer Test Plan 场景 1)

放入 PR 给出的那份 v4 ~/.qwen/settings.json(5 个 provider,旧数组格式,$version: 4),然后跑真实二进制:

$version: 4 → 5
openai      → { protocol: "openai",      models: […] }
qwen-oauth  → { protocol: "qwen-oauth",  models: […] }
gemini      → { protocol: "gemini",      models: […] }
vertex-ai   → { protocol: "gemini",      models: […] }   ← 注意:vertex-ai 映射到 gemini 协议
anthropic   → { protocol: "anthropic",   models: […] }
  • 每个 model 条目逐字节保留;无关设置(security、privacy)保留。
  • 幂等:对已是 v5 的文件再跑一次不会改变它(不会重复迁移 / 无抖动)。
  • 之后 -p 运行以 "No auth type is selected" 退出 —— 符合预期,因为迁移在设置加载时、prompt 之前发生;关键是文件干净转换、无解析/崩溃。

真实 CLI 端到端 —— 按 protocol 分发的路由

createContentGenerator 现在按 protocol 而非 authType 分发。用 openai 鉴权对接 mock 接口驱动真实二进制 → 返回了回复(PROTOCOL_ROUTED_OK,exit 0)。所以解耦后的路由在二进制层面端到端可用,而不仅是单测。

说明

  • HEAD 660ea26d 是把 main 合并进分支的提交;75 文件的范围大多是机械的类型/import 更新,以及对新 ProviderConfig { protocol, models } 形状的测试更新 —— 全绿的套件证明该形状在 core / cli / providers 间一致。
  • 在 Linux 上验证。

未发现问题 —— 重构保持行为不变,v4→v5 迁移正确(含 vertex-ai → gemini 映射)、保留内容、且幂等,protocol 路由在真实二进制上可用。LGTM 🚀

@wenshao
wenshao merged commit 0c5221c into QwenLM:main Jun 22, 2026
34 checks passed
yiliang114 pushed a commit that referenced this pull request Jun 23, 2026
…s listing (#5729)

#5089 switched getAllConfiguredModels from Object.values(AuthType) to
modelRegistry.getAuthTypes(), which only returns authTypes that have registry
models. A runtime model resolved from env/CLI overrides (e.g. OPENAI_API_KEY /
OPENAI_BASE_URL / OPENAI_MODEL with no modelProviders entry) uses an authType
that has no registry models, so it was dropped from the default listing — even
when it is the active/current model.

This hid the active model from the ACP `availableModels` list and the
interactive `/model` picker, and deterministically failed the ACP
`set_config_option` integration test (`expect(openaiModel).toBeDefined()`) on
every main run since #5089.

Include the active runtime model's authType in the default (no-filter) case so
it is enumerated alongside registry models. Explicit-authType callers are
unchanged. Add a core unit regression test.
pomelo-nwu added a commit that referenced this pull request Jun 23, 2026
Add Stage 0b that blocks community-contributed refactor PRs touching
core infrastructure (packages/core, auth, providers, models, config,
tools, services) unless maintainer-initiated with prior design discussion.

Triggered by PR #5089: 75-file refactor across core/auth/providers/models
that should never have been accepted from a community contributor.
pomelo-nwu added a commit that referenced this pull request Jun 23, 2026
Add Stage 0b that flatly rejects community-contributed refactor PRs
touching core infrastructure (packages/core, auth, providers, models,
config, tools, services). No exceptions — core refactors must be
maintainer-initiated with prior design discussion.

Triggered by PR #5089: 75-file refactor across core/auth/providers/models
that should never have been accepted from a community contributor.
yiliang114 pushed a commit that referenced this pull request Jun 23, 2026
A fork (cross-repository) PR whose title is a `refactor` type could be
auto-approved by the /triage skill and merged without a maintainer
reviewing the structural changes (this happened with #5089).

Add a deterministic approval guardrail to Stage 3: before approving,
check `isCrossRepository && title ~ /^refactor/i`; on a match, skip
`gh pr review --approve` and escalate to the maintainer instead.
Approval is now a positive condition (the guard must explicitly pass),
so a blocked or empty check never approves. Document the rule in the
skill's global Rules section as well.
wenshao added a commit to wenshao/qwen-code that referenced this pull request Jun 23, 2026
Addresses /qreview feedback on the revert:

- vscode findOpenaiModels: restore read-side tolerance for the V5
  { protocol, models } shape. The extension reads/writes settings.json
  without running the CLI v5->v4 migration, so a not-yet-downgraded
  $version:5 file would otherwise return [] and silently drop existing
  OpenAI models on the next write. (Critical)
- modelRegistry.registerAuthTypeModels: guard against a non-array provider
  value (skip + warn) instead of throwing an opaque "models is not
  iterable" — covers hand-edited or unmigrated files the downgrade misses.
- needsMigration JSDoc: update the stale ">= SETTINGS_VERSION" wording to
  match the "=== SETTINGS_VERSION, else fall through" logic the downgrade
  path depends on.
- settings.test.ts: also assert the v5->v4 downgrade is persisted to disk
  (.tmp write-back), not just the in-memory merged result.

Adds tests for the registry guard and the vscode V5 read tolerance.
wenshao added a commit to wenshao/qwen-code that referenced this pull request Jun 23, 2026
…M#5089)

Reverts the structural changes from QwenLM#5089 back to the pre-QwenLM#5089 shape:
AuthType stays a fixed enum (not `string`), the Protocol enum is removed,
modelProviders is `Record<authType, ModelConfig[]>` again (not
`{ protocol, models }`), and createContentGenerator dispatches on authType.
The v4->v5 settings migration is removed and SETTINGS_VERSION reverts to 4.

Features merged on top of QwenLM#5089 are kept and re-adapted to the old
enum+array structure (not reverted):
- QwenLM#5632 fastOnly/voiceOnly model flags (test fixtures reshaped to arrays)
- QwenLM#5638 workspace provider defaults (readProviderModels already tolerates
  both shapes; test fixtures reshaped to arrays)
- QwenLM#5729 active-runtime-model listing (pre-QwenLM#5089 getAllConfiguredModels
  already enumerates Object.values(AuthType), so the runtime model is
  included natively)
- QwenLM#5728 ACP set_config_option deterministic provider fixture (reshaped to
  array; the flake fix is preserved)

KNOWN DOWNGRADE CAVEAT: settings already migrated to $version:5 (shipped in
v0.19.0) retain the v5 `{ protocol, models }` modelProviders shape, which
the reverted ModelRegistry consumes as an array. Such settings will throw
on load until re-configured. A v5->v4 downgrade guard/migration is a
separate follow-up if backward compatibility for migrated users is needed.
wenshao added a commit to wenshao/qwen-code that referenced this pull request Jun 23, 2026
…vert

After reverting QwenLM#5089, settings already migrated to $version:5 (shipped in
v0.19.0) carry a modelProviders `{ protocol, models }` shape that the
reverted v4 readers consume as arrays, throwing "models is not iterable"
on load. This adds the inverse migration so those configs auto-converge to
v4 on load (the user-facing "automatically migrate $version:5 to 4").

- V5ToV4Migration: unwraps each modelProviders `{ protocol, models }` back
  to its `models` array, drops the now-implicit protocol (warning only when
  the explicit protocol differs from the key-derived one), and resets
  $version to 4.
- DOWNGRADE_MIGRATIONS keeps the downgrade out of the ascending forward
  ALL_MIGRATIONS chain (preserving its invariants); runMigrations and
  needsMigration consider both via a combined convergence set.
- needsMigration now gates on `=== SETTINGS_VERSION` instead of `>=`, so a
  newer-but-handled version (v5) is reported as needing migration while a
  genuinely unknown newer version (v6+) is still left untouched.

Covered by unit tests for the migration, the framework wiring, and an
end-to-end loadSettings downgrade-on-load test.
wenshao added a commit to wenshao/qwen-code that referenced this pull request Jun 23, 2026
Addresses /qreview feedback on the revert:

- vscode findOpenaiModels: restore read-side tolerance for the V5
  { protocol, models } shape. The extension reads/writes settings.json
  without running the CLI v5->v4 migration, so a not-yet-downgraded
  $version:5 file would otherwise return [] and silently drop existing
  OpenAI models on the next write. (Critical)
- modelRegistry.registerAuthTypeModels: guard against a non-array provider
  value (skip + warn) instead of throwing an opaque "models is not
  iterable" — covers hand-edited or unmigrated files the downgrade misses.
- needsMigration JSDoc: update the stale ">= SETTINGS_VERSION" wording to
  match the "=== SETTINGS_VERSION, else fall through" logic the downgrade
  path depends on.
- settings.test.ts: also assert the v5->v4 downgrade is persisted to disk
  (.tmp write-back), not just the in-memory merged result.

Adds tests for the registry guard and the vscode V5 read tolerance.
wenshao added a commit to wenshao/qwen-code that referenced this pull request Jun 23, 2026
… paths

Addresses /review suggestions on the revert:

- contentGenerator: import PROVIDER_SOURCED_FIELDS from constants.js (where
  it is actually defined) instead of modelsConfig.js, breaking the runtime
  import cycle contentGenerator -> modelsConfig -> contentGenerator.
  constants.js only references contentGenerator at the type level, which is
  erased at runtime, so no cycle remains.
- contentGenerator.test: add coverage for the two authType error paths the
  revert restored (missing authType -> "must have an authType"; unknown
  authType -> "Unsupported authType"), which QwenLM#5089's protocol-based tests
  had replaced. Neither was covered before.

The acpAgent z.nativeEnum(AuthType).parse(methodId) suggestion is left as-is:
that line is byte-identical to pre-QwenLM#5089, so it is pre-existing behavior the
revert faithfully restores rather than a regression of this PR.
wenshao added a commit that referenced this pull request Jun 23, 2026
#5745)

* revert(core): revert Protocol enum & model-identity decoupling (#5089)

Reverts the structural changes from #5089 back to the pre-#5089 shape:
AuthType stays a fixed enum (not `string`), the Protocol enum is removed,
modelProviders is `Record<authType, ModelConfig[]>` again (not
`{ protocol, models }`), and createContentGenerator dispatches on authType.
The v4->v5 settings migration is removed and SETTINGS_VERSION reverts to 4.

Features merged on top of #5089 are kept and re-adapted to the old
enum+array structure (not reverted):
- #5632 fastOnly/voiceOnly model flags (test fixtures reshaped to arrays)
- #5638 workspace provider defaults (readProviderModels already tolerates
  both shapes; test fixtures reshaped to arrays)
- #5729 active-runtime-model listing (pre-#5089 getAllConfiguredModels
  already enumerates Object.values(AuthType), so the runtime model is
  included natively)
- #5728 ACP set_config_option deterministic provider fixture (reshaped to
  array; the flake fix is preserved)

KNOWN DOWNGRADE CAVEAT: settings already migrated to $version:5 (shipped in
v0.19.0) retain the v5 `{ protocol, models }` modelProviders shape, which
the reverted ModelRegistry consumes as an array. Such settings will throw
on load until re-configured. A v5->v4 downgrade guard/migration is a
separate follow-up if backward compatibility for migrated users is needed.

* feat(cli): add v5->v4 settings downgrade migration for #5089 revert

After reverting #5089, settings already migrated to $version:5 (shipped in
v0.19.0) carry a modelProviders `{ protocol, models }` shape that the
reverted v4 readers consume as arrays, throwing "models is not iterable"
on load. This adds the inverse migration so those configs auto-converge to
v4 on load (the user-facing "automatically migrate $version:5 to 4").

- V5ToV4Migration: unwraps each modelProviders `{ protocol, models }` back
  to its `models` array, drops the now-implicit protocol (warning only when
  the explicit protocol differs from the key-derived one), and resets
  $version to 4.
- DOWNGRADE_MIGRATIONS keeps the downgrade out of the ascending forward
  ALL_MIGRATIONS chain (preserving its invariants); runMigrations and
  needsMigration consider both via a combined convergence set.
- needsMigration now gates on `=== SETTINGS_VERSION` instead of `>=`, so a
  newer-but-handled version (v5) is reported as needing migration while a
  genuinely unknown newer version (v6+) is still left untouched.

Covered by unit tests for the migration, the framework wiring, and an
end-to-end loadSettings downgrade-on-load test.

* fix(test): align integration settings-version constant with reverted v4

The integration suites hard-coded CURRENT_SETTINGS_VERSION = 5 (introduced
by #5676), which mismatched the reverted SETTINGS_VERSION = 4 and failed the
migration assertions ($version now writes 4, not 5). Revert the constant to
4 in both settings-migration and qwen-config-dir integration tests.

Verified: QWEN_SANDBOX=false vitest run --root ./integration-tests
cli/settings-migration.test.ts cli/qwen-config-dir.test.ts → 21 passed.

* fix: harden v5-era settings handling on the #5089 revert path

Addresses /qreview feedback on the revert:

- vscode findOpenaiModels: restore read-side tolerance for the V5
  { protocol, models } shape. The extension reads/writes settings.json
  without running the CLI v5->v4 migration, so a not-yet-downgraded
  $version:5 file would otherwise return [] and silently drop existing
  OpenAI models on the next write. (Critical)
- modelRegistry.registerAuthTypeModels: guard against a non-array provider
  value (skip + warn) instead of throwing an opaque "models is not
  iterable" — covers hand-edited or unmigrated files the downgrade misses.
- needsMigration JSDoc: update the stale ">= SETTINGS_VERSION" wording to
  match the "=== SETTINGS_VERSION, else fall through" logic the downgrade
  path depends on.
- settings.test.ts: also assert the v5->v4 downgrade is persisted to disk
  (.tmp write-back), not just the in-memory merged result.

Adds tests for the registry guard and the vscode V5 read tolerance.

* fix(core): break contentGenerator import cycle + cover reverted error paths

Addresses /review suggestions on the revert:

- contentGenerator: import PROVIDER_SOURCED_FIELDS from constants.js (where
  it is actually defined) instead of modelsConfig.js, breaking the runtime
  import cycle contentGenerator -> modelsConfig -> contentGenerator.
  constants.js only references contentGenerator at the type level, which is
  erased at runtime, so no cycle remains.
- contentGenerator.test: add coverage for the two authType error paths the
  revert restored (missing authType -> "must have an authType"; unknown
  authType -> "Unsupported authType"), which #5089's protocol-based tests
  had replaced. Neither was covered before.

The acpAgent z.nativeEnum(AuthType).parse(methodId) suggestion is left as-is:
that line is byte-identical to pre-#5089, so it is pre-existing behavior the
revert faithfully restores rather than a regression of this PR.
pull Bot pushed a commit to Little-Star888/qwen-code that referenced this pull request Jun 25, 2026
…wenLM#5758) (QwenLM#5793)

* feat(config): map provider id to SDK protocol via providerProtocol (QwenLM#5758)

Decouple provider identity from SDK routing in a backward-compatible way
(issue QwenLM#5758, Approach A), without re-introducing the modelProviders
structural change that got QwenLM#5089 reverted.

- Add a `providerProtocol` settings dictionary mapping a provider id to the
  built-in protocol (AuthType) that should route it. `modelProviders` stays a
  Record<providerId, ModelConfig[]> array; old versions ignore the new key.
- ModelRegistry resolves each provider id to a protocol (explicit map entry, or
  the id itself when built-in) and registers custom ids under that protocol,
  merging providers that share one protocol. Unmapped unknown ids are skipped
  (typo guard); the resolver is a pure function.
- Honor a custom provider's envKey/metadata: auth pre-flight and CLI model
  resolution now look up models by resolved protocol across all provider ids,
  not just the protocol key.
- Surface skipped providers (no mapping / unknown protocol) through the visible
  CLI warnings path instead of debug-only logging.
- reloadModels preserves the protocol map when omitted (undefined) and replaces
  it when given; ACP workspaceReload threads providerProtocol so long-lived
  sessions pick up changes.

End-to-end providerId identity and the ACP/VSCode wire format are deferred to a
follow-up.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(config): update generated settings schema

* fix(config): refresh provider protocol reloads

* fix(config): cover provider protocol auth reloads

* fix(config): improve provider protocol warnings

* test(serve): cover provider protocol status

* fix(config): guard provider protocol lookups

* fix(config): clarify invalid provider protocol warnings

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
pomelo-nwu added a commit that referenced this pull request Jul 1, 2026
Add Stage 0b that flatly rejects community-contributed refactor PRs
touching core infrastructure (packages/core, auth, providers, models,
config, tools, services). No exceptions — core refactors must be
maintainer-initiated with prior design discussion.

Triggered by PR #5089: 75-file refactor across core/auth/providers/models
that should never have been accepted from a community contributor.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants