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,55 @@
import { describe, it, expect } from 'vitest';
import { getBuiltInProxyModels } from '../src/proxymodel/built-in-models.js';
import { validateProxyModel } from '../src/proxymodel/schema.js';
describe('built-in proxymodels', () => {
it('provides default and subindex models', () => {
const models = getBuiltInProxyModels();
expect(models.has('default')).toBe(true);
expect(models.has('subindex')).toBe(true);
expect(models.size).toBe(2);
});
it('default model uses passthrough + paginate', () => {
const models = getBuiltInProxyModels();
const def = models.get('default')!;
expect(def.spec.stages.map((s) => s.type)).toEqual(['passthrough', 'paginate']);
});
it('subindex model uses section-split + summarize-tree', () => {
const models = getBuiltInProxyModels();
const sub = models.get('subindex')!;
expect(sub.spec.stages.map((s) => s.type)).toEqual(['section-split', 'summarize-tree']);
});
it('all built-in models pass schema validation', () => {
const models = getBuiltInProxyModels();
for (const [name, model] of models) {
expect(() => validateProxyModel(model, 'built-in')).not.toThrow();
}
});
it('default model is not cacheable', () => {
const models = getBuiltInProxyModels();
expect(models.get('default')!.spec.cacheable).toBe(false);
});
it('subindex model is cacheable', () => {
const models = getBuiltInProxyModels();
expect(models.get('subindex')!.spec.cacheable).toBe(true);
});
it('both models use gate controller', () => {
const models = getBuiltInProxyModels();
for (const [, model] of models) {
expect(model.spec.controller).toBe('gate');
}
});
it('all models are marked as built-in source', () => {
const models = getBuiltInProxyModels();
for (const [, model] of models) {
expect(model.source).toBe('built-in');
}
});
});