feat(ui-models): drop the provider-scoped reasoning effort, and red-flag a bad route id

**Reasoning effort leaves the provider cards entirely.** It is a per-MODEL
capability and the models under one provider disagree about which levels
they accept: setting `anthropic` to `max` made six of its eight models
throw UNSUPPORTED_REASONING_EFFORT, and because the catalog build catches
per provider, the whole provider vanished from the picker behind one error
row. A provider-scoped control can only ever be set to a value some of its
models reject.

The composer's model picker already offers each model its own levels, and
a switch there now records provider, model, and effort together as the
next session's default — so the setting has a better home at the right
granularity. The profile field stays in `settings.yaml` for a deployment
that knows its route; only the control is gone, from both cards and both
adapter families. Two `components.spec` cases used the control as the
vehicle for their op assertions and now use `baseURL`, which is what they
were actually testing.

**A rejected Provider ID now reads as a fault.** It shared the neutral
hint paragraph with the field's guidance, so the copy telling the user
what they got wrong looked like advice. Reuses the existing `.error`
style, matching the split the key field already makes.
This commit is contained in:
Yichen Jiang
2026-08-07 17:36:08 +08:00
parent 135064c831
commit 2dc1406dfd
12 changed files with 70 additions and 178 deletions

View File

@@ -159,23 +159,23 @@ describe('web e2e: Models settings page configures a dormant provider', () => {
const dialog = page.getByRole('dialog', { name: '设置' })
await dialog.getByRole('button', { name: '编辑 minimax-cn' }).click()
await dialog.getByText('自定义设置').click()
const effort = dialog.getByLabel('推理强度')
await effort.waitFor({ timeout: 10_000 })
await effort.selectOption('high')
const url = dialog.getByLabel('API 地址')
await url.waitFor({ timeout: 10_000 })
await url.fill('https://gateway.minimax.example/v1')
await dialog.getByRole('button', { name: '保存', exact: true }).click()
// The editor closes back to the row; the fold's write merged into the
// stored profile beside the reference.
await expect.poll(async () => dialog.getByLabel('推理强度').count(), { timeout: 10_000 }).toBe(0)
await expect.poll(async () => dialog.getByLabel('API 地址').count(), { timeout: 10_000 }).toBe(0)
await dialog.getByText('已保存 minimax-cn。', { exact: true }).waitFor({ timeout: 10_000 })
const document = await readFile(join(scaffold.harnessHome, 'settings.yaml'), 'utf8')
expect(document).toContain('reasoning: high')
expect(document).toContain('baseURL: https://gateway.minimax.example/v1')
expect(document).toContain('apiKeyEnv: MINIMAX_CN_API_KEY')
const snapshot = await captureStableAria(page, '[role="dialog"]', scaffold.workspaceCwd)
await compareOrRefreshGolden(CONFIGURED_EXPECTED, snapshot, MODE)
expect(tripwire.pageErrors).toEqual([])
}, 60_000)
it('declares a route the adapter does not ship, without a reasoning control', async () => {
it('declares a route the adapter does not ship', async () => {
onTestFailed(() => saveFailureShot(page, 'web-e2e-models-declare'))
const dialog = page.getByRole('dialog', { name: '设置' })
const declare = dialog.getByRole('button', { name: '添加自定义提供方' })
@@ -184,9 +184,9 @@ describe('web e2e: Models settings page configures a dormant provider', () => {
await dialog.getByLabel('Provider ID').fill('acme-gateway')
await dialog.getByLabel('显示名称').fill('Acme Gateway')
await dialog.getByLabel('API 地址').fill('https://gateway.acme.example/v1')
// No reasoning effort anywhere for a hand-declared route: its models carry
// no reasoning capability, so a profile effort would make every model on
// the route fail to resolve and drop the provider out of the picker.
// No reasoning effort on a provider card at all: effort is a per-model
// capability, the models under one provider disagree about it, and a
// switch in the composer already records provider+model+effort together.
expect(await dialog.getByLabel('推理强度').count()).toBe(0)
await dialog.getByRole('button', { name: '添加模型' }).click()
await dialog.getByLabel('模型 ID 1').fill('acme-large')

View File

@@ -25,12 +25,6 @@
- text: 自定义设置 API 地址
- textbox "API 地址":
- /placeholder: https://api.deepseek.com
- text: 推理强度
- combobox "推理强度":
- option "默认" [selected]
- option "off"
- option "high"
- option "max"
- region "模型目录":
- text: 模型目录 已自定义模型目录
- button "恢复默认模型"