feat: eager vLLM warmup and smart page titles in paginate stage

- Add warmup() to LlmProvider interface for eager subprocess startup
- ManagedVllmProvider.warmup() starts vLLM in background on project load
- ProviderRegistry.warmupAll() triggers all managed providers
- NamedProvider proxies warmup() to inner provider
- paginate stage generates LLM-powered descriptive page titles when
  available, cached by content hash, falls back to generic "Page N"
- project-mcp-endpoint calls warmupAll() on router creation so vLLM
  is loading while the session initializes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Michal
2026-03-03 19:07:39 +00:00
parent 0427d7dc1a
commit 03827f11e4
147 changed files with 17561 additions and 2093 deletions

View File

@@ -0,0 +1,228 @@
import { describe, it, expect } from 'vitest';
import type { ProxyModelPlugin, PluginSessionContext } from '../src/proxymodel/plugin.js';
import { PluginRegistry, resolveInheritance, loadPlugins } from '../src/proxymodel/plugin-loader.js';
function stubCtx(): PluginSessionContext {
return {} as PluginSessionContext;
}
describe('PluginRegistry', () => {
it('registers and resolves plugins', () => {
const registry = new PluginRegistry();
const plugin: ProxyModelPlugin = { name: 'test' };
registry.register({ name: 'test', plugin, source: 'built-in' });
expect(registry.resolve('test')).toBe(plugin);
expect(registry.has('test')).toBe(true);
expect(registry.resolve('nonexistent')).toBeNull();
expect(registry.has('nonexistent')).toBe(false);
});
it('lists all registered plugins', () => {
const registry = new PluginRegistry();
registry.register({ name: 'a', plugin: { name: 'a' }, source: 'built-in' });
registry.register({ name: 'b', plugin: { name: 'b' }, source: 'local' });
const list = registry.list();
expect(list).toHaveLength(2);
expect(list.map((e) => e.name)).toEqual(['a', 'b']);
});
});
describe('resolveInheritance', () => {
it('returns plugin unchanged when no extends', () => {
const registry = new PluginRegistry();
const plugin: ProxyModelPlugin = { name: 'simple' };
const resolved = resolveInheritance(plugin, registry);
expect(resolved.name).toBe('simple');
});
it('inherits hooks from single parent', async () => {
const registry = new PluginRegistry();
const parent: ProxyModelPlugin = {
name: 'parent',
async onToolsList(tools) {
return tools.filter((t) => t.name !== 'hidden');
},
};
registry.register({ name: 'parent', plugin: parent, source: 'built-in' });
const child: ProxyModelPlugin = { name: 'child', extends: ['parent'] };
registry.register({ name: 'child', plugin: child, source: 'built-in' });
const resolved = resolveInheritance(child, registry);
expect(resolved.onToolsList).toBeDefined();
const tools = [
{ name: 'visible', description: 'yes', inputSchema: {} },
{ name: 'hidden', description: 'no', inputSchema: {} },
];
const result = await resolved.onToolsList!(tools, stubCtx());
expect(result).toHaveLength(1);
expect(result[0]!.name).toBe('visible');
});
it('child hook overrides parent hook', async () => {
const registry = new PluginRegistry();
const parent: ProxyModelPlugin = {
name: 'parent',
async onToolsList(tools) {
return tools.filter((t) => t.name !== 'hidden');
},
};
registry.register({ name: 'parent', plugin: parent, source: 'built-in' });
const child: ProxyModelPlugin = {
name: 'child',
extends: ['parent'],
async onToolsList(tools) {
return [...tools, { name: 'added', description: 'new', inputSchema: {} }];
},
};
registry.register({ name: 'child', plugin: child, source: 'built-in' });
const resolved = resolveInheritance(child, registry);
const result = await resolved.onToolsList!(
[{ name: 'existing', description: 'yes', inputSchema: {} }],
stubCtx(),
);
expect(result).toHaveLength(2);
expect(result.map((t) => t.name)).toEqual(['existing', 'added']);
});
it('detects conflict when two parents define the same non-chainable hook', () => {
const registry = new PluginRegistry();
const parentA: ProxyModelPlugin = {
name: 'parent-a',
async onToolsList(tools) { return tools; },
};
const parentB: ProxyModelPlugin = {
name: 'parent-b',
async onToolsList(tools) { return tools; },
};
registry.register({ name: 'parent-a', plugin: parentA, source: 'built-in' });
registry.register({ name: 'parent-b', plugin: parentB, source: 'built-in' });
const child: ProxyModelPlugin = {
name: 'child',
extends: ['parent-a', 'parent-b'],
};
registry.register({ name: 'child', plugin: child, source: 'built-in' });
expect(() => resolveInheritance(child, registry)).toThrow(/onToolsList.*parent-a.*parent-b/);
});
it('resolves conflict when child overrides the conflicting hook', async () => {
const registry = new PluginRegistry();
const parentA: ProxyModelPlugin = {
name: 'parent-a',
async onToolsList(tools) { return tools.slice(0, 1); },
};
const parentB: ProxyModelPlugin = {
name: 'parent-b',
async onToolsList(tools) { return tools.slice(1); },
};
registry.register({ name: 'parent-a', plugin: parentA, source: 'built-in' });
registry.register({ name: 'parent-b', plugin: parentB, source: 'built-in' });
const child: ProxyModelPlugin = {
name: 'child',
extends: ['parent-a', 'parent-b'],
async onToolsList(tools) { return tools; },
};
registry.register({ name: 'child', plugin: child, source: 'built-in' });
const resolved = resolveInheritance(child, registry);
const tools = [
{ name: 'a', description: '', inputSchema: {} },
{ name: 'b', description: '', inputSchema: {} },
];
const result = await resolved.onToolsList!(tools, stubCtx());
expect(result).toHaveLength(2);
});
it('chains lifecycle hooks from multiple parents', async () => {
const registry = new PluginRegistry();
const order: string[] = [];
const parentA: ProxyModelPlugin = {
name: 'parent-a',
async onSessionCreate() { order.push('a'); },
};
const parentB: ProxyModelPlugin = {
name: 'parent-b',
async onSessionCreate() { order.push('b'); },
};
registry.register({ name: 'parent-a', plugin: parentA, source: 'built-in' });
registry.register({ name: 'parent-b', plugin: parentB, source: 'built-in' });
const child: ProxyModelPlugin = {
name: 'child',
extends: ['parent-a', 'parent-b'],
};
registry.register({ name: 'child', plugin: child, source: 'built-in' });
const resolved = resolveInheritance(child, registry);
await resolved.onSessionCreate!(stubCtx());
expect(order).toEqual(['a', 'b']);
});
it('detects circular inheritance', () => {
const registry = new PluginRegistry();
const a: ProxyModelPlugin = { name: 'a', extends: ['b'] };
const b: ProxyModelPlugin = { name: 'b', extends: ['a'] };
registry.register({ name: 'a', plugin: a, source: 'built-in' });
registry.register({ name: 'b', plugin: b, source: 'built-in' });
expect(() => resolveInheritance(a, registry)).toThrow(/Circular/);
});
it('errors when extending unknown parent', () => {
const registry = new PluginRegistry();
const child: ProxyModelPlugin = { name: 'child', extends: ['nonexistent'] };
registry.register({ name: 'child', plugin: child, source: 'built-in' });
expect(() => resolveInheritance(child, registry)).toThrow(/unknown plugin 'nonexistent'/);
});
it('resolves deep inheritance (grandparent)', async () => {
const registry = new PluginRegistry();
const grandparent: ProxyModelPlugin = {
name: 'grandparent',
async onToolCallAfter(_toolName, _args, response) { return response; },
};
const parent: ProxyModelPlugin = { name: 'parent', extends: ['grandparent'] };
const child: ProxyModelPlugin = { name: 'child', extends: ['parent'] };
registry.register({ name: 'grandparent', plugin: grandparent, source: 'built-in' });
registry.register({ name: 'parent', plugin: parent, source: 'built-in' });
registry.register({ name: 'child', plugin: child, source: 'built-in' });
const resolved = resolveInheritance(child, registry);
expect(resolved.onToolCallAfter).toBeDefined();
});
});
describe('loadPlugins', () => {
it('loads built-in plugins into registry', async () => {
const pluginA: ProxyModelPlugin = { name: 'a' };
const pluginB: ProxyModelPlugin = { name: 'b', extends: ['a'] };
const registry = await loadPlugins([pluginA, pluginB], '/tmp/nonexistent-plugins-dir');
expect(registry.has('a')).toBe(true);
expect(registry.has('b')).toBe(true);
expect(registry.list()).toHaveLength(2);
});
it('resolves inheritance during load', async () => {
const parent: ProxyModelPlugin = {
name: 'parent',
async onToolsList(tools) { return tools; },
};
const child: ProxyModelPlugin = { name: 'child', extends: ['parent'] };
const registry = await loadPlugins([parent, child], '/tmp/nonexistent-plugins-dir');
const resolved = registry.resolve('child');
expect(resolved).toBeDefined();
expect(resolved!.onToolsList).toBeDefined();
});
});