feat(session): persist model tier overrides per session

Store per-session config in SQLite and route /model and /reset through command fast-paths so channel sessions keep independent model selection across reconnects and restarts.
This commit is contained in:
William Valentin
2026-02-13 01:04:26 -08:00
parent 3472a0b926
commit 9f81c01603
35 changed files with 1438 additions and 144 deletions
+89
View File
@@ -9,6 +9,9 @@ const mockSession = {
getHistory: vi.fn(() => []),
clear: vi.fn(),
replaceHistory: vi.fn(),
getConfig: vi.fn((_key: string) => undefined as string | undefined),
setConfig: vi.fn(),
deleteConfig: vi.fn(),
};
const mockSessionManager = {
@@ -48,9 +51,21 @@ function createBridge(): SessionBridge {
});
}
function createBridgeWithConfig(config: SessionBridgeConfig['config']): SessionBridge {
return new SessionBridge({
sessionManager: mockSessionManager as unknown as SessionBridgeConfig['sessionManager'],
modelClient: mockModelClient,
systemPrompt: 'test prompt',
toolRegistry: mockToolRegistry as unknown as SessionBridgeConfig['toolRegistry'],
toolExecutor: mockToolExecutor as unknown as SessionBridgeConfig['toolExecutor'],
config,
});
}
describe('SessionBridge', () => {
beforeEach(() => {
vi.clearAllMocks();
mockSession.getConfig.mockImplementation((_key: string) => undefined);
});
it('connect assigns a connection ID', () => {
@@ -142,4 +157,78 @@ describe('SessionBridge', () => {
expect(bridge.getAgent('conn-2')).toBeDefined();
expect(bridge.connectionCount).toBe(1);
});
it('loads model tier from per-session config when creating a session agent', () => {
mockSession.getConfig.mockImplementation((key: string) => (key === 'modelTier' ? 'local' : undefined));
const bridge = createBridgeWithConfig({
agents: {
primary_tier: 'default',
delegation: {
compaction: 'fast',
memory_extraction: 'fast',
classification: 'fast',
tool_summarisation: 'fast',
complex_reasoning: 'complex',
},
max_delegation_depth: 3,
},
compaction: { enabled: false },
models: { default: { provider: 'anthropic', model: 'claude-3-haiku' } },
} as any);
bridge.connect('conn-tier');
const agent = bridge.getAgent('conn-tier');
expect(agent?.getModelTier()).toBe('local');
});
it('keeps different sessions isolated by persisted model tier', () => {
const sessionById: Record<string, any> = {};
const localSessionManager = {
...mockSessionManager,
getSession: vi.fn((frontend: string, sessionId: string) => {
const fullId = `${frontend}:${sessionId}`;
if (!sessionById[fullId]) {
const tier = fullId === 'ws:ws:conn-a' ? 'fast' : 'complex';
sessionById[fullId] = {
...mockSession,
id: fullId,
getConfig: vi.fn((key: string) => (key === 'modelTier' ? tier : undefined)),
};
}
return sessionById[fullId];
}),
};
const bridge = new SessionBridge({
sessionManager: localSessionManager as unknown as SessionBridgeConfig['sessionManager'],
modelClient: mockModelClient,
systemPrompt: 'test prompt',
toolRegistry: mockToolRegistry as unknown as SessionBridgeConfig['toolRegistry'],
toolExecutor: mockToolExecutor as unknown as SessionBridgeConfig['toolExecutor'],
config: {
agents: {
primary_tier: 'default',
delegation: {
compaction: 'fast',
memory_extraction: 'fast',
classification: 'fast',
tool_summarisation: 'fast',
complex_reasoning: 'complex',
},
max_delegation_depth: 3,
},
compaction: { enabled: false },
models: { default: { provider: 'anthropic', model: 'claude-3-haiku' } },
} as any,
});
bridge.connect('conn-a');
bridge.connect('conn-b');
const agentA = bridge.getAgent('conn-a');
const agentB = bridge.getAgent('conn-b');
expect(agentA?.getModelTier()).toBe('fast');
expect(agentB?.getModelTier()).toBe('complex');
});
});