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:
@@ -1,5 +1,6 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { ProviderRegistry } from '../src/providers/registry.js';
|
||||
import { AnthropicProvider } from '../src/providers/anthropic.js';
|
||||
import type { LlmProvider, CompletionOptions, CompletionResult } from '../src/providers/types.js';
|
||||
|
||||
function mockProvider(name: string): LlmProvider {
|
||||
@@ -217,3 +218,64 @@ describe('ProviderRegistry', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
vi.mock('node:https', () => {
|
||||
let capturedOpts: Record<string, unknown> = {};
|
||||
const mockRequest = vi.fn((opts: unknown, cb?: unknown) => {
|
||||
capturedOpts = opts as Record<string, unknown>;
|
||||
const mockRes = {
|
||||
on: (event: string, handler: (data?: unknown) => void) => {
|
||||
if (event === 'data') {
|
||||
handler(Buffer.from(JSON.stringify({
|
||||
content: [{ type: 'text', text: 'ok' }],
|
||||
usage: { input_tokens: 1, output_tokens: 1 },
|
||||
stop_reason: 'end_turn',
|
||||
})));
|
||||
}
|
||||
if (event === 'end') handler();
|
||||
return mockRes;
|
||||
},
|
||||
};
|
||||
if (typeof cb === 'function') cb(mockRes);
|
||||
return {
|
||||
on: vi.fn().mockReturnThis(),
|
||||
write: vi.fn(),
|
||||
end: vi.fn(),
|
||||
};
|
||||
});
|
||||
return {
|
||||
default: { request: mockRequest },
|
||||
__capturedOpts: () => capturedOpts,
|
||||
__mockRequest: mockRequest,
|
||||
};
|
||||
});
|
||||
|
||||
describe('AnthropicProvider auth headers', () => {
|
||||
it('uses Authorization: Bearer for OAuth tokens', async () => {
|
||||
const { __capturedOpts } = await import('node:https') as unknown as { __capturedOpts: () => Record<string, unknown> };
|
||||
const provider = new AnthropicProvider({ apiKey: 'sk-ant-oat01-test-token' });
|
||||
await provider.complete({ messages: [{ role: 'user', content: 'hi' }], maxTokens: 1 });
|
||||
|
||||
const headers = __capturedOpts().headers as Record<string, string>;
|
||||
expect(headers['Authorization']).toBe('Bearer sk-ant-oat01-test-token');
|
||||
expect(headers['x-api-key']).toBeUndefined();
|
||||
});
|
||||
|
||||
it('uses x-api-key for standard API keys', async () => {
|
||||
const { __capturedOpts } = await import('node:https') as unknown as { __capturedOpts: () => Record<string, unknown> };
|
||||
const provider = new AnthropicProvider({ apiKey: 'sk-ant-api03-standard-key' });
|
||||
await provider.complete({ messages: [{ role: 'user', content: 'hi' }], maxTokens: 1 });
|
||||
|
||||
const headers = __capturedOpts().headers as Record<string, string>;
|
||||
expect(headers['x-api-key']).toBe('sk-ant-api03-standard-key');
|
||||
expect(headers['Authorization']).toBeUndefined();
|
||||
});
|
||||
|
||||
it('includes claude-sonnet-4-5 in model list', async () => {
|
||||
const provider = new AnthropicProvider({ apiKey: 'test' });
|
||||
const models = await provider.listModels();
|
||||
expect(models).toContain('claude-sonnet-4-5-20250514');
|
||||
expect(models).toContain('claude-opus-4-20250514');
|
||||
expect(models).toContain('claude-haiku-3-5-20241022');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user