Merge origin/master at f1402327fa

This commit is contained in:
Tianyi Cui
2026-08-07 23:57:23 +08:00
447 changed files with 20295 additions and 1680 deletions

View File

@@ -89,13 +89,12 @@ function wireNamespaces(): SettingsNamespaceView[] {
value: {
apiKeyEnv: 'DEEPSEEK_API_KEY',
baseURL: 'https://base',
reasoningEffort: 'high',
defaultContextWindow: 1_000_000,
maxTokens: 256_000,
models: DEFAULT_DEEPSEEK_MODELS,
},
base: { defaultContextWindow: 1_000_000, maxTokens: 256_000, models: DEFAULT_DEEPSEEK_MODELS },
user: { reasoningEffort: 'high' },
user: { baseURL: 'https://base' },
applies: 'live',
secrets: [],
revision: 0,
@@ -718,16 +717,16 @@ describe('ModelsSection', () => {
// The old path rebuilt the whole user section to clear one inherited field.
const { replace, update, mutate } = await mountSection()
fireEvent.click(screen.getByText(en.customized))
const effort = screen.getByLabelText<HTMLSelectElement>(en.effort)
expect(effort.value).toBe('high')
fireEvent.change(effort, { target: { value: '' } })
const url = screen.getByLabelText<HTMLInputElement>(en.baseUrl)
expect(url.value).toBe('https://base')
fireEvent.change(url, { target: { value: '' } })
fireEvent.click(screen.getByText(en.apply))
await waitFor(() => { expect(mutate).toHaveBeenCalledTimes(1) })
expect(replace).not.toHaveBeenCalled()
expect(update).not.toHaveBeenCalled()
expect(mutate.mock.calls[0]?.[0]).toEqual({
ns: 'llm-deepseek',
ops: [{ op: 'unset', path: ['reasoningEffort'] }],
ops: [{ op: 'unset', path: ['baseURL'] }],
expectedRevision: 0,
})
})
@@ -784,15 +783,14 @@ describe('ModelsSection', () => {
const urls = screen.getAllByLabelText<HTMLInputElement>(en.baseUrl)
expect(urls).toHaveLength(2)
expect((urls[1] as HTMLInputElement).value).toBe('https://proxy')
const effort = screen.getAllByLabelText<HTMLSelectElement>(en.effort)
fireEvent.change(effort[effort.length - 1] as HTMLSelectElement, { target: { value: 'xhigh' } })
fireEvent.change(urls[1] as HTMLInputElement, { target: { value: 'https://proxy/v2' } })
fireEvent.click(screen.getAllByText(en.apply)[1] as HTMLElement)
await waitFor(() => { expect(mutate).toHaveBeenCalledTimes(1) })
// Only the edited field travels: apiKeyEnv, baseURL and headers were
// already stored with these values, so no op restates them.
// Only the edited field travels: apiKeyEnv and headers were already stored
// with these values, so no op restates them.
expect(mutate.mock.calls[0]?.[0]).toEqual({
ns: 'llm-pi-ai',
ops: [{ op: 'set', path: ['providers', 'openai', 'reasoning'], value: 'xhigh' }],
ops: [{ op: 'set', path: ['providers', 'openai', 'baseURL'], value: 'https://proxy/v2' }],
expectedRevision: 0,
})
})

View File

@@ -9,7 +9,7 @@ import { ModelsSection } from '../src/client/ModelsSection.tsx'
import type { ModelsSectionInjected } from '../src/client/ModelsSection.tsx'
import { CustomProviderCard } from '../src/client/CustomProviderCard.tsx'
import { formatCapacity, parseCapacity } from '../src/client/DeepSeekModelsEditor.tsx'
import { ModelsSettingsStore, protocolChoices } from '../src/client/store.ts'
import { ModelsSettingsStore, deriveKeyRef, protocolChoices } from '../src/client/store.ts'
import { en } from '../src/client/locales.ts'
afterEach(cleanup)
@@ -66,6 +66,8 @@ function scriptedFace(options: {
providers?: Record<string, unknown>
/** User layer, when it differs from the effective section. */
userProviders?: Record<string, unknown>
/** Routes the adapter reports as hand-declared; the rest come back as shipped. */
declaredRoutes?: readonly string[]
discover?: ReturnType<typeof vi.fn>
mutate?: ReturnType<typeof vi.fn>
set?: ReturnType<typeof vi.fn>
@@ -86,6 +88,7 @@ function scriptedFace(options: {
settingsNs: 'llm-pi-ai',
settingsPath: ['providers', provider],
active: true,
declared: options.declaredRoutes?.includes(provider) ?? false,
})),
}))),
models: vi.fn(() => Promise.resolve(ok({ groups: [], failures: [] }))),
@@ -599,9 +602,59 @@ describe('endpoint interrogation', () => {
})
})
describe('provider rows', () => {
it('tags the routes the adapter declared, and only those', async () => {
await mountSection({
providers: {
openai: { apiKeyEnv: 'OPENAI_API_KEY' },
'acme-gateway': { apiKeyEnv: 'ACME_GATEWAY_API_KEY', baseURL: 'https://acme.test/v1' },
},
declaredRoutes: ['acme-gateway'],
})
const rowOf = (provider: string): HTMLElement => {
const row = screen.getByText(provider).closest('li')
if (row === null) throw new Error(`no row for ${provider}`)
return row
}
expect(rowOf('acme-gateway').textContent).toContain(en.customTag)
// `openai` carries a stored profile too — the tag follows the adapter's
// catalog, not the presence of settings, so it stays off here.
expect(rowOf('openai').textContent).not.toContain(en.customTag)
})
it('shows no tag when the adapter draws no catalog distinction', async () => {
const scripted = scriptedFace({ providers: { openai: { apiKeyEnv: 'OPENAI_API_KEY' } } })
scripted.face.llm.providers = vi.fn(() => Promise.resolve(ok({
providers: [{
provider: 'openai',
displayName: 'openai',
settingsNs: 'llm-pi-ai',
settingsPath: ['providers', 'openai'],
active: true,
}],
}))) as never
const controller = new ModelsSettingsStore(scripted.face as unknown as WireFace)
await controller.load()
render(<ModelsSection
controller={controller}
useSnapshot={bindSnapshotSelector(controller.store)}
api={scripted.face as never}
t={t}
/>)
// Absent is "unknown", never "shipped": an adapter that answers nothing
// must not have its routes labelled either way.
expect(screen.queryByText(en.customTag)).toBeNull()
})
})
describe('hand-declared providers', () => {
function mountCard(overrides: Partial<Parameters<typeof CustomProviderCard>[0]> = {}) {
const scripted = scriptedFace()
function mountCard(
overrides: Partial<Parameters<typeof CustomProviderCard>[0]> = {},
wire: Parameters<typeof scriptedFace>[0] = {},
) {
const scripted = scriptedFace(wire)
const onClose = vi.fn()
render(
<CustomProviderCard
@@ -652,6 +705,140 @@ describe('hand-declared providers', () => {
expect(set).toHaveBeenCalledWith({ ref: 'ACME_GATEWAY_API_KEY', value: 'gw-key' })
})
it('scopes each card to fields a provider can actually own', async () => {
// Reasoning effort used to sit here. It is a per-MODEL capability and the
// models under one provider disagree about it, so a provider-scoped
// control could only be set to a value some of them reject — which took
// the whole provider out of the picker. The composer's model picker owns
// the choice, and a switch there records provider+model+effort together.
const fields = () => [...document.querySelectorAll('input,select')]
.map(el => el.getAttribute('aria-label')).filter(Boolean)
mountCard()
fireEvent.change(screen.getByLabelText(en.customRoute), { target: { value: 'acme' } })
expect(fields()).toEqual([en.customRoute, en.customDisplayName, en.baseUrl, en.customApi, en.keyInput])
cleanup()
await mountSection({ providers: { openai: { apiKeyEnv: 'OPENAI_API_KEY' } } })
openEditor('openai')
fireEvent.click(screen.getByText(en.customized))
expect(fields()).toEqual([en.keyInput, en.baseUrl])
})
it('retries only the key after the profile landed, and reports the provider on cancel', async () => {
const set = vi.fn()
.mockResolvedValueOnce(fail('credential store is read-only', 'credential-rejected'))
.mockResolvedValueOnce(ok({}))
const { mutate, onClose } = mountCard({}, { set })
fireEvent.change(screen.getByLabelText(en.customRoute), { target: { value: 'acme' } })
fireEvent.change(screen.getByLabelText(en.baseUrl), { target: { value: 'https://acme.test/v1' } })
fireEvent.change(screen.getByLabelText(en.keyInput), { target: { value: ' gw-key ' } })
fireEvent.click(screen.getByRole('button', { name: en.addModel }))
fireEvent.change(screen.getByLabelText(`${en.modelId} 1`), { target: { value: 'm' } })
fireEvent.click(screen.getByText(en.create))
// The profile landed; only the key failed. The card says so and stays open.
await waitFor(() => { expect(screen.getByText('credential store is read-only')).toBeTruthy() })
expect(onClose).not.toHaveBeenCalled()
expect(mutate).toHaveBeenCalledTimes(1)
// The key is stored trimmed, matching the editor.
expect(set).toHaveBeenNthCalledWith(1, { ref: 'ACME_API_KEY', value: 'gw-key' })
// The provider exists now, so the fields describing it are settled and
// only the key can still be corrected.
expect(screen.getByLabelText<HTMLInputElement>(en.customRoute).disabled).toBe(true)
expect(screen.getByLabelText<HTMLInputElement>(en.baseUrl).disabled).toBe(true)
expect(screen.getByLabelText<HTMLInputElement>(en.keyInput).disabled).toBe(false)
fireEvent.change(screen.getByLabelText(en.keyInput), { target: { value: 'gw-key-2' } })
fireEvent.click(screen.getByText(en.create))
await waitFor(() => { expect(onClose).toHaveBeenCalledWith(true) })
// Re-running the profile write would carry the revision this card's own
// first write superseded, so the Host would answer settings-conflict and
// the key could never be stored from here at all.
expect(mutate).toHaveBeenCalledTimes(1)
expect(set).toHaveBeenNthCalledWith(2, { ref: 'ACME_API_KEY', value: 'gw-key-2' })
})
it('reports the created provider when cancelled after its profile landed', async () => {
const set = vi.fn().mockResolvedValue(fail('nope', 'credential-rejected'))
const { onClose } = mountCard({}, { set })
fireEvent.change(screen.getByLabelText(en.customRoute), { target: { value: 'acme' } })
fireEvent.change(screen.getByLabelText(en.baseUrl), { target: { value: 'https://acme.test/v1' } })
fireEvent.change(screen.getByLabelText(en.keyInput), { target: { value: 'gw-key' } })
fireEvent.click(screen.getByRole('button', { name: en.addModel }))
fireEvent.change(screen.getByLabelText(`${en.modelId} 1`), { target: { value: 'm' } })
fireEvent.click(screen.getByText(en.create))
await waitFor(() => { expect(screen.getByText('nope')).toBeTruthy() })
// Walking away leaves a real provider behind; reporting no change would
// leave the page without the row it now has.
fireEvent.click(screen.getByText(en.cancel))
expect(onClose).toHaveBeenCalledWith(true)
})
it('never contradicts a filled-in field with the next gate\u2019s copy', () => {
mountCard()
const routeField = screen.getByLabelText(en.customRoute)
fireEvent.change(routeField, { target: { value: '2' } })
fireEvent.change(screen.getByLabelText(en.baseUrl), { target: { value: 'https://acme.test/v1' } })
fireEvent.click(screen.getByRole('button', { name: en.addModel }))
fireEvent.change(screen.getByLabelText(`${en.modelId} 1`), { target: { value: 'm' } })
// The route field explains itself right under the input; the shared line
// must stay silent rather than falling through to "no models yet" while
// the list above plainly has one.
expect(screen.getByText(en.customRouteInvalid)).toBeTruthy()
expect(screen.queryByText(en.customNeedsModels)).toBeNull()
// Fixing the route hands the line back to the gate that is actually unmet.
fireEvent.change(routeField, { target: { value: 'acme' } })
expect(screen.queryByText(en.customNeedsModels)).toBeNull()
expect(buttonNamed(en.create).disabled).toBe(false)
})
it('refuses a route id whose derived credential reference would be illegal', () => {
mountCard()
const routeField = screen.getByLabelText(en.customRoute)
fireEvent.change(routeField, { target: { value: 'https://acme.test/v1' } })
// A digit-leading id used to pass every check this card makes and then
// fail at the credential seam with a raw regular expression: the
// reference derives as `123_API_KEY`, and a credential reference is a
// POSIX shell identifier, which cannot start with a digit.
fireEvent.change(routeField, { target: { value: '123' } })
expect(screen.getByText(en.customRouteInvalid)).toBeTruthy()
expect(buttonNamed(en.create).disabled).toBe(true)
fireEvent.change(routeField, { target: { value: 'a1' } })
expect(screen.queryByText(en.customRouteInvalid)).toBeNull()
})
it('styles a rejected route id as a fault and its guidance as a hint', () => {
mountCard()
const routeField = screen.getByLabelText(en.customRoute)
// Same split the key field makes: what the user got wrong reads as a
// fault, what they have yet to do reads as guidance.
expect(screen.getByText(en.customRouteHint).className).toMatch(/advancedHint/)
fireEvent.change(routeField, { target: { value: '2' } })
expect(screen.getByText(en.customRouteInvalid).className).toMatch(/error/)
fireEvent.change(routeField, { target: { value: 'openai' } })
expect(screen.getByText(en.customRouteTaken).className).toMatch(/error/)
})
it('derives a reference the credential seam accepts for every id it admits', () => {
// The two rules have to stay in step; this is the relation, checked
// directly rather than through the DOM.
const CREDENTIAL_REF = /^[A-Za-z_][A-Za-z0-9_]*$/
for (const id of ['a', 'ds', 'a1', 'acme-gateway', 'x-1-y', 'zz9']) {
expect(CREDENTIAL_REF.test(deriveKeyRef(id))).toBe(true)
}
})
it('names the blocked gate under the form, and nothing once it is satisfied', () => {
mountCard()
fireEvent.change(screen.getByLabelText(en.customRoute), { target: { value: 'acme' } })
@@ -815,8 +1002,10 @@ describe('hand-declared providers', () => {
await waitFor(() => { expect(onClose).toHaveBeenCalledWith(true) })
// No display name configured means none stored; the route id is the name.
// No key typed means no reference either, matching the editor: the route
// keeps its provider-native auth path instead of resolving a reference
// nothing ever sets. The with-key case is covered above.
expect(firstMutate(mutate).ops[0]?.value).toEqual({
apiKeyEnv: 'ACME_API_KEY',
api: 'anthropic-messages',
baseURL: 'https://acme.test/v1',
models: [{ id: 'm' }],