Fix: getProviderSnapshot returned synchronous installed:false 'loading' entries on a cache miss (v2.5.5/Phase 2), which AgentComposerBar filters out — with the Phase 5 client poll not yet built, a single fetch stranded on 'loading' and the picker showed no providers. It now awaits the build and returns terminal entries; the sync loading-return is deferred until Phase 5. Builds stay fast via the tier-2 cold-probe skip. Feature: wire the v2.3 config schema's models/additionalModels — buildResolvedRegistry carries them onto ResolvedProviderDef (models replace, additionalModels merge) and provider-snapshot applies them to every ready model list, so /data/coder-providers.json can edit any provider's models with no code change. Claude staticModels bumped from the stale 2-entry list to opus/sonnet/haiku latest-aliases + pinned claude-opus-4-8 / claude-sonnet-4-6 / claude-haiku-4-5-20251001 (passed verbatim to claude --model). +2 tests (109 total). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
94 lines
3.8 KiB
TypeScript
94 lines
3.8 KiB
TypeScript
import { describe, it, expect, vi } from 'vitest';
|
|
import { buildResolvedRegistry } from '../provider-config-registry.js';
|
|
import { PROVIDERS } from '../provider-registry.js';
|
|
import type { CoderProvidersFile } from '../provider-config.js';
|
|
|
|
describe('buildResolvedRegistry', () => {
|
|
it('applies a built-in override (goose label)', () => {
|
|
const config: CoderProvidersFile = { providers: { goose: { label: 'Goosey' } } };
|
|
const reg = buildResolvedRegistry(PROVIDERS, config);
|
|
const goose = reg.get('goose');
|
|
expect(goose).toBeDefined();
|
|
expect(goose!.label).toBe('Goosey');
|
|
expect(goose!.configLabel).toBe('Goosey');
|
|
expect(goose!.enabled).toBe(true);
|
|
expect(goose!.isBuiltin).toBe(true);
|
|
expect(goose!.isCustomAcp).toBe(false);
|
|
});
|
|
|
|
it('adds a custom ACP entry (extends:acp + label + command)', () => {
|
|
const config: CoderProvidersFile = {
|
|
providers: {
|
|
'amp-acp': { extends: 'acp', label: 'Amp', description: 'ACP wrapper', command: ['amp-acp', '--acp'], env: { AMP: '1' } },
|
|
},
|
|
};
|
|
const reg = buildResolvedRegistry(PROVIDERS, config);
|
|
const amp = reg.get('amp-acp');
|
|
expect(amp).toBeDefined();
|
|
expect(amp!.isCustomAcp).toBe(true);
|
|
expect(amp!.isBuiltin).toBe(false);
|
|
expect(amp!.transport).toBe('acp');
|
|
expect(amp!.modelSource).toBe('probe');
|
|
expect(amp!.launchCommand).toEqual(['amp-acp', '--acp']);
|
|
expect(amp!.env).toEqual({ AMP: '1' });
|
|
expect(amp!.enabled).toBe(true);
|
|
});
|
|
|
|
it('keeps a disabled built-in in the registry flagged disabled (goose)', () => {
|
|
const config: CoderProvidersFile = { providers: { goose: { enabled: false } } };
|
|
const reg = buildResolvedRegistry(PROVIDERS, config);
|
|
expect(reg.has('goose')).toBe(true);
|
|
expect(reg.get('goose')!.enabled).toBe(false);
|
|
});
|
|
|
|
it('skips a custom id without extends (no throw)', () => {
|
|
const config: CoderProvidersFile = { providers: { weird: { label: 'Weird', command: ['weird'] } } };
|
|
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
const reg = buildResolvedRegistry(PROVIDERS, config);
|
|
expect(reg.has('weird')).toBe(false);
|
|
// built-ins untouched
|
|
expect(reg.size).toBe(PROVIDERS.length);
|
|
expect(warn).toHaveBeenCalled();
|
|
warn.mockRestore();
|
|
});
|
|
|
|
it('ignores enabled:false on boocode and warns', () => {
|
|
const config: CoderProvidersFile = { providers: { boocode: { enabled: false } } };
|
|
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
const reg = buildResolvedRegistry(PROVIDERS, config);
|
|
expect(reg.get('boocode')!.enabled).toBe(true);
|
|
expect(warn).toHaveBeenCalled();
|
|
warn.mockRestore();
|
|
});
|
|
|
|
it('carries config models + additionalModels onto built-in and custom defs', () => {
|
|
const reg = buildResolvedRegistry(PROVIDERS, {
|
|
providers: {
|
|
claude: { models: [{ id: 'claude-opus-4-8', label: 'Opus 4.8' }] },
|
|
'amp-acp': {
|
|
extends: 'acp',
|
|
label: 'Amp',
|
|
command: ['amp-acp'],
|
|
additionalModels: [{ id: 'amp-1', label: 'Amp 1' }],
|
|
},
|
|
},
|
|
});
|
|
expect(reg.get('claude')!.configModels).toEqual([{ id: 'claude-opus-4-8', label: 'Opus 4.8' }]);
|
|
expect(reg.get('amp-acp')!.configAdditionalModels).toEqual([{ id: 'amp-1', label: 'Amp 1' }]);
|
|
});
|
|
|
|
it('REGRESSION: empty config returns exactly the built-ins, all enabled', () => {
|
|
const reg = buildResolvedRegistry(PROVIDERS, { providers: {} });
|
|
expect(reg.size).toBe(PROVIDERS.length);
|
|
expect([...reg.keys()]).toEqual(PROVIDERS.map((p) => p.name));
|
|
for (const def of PROVIDERS) {
|
|
const r = reg.get(def.name)!;
|
|
expect(r.enabled).toBe(true);
|
|
expect(r.isBuiltin).toBe(true);
|
|
expect(r.isCustomAcp).toBe(false);
|
|
expect(r.launchCommand).toBeNull();
|
|
expect(r.label).toBe(def.label);
|
|
}
|
|
});
|
|
});
|