Files
zeekay 83464f5d54 One default model on every surface: zen5
Office, Outlook, PDF, Clio, Meetings, Google Workspace and Salesforce
defaulted to zen-1, Teams offered zen-eco-3b, zen-mini-8b and zen-4b,
and JetBrains defaulted to claude-3-5-sonnet-20241022 with a fallback
list of retired upstream ids. None of those are in api.hanzo.ai/v1/models,
so a first request on each of them named a model the gateway does not
serve.

zen5 is the id the catalog itself marks as the default: catalog.hanzo.ai
/v1/pricing lists it as "Zen5 (Default)", "the everyday Zen5 chat model",
and nineteen other surfaces here already defaulted to it. Teams now offers
the catalog's Zen 5 family with zen5 first. JetBrains preselects zen5
when the live list carries it, and offers only zen5 when it cannot load
the list, rather than a fabricated one.

Claude-Session: https://claude.ai/code/session_01Dc8DXc3fEQCRHUa6XWK4Ku
2026-09-29 12:29:56 -07:00

74 lines
2.6 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import { loadConfig, DEFAULT_MODELS, type Env } from '../src/config';
const base: Env = {
MICROSOFT_APP_ID: 'app-id',
MICROSOFT_APP_PASSWORD: 'app-secret',
HANZO_API_KEY: 'sk-test',
};
describe('loadConfig', () => {
it('loads required vars and applies defaults', () => {
const c = loadConfig(base);
expect(c.appId).toBe('app-id');
expect(c.appPassword).toBe('app-secret');
expect(c.hanzoApiKey).toBe('sk-test');
expect(c.appType).toBe('MultiTenant');
expect(c.appTenantId).toBe('');
expect(c.port).toBe(3978);
expect(c.models).toEqual([...DEFAULT_MODELS]);
expect(c.defaultModel).toBe(DEFAULT_MODELS[0]);
expect(DEFAULT_MODELS).toEqual(['zen5', 'zen5-flash', 'zen5-mini', 'zen5-coder', 'zen5-pro']);
expect(c.defaultModel).toBe('zen5');
expect(c.hanzoBaseUrl).toBeUndefined();
});
it('is frozen', () => {
const c = loadConfig(base);
expect(Object.isFrozen(c)).toBe(true);
});
it('parses a custom model list and default model', () => {
const c = loadConfig({
...base,
HANZO_MODELS: 'zen5-flash, zen5-coder ,zen5-mini',
HANZO_DEFAULT_MODEL: 'zen5-mini',
});
expect(c.models).toEqual(['zen5-flash', 'zen5-coder', 'zen5-mini']);
expect(c.defaultModel).toBe('zen5-mini');
});
it('falls back to the first model when default is unset', () => {
const c = loadConfig({ ...base, HANZO_MODELS: 'zen5-coder,zen5' });
expect(c.defaultModel).toBe('zen5-coder');
});
it('honors a custom port and base url', () => {
const c = loadConfig({ ...base, PORT: '8080', HANZO_BASE_URL: 'http://localhost:1234' });
expect(c.port).toBe(8080);
expect(c.hanzoBaseUrl).toBe('http://localhost:1234');
});
it('throws on a missing required var', () => {
const { MICROSOFT_APP_PASSWORD: _omit, ...partial } = base;
expect(() => loadConfig(partial)).toThrow(/MICROSOFT_APP_PASSWORD/);
});
it('throws on an invalid port', () => {
expect(() => loadConfig({ ...base, PORT: '0' })).toThrow(/PORT/);
expect(() => loadConfig({ ...base, PORT: '99999' })).toThrow(/PORT/);
expect(() => loadConfig({ ...base, PORT: 'abc' })).toThrow(/PORT/);
});
it('rejects a port with trailing garbage instead of truncating it', () => {
expect(() => loadConfig({ ...base, PORT: '80x' })).toThrow(/PORT/);
expect(() => loadConfig({ ...base, PORT: '3978 #http' })).toThrow(/PORT/);
});
it('rejects a default model that is not in the model list', () => {
expect(() =>
loadConfig({ ...base, HANZO_MODELS: 'zen5-flash', HANZO_DEFAULT_MODEL: 'zen-99b' }),
).toThrow(/HANZO_DEFAULT_MODEL/);
});
});