feat(gateway): global tier provider/model defaults with catalog-backed options

This commit is contained in:
William Valentin
2026-02-19 10:17:16 -08:00
parent 5883e046ac
commit 708683297a
7 changed files with 495 additions and 5 deletions
+79
View File
@@ -106,6 +106,42 @@ describe('system handlers', () => {
]);
});
it('system.modelCatalog returns empty providers when callback is not provided', async () => {
const req: GatewayRequest = { id: 33, method: 'system.modelCatalog' };
const result = await handlers['system.modelCatalog'](req) as GatewayResponse;
expect(getPath(result.result, 'providers')).toEqual([]);
});
it('system.modelCatalog returns provider models from callback', async () => {
const getModelCatalog = vi.fn(async () => [
{
provider: 'openai',
models: ['gpt-4o-mini', 'gpt-4.1'],
source: 'api' as const,
fetchedAt: 123,
},
]);
const handlers = createSystemHandlers({
...deps,
getModelCatalog,
});
const req: GatewayRequest = {
id: 34,
method: 'system.modelCatalog',
params: { provider: 'openai', forceRefresh: true },
};
const result = await handlers['system.modelCatalog'](req) as GatewayResponse;
expect(getModelCatalog).toHaveBeenCalledWith({ provider: 'openai', forceRefresh: true });
expect(getPath(result.result, 'providers')).toEqual([
{
provider: 'openai',
models: ['gpt-4o-mini', 'gpt-4.1'],
source: 'api',
fetchedAt: 123,
},
]);
});
it('system.presence returns empty result when getPresence is not provided', async () => {
const req: GatewayRequest = { id: 4, method: 'system.presence' };
const result = await handlers['system.presence'](req) as GatewayResponse;
@@ -1302,6 +1338,49 @@ describe('config handlers', () => {
expect(result.error.code).toBe(ErrorCode.InvalidRequest);
});
it('config.patch updates tier provider/model defaults and syncs runtime model router', async () => {
const config = makeConfig();
const modelRouter = {
setClient: vi.fn(),
setTierStrict: vi.fn(),
} as unknown as NonNullable<Parameters<typeof createConfigHandlers>[0]['modelRouter']>;
const handlers = createConfigHandlers({
config: asConfigValue(config),
modelRouter,
});
const req: GatewayRequest = {
id: 8,
method: 'config.patch',
params: {
patches: {
'models.default.provider': 'synthetic',
'models.default.model': 'synthetic-default',
'models.fast.provider': 'synthetic',
'models.fast.model': 'synthetic-fast',
},
},
};
const result = await handlers['config.patch'](req) as GatewayResponse;
const r = result.result as { applied: string[]; rejected: string[]; persisted: boolean };
expect(r.applied).toEqual([
'models.default.provider',
'models.default.model',
'models.fast.provider',
'models.fast.model',
]);
expect(r.rejected).toEqual([]);
expect(r.persisted).toBe(false);
expect(getPath(config, 'models', 'default', 'provider')).toBe('synthetic');
expect(getPath(config, 'models', 'default', 'model')).toBe('synthetic-default');
expect(getPath(config, 'models', 'fast', 'provider')).toBe('synthetic');
expect(getPath(config, 'models', 'fast', 'model')).toBe('synthetic-fast');
expect(modelRouter.setClient).toHaveBeenCalledWith('default', expect.any(Object), 'synthetic/synthetic-default');
expect(modelRouter.setClient).toHaveBeenCalledWith('fast', expect.any(Object), 'synthetic/synthetic-fast');
expect(modelRouter.setTierStrict).toHaveBeenCalledWith('default', false);
expect(modelRouter.setTierStrict).toHaveBeenCalledWith('fast', false);
});
});
describe('redactConfig comprehensive credential redaction', () => {