Files
waggle-os/packages/server/tests/local/anthropic-proxy.test.ts
Oleg Maslov b20b138fe4 moving
2026-09-02 10:14:22 +02:00

2925 lines
112 KiB
TypeScript

/**
* Built-in Provider Proxy Route Tests (PRQ-043)
*
* Tests Anthropic translation and direct OpenAI-compatible provider routing:
* GET /v1/health/liveliness — health check
* POST /v1/chat/completions — translate OpenAI format to Anthropic (non-streaming)
*
* Uses a lightweight Fastify server with just the proxy routes registered,
* mocking the external Anthropic API call via globalThis.fetch.
*/
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import Fastify from 'fastify';
import type { FastifyInstance } from 'fastify';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { CostTracker, runAgentLoop } from '@waggle/agent';
import { ExecutionTraceStore, MindDB } from '@waggle/core';
import { AgentRunRegistry } from '../../src/local/agent-run-registry.js';
import { anthropicProxyRoutes } from '../../src/local/routes/anthropic-proxy.js';
import { securityMiddleware, type RunTokenAuthResult } from '../../src/local/security-middleware.js';
import { PROVIDER_ENV_NAMES } from '../../src/local/provider-env.js';
const MODEL_SPEND_RESERVATION_HEADER = 'x-waggle-model-spend-reservation';
function createTestServer(options: {
vaultApiKey?: string;
vaultProviders?: Record<string, { value: string; metadata?: Record<string, unknown> }>;
vaultGet?: (name: string) => { value: string; metadata?: Record<string, unknown> } | null;
envApiKey?: string;
configApiKey?: string;
dataDir?: string;
sessionToken?: string;
authenticateRunToken?: (token: string) => RunTokenAuthResult;
costTracker?: CostTracker;
traceStore?: FastifyInstance['traceStore'];
registerSpendTarget?: string;
} = {}) {
const server = Fastify({ logger: false });
// Mock vault
if (options.vaultApiKey || options.vaultProviders || options.vaultGet) {
server.decorate('vault', {
get: (name: string) => {
if (options.vaultGet) return options.vaultGet(name);
if (name === 'anthropic' && options.vaultApiKey) return { value: options.vaultApiKey };
return options.vaultProviders?.[name] ?? null;
},
});
} else {
server.decorate('vault', null);
}
if (options.costTracker) {
server.decorate('agentState', {
costTracker: options.costTracker,
} as FastifyInstance['agentState']);
if (options.registerSpendTarget) {
options.costTracker.registerModelSpendReservationTarget(options.registerSpendTarget);
}
}
if (options.traceStore) {
server.decorate('traceStore', options.traceStore);
}
// Mock localConfig (needed by getAnthropicKey for config.json fallback)
server.decorate('localConfig', {
dataDir: options.dataDir ?? '/tmp/nonexistent-waggle-test',
});
if (options.sessionToken || options.authenticateRunToken) {
server.register(securityMiddleware, {
sessionToken: options.sessionToken,
authenticateRunToken: options.authenticateRunToken,
});
}
server.register(anthropicProxyRoutes);
return server;
}
describe('Anthropic Proxy Routes', () => {
let server: FastifyInstance;
const originalFetch = globalThis.fetch;
const originalApiKey = process.env.ANTHROPIC_API_KEY;
beforeEach(() => {
// Clear env var by default
delete process.env.ANTHROPIC_API_KEY;
});
afterEach(async () => {
if (server) await server.close();
globalThis.fetch = originalFetch;
vi.restoreAllMocks();
vi.unstubAllEnvs();
// Restore env var
if (originalApiKey !== undefined) {
process.env.ANTHROPIC_API_KEY = originalApiKey;
} else {
delete process.env.ANTHROPIC_API_KEY;
}
});
// ── Health check ──────────────────────────────────────────────
describe('GET /v1/health/liveliness', () => {
it('returns healthy status', async () => {
server = createTestServer();
const res = await server.inject({
method: 'GET',
url: '/v1/health/liveliness',
});
expect(res.statusCode).toBe(200);
const body = res.json();
expect(body.status).toBe('healthy');
});
});
describe('GET /v1/health/readiness', () => {
it('reports unavailable when the proxy has no provider credential', async () => {
for (const envName of new Set(Object.values(PROVIDER_ENV_NAMES).flat())) {
vi.stubEnv(envName, '');
}
server = createTestServer();
const unavailableFetch = vi.fn(async () => new Response(null, { status: 503 }));
globalThis.fetch = unavailableFetch as unknown as typeof globalThis.fetch;
const res = await server.inject({
method: 'GET',
url: '/v1/health/readiness',
});
expect(res.statusCode).toBe(503);
expect(res.json()).toMatchObject({ status: 'unavailable' });
});
it('reports ready when at least one provider credential is configured', async () => {
server = createTestServer({ vaultApiKey: 'test-key' });
const res = await server.inject({
method: 'GET',
url: '/v1/health/readiness',
});
expect(res.statusCode).toBe(200);
expect(res.json()).toMatchObject({ status: 'ready' });
});
it('reports ready when a loopback Ollama runtime has a local model', async () => {
for (const envName of new Set(Object.values(PROVIDER_ENV_NAMES).flat())) {
vi.stubEnv(envName, '');
}
vi.stubEnv('OLLAMA_HOST', 'http://127.0.0.1:11455');
server = createTestServer();
const localFetch = vi.fn(async (url: string | URL | Request) => {
const payload = String(url).endsWith('/api/tags')
? {
models: [
{ name: '' },
{ name: '' },
{ name: '' },
{ name: '' },
{ name: 'qwen3:1.7b' },
],
}
: { capabilities: ['completion', 'tools'] };
return new Response(JSON.stringify(payload), {
status: 200,
headers: { 'content-type': 'application/json' },
});
});
globalThis.fetch = localFetch as unknown as typeof globalThis.fetch;
const res = await server.inject({
method: 'GET',
url: '/v1/health/readiness',
});
expect(res.statusCode).toBe(200);
expect(res.json()).toMatchObject({ status: 'ready' });
expect(globalThis.fetch).toHaveBeenCalledWith(
'http://127.0.0.1:11455/api/tags',
expect.objectContaining({ redirect: 'error' }),
);
expect(globalThis.fetch).toHaveBeenCalledWith(
'http://127.0.0.1:11455/api/show',
expect.objectContaining({
body: JSON.stringify({ model: 'qwen3:1.7b' }),
redirect: 'error',
}),
);
});
it('does not report ready for embedding-only or Ollama cloud aliases', async () => {
for (const envName of new Set(Object.values(PROVIDER_ENV_NAMES).flat())) {
vi.stubEnv(envName, '');
}
vi.stubEnv('OLLAMA_HOST', 'http://127.0.0.1:11455');
server = createTestServer();
const shownModels: string[] = [];
const localFetch = vi.fn(async (url: string | URL | Request, init?: RequestInit) => {
const payload = String(url).endsWith('/api/tags')
? {
models: [
{ name: 'all-minilm:latest' },
{ name: 'bge-m3:latest' },
{ name: 'qwen3:cloud' },
{ name: 'llama3.2:3b', remote_host: 'https://ollama.com' },
],
}
: (() => {
shownModels.push(JSON.parse(String(init?.body)).model);
return { capabilities: ['embedding'] };
})();
return new Response(JSON.stringify(payload), {
status: 200,
headers: { 'content-type': 'application/json' },
});
});
globalThis.fetch = localFetch as unknown as typeof globalThis.fetch;
const res = await server.inject({
method: 'GET',
url: '/v1/health/readiness',
});
expect(res.statusCode).toBe(503);
expect(res.json()).toMatchObject({ status: 'unavailable' });
expect(shownModels).toEqual(['all-minilm:latest', 'bge-m3:latest']);
});
it('coalesces concurrent Ollama readiness probes and bounds capability checks', async () => {
for (const envName of new Set(Object.values(PROVIDER_ENV_NAMES).flat())) {
vi.stubEnv(envName, '');
}
vi.stubEnv('OLLAMA_HOST', 'http://127.0.0.1:11456');
server = createTestServer();
let tagsCalls = 0;
let showCalls = 0;
let activeShowCalls = 0;
let peakShowCalls = 0;
const models = Array.from({ length: 12 }, (_, index) => ({ name: `model-${index}` }));
const localFetch = vi.fn(async (url: string | URL | Request, init?: RequestInit) => {
if (String(url).endsWith('/api/tags')) {
tagsCalls += 1;
return new Response(JSON.stringify({ models }), {
status: 200,
headers: { 'content-type': 'application/json' },
});
}
showCalls += 1;
activeShowCalls += 1;
peakShowCalls = Math.max(peakShowCalls, activeShowCalls);
await new Promise((resolve) => setTimeout(resolve, 10));
activeShowCalls -= 1;
const shownModel = JSON.parse(String(init?.body)).model;
const capabilities = shownModel === 'model-11' ? ['completion'] : ['embedding'];
return new Response(JSON.stringify({ capabilities }), {
status: 200,
headers: { 'content-type': 'application/json' },
});
});
globalThis.fetch = localFetch as unknown as typeof globalThis.fetch;
const responses = await Promise.all(Array.from({ length: 8 }, () => server!.inject({
method: 'GET',
url: '/v1/health/readiness',
})));
const cachedResponse = await server.inject({
method: 'GET',
url: '/v1/health/readiness',
});
expect(responses.every((response) => response.statusCode === 200)).toBe(true);
expect(cachedResponse.statusCode).toBe(200);
expect(tagsCalls).toBe(1);
expect(showCalls).toBe(models.length);
expect(peakShowCalls).toBeLessThanOrEqual(4);
});
it('gives later queued Ollama models a full capability timeout window', async () => {
for (const envName of new Set(Object.values(PROVIDER_ENV_NAMES).flat())) {
vi.stubEnv(envName, '');
}
vi.stubEnv('OLLAMA_HOST', 'http://127.0.0.1:11458');
server = createTestServer();
const models = Array.from({ length: 8 }, (_, index) => ({ name: `slow-model-${index}` }));
const localFetch = vi.fn(async (
url: string | URL | Request,
init?: RequestInit,
) => {
if (String(url).endsWith('/api/tags')) {
return new Response(JSON.stringify({ models }), {
status: 200,
headers: { 'content-type': 'application/json' },
});
}
const signal = init?.signal;
await new Promise<void>((resolve, reject) => {
const onAbort = () => {
clearTimeout(timer);
reject(signal?.reason ?? new Error('capability probe aborted'));
};
const timer = setTimeout(() => {
signal?.removeEventListener('abort', onAbort);
resolve();
}, 1_100);
if (signal?.aborted) onAbort();
else signal?.addEventListener('abort', onAbort, { once: true });
});
const shownModel = JSON.parse(String(init?.body)).model;
const capabilities = shownModel === 'slow-model-7' ? ['completion'] : ['embedding'];
return new Response(JSON.stringify({ capabilities }), {
status: 200,
headers: { 'content-type': 'application/json' },
});
});
globalThis.fetch = localFetch as unknown as typeof globalThis.fetch;
const response = await server.inject({
method: 'GET',
url: '/v1/health/readiness',
});
expect(response.statusCode).toBe(200);
});
it('bounds readiness latency when many Ollama capability probes stall', async () => {
for (const envName of new Set(Object.values(PROVIDER_ENV_NAMES).flat())) {
vi.stubEnv(envName, '');
}
vi.stubEnv('OLLAMA_HOST', 'http://127.0.0.1:11459');
server = createTestServer();
const models = Array.from({ length: 64 }, (_, index) => ({ name: `stalled-${index}` }));
let showCalls = 0;
let activeShowCalls = 0;
let peakShowCalls = 0;
const localFetch = vi.fn(async (
url: string | URL | Request,
init?: RequestInit,
) => {
if (String(url).endsWith('/api/tags')) {
return new Response(JSON.stringify({ models }), {
status: 200,
headers: { 'content-type': 'application/json' },
});
}
showCalls += 1;
activeShowCalls += 1;
peakShowCalls = Math.max(peakShowCalls, activeShowCalls);
const signal = init?.signal;
try {
await new Promise<void>((resolve, reject) => {
const onAbort = () => {
clearTimeout(timer);
reject(signal?.reason ?? new Error('capability probe aborted'));
};
const timer = setTimeout(() => {
signal?.removeEventListener('abort', onAbort);
resolve();
}, 300);
if (signal?.aborted) onAbort();
else signal?.addEventListener('abort', onAbort, { once: true });
});
} finally {
activeShowCalls -= 1;
}
return new Response(JSON.stringify({ capabilities: ['embedding'] }), {
status: 200,
headers: { 'content-type': 'application/json' },
});
});
globalThis.fetch = localFetch as unknown as typeof globalThis.fetch;
const startedAt = performance.now();
const response = await server.inject({
method: 'GET',
url: '/v1/health/readiness',
});
const elapsedMs = performance.now() - startedAt;
expect(response.statusCode).toBe(503);
expect(elapsedMs).toBeLessThan(4_000);
expect(showCalls).toBeLessThan(models.length);
expect(activeShowCalls).toBe(0);
expect(peakShowCalls).toBeLessThanOrEqual(4);
});
it('refreshes cached Ollama readiness after the short TTL expires', async () => {
for (const envName of new Set(Object.values(PROVIDER_ENV_NAMES).flat())) {
vi.stubEnv(envName, '');
}
vi.stubEnv('OLLAMA_HOST', 'http://127.0.0.1:11457');
let now = 10_000;
let ready = false;
let tagsCalls = 0;
vi.spyOn(Date, 'now').mockImplementation(() => now);
server = createTestServer();
const localFetch = vi.fn(async (url: string | URL | Request) => {
if (String(url).endsWith('/api/tags')) {
tagsCalls += 1;
return new Response(JSON.stringify({ models: [{ name: 'qwen3:1.7b' }] }), {
status: 200,
headers: { 'content-type': 'application/json' },
});
}
return new Response(JSON.stringify({
capabilities: ready ? ['completion'] : ['embedding'],
}), {
status: 200,
headers: { 'content-type': 'application/json' },
});
});
globalThis.fetch = localFetch as unknown as typeof globalThis.fetch;
const unavailable = await server.inject({
method: 'GET',
url: '/v1/health/readiness',
});
const cachedUnavailable = await server.inject({
method: 'GET',
url: '/v1/health/readiness',
});
ready = true;
now += 2_001;
const refreshed = await server.inject({
method: 'GET',
url: '/v1/health/readiness',
});
expect(unavailable.statusCode).toBe(503);
expect(cachedUnavailable.statusCode).toBe(503);
expect(refreshed.statusCode).toBe(200);
expect(tagsCalls).toBe(2);
});
});
// ── POST /v1/chat/completions ─────────────────────────────────
describe('POST /v1/chat/completions (non-streaming)', () => {
it('rejects a paid provider request before dispatch when the shared hard cap is exhausted', async () => {
const costTracker = new CostTracker();
costTracker.setBudget(0.000001, 'hard');
server = createTestServer({
vaultApiKey: 'test-key-budget-cap',
costTracker,
});
globalThis.fetch = vi.fn(async () => new Response(JSON.stringify({
content: [{ type: 'text', text: 'must not dispatch' }],
stop_reason: 'end_turn',
usage: { input_tokens: 1, output_tokens: 1 },
}), { status: 200, headers: { 'content-type': 'application/json' } })) as unknown as typeof globalThis.fetch;
const response = await server.inject({
method: 'POST',
url: '/v1/chat/completions',
payload: {
model: 'claude-sonnet-4-6',
messages: [{ role: 'user', content: 'Do not cross the configured daily model budget.' }],
max_tokens: 4096,
stream: false,
},
});
expect(response.statusCode, response.body).toBe(429);
expect(response.json()).toMatchObject({
error: { code: 'DAILY_MODEL_BUDGET_EXCEEDED' },
});
expect(globalThis.fetch).not.toHaveBeenCalled();
});
it('accepts exactly one request-bound reservation handoff without double charging', async () => {
const costTracker = new CostTracker();
costTracker.setBudget(0.07, 'soft');
server = createTestServer({
vaultApiKey: 'test-key-reservation-handoff',
costTracker,
});
globalThis.fetch = vi.fn(async () => new Response(JSON.stringify({
content: [{ type: 'text', text: 'reserved once' }],
model: 'claude-sonnet-4-6',
stop_reason: 'end_turn',
usage: { input_tokens: 10, output_tokens: 10 },
}), { status: 200, headers: { 'content-type': 'application/json' } })) as unknown as typeof globalThis.fetch;
const body = {
model: 'claude-sonnet-4-6',
messages: [{ role: 'user' as const, content: 'Use the already reserved provider call.' }],
max_tokens: 4096,
stream: false,
};
const estimatedInputTokens = Math.max(1, Math.ceil(JSON.stringify({
messages: body.messages,
tools: [],
}).length / 4));
const reservation = costTracker.reserveModelSpend({
model: body.model,
inputTokens: estimatedInputTokens,
maxOutputTokens: body.max_tokens,
billingClass: 'priced',
});
const targetUrl = 'http://127.0.0.1:3333/v1';
costTracker.registerModelSpendReservationTarget(targetUrl);
const handoff = costTracker.issueModelSpendReservationHandoff(
reservation,
JSON.stringify(body),
targetUrl,
);
expect(handoff).toBeDefined();
const response = await server.inject({
method: 'POST',
url: '/v1/chat/completions',
headers: { [MODEL_SPEND_RESERVATION_HEADER]: handoff!.token },
payload: body,
});
expect(response.statusCode, response.body).toBe(200);
expect(globalThis.fetch).toHaveBeenCalledOnce();
expect(costTracker.getReservedDailyTotal()).toBeGreaterThan(0);
expect(costTracker.getDailyTotal()).toBe(0);
expect(costTracker.reconcileModelSpend(reservation, {
inputTokens: 10,
outputTokens: 10,
})).toBe(true);
expect(costTracker.getReservedDailyTotal()).toBe(0);
expect(costTracker.getDailyTotal()).toBeGreaterThan(0);
});
it('registers only the actual listening self-proxy target and revokes it on close', async () => {
const costTracker = new CostTracker();
server = createTestServer({
vaultApiKey: 'test-key-lifecycle-handoff',
costTracker,
});
await server.listen({ host: '127.0.0.1', port: 0 });
const address = server.server.address();
if (!address || typeof address === 'string') throw new Error('Test server did not bind TCP');
const targetUrl = `http://127.0.0.1:${address.port}/v1`;
const body = {
model: 'claude-sonnet-4-6',
messages: [{ role: 'user' as const, content: 'Bind this token to the active proxy.' }],
max_tokens: 64,
stream: false,
};
const reservation = costTracker.reserveModelSpend({
model: body.model,
inputTokens: 12,
maxOutputTokens: body.max_tokens,
});
expect(costTracker.issueModelSpendReservationHandoff(
reservation,
JSON.stringify(body),
targetUrl,
)).toBeDefined();
expect(costTracker.issueModelSpendReservationHandoff(
reservation,
JSON.stringify(body),
`${targetUrl}/`,
)).toBeUndefined();
expect(costTracker.issueModelSpendReservationHandoff(
reservation,
JSON.stringify(body),
`http://127.0.0.1:${address.port + 1}/v1`,
)).toBeUndefined();
expect(costTracker.issueModelSpendReservationHandoff(
reservation,
JSON.stringify(body),
'https://router.example/v1',
)).toBeUndefined();
await server.close();
expect(costTracker.issueModelSpendReservationHandoff(
reservation,
JSON.stringify(body),
targetUrl,
)).toBeUndefined();
costTracker.releaseReservedModelSpend(reservation);
});
it('claims a reservation handoff once and rejects replay', () => {
const costTracker = new CostTracker();
const targetUrl = 'http://127.0.0.1:3333/v1';
const binding = JSON.stringify({ model: 'claude-sonnet-4-6', messages: [] });
costTracker.registerModelSpendReservationTarget(targetUrl);
const reservation = costTracker.reserveModelSpend({
model: 'claude-sonnet-4-6',
inputTokens: 1,
maxOutputTokens: 1,
});
const handoff = costTracker.issueModelSpendReservationHandoff(
reservation,
binding,
targetUrl,
);
expect(costTracker.claimModelSpendReservationHandoff(handoff!.token, binding)).toMatchObject({
reservation,
estimatedCostUsd: expect.any(Number),
});
expect(costTracker.claimModelSpendReservationHandoff(handoff!.token, binding)).toBeUndefined();
costTracker.releaseReservedModelSpend(reservation);
});
it('allows only one live handoff token per reservation', () => {
const costTracker = new CostTracker();
const targetUrl = 'http://127.0.0.1:3333/v1';
const binding = JSON.stringify({ model: 'claude-sonnet-4-6', messages: [] });
costTracker.registerModelSpendReservationTarget(targetUrl);
const reservation = costTracker.reserveModelSpend({
model: 'claude-sonnet-4-6',
inputTokens: 1,
maxOutputTokens: 1,
});
const first = costTracker.issueModelSpendReservationHandoff(
reservation,
binding,
targetUrl,
);
const second = costTracker.issueModelSpendReservationHandoff(
reservation,
binding,
targetUrl,
);
const claims = [first, second].filter((handoff) => (
handoff
&& costTracker.claimModelSpendReservationHandoff(handoff.token, binding) !== undefined
));
expect(first).toBeDefined();
expect(second).toBeUndefined();
expect(claims).toHaveLength(1);
costTracker.releaseReservedModelSpend(reservation);
});
it('hands an AgentLoop reservation to the local proxy and settles it exactly once', async () => {
const costTracker = new CostTracker();
costTracker.setBudget(0.07, 'hard');
const traceStore = {
start: vi.fn(() => 75),
reserveCost: vi.fn(() => 91),
settleReservedCost: vi.fn(() => true),
releaseReservedCost: vi.fn(() => true),
recordCost: vi.fn(),
finalize: vi.fn(),
} as unknown as FastifyInstance['traceStore'];
server = createTestServer({
vaultApiKey: 'test-key-agent-loop-handoff',
costTracker,
traceStore,
registerSpendTarget: 'http://127.0.0.1:3333/v1',
});
globalThis.fetch = vi.fn(async () => new Response(JSON.stringify({
content: [{ type: 'text', text: 'one owner' }],
model: 'claude-sonnet-4-6',
stop_reason: 'end_turn',
usage: { input_tokens: 10, output_tokens: 10 },
}), { status: 200, headers: { 'content-type': 'application/json' } })) as unknown as typeof globalThis.fetch;
const localFetch = vi.fn(async (_url: string | URL | Request, init?: RequestInit) => {
const injected = await server.inject({
method: 'POST',
url: '/v1/chat/completions',
headers: Object.fromEntries(new Headers(init?.headers).entries()),
payload: JSON.parse(String(init?.body)),
});
return new Response(injected.body, {
status: injected.statusCode,
headers: { 'content-type': injected.headers['content-type'] ?? 'application/json' },
});
}) as unknown as typeof globalThis.fetch;
const result = await runAgentLoop({
litellmUrl: 'http://127.0.0.1:3333/v1',
litellmApiKey: 'local-token',
model: 'claude-sonnet-4-6',
systemPrompt: 'Answer directly.',
messages: [{ role: 'user', content: 'Prove single reservation ownership.' }],
tools: [],
fetch: localFetch,
stream: false,
maxOutputTokens: 4096,
verificationGate: false,
skillDistillationGate: false,
modelSpendBudget: costTracker,
modelSpendTraceId: 75,
});
expect(result.content).toBe('one owner');
expect(localFetch).toHaveBeenCalledOnce();
expect(costTracker.getReservedDailyTotal()).toBe(0);
expect(costTracker.getDailyTotal()).toBeGreaterThan(0);
expect(traceStore.reserveCost).toHaveBeenCalledWith(75, expect.any(Number));
expect(traceStore.settleReservedCost).toHaveBeenCalledWith(91, expect.any(Number));
expect(traceStore.releaseReservedCost).not.toHaveBeenCalled();
expect(traceStore.start).not.toHaveBeenCalled();
expect(traceStore.recordCost).not.toHaveBeenCalled();
expect(traceStore.finalize).not.toHaveBeenCalled();
});
it('persists an AgentLoop handoff before provider dispatch and settles it once', async () => {
const dataDir = fs.mkdtempSync(path.join(os.tmpdir(), 'waggle-proxy-spend-restart-'));
const dbPath = path.join(dataDir, 'personal.mind');
const mind = new MindDB(dbPath);
const traceStore = new ExecutionTraceStore(mind);
const traceId = traceStore.start({
sessionId: 'caller-session',
model: 'claude-sonnet-4-6',
input: 'Persist the reservation before dispatch.',
});
const costTracker = new CostTracker();
costTracker.setBudget(0.07, 'hard');
server = createTestServer({
vaultApiKey: 'test-key-durable-agent-loop-handoff',
costTracker,
traceStore,
});
await server.listen({ host: '127.0.0.1', port: 0 });
const address = server.server.address();
if (!address || typeof address === 'string') throw new Error('Test server did not bind TCP');
let signalProviderStarted!: () => void;
const providerStarted = new Promise<void>((resolve) => { signalProviderStarted = resolve; });
let resolveProvider!: (response: Response) => void;
const providerResponse = new Promise<Response>((resolve) => { resolveProvider = resolve; });
let providerReleased = false;
globalThis.fetch = vi.fn(async () => {
signalProviderStarted();
return providerResponse;
}) as unknown as typeof globalThis.fetch;
const run = runAgentLoop({
litellmUrl: `http://127.0.0.1:${address.port}/v1`,
litellmApiKey: 'local-token',
model: 'claude-sonnet-4-6',
systemPrompt: 'Answer directly.',
messages: [{ role: 'user', content: 'Persist before dispatch.' }],
tools: [],
fetch: originalFetch,
stream: false,
maxOutputTokens: 64,
verificationGate: false,
skillDistillationGate: false,
modelSpendBudget: costTracker,
modelSpendTraceId: traceId,
});
try {
await providerStarted;
const restartedMind = new MindDB(dbPath);
const restartedStore = new ExecutionTraceStore(restartedMind);
const pendingTotal = restartedStore.getTotalCostSince('2000-01-01T00:00:00.000Z');
const pendingRows = restartedMind.getDatabase().prepare(`
SELECT trace_id AS traceId, state, estimated_cost_usd AS estimatedCostUsd
FROM execution_trace_spend_reservations
`).all() as Array<{ traceId: number; state: string; estimatedCostUsd: number }>;
restartedMind.close();
providerReleased = true;
resolveProvider(new Response(JSON.stringify({
content: [{ type: 'text', text: 'durably settled' }],
model: 'claude-sonnet-4-6',
stop_reason: 'end_turn',
usage: { input_tokens: 10, output_tokens: 10 },
}), { status: 200, headers: { 'content-type': 'application/json' } }));
const result = await run;
const finalReservations = mind.getDatabase().prepare(`
SELECT trace_id AS traceId, state, actual_cost_usd AS actualCostUsd
FROM execution_trace_spend_reservations
`).all() as Array<{ traceId: number; state: string; actualCostUsd: number }>;
const settledLedger = mind.getDatabase().prepare(`
SELECT COUNT(*) AS entries, COALESCE(SUM(cost_usd), 0) AS total
FROM execution_trace_spend
WHERE trace_id = ?
`).get(traceId) as { entries: number; total: number };
expect(pendingTotal).toBeGreaterThan(0);
expect(pendingRows).toEqual([
expect.objectContaining({ traceId, state: 'pending', estimatedCostUsd: expect.any(Number) }),
]);
expect(result.content).toBe('durably settled');
expect(finalReservations).toEqual([
expect.objectContaining({ traceId, state: 'settled', actualCostUsd: expect.any(Number) }),
]);
expect(finalReservations[0]!.actualCostUsd).toBeGreaterThan(0);
expect(settledLedger.entries).toBe(1);
expect(settledLedger.total).toBeCloseTo(finalReservations[0]!.actualCostUsd);
expect(traceStore.get(traceId)?.cost_usd).toBeCloseTo(settledLedger.total);
expect(costTracker.getReservedDailyTotal()).toBe(0);
} finally {
if (!providerReleased) {
resolveProvider(new Response(JSON.stringify({ error: 'test cleanup' }), { status: 500 }));
}
await run.catch(() => undefined);
mind.close();
fs.rmSync(dataDir, { recursive: true, force: true });
}
});
it('hands AgentLoop ownership to the self-proxy on its actual listening port', async () => {
const costTracker = new CostTracker();
costTracker.setBudget(0.07, 'hard');
const traceStore = {
reserveCost: vi.fn(() => 92),
settleReservedCost: vi.fn(() => true),
releaseReservedCost: vi.fn(() => true),
} as unknown as FastifyInstance['traceStore'];
server = createTestServer({
vaultApiKey: 'test-key-real-port-handoff',
costTracker,
traceStore,
});
globalThis.fetch = vi.fn(async () => new Response(JSON.stringify({
content: [{ type: 'text', text: 'actual port owner' }],
model: 'claude-sonnet-4-6',
stop_reason: 'end_turn',
usage: { input_tokens: 10, output_tokens: 10 },
}), { status: 200, headers: { 'content-type': 'application/json' } })) as unknown as typeof globalThis.fetch;
await server.listen({ host: '127.0.0.1', port: 0 });
const address = server.server.address();
if (!address || typeof address === 'string') throw new Error('Test server did not bind TCP');
const result = await runAgentLoop({
litellmUrl: `http://127.0.0.1:${address.port}/v1`,
litellmApiKey: 'local-token',
model: 'claude-sonnet-4-6',
systemPrompt: 'Answer directly.',
messages: [{ role: 'user', content: 'Prove actual-port ownership.' }],
tools: [],
fetch: originalFetch,
stream: false,
maxOutputTokens: 4096,
verificationGate: false,
skillDistillationGate: false,
modelSpendBudget: costTracker,
modelSpendTraceId: 76,
});
expect(result.content).toBe('actual port owner');
expect(costTracker.getReservedDailyTotal()).toBe(0);
expect(costTracker.getDailyTotal()).toBeGreaterThan(0);
expect(globalThis.fetch).toHaveBeenCalledOnce();
expect(traceStore.reserveCost).toHaveBeenCalledWith(76, expect.any(Number));
expect(traceStore.settleReservedCost).toHaveBeenCalledWith(92, expect.any(Number));
expect(traceStore.releaseReservedCost).not.toHaveBeenCalled();
});
it('does not disclose reservation handoff tokens to a remote model endpoint', async () => {
const costTracker = new CostTracker();
const remoteFetch = vi.fn(async (_url: string | URL | Request, init?: RequestInit) => {
expect(new Headers(init?.headers).has(MODEL_SPEND_RESERVATION_HEADER)).toBe(false);
return new Response(JSON.stringify({
choices: [{ message: { role: 'assistant', content: 'remote complete' }, finish_reason: 'stop' }],
usage: { prompt_tokens: 10, completion_tokens: 5 },
}), { status: 200, headers: { 'content-type': 'application/json' } });
}) as unknown as typeof globalThis.fetch;
const result = await runAgentLoop({
litellmUrl: 'https://router.example/v1',
litellmApiKey: 'remote-token',
model: 'claude-sonnet-4-6',
systemPrompt: 'Answer directly.',
messages: [{ role: 'user', content: 'Do not disclose local handoff state.' }],
tools: [],
fetch: remoteFetch,
stream: false,
maxOutputTokens: 64,
verificationGate: false,
skillDistillationGate: false,
modelSpendBudget: costTracker,
});
expect(result.content).toBe('remote complete');
expect(remoteFetch).toHaveBeenCalledOnce();
});
it('leaves a caller-owned reservation for the caller to release after provider rejection', async () => {
const costTracker = new CostTracker();
costTracker.setBudget(0.07, 'hard');
const traceStore = {
reserveCost: vi.fn(() => 93),
settleReservedCost: vi.fn(() => true),
releaseReservedCost: vi.fn(() => true),
} as unknown as FastifyInstance['traceStore'];
server = createTestServer({
vaultApiKey: 'test-key-rejected-handoff',
costTracker,
traceStore,
});
globalThis.fetch = vi.fn(async () => new Response('Invalid API key', {
status: 401,
})) as unknown as typeof globalThis.fetch;
const body = {
model: 'claude-sonnet-4-6',
messages: [{ role: 'user' as const, content: 'Keep caller ownership on rejection.' }],
max_tokens: 4096,
stream: false,
};
const reservation = costTracker.reserveModelSpend({
model: body.model,
inputTokens: 10,
maxOutputTokens: body.max_tokens,
billingClass: 'priced',
});
const targetUrl = 'http://127.0.0.1:3333/v1';
costTracker.registerModelSpendReservationTarget(targetUrl);
const handoff = costTracker.issueModelSpendReservationHandoff(
reservation,
JSON.stringify(body),
targetUrl,
77,
);
const response = await server.inject({
method: 'POST',
url: '/v1/chat/completions',
headers: { [MODEL_SPEND_RESERVATION_HEADER]: handoff!.token },
payload: body,
});
expect(response.statusCode, response.body).toBe(401);
expect(traceStore.reserveCost).toHaveBeenCalledWith(77, expect.any(Number));
expect(traceStore.releaseReservedCost).toHaveBeenCalledWith(93);
expect(traceStore.settleReservedCost).not.toHaveBeenCalled();
expect(costTracker.takeModelSpendReservationHandoffDisposition(handoff!.token)).toBe('release');
expect(costTracker.getReservedDailyTotal()).toBeGreaterThan(0);
expect(costTracker.releaseReservedModelSpend(reservation)).toBe(true);
});
it.each([
{
label: 'definite streaming 401 rejection',
traceId: 78,
durableReservationId: 94,
stream: true,
upstreamStatus: 401,
expectedStatus: 401,
expectedDisposition: 'release' as const,
},
{
label: 'ambiguous 409 response',
traceId: 79,
durableReservationId: 95,
stream: false,
upstreamStatus: 409,
expectedStatus: 409,
expectedDisposition: 'commit' as const,
},
{
label: 'ambiguous network failure',
traceId: 80,
durableReservationId: 96,
stream: false,
upstreamStatus: null,
expectedStatus: 502,
expectedDisposition: 'commit' as const,
},
])('classifies caller-owned durable spend for $label', async ({
traceId,
durableReservationId,
stream,
upstreamStatus,
expectedStatus,
expectedDisposition,
}) => {
vi.stubEnv('OPENAI_API_KEY', '');
const costTracker = new CostTracker();
costTracker.setBudget(0.07, 'hard');
const traceStore = {
reserveCost: vi.fn(() => durableReservationId),
settleReservedCost: vi.fn(() => true),
releaseReservedCost: vi.fn(() => true),
} as unknown as FastifyInstance['traceStore'];
server = createTestServer({
vaultProviders: { openai: { value: 'openai-durable-spend-key' } },
costTracker,
traceStore,
});
globalThis.fetch = vi.fn(async () => {
if (upstreamStatus === null) throw new Error('provider connection lost');
return new Response(stream ? 'data: unauthorized\n\n' : JSON.stringify({ error: 'upstream' }), {
status: upstreamStatus,
headers: { 'content-type': stream ? 'text/event-stream' : 'application/json' },
});
}) as unknown as typeof globalThis.fetch;
const body = {
model: 'openai/gpt-5.3-codex',
messages: [{ role: 'user' as const, content: 'Classify this dispatched request safely.' }],
max_tokens: 64,
stream,
};
const reservation = costTracker.reserveModelSpend({
model: body.model,
// Deliberately exceed the proxy's serialized-body estimate so an ambiguous
// outcome proves the durable ledger retains the caller's full reservation.
inputTokens: 10_000,
maxOutputTokens: body.max_tokens,
billingClass: 'priced',
});
const targetUrl = 'http://127.0.0.1:3333/v1';
costTracker.registerModelSpendReservationTarget(targetUrl);
const handoff = costTracker.issueModelSpendReservationHandoff(
reservation,
JSON.stringify(body),
targetUrl,
traceId,
);
const response = await server.inject({
method: 'POST',
url: '/v1/chat/completions',
headers: { [MODEL_SPEND_RESERVATION_HEADER]: handoff!.token },
payload: body,
});
expect(response.statusCode, response.body).toBe(expectedStatus);
expect(globalThis.fetch).toHaveBeenCalledOnce();
expect(traceStore.reserveCost).toHaveBeenCalledWith(traceId, expect.any(Number));
expect(costTracker.takeModelSpendReservationHandoffDisposition(handoff!.token))
.toBe(expectedDisposition);
if (expectedDisposition === 'release') {
expect(traceStore.releaseReservedCost).toHaveBeenCalledWith(durableReservationId);
expect(traceStore.settleReservedCost).not.toHaveBeenCalled();
expect(costTracker.releaseReservedModelSpend(reservation)).toBe(true);
} else {
const durableEstimate = vi.mocked(traceStore.reserveCost).mock.calls[0]?.[1];
expect(traceStore.settleReservedCost)
.toHaveBeenCalledWith(durableReservationId, durableEstimate);
expect(traceStore.releaseReservedCost).not.toHaveBeenCalled();
expect(costTracker.commitReservedModelSpend(reservation)).toBe(true);
}
});
it('fails only a missing-trace hard handoff and accepts the next traced handoff', async () => {
const costTracker = new CostTracker();
costTracker.setBudget(0.07, 'hard');
const traceStore = {
reserveCost: vi.fn(() => 97),
settleReservedCost: vi.fn(() => true),
releaseReservedCost: vi.fn(() => true),
} as unknown as FastifyInstance['traceStore'];
server = createTestServer({
vaultApiKey: 'test-key-request-local-ledger-failure',
costTracker,
traceStore,
});
globalThis.fetch = vi.fn(async () => new Response(JSON.stringify({
content: [{ type: 'text', text: 'valid trace dispatched' }],
model: 'claude-sonnet-4-6',
stop_reason: 'end_turn',
usage: { input_tokens: 8, output_tokens: 6 },
}), { status: 200, headers: { 'content-type': 'application/json' } })) as unknown as typeof globalThis.fetch;
const targetUrl = 'http://127.0.0.1:3333/v1';
costTracker.registerModelSpendReservationTarget(targetUrl);
const firstBody = {
model: 'claude-sonnet-4-6',
messages: [{ role: 'user' as const, content: 'Missing trace must fail locally.' }],
max_tokens: 64,
stream: false,
};
const firstReservation = costTracker.reserveModelSpend({
model: firstBody.model,
inputTokens: 10,
maxOutputTokens: firstBody.max_tokens,
billingClass: 'priced',
});
const firstHandoff = costTracker.issueModelSpendReservationHandoff(
firstReservation,
JSON.stringify(firstBody),
targetUrl,
);
const rejected = await server.inject({
method: 'POST',
url: '/v1/chat/completions',
headers: { [MODEL_SPEND_RESERVATION_HEADER]: firstHandoff!.token },
payload: firstBody,
});
expect(rejected.statusCode, rejected.body).toBe(503);
expect(globalThis.fetch).not.toHaveBeenCalled();
expect(costTracker.takeModelSpendReservationHandoffDisposition(firstHandoff!.token)).toBe('release');
costTracker.discardModelSpendReservationHandoff(firstHandoff!.token);
expect(costTracker.releaseReservedModelSpend(firstReservation)).toBe(true);
const secondBody = {
...firstBody,
messages: [{ role: 'user' as const, content: 'A valid trace must still dispatch.' }],
};
const secondReservation = costTracker.reserveModelSpend({
model: secondBody.model,
inputTokens: 10,
maxOutputTokens: secondBody.max_tokens,
billingClass: 'priced',
});
const secondHandoff = costTracker.issueModelSpendReservationHandoff(
secondReservation,
JSON.stringify(secondBody),
targetUrl,
81,
);
const accepted = await server.inject({
method: 'POST',
url: '/v1/chat/completions',
headers: { [MODEL_SPEND_RESERVATION_HEADER]: secondHandoff!.token },
payload: secondBody,
});
expect(accepted.statusCode, accepted.body).toBe(200);
expect(globalThis.fetch).toHaveBeenCalledOnce();
expect(traceStore.reserveCost).toHaveBeenCalledWith(81, expect.any(Number));
expect(traceStore.settleReservedCost).toHaveBeenCalledWith(97, expect.any(Number));
expect(traceStore.releaseReservedCost).not.toHaveBeenCalled();
expect(costTracker.takeModelSpendReservationHandoffDisposition(secondHandoff!.token)).toBe('commit');
costTracker.discardModelSpendReservationHandoff(secondHandoff!.token);
expect(costTracker.commitReservedModelSpend(secondReservation)).toBe(true);
});
it('lets AgentLoop release its reservation when the self-proxy has no provider credential', async () => {
const costTracker = new CostTracker();
costTracker.setBudget(0.07, 'hard');
server = createTestServer({
costTracker,
registerSpendTarget: 'http://127.0.0.1:3333/v1',
});
const upstreamFetch = vi.fn() as unknown as typeof globalThis.fetch;
globalThis.fetch = upstreamFetch;
const localFetch = vi.fn(async (_url: string | URL | Request, init?: RequestInit) => {
const injected = await server.inject({
method: 'POST',
url: '/v1/chat/completions',
headers: Object.fromEntries(new Headers(init?.headers).entries()),
payload: JSON.parse(String(init?.body)),
});
return new Response(injected.body, {
status: injected.statusCode,
headers: { 'content-type': injected.headers['content-type'] ?? 'application/json' },
});
}) as unknown as typeof globalThis.fetch;
await expect(runAgentLoop({
litellmUrl: 'http://127.0.0.1:3333/v1',
litellmApiKey: 'local-token',
model: 'claude-sonnet-4-6',
systemPrompt: 'Answer directly.',
messages: [{ role: 'user', content: 'Do not charge before provider dispatch.' }],
tools: [],
fetch: localFetch,
stream: false,
maxOutputTokens: 4096,
verificationGate: false,
skillDistillationGate: false,
modelSpendBudget: costTracker,
})).rejects.toThrow(/No Anthropic API key/i);
expect(localFetch).toHaveBeenCalledOnce();
expect(upstreamFetch).not.toHaveBeenCalled();
expect(costTracker.getReservedDailyTotal()).toBe(0);
expect(costTracker.getDailyTotal()).toBe(0);
});
it.each([
{ messages: null },
{ messages: [{ role: 'user', content: 'test' }], tools: {} },
{
messages: [{ role: 'user', content: 'test' }],
tools: [{ type: 'function', function: null }],
},
{
messages: [{
role: 'assistant',
content: null,
tool_calls: [{ id: 'call-1', type: 'function', function: { name: 'read_file' } }],
}],
},
])('rejects malformed request shape before reserving or dispatching: $messages', async (invalid) => {
const costTracker = new CostTracker();
server = createTestServer({
vaultApiKey: 'test-key-invalid-shape',
costTracker,
});
globalThis.fetch = vi.fn() as unknown as typeof globalThis.fetch;
const response = await server.inject({
method: 'POST',
url: '/v1/chat/completions',
payload: { model: 'claude-sonnet-4-6', ...invalid },
});
expect(response.statusCode, response.body).toBe(400);
expect(globalThis.fetch).not.toHaveBeenCalled();
expect(costTracker.getReservedDailyTotal()).toBe(0);
});
it('preserves OpenAI content-part arrays while validating before reservation', async () => {
server = createTestServer({ vaultProviders: { openai: { value: 'openai-key' } } });
globalThis.fetch = vi.fn(async () => new Response(JSON.stringify({
choices: [{ message: { role: 'assistant', content: 'accepted' }, finish_reason: 'stop' }],
usage: { prompt_tokens: 1, completion_tokens: 1 },
}), { status: 200, headers: { 'content-type': 'application/json' } })) as unknown as typeof globalThis.fetch;
const content = [
{ type: 'text', text: 'Describe the image.' },
{ type: 'image_url', image_url: { url: 'data:image/png;base64,AA==' } },
];
const response = await server.inject({
method: 'POST',
url: '/v1/chat/completions',
payload: {
model: 'openai/gpt-5.4',
messages: [{ role: 'user', content }],
},
});
expect(response.statusCode, response.body).toBe(200);
const outbound = JSON.parse(String(vi.mocked(globalThis.fetch).mock.calls[0]?.[1]?.body));
expect(outbound.messages[0].content).toEqual(content);
});
it('persists a directly-owned proxy settlement in the execution trace ledger', async () => {
const costTracker = new CostTracker();
const traceStore = {
start: vi.fn(() => 73),
reserveCost: vi.fn(() => 91),
settleReservedCost: vi.fn(() => true),
releaseReservedCost: vi.fn(() => true),
finalize: vi.fn(),
} as unknown as FastifyInstance['traceStore'];
server = createTestServer({
vaultApiKey: 'test-key-direct-proxy-ledger',
costTracker,
traceStore,
});
globalThis.fetch = vi.fn(async () => new Response(JSON.stringify({
content: [{ type: 'text', text: 'persisted once' }],
model: 'claude-sonnet-4-6',
stop_reason: 'end_turn',
usage: { input_tokens: 12, output_tokens: 8 },
}), { status: 200, headers: { 'content-type': 'application/json' } })) as unknown as typeof globalThis.fetch;
const response = await server.inject({
method: 'POST',
url: '/v1/chat/completions',
payload: {
model: 'claude-sonnet-4-6',
messages: [{ role: 'user', content: 'Persist this direct proxy spend.' }],
max_tokens: 64,
stream: false,
},
});
expect(response.statusCode, response.body).toBe(200);
expect(traceStore.start).toHaveBeenCalledOnce();
expect(traceStore.reserveCost).toHaveBeenCalledWith(73, expect.any(Number));
expect(traceStore.settleReservedCost).toHaveBeenCalledWith(91, expect.any(Number));
expect(traceStore.finalize).toHaveBeenCalledWith(73, expect.objectContaining({
outcome: 'success',
model: 'claude-sonnet-4-6',
tokens: { input: 12, output: 8 },
costUsd: expect.any(Number),
}));
expect(vi.mocked(traceStore.finalize).mock.calls[0]?.[1]?.costUsd).toBeGreaterThan(0);
});
it('settles conservatively when credentials disappear after an ambiguous provider 500', async () => {
const costTracker = new CostTracker();
costTracker.setBudget(1, 'hard');
let openAiKey: string | undefined = 'openai-key-before-dispatch';
const traceStore = {
start: vi.fn(() => 83),
reserveCost: vi.fn(() => 93),
settleReservedCost: vi.fn(() => true),
releaseReservedCost: vi.fn(() => true),
finalize: vi.fn(),
} as unknown as FastifyInstance['traceStore'];
server = createTestServer({
costTracker,
traceStore,
vaultGet: (name) => (
name === 'openai' && openAiKey ? { value: openAiKey } : null
),
});
globalThis.fetch = vi.fn(async () => {
openAiKey = undefined;
return new Response(JSON.stringify({ error: { message: 'ambiguous upstream failure' } }), {
status: 500,
headers: { 'content-type': 'application/json' },
});
}) as unknown as typeof globalThis.fetch;
const response = await server.inject({
method: 'POST',
url: '/v1/chat/completions',
payload: {
model: 'openai/gpt-5.3-codex',
messages: [{ role: 'user', content: 'Account for every dispatched request.' }],
max_tokens: 64,
stream: false,
},
});
expect(response.statusCode).toBe(500);
expect(globalThis.fetch).toHaveBeenCalledOnce();
expect(traceStore.settleReservedCost).toHaveBeenCalledWith(93, expect.any(Number));
expect(traceStore.releaseReservedCost).not.toHaveBeenCalled();
expect(costTracker.getReservedDailyTotal()).toBe(0);
expect(costTracker.getDailyTotal()).toBeGreaterThan(0);
});
it('fails closed before direct dispatch when a hard cap has no durable spend ledger', async () => {
const costTracker = new CostTracker();
costTracker.setBudget(1, 'hard');
server = createTestServer({
vaultApiKey: 'test-key-missing-ledger',
costTracker,
});
globalThis.fetch = vi.fn() as unknown as typeof globalThis.fetch;
const response = await server.inject({
method: 'POST',
url: '/v1/chat/completions',
payload: {
model: 'claude-sonnet-4-6',
messages: [{ role: 'user', content: 'Do not dispatch without a durable ledger.' }],
max_tokens: 64,
stream: false,
},
});
expect(response.statusCode, response.body).toBe(503);
expect(response.json()).toMatchObject({
error: { code: 'DAILY_MODEL_BUDGET_LEDGER_UNAVAILABLE' },
});
expect(globalThis.fetch).not.toHaveBeenCalled();
expect(costTracker.getReservedDailyTotal()).toBe(0);
});
it('blocks the next hard-cap dispatch after a direct proxy ledger write fails', async () => {
const costTracker = new CostTracker();
costTracker.setBudget(1, 'hard');
const traceStore = {
start: vi.fn(() => 74),
reserveCost: vi.fn(() => { throw new Error('ledger is read-only'); }),
finalize: vi.fn(),
} as unknown as FastifyInstance['traceStore'];
server = createTestServer({
vaultApiKey: 'test-key-ledger-write-failure',
costTracker,
traceStore,
});
globalThis.fetch = vi.fn(async () => new Response(JSON.stringify({
content: [{ type: 'text', text: 'first dispatch completed' }],
model: 'claude-sonnet-4-6',
stop_reason: 'end_turn',
usage: { input_tokens: 12, output_tokens: 8 },
}), { status: 200, headers: { 'content-type': 'application/json' } })) as unknown as typeof globalThis.fetch;
const payload = {
model: 'claude-sonnet-4-6',
messages: [{ role: 'user', content: 'Persist every hard-cap settlement.' }],
max_tokens: 64,
stream: false,
};
const first = await server.inject({ method: 'POST', url: '/v1/chat/completions', payload });
const second = await server.inject({ method: 'POST', url: '/v1/chat/completions', payload });
expect(first.statusCode, first.body).toBe(503);
expect(second.statusCode, second.body).toBe(503);
expect(first.json()).toMatchObject({
error: { code: 'DAILY_MODEL_BUDGET_LEDGER_UNAVAILABLE' },
});
expect(globalThis.fetch).not.toHaveBeenCalled();
});
it('does not consume or trust a reservation handoff for a mutated request body', async () => {
const costTracker = new CostTracker();
costTracker.setBudget(0.07, 'hard');
server = createTestServer({
vaultApiKey: 'test-key-mutated-handoff',
costTracker,
});
globalThis.fetch = vi.fn() as unknown as typeof globalThis.fetch;
const originalBody = {
model: 'claude-sonnet-4-6',
messages: [{ role: 'user' as const, content: 'Original reserved request.' }],
max_tokens: 4096,
stream: false,
};
const reservation = costTracker.reserveModelSpend({
model: originalBody.model,
inputTokens: 8,
maxOutputTokens: originalBody.max_tokens,
billingClass: 'priced',
});
const targetUrl = 'http://127.0.0.1:3333/v1';
costTracker.registerModelSpendReservationTarget(targetUrl);
const handoff = costTracker.issueModelSpendReservationHandoff(
reservation,
JSON.stringify(originalBody),
targetUrl,
);
const response = await server.inject({
method: 'POST',
url: '/v1/chat/completions',
headers: { [MODEL_SPEND_RESERVATION_HEADER]: handoff!.token },
payload: {
...originalBody,
model: 'claude-opus-4-6',
messages: [{ role: 'user', content: 'Mutated higher-cost request.' }],
},
});
expect(response.statusCode, response.body).toBe(429);
expect(globalThis.fetch).not.toHaveBeenCalled();
expect(costTracker.getReservedDailyTotal()).toBeGreaterThan(0);
costTracker.releaseReservedModelSpend(reservation);
});
it('rejects a run token completion when the requested model is outside the assigned run model', async () => {
const dataDir = fs.mkdtempSync(path.join(os.tmpdir(), 'waggle-run-token-model-scope-'));
const registry = new AgentRunRegistry(path.join(dataDir, 'agent-runs.json'));
const room = registry.createRoom({
workspaceIds: ['workspace-1'],
source: 'external_tool',
title: 'OpenClaw room',
task: 'Use the assigned model only',
});
const run = registry.createWorker({
parentRunId: room.id,
workspaceId: 'workspace-1',
source: 'external_tool',
executor: { kind: 'external_tool', toolId: 'openclaw', model: 'anthropic/claude-sonnet-4-6' },
title: 'OpenClaw',
task: room.task,
});
const runToken = registry.issueCredential(run.id);
server = createTestServer({
vaultApiKey: 'anthropic-key',
vaultProviders: { openai: { value: 'openai-key' } },
});
server.decorate('agentRunRegistry', registry);
globalThis.fetch = vi.fn(async () => new Response(JSON.stringify({
content: [{ type: 'text', text: 'assigned model response' }],
model: 'claude-sonnet-4-6',
stop_reason: 'end_turn',
usage: { input_tokens: 1, output_tokens: 2 },
}), { status: 200, headers: { 'content-type': 'application/json' } })) as unknown as typeof globalThis.fetch;
try {
const response = await server.inject({
method: 'POST',
url: '/v1/chat/completions',
headers: { authorization: `Bearer ${runToken}` },
payload: {
model: 'openai/gpt-4.1',
messages: [{ role: 'user', content: 'Use a different provider' }],
stream: false,
},
});
expect(response.statusCode).toBe(403);
expect(response.json().error.message).toContain('outside the assigned run model');
expect(globalThis.fetch).not.toHaveBeenCalled();
const sameProvider = await server.inject({
method: 'POST',
url: '/v1/chat/completions',
headers: { authorization: `Bearer ${runToken}` },
payload: {
model: 'anthropic/claude-opus-4-6',
messages: [{ role: 'user', content: 'Use a different Anthropic model' }],
stream: false,
},
});
expect(sameProvider.statusCode).toBe(403);
expect(sameProvider.json().error.message).toContain('outside the assigned run model');
expect(globalThis.fetch).not.toHaveBeenCalled();
const missingToken = await server.inject({
method: 'POST',
url: '/v1/chat/completions',
payload: {
model: 'openai/gpt-4.1',
messages: [{ role: 'user', content: 'Use a different provider without a run token' }],
stream: false,
},
});
expect(missingToken.statusCode).toBe(401);
expect(globalThis.fetch).not.toHaveBeenCalled();
const invalidToken = await server.inject({
method: 'POST',
url: '/v1/chat/completions',
headers: { authorization: 'Bearer invalid-run-token-with-enough-entropy-123' },
payload: {
model: 'openai/gpt-4.1',
messages: [{ role: 'user', content: 'Use a different provider with an invalid run token' }],
stream: false,
},
});
expect(invalidToken.statusCode).toBe(401);
expect(globalThis.fetch).not.toHaveBeenCalled();
const assigned = await server.inject({
method: 'POST',
url: '/v1/chat/completions',
headers: { authorization: `bearer ${runToken}` },
payload: {
model: 'claude-sonnet-4.6',
messages: [{ role: 'user', content: 'Use the assigned provider' }],
stream: false,
},
});
expect(assigned.statusCode).toBe(200);
expect(assigned.json().choices[0].message.content).toBe('assigned model response');
expect(vi.mocked(globalThis.fetch).mock.calls[0][0]).toBe('https://api.anthropic.com/v1/messages');
} finally {
registry.close();
fs.rmSync(dataDir, { recursive: true, force: true });
}
});
it('rejects a run token completion when the token is revoked between auth and route handling', async () => {
const dataDir = fs.mkdtempSync(path.join(os.tmpdir(), 'waggle-run-token-revocation-race-'));
const registry = new AgentRunRegistry(path.join(dataDir, 'agent-runs.json'));
const room = registry.createRoom({
workspaceIds: ['workspace-1'],
source: 'external_tool',
title: 'OpenClaw room',
task: 'Use the assigned model only',
});
const run = registry.createWorker({
parentRunId: room.id,
workspaceId: 'workspace-1',
source: 'external_tool',
executor: { kind: 'external_tool', toolId: 'openclaw', model: 'anthropic/claude-sonnet-4-6' },
title: 'OpenClaw',
task: room.task,
});
const runToken = registry.issueCredential(run.id);
let revokedAfterMiddlewareAuth = false;
server = createTestServer({
vaultApiKey: 'anthropic-key',
sessionToken: 'desktop-session-token',
authenticateRunToken: (candidate) => {
if (candidate !== runToken) return false;
const authenticatedRun = registry.authenticateCredential(candidate);
if (!authenticatedRun) return false;
if (!revokedAfterMiddlewareAuth) {
revokedAfterMiddlewareAuth = true;
registry.revokeCredential(candidate);
}
return { runId: authenticatedRun.id, model: authenticatedRun.executor.model };
},
});
server.decorate('agentRunRegistry', registry);
globalThis.fetch = vi.fn(async () => new Response(JSON.stringify({
content: [{ type: 'text', text: 'should not forward' }],
model: 'claude-sonnet-4-6',
stop_reason: 'end_turn',
usage: { input_tokens: 1, output_tokens: 2 },
}), { status: 200, headers: { 'content-type': 'application/json' } })) as unknown as typeof globalThis.fetch;
try {
const response = await server.inject({
method: 'POST',
url: '/v1/chat/completions',
headers: { authorization: `Bearer ${runToken}` },
payload: {
model: 'openai/gpt-4.1',
messages: [{ role: 'user', content: 'Use a different provider after token revocation' }],
stream: false,
},
});
expect(response.statusCode).toBe(401);
expect(response.json().error.message).toContain('no longer active');
expect(globalThis.fetch).not.toHaveBeenCalled();
} finally {
registry.close();
fs.rmSync(dataDir, { recursive: true, force: true });
}
});
it('rejects a run token completion when the assigned model changes after middleware auth', async () => {
const dataDir = fs.mkdtempSync(path.join(os.tmpdir(), 'waggle-run-token-model-reassignment-'));
const registry = new AgentRunRegistry(path.join(dataDir, 'agent-runs.json'));
const room = registry.createRoom({
workspaceIds: ['workspace-1'],
source: 'external_tool',
title: 'OpenClaw room',
task: 'Use the currently assigned model only',
});
const run = registry.createWorker({
parentRunId: room.id,
workspaceId: 'workspace-1',
source: 'external_tool',
executor: { kind: 'external_tool', toolId: 'openclaw', model: 'anthropic/claude-sonnet-4-6' },
title: 'OpenClaw',
task: room.task,
});
const runToken = registry.issueCredential(run.id);
let reassignedAfterMiddlewareAuth = false;
server = createTestServer({
vaultApiKey: 'anthropic-key',
sessionToken: 'desktop-session-token',
authenticateRunToken: (candidate) => {
if (candidate !== runToken) return false;
const authenticatedRun = registry.authenticateCredential(candidate);
if (!authenticatedRun) return false;
if (!reassignedAfterMiddlewareAuth) {
reassignedAfterMiddlewareAuth = true;
registry.update(authenticatedRun.id, { executor: { model: 'anthropic/claude-haiku-4-5' } });
}
return { runId: authenticatedRun.id, model: authenticatedRun.executor.model };
},
});
server.decorate('agentRunRegistry', registry);
globalThis.fetch = vi.fn(async () => new Response(JSON.stringify({
content: [{ type: 'text', text: 'should not forward' }],
model: 'claude-sonnet-4-6',
stop_reason: 'end_turn',
usage: { input_tokens: 1, output_tokens: 2 },
}), { status: 200, headers: { 'content-type': 'application/json' } })) as unknown as typeof globalThis.fetch;
try {
const response = await server.inject({
method: 'POST',
url: '/v1/chat/completions',
headers: { authorization: `Bearer ${runToken}` },
payload: {
model: 'claude-sonnet-4.6',
messages: [{ role: 'user', content: 'Use the model that was assigned during middleware auth' }],
stream: false,
},
});
expect(response.statusCode).toBe(403);
expect(response.json().error.message).toContain('outside the assigned run model');
expect(globalThis.fetch).not.toHaveBeenCalled();
} finally {
registry.close();
fs.rmSync(dataDir, { recursive: true, force: true });
}
});
it('returns 500 when no API key is configured', async () => {
// No vault key, no env key, no config key
server = createTestServer();
const res = await server.inject({
method: 'POST',
url: '/v1/chat/completions',
payload: {
model: 'claude-sonnet-4-6',
messages: [{ role: 'user', content: 'Hello' }],
stream: false,
},
});
expect(res.statusCode).toBe(500);
const body = res.json();
expect(body.error.message).toContain('No Anthropic API key');
});
it('translates OpenAI format to Anthropic format and returns response', async () => {
process.env.ANTHROPIC_API_KEY = 'test-key-12345';
server = createTestServer();
// Mock the Anthropic API response
globalThis.fetch = vi.fn(async () => ({
ok: true,
status: 200,
json: async () => ({
content: [
{ type: 'text', text: 'Hello! How can I help you?' },
],
model: 'claude-sonnet-4-20250514',
stop_reason: 'end_turn',
usage: { input_tokens: 12, output_tokens: 8 },
}),
})) as unknown as typeof globalThis.fetch;
const res = await server.inject({
method: 'POST',
url: '/v1/chat/completions',
payload: {
model: 'claude-sonnet-4-6',
messages: [
{ role: 'system', content: 'You are a helpful assistant.' },
{ role: 'user', content: 'Hello' },
],
stream: false,
},
});
expect(res.statusCode).toBe(200);
const body = res.json();
// Verify OpenAI response format
expect(body.choices).toHaveLength(1);
expect(body.choices[0].message.role).toBe('assistant');
expect(body.choices[0].message.content).toBe('Hello! How can I help you?');
expect(body.choices[0].finish_reason).toBe('stop');
expect(body.usage.prompt_tokens).toBe(12);
expect(body.usage.completion_tokens).toBe(8);
expect(body.usage.total_tokens).toBe(20);
// Verify the Anthropic API was called with correct parameters
const fetchCall = vi.mocked(globalThis.fetch).mock.calls[0];
expect(fetchCall[0]).toBe('https://api.anthropic.com/v1/messages');
const requestBody = JSON.parse(String(fetchCall[1]?.body ?? ''));
// B3 cleanup (2026-04-22) — proxy now passes floating alias through
// unchanged per decisions/2026-04-22-model-route-naming-locked.md §3.
// Previous behavior rewrote to invalid -20250514 snapshot.
expect(requestBody.model).toBe('claude-sonnet-4-6');
// system is either a string or an Anthropic cache-control block array —
// extract the text in either case.
const systemText = Array.isArray(requestBody.system)
? requestBody.system.map((b: { text?: string }) => b.text ?? '').join('\n')
: String(requestBody.system ?? '');
expect(systemText).toContain('You are a helpful assistant');
expect(requestBody.stream).toBe(false);
});
it('counts and preserves Anthropic cache tokens in response usage', async () => {
process.env.ANTHROPIC_API_KEY = 'test-key-cache-usage';
server = createTestServer();
globalThis.fetch = vi.fn(async () => new Response(JSON.stringify({
content: [{ type: 'text', text: 'Cached response' }],
model: 'claude-sonnet-4-6',
stop_reason: 'end_turn',
usage: {
input_tokens: 100,
cache_creation_input_tokens: 2_000,
cache_read_input_tokens: 5_000,
output_tokens: 20,
},
}), { status: 200, headers: { 'content-type': 'application/json' } })) as unknown as typeof globalThis.fetch;
const res = await server.inject({
method: 'POST',
url: '/v1/chat/completions',
payload: {
model: 'anthropic/claude-sonnet-4-6',
messages: [{ role: 'user', content: 'Use the cached context' }],
stream: false,
},
});
expect(res.statusCode).toBe(200);
expect(res.json().usage).toEqual({
prompt_tokens: 7_100,
completion_tokens: 20,
total_tokens: 7_120,
prompt_tokens_details: { cached_tokens: 5_000 },
cache_creation_input_tokens: 2_000,
cache_read_input_tokens: 5_000,
});
});
it('uses API key from vault when available', async () => {
server = createTestServer({ vaultApiKey: 'vault-key-abc' });
globalThis.fetch = vi.fn(async () => ({
ok: true,
status: 200,
json: async () => ({
content: [{ type: 'text', text: 'Response' }],
model: 'claude-sonnet-4-20250514',
stop_reason: 'end_turn',
usage: { input_tokens: 5, output_tokens: 3 },
}),
})) as unknown as typeof globalThis.fetch;
const res = await server.inject({
method: 'POST',
url: '/v1/chat/completions',
payload: {
model: 'claude-sonnet-4-6',
messages: [{ role: 'user', content: 'Hi' }],
stream: false,
},
});
expect(res.statusCode).toBe(200);
// Verify vault key was used in the request
const fetchCall = vi.mocked(globalThis.fetch).mock.calls[0];
const headers = fetchCall[1]?.headers as Record<string, string> | undefined;
expect(headers?.['x-api-key']).toBe('vault-key-abc');
});
it('forwards Anthropic API errors to client', async () => {
process.env.ANTHROPIC_API_KEY = 'test-key';
server = createTestServer();
globalThis.fetch = vi.fn(async () => ({
ok: false,
status: 401,
text: async () => 'Invalid API key',
})) as unknown as typeof globalThis.fetch;
const res = await server.inject({
method: 'POST',
url: '/v1/chat/completions',
payload: {
model: 'claude-sonnet-4-6',
messages: [{ role: 'user', content: 'Hi' }],
stream: false,
},
});
expect(res.statusCode).toBe(401);
const body = res.json();
expect(body.error.message).toContain('Anthropic API error');
expect(body.error.message).toContain('Invalid API key');
});
it('translates tool_use response to OpenAI format', async () => {
process.env.ANTHROPIC_API_KEY = 'test-key';
server = createTestServer();
globalThis.fetch = vi.fn(async () => ({
ok: true,
status: 200,
json: async () => ({
content: [
{ type: 'text', text: 'Let me search for that.' },
{
type: 'tool_use',
id: 'toolu_123',
name: 'web_search',
input: { query: 'Waggle AI agent' },
},
],
model: 'claude-sonnet-4-20250514',
stop_reason: 'tool_use',
usage: { input_tokens: 20, output_tokens: 15 },
}),
})) as unknown as typeof globalThis.fetch;
const res = await server.inject({
method: 'POST',
url: '/v1/chat/completions',
payload: {
model: 'claude-sonnet-4-6',
messages: [{ role: 'user', content: 'Search for Waggle' }],
tools: [
{
type: 'function',
function: {
name: 'web_search',
description: 'Search the web',
parameters: {
type: 'object',
properties: { query: { type: 'string' } },
},
},
},
],
stream: false,
},
});
expect(res.statusCode).toBe(200);
const body = res.json();
expect(body.choices[0].finish_reason).toBe('tool_calls');
expect(body.choices[0].message.content).toBe('Let me search for that.');
expect(body.choices[0].message.tool_calls).toHaveLength(1);
expect(body.choices[0].message.tool_calls[0].id).toBe('toolu_123');
expect(body.choices[0].message.tool_calls[0].type).toBe('function');
expect(body.choices[0].message.tool_calls[0].function.name).toBe('web_search');
expect(JSON.parse(body.choices[0].message.tool_calls[0].function.arguments)).toEqual({ query: 'Waggle AI agent' });
});
it('preserves Anthropic max_tokens as an incomplete OpenAI length reason', async () => {
process.env.ANTHROPIC_API_KEY = 'test-key-max-tokens';
server = createTestServer();
globalThis.fetch = vi.fn(async () => new Response(JSON.stringify({
content: [{ type: 'text', text: 'Partial answer' }],
model: 'claude-sonnet-4-6',
stop_reason: 'max_tokens',
usage: { input_tokens: 100, output_tokens: 50 },
}), { status: 200, headers: { 'content-type': 'application/json' } })) as unknown as typeof globalThis.fetch;
const res = await server.inject({
method: 'POST',
url: '/v1/chat/completions',
payload: {
model: 'anthropic/claude-sonnet-4-6',
messages: [{ role: 'user', content: 'Answer fully' }],
stream: false,
},
});
expect(res.statusCode).toBe(200);
expect(res.json().choices[0].finish_reason).toBe('length');
});
it.each([
[undefined, 'anthropic_missing_stop_reason'],
[null, 'anthropic_missing_stop_reason'],
['refusal', 'refusal'],
['pause_turn', 'pause_turn'],
['tool_use', 'anthropic_inconsistent_tool_use'],
])('fails closed for non-stream stop reason %s', async (anthropicReason, openAiReason) => {
process.env.ANTHROPIC_API_KEY = 'test-key-nonstream-stop-reason';
server = createTestServer();
globalThis.fetch = vi.fn(async () => new Response(JSON.stringify({
content: [{ type: 'text', text: 'Unaccepted partial answer' }],
model: 'claude-sonnet-4-6',
stop_reason: anthropicReason,
usage: { input_tokens: 100, output_tokens: 50 },
}), { status: 200, headers: { 'content-type': 'application/json' } })) as unknown as typeof globalThis.fetch;
const res = await server.inject({
method: 'POST',
url: '/v1/chat/completions',
payload: {
model: 'anthropic/claude-sonnet-4-6',
messages: [{ role: 'user', content: 'Answer fully' }],
stream: false,
},
});
expect(res.statusCode).toBe(200);
expect(res.json().choices[0].finish_reason).toBe(openAiReason);
});
});
describe('POST /v1/chat/completions (streaming)', () => {
it('counts and preserves Anthropic cache tokens in the final usage chunk', async () => {
process.env.ANTHROPIC_API_KEY = 'test-key-stream-cache-usage';
server = createTestServer();
const anthropicStream = [
'data: {"type":"message_start","message":{"usage":{"input_tokens":100,"cache_creation_input_tokens":2000,"cache_read_input_tokens":5000}}}',
'data: {"type":"message_delta","delta":{"stop_reason":"end_turn"},"usage":{"output_tokens":20}}',
'data: {"type":"message_stop"}',
].join('\n\n') + '\n\n';
globalThis.fetch = vi.fn(async () => new Response(anthropicStream, {
status: 200,
headers: { 'content-type': 'text/event-stream' },
})) as unknown as typeof globalThis.fetch;
const res = await server.inject({
method: 'POST',
url: '/v1/chat/completions',
payload: {
model: 'anthropic/claude-sonnet-4-6',
messages: [{ role: 'user', content: 'Stream cached context' }],
stream: true,
stream_options: { include_usage: true },
},
});
expect(res.statusCode).toBe(200);
const usageChunk = res.body
.split('\n')
.filter(line => line.startsWith('data: {'))
.map(line => JSON.parse(line.slice(6)))
.find(chunk => chunk.usage);
expect(usageChunk?.usage).toEqual({
prompt_tokens: 7_100,
completion_tokens: 20,
total_tokens: 7_120,
prompt_tokens_details: { cached_tokens: 5_000 },
cache_creation_input_tokens: 2_000,
cache_read_input_tokens: 5_000,
});
});
it.each([
['end_turn', 'stop', false],
['stop_sequence', 'stop', false],
['tool_use', 'tool_calls', true],
['tool_use', 'anthropic_inconsistent_tool_use', false],
['max_tokens', 'length', false],
['pause_turn', 'pause_turn', false],
['refusal', 'refusal', false],
])('translates streaming stop reason %s to %s before DONE', async (anthropicReason, openAiReason, withToolCall) => {
process.env.ANTHROPIC_API_KEY = 'test-key-stream-stop-reason';
server = createTestServer();
const anthropicStream = [
'data: {"type":"message_start","message":{"usage":{"input_tokens":100}}}',
...(withToolCall
? ['data: {"type":"content_block_start","content_block":{"type":"tool_use","id":"toolu_1","name":"web_search"}}']
: []),
`data: ${JSON.stringify({
type: 'message_delta',
delta: { stop_reason: anthropicReason },
usage: { output_tokens: 20 },
})}`,
'data: {"type":"message_stop"}',
].join('\n\n') + '\n\n';
globalThis.fetch = vi.fn(async () => new Response(anthropicStream, {
status: 200,
headers: { 'content-type': 'text/event-stream' },
})) as unknown as typeof globalThis.fetch;
const res = await server.inject({
method: 'POST',
url: '/v1/chat/completions',
payload: {
model: 'anthropic/claude-sonnet-4-6',
messages: [{ role: 'user', content: 'Stream the answer' }],
stream: true,
stream_options: { include_usage: true },
},
});
expect(res.statusCode).toBe(200);
const dataLines = res.body.split('\n').filter(line => line.startsWith('data: '));
const terminal = dataLines
.filter(line => line !== 'data: [DONE]')
.map(line => JSON.parse(line.slice(6)))
.find(chunk => chunk.choices?.[0]?.finish_reason);
expect(terminal?.choices[0].finish_reason).toBe(openAiReason);
expect(dataLines.at(-1)).toBe('data: [DONE]');
});
it('does not synthesize DONE when the upstream stream ends before message_stop', async () => {
process.env.ANTHROPIC_API_KEY = 'test-key-stream-premature-eof';
const costTracker = new CostTracker();
server = createTestServer({ costTracker });
const anthropicStream = [
'data: {"type":"message_start","message":{"usage":{"input_tokens":100}}}',
'data: {"type":"content_block_delta","delta":{"type":"text_delta","text":"Partial answer"}}',
'data: {"type":"message_delta","delta":{"stop_reason":"max_tokens"},"usage":{"output_tokens":20}}',
].join('\n\n') + '\n\n';
globalThis.fetch = vi.fn(async () => new Response(anthropicStream, {
status: 200,
headers: { 'content-type': 'text/event-stream' },
})) as unknown as typeof globalThis.fetch;
const res = await server.inject({
method: 'POST',
url: '/v1/chat/completions',
payload: {
model: 'anthropic/claude-sonnet-4-6',
messages: [{ role: 'user', content: 'Stream the answer' }],
stream: true,
},
});
expect(res.statusCode).toBe(200);
expect(res.body).toContain('Partial answer');
expect(res.body).not.toContain('finish_reason');
expect(res.body).not.toContain('data: [DONE]');
expect(costTracker.getDailyTotal()).toBeGreaterThan(0.05);
});
it('fails closed when message_stop arrives without a stop reason', async () => {
process.env.ANTHROPIC_API_KEY = 'test-key-stream-missing-stop-reason';
server = createTestServer();
const anthropicStream = [
'data: {"type":"message_start","message":{"usage":{"input_tokens":100}}}',
'data: {"type":"content_block_delta","delta":{"type":"text_delta","text":"Partial answer"}}',
'data: {"type":"message_stop"}',
].join('\n\n') + '\n\n';
globalThis.fetch = vi.fn(async () => new Response(anthropicStream, {
status: 200,
headers: { 'content-type': 'text/event-stream' },
})) as unknown as typeof globalThis.fetch;
const res = await server.inject({
method: 'POST',
url: '/v1/chat/completions',
payload: {
model: 'anthropic/claude-sonnet-4-6',
messages: [{ role: 'user', content: 'Stream the answer' }],
stream: true,
},
});
expect(res.statusCode).toBe(200);
expect(res.body).toContain('"finish_reason":"anthropic_missing_stop_reason"');
expect(res.body.trimEnd()).toMatch(/data: \[DONE\]$/);
});
it('cancels the upstream reader immediately after message_stop', async () => {
process.env.ANTHROPIC_API_KEY = 'test-key-stream-terminal-cancel';
server = createTestServer();
const cancel = vi.fn();
let delivered = false;
const hangingAfterTerminal = new ReadableStream<Uint8Array>({
pull(controller) {
if (!delivered) {
delivered = true;
controller.enqueue(new TextEncoder().encode([
'data: {"type":"message_delta","delta":{"stop_reason":"end_turn"}}',
'data: {"type":"message_stop"}',
].join('\n\n') + '\n\n'));
return;
}
return new Promise(() => undefined);
},
cancel,
});
globalThis.fetch = vi.fn(async () => new Response(hangingAfterTerminal, {
status: 200,
headers: { 'content-type': 'text/event-stream' },
})) as unknown as typeof globalThis.fetch;
const res = await server.inject({
method: 'POST',
url: '/v1/chat/completions',
payload: {
model: 'anthropic/claude-sonnet-4-6',
messages: [{ role: 'user', content: 'Stream the answer' }],
stream: true,
},
});
expect(res.statusCode).toBe(200);
expect(res.body.trimEnd()).toMatch(/data: \[DONE\]$/);
expect(cancel).toHaveBeenCalledOnce();
}, 2_000);
it('does not synthesize DONE when the upstream stream reader fails', async () => {
process.env.ANTHROPIC_API_KEY = 'test-key-stream-read-failure';
server = createTestServer();
const encoder = new TextEncoder();
let pullCount = 0;
const failingStream = new ReadableStream<Uint8Array>({
pull(controller) {
if (pullCount++ === 0) {
controller.enqueue(encoder.encode(
'data: {"type":"content_block_delta","delta":{"type":"text_delta","text":"Partial answer"}}\n\n',
));
} else {
controller.error(new Error('upstream socket closed'));
}
},
});
globalThis.fetch = vi.fn(async () => new Response(failingStream, {
status: 200,
headers: { 'content-type': 'text/event-stream' },
})) as unknown as typeof globalThis.fetch;
const res = await server.inject({
method: 'POST',
url: '/v1/chat/completions',
payload: {
model: 'anthropic/claude-sonnet-4-6',
messages: [{ role: 'user', content: 'Stream the answer' }],
stream: true,
},
});
expect(res.statusCode).toBe(200);
expect(res.body).toContain('Partial answer');
expect(res.body).not.toContain('data: [DONE]');
});
});
describe('POST /v1/chat/completions (streaming)', () => {
it('counts and preserves Anthropic cache tokens in the final usage chunk', async () => {
process.env.ANTHROPIC_API_KEY = 'test-key-stream-cache-usage';
server = createTestServer();
const anthropicStream = [
'data: {"type":"message_start","message":{"usage":{"input_tokens":100,"cache_creation_input_tokens":2000,"cache_read_input_tokens":5000}}}',
'data: {"type":"message_delta","usage":{"output_tokens":20}}',
'data: {"type":"message_stop"}',
].join('\n\n') + '\n\n';
globalThis.fetch = vi.fn(async () => new Response(anthropicStream, {
status: 200,
headers: { 'content-type': 'text/event-stream' },
})) as unknown as typeof globalThis.fetch;
const res = await server.inject({
method: 'POST',
url: '/v1/chat/completions',
payload: {
model: 'anthropic/claude-sonnet-4-6',
messages: [{ role: 'user', content: 'Stream cached context' }],
stream: true,
stream_options: { include_usage: true },
},
});
expect(res.statusCode).toBe(200);
const usageChunk = res.body
.split('\n')
.filter(line => line.startsWith('data: {'))
.map(line => JSON.parse(line.slice(6)))
.find(chunk => chunk.usage);
expect(usageChunk?.usage).toEqual({
prompt_tokens: 7_100,
completion_tokens: 20,
total_tokens: 7_120,
prompt_tokens_details: { cached_tokens: 5_000 },
cache_creation_input_tokens: 2_000,
cache_read_input_tokens: 5_000,
});
});
});
// B3 cleanup regression guard per decisions/2026-04-22-model-route-naming-locked.md §4
describe('invalid snapshot regression guard (B3 cleanup 2026-04-22)', () => {
it('does NOT inject -20250514 snapshot for any Claude 4.6 family floating alias', async () => {
process.env.ANTHROPIC_API_KEY = 'test-key-snapshot-guard';
server = createTestServer();
const captures: Array<{ model: string }> = [];
globalThis.fetch = vi.fn(async (_url: string | URL | Request, init?: RequestInit) => {
const body = init?.body ? JSON.parse(String(init.body)) : {};
captures.push({ model: body.model });
return {
ok: true,
status: 200,
json: async () => ({
content: [{ type: 'text', text: 'ok' }],
model: body.model,
stop_reason: 'end_turn',
usage: { input_tokens: 1, output_tokens: 1 },
}),
};
}) as unknown as typeof globalThis.fetch;
const floatingAliases = ['claude-sonnet-4-6', 'claude-opus-4-6', 'anthropic/claude-sonnet-4.6', 'anthropic/claude-opus-4.6'];
for (const alias of floatingAliases) {
await server.inject({
method: 'POST',
url: '/v1/chat/completions',
payload: {
model: alias,
messages: [{ role: 'user', content: 'test' }],
stream: false,
},
});
}
// Every outbound model must NOT be the invalid -20250514 snapshot.
for (const cap of captures) {
expect(cap.model).not.toMatch(/-20250514$/);
// Positive assertion: floating alias passes through as the canonical
// dash-form (mapModel normalizes dots to dashes).
expect(cap.model).toMatch(/^claude-(sonnet|opus)-4-6$/);
}
expect(captures).toHaveLength(floatingAliases.length);
});
});
describe('max token forwarding', () => {
it.each([
{
label: 'Anthropic',
model: 'anthropic/claude-sonnet-4-6',
providerId: 'anthropic',
response: {
content: [{ type: 'text', text: 'ok' }],
model: 'claude-sonnet-4-6',
stop_reason: 'end_turn',
usage: { input_tokens: 1, output_tokens: 1 },
},
},
{
label: 'OpenRouter',
model: 'openrouter/openai/gpt-5.4',
providerId: 'openrouter',
response: {
choices: [{ message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' }],
},
},
{
label: 'Gemini',
model: 'google/gemini-3.5-flash',
providerId: 'google',
response: {
choices: [{ message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' }],
},
},
{
label: 'non-reasoning direct OpenAI',
model: 'openai/gpt-4.1',
providerId: 'openai',
response: {
choices: [{ message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' }],
},
},
])('keeps max_tokens for $label requests', async ({ model, providerId, response }) => {
server = createTestServer({
vaultProviders: { [providerId]: { value: `${providerId}-vault-key` } },
});
globalThis.fetch = vi.fn(async () => new Response(JSON.stringify(response), {
status: 200,
headers: { 'content-type': 'application/json' },
})) as unknown as typeof globalThis.fetch;
const res = await server.inject({
method: 'POST',
url: '/v1/chat/completions',
payload: {
model,
messages: [{ role: 'user', content: 'test' }],
max_tokens: 321,
stream: false,
},
});
expect(res.statusCode).toBe(200);
const outbound = JSON.parse(String(vi.mocked(globalThis.fetch).mock.calls[0][1]?.body));
expect(outbound.max_tokens).toBe(321);
expect(outbound).not.toHaveProperty('max_completion_tokens');
});
it.each(['gpt-5.4', 'openai/o3-mini', 'openai/codex-mini-latest'])(
'translates max_tokens for direct OpenAI reasoning model %s',
async (model) => {
server = createTestServer({
vaultProviders: { openai: { value: 'openai-vault-key' } },
});
globalThis.fetch = vi.fn(async () => new Response(JSON.stringify({
choices: [{ message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' }],
}), { status: 200, headers: { 'content-type': 'application/json' } })) as unknown as typeof globalThis.fetch;
const res = await server.inject({
method: 'POST',
url: '/v1/chat/completions',
payload: {
model,
messages: [{ role: 'user', content: 'test' }],
max_tokens: 321,
stream: false,
},
});
expect(res.statusCode).toBe(200);
const outbound = JSON.parse(String(vi.mocked(globalThis.fetch).mock.calls[0][1]?.body));
expect(outbound.max_completion_tokens).toBe(321);
expect(outbound).not.toHaveProperty('max_tokens');
},
);
});
describe('Docker-independent provider routing', () => {
it('forwards Ollama models to the loopback runtime without cloud credentials', async () => {
vi.stubEnv('OLLAMA_HOST', 'http://127.0.0.1:11455');
server = createTestServer();
globalThis.fetch = vi.fn(async (url: string | URL | Request) => new Response(JSON.stringify(
String(url).endsWith('/api/tags')
? { models: [{ name: 'qwen3:1.7b' }] }
: {
choices: [{ message: { role: 'assistant', content: 'Local route works.' }, finish_reason: 'stop' }],
model: 'qwen3:1.7b',
},
), { status: 200, headers: { 'content-type': 'application/json' } })) as unknown as typeof globalThis.fetch;
const res = await server.inject({
method: 'POST',
url: '/v1/chat/completions',
payload: {
model: 'ollama/qwen3:1.7b',
messages: [{ role: 'user', content: 'test' }],
tools: [{
type: 'function',
function: { name: 'read_file', description: 'Read', parameters: { type: 'object' } },
}],
stream: false,
},
});
expect(res.statusCode).toBe(200);
expect(res.json().choices[0].message.content).toBe('Local route works.');
const [url, init] = vi.mocked(globalThis.fetch).mock.calls[1];
expect(String(url)).toBe('http://127.0.0.1:11455/v1/chat/completions');
expect(init?.method).toBe('POST');
expect((init?.headers as Record<string, string>).Authorization).toBeUndefined();
expect((init?.headers as Record<string, string>)['Content-Type']).toBe('application/json');
expect(init?.redirect).toBe('error');
const outbound = JSON.parse(String(init?.body));
expect(outbound.model).toBe('qwen3:1.7b');
expect(outbound.tools[0].function.name).toBe('read_file');
});
it('passes through Ollama SSE without buffering it into JSON', async () => {
vi.stubEnv('OLLAMA_HOST', 'http://localhost:11455');
server = createTestServer();
const upstream = 'data: {"choices":[{"delta":{"content":"Hi"}}]}\n\ndata: [DONE]\n\n';
globalThis.fetch = vi.fn(async (url: string | URL | Request) => String(url).endsWith('/api/tags')
? new Response(JSON.stringify({ models: [{ name: 'qwen3:1.7b' }] }), {
status: 200,
headers: { 'content-type': 'application/json' },
})
: new Response(upstream, {
status: 200,
headers: { 'content-type': 'text/event-stream' },
})) as unknown as typeof globalThis.fetch;
const res = await server.inject({
method: 'POST',
url: '/v1/chat/completions',
payload: {
model: 'ollama/qwen3:1.7b',
messages: [{ role: 'user', content: 'test' }],
stream: true,
},
});
expect(res.statusCode).toBe(200);
expect(res.headers['content-type']).toContain('text/event-stream');
expect(res.body).toBe(upstream);
expect(String(vi.mocked(globalThis.fetch).mock.calls[1][0]))
.toBe('http://localhost:11455/v1/chat/completions');
});
it('aborts the Ollama generation when the client disconnects', async () => {
vi.stubEnv('OLLAMA_HOST', 'http://127.0.0.1:11455');
server = createTestServer();
let upstreamSignal: AbortSignal | undefined;
globalThis.fetch = vi.fn(async (url, init) => {
if (String(url).endsWith('/api/tags')) {
return new Response(JSON.stringify({ models: [{ name: 'qwen3:1.7b' }] }), {
status: 200,
headers: { 'content-type': 'application/json' },
});
}
upstreamSignal = init?.signal as AbortSignal | undefined;
return new Response(new ReadableStream<Uint8Array>({
start(controller) {
controller.enqueue(new TextEncoder().encode(
'data: {"choices":[{"delta":{"content":"Hi"}}]}\n\n',
));
const fallback = setTimeout(() => controller.close(), 500);
upstreamSignal?.addEventListener('abort', () => {
clearTimeout(fallback);
controller.close();
}, { once: true });
},
}), {
status: 200,
headers: { 'content-type': 'text/event-stream' },
});
}) as unknown as typeof globalThis.fetch;
await server.listen({ host: '127.0.0.1', port: 0 });
const address = server.server.address();
if (!address || typeof address === 'string') throw new Error('Test server did not bind TCP');
const clientAbort = new AbortController();
const clientResponse = await originalFetch(
`http://127.0.0.1:${address.port}/v1/chat/completions`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
model: 'ollama/qwen3:1.7b',
messages: [{ role: 'user', content: 'test' }],
stream: true,
}),
signal: clientAbort.signal,
},
);
const clientReader = clientResponse.body?.getReader();
expect(clientReader).toBeDefined();
await clientReader!.read();
clientAbort.abort();
await vi.waitFor(() => {
expect(upstreamSignal?.aborted).toBe(true);
}, { timeout: 1_000 });
});
it.each([
{
label: 'OpenAI-compatible',
model: 'openai/gpt-5.4',
options: { vaultProviders: { openai: { value: 'openai-vault-key' } } },
},
{
label: 'native Anthropic',
model: 'anthropic/claude-sonnet-4-6',
options: { vaultApiKey: 'anthropic-vault-key' },
},
])('bounds a stalled $label cloud request with a 504', async ({ model, options }) => {
vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout'] });
try {
server = createTestServer(options);
let upstreamSignal: AbortSignal | undefined;
let markFetchStarted!: () => void;
const fetchStarted = new Promise<void>((resolve) => { markFetchStarted = resolve; });
globalThis.fetch = vi.fn((_url, init) => new Promise<Response>((_resolve, reject) => {
upstreamSignal = init?.signal as AbortSignal | undefined;
markFetchStarted();
const fallback = setTimeout(
() => reject(new Error('unbounded cloud request')),
120_001,
);
upstreamSignal?.addEventListener('abort', () => {
clearTimeout(fallback);
reject(upstreamSignal?.reason ?? new Error('cloud request aborted'));
}, { once: true });
})) as unknown as typeof globalThis.fetch;
const responsePromise = server.inject({
method: 'POST',
url: '/v1/chat/completions',
payload: {
model,
messages: [{ role: 'user', content: 'test' }],
stream: false,
},
});
await fetchStarted;
await vi.advanceTimersByTimeAsync(120_001);
const response = await responsePromise;
expect(upstreamSignal?.aborted).toBe(true);
expect(response.statusCode).toBe(504);
expect(response.json().error.message).toContain('timed out after 120000ms');
} finally {
vi.useRealTimers();
}
});
it.each([
{
label: 'OpenAI-compatible',
model: 'openai/gpt-5.4',
options: { vaultProviders: { openai: { value: 'openai-vault-key' } } },
response: {
choices: [{ message: { role: 'assistant', content: 'done' }, finish_reason: 'stop' }],
},
},
{
label: 'native Anthropic',
model: 'anthropic/claude-sonnet-4-6',
options: { vaultApiKey: 'anthropic-vault-key' },
response: {
content: [{ type: 'text', text: 'done' }],
stop_reason: 'end_turn',
usage: { input_tokens: 1, output_tokens: 1 },
},
},
])('clears the $label timeout after a completed response', async ({
model,
options,
response,
}) => {
vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout'] });
try {
server = createTestServer(options);
let upstreamSignal: AbortSignal | undefined;
globalThis.fetch = vi.fn(async (_url, init) => {
upstreamSignal = init?.signal as AbortSignal | undefined;
return new Response(JSON.stringify(response), {
status: 200,
headers: { 'content-type': 'application/json' },
});
}) as unknown as typeof globalThis.fetch;
const result = await server.inject({
method: 'POST',
url: '/v1/chat/completions',
payload: {
model,
messages: [{ role: 'user', content: 'test' }],
stream: false,
},
});
expect(result.statusCode).toBe(200);
await vi.advanceTimersByTimeAsync(120_001);
expect(upstreamSignal?.aborted).toBe(false);
} finally {
vi.useRealTimers();
}
});
it.each([
{
label: 'OpenAI-compatible',
model: 'openai/gpt-5.4',
options: { vaultProviders: { openai: { value: 'openai-vault-key' } } },
firstChunk: 'data: {"choices":[{"delta":{"content":"Hi"}}]}\n\n',
},
{
label: 'native Anthropic',
model: 'anthropic/claude-sonnet-4-6',
options: { vaultApiKey: 'anthropic-vault-key' },
firstChunk: 'data: {"type":"content_block_delta","delta":{"type":"text_delta","text":"Hi"}}\n\n',
},
])('aborts the $label cloud stream when the client disconnects', async ({
model,
options,
firstChunk,
}) => {
server = createTestServer(options);
let upstreamSignal: AbortSignal | undefined;
globalThis.fetch = vi.fn(async (_url, init) => {
upstreamSignal = init?.signal as AbortSignal | undefined;
return new Response(new ReadableStream<Uint8Array>({
start(controller) {
controller.enqueue(new TextEncoder().encode(firstChunk));
const fallback = setTimeout(() => controller.close(), 500);
upstreamSignal?.addEventListener('abort', () => {
clearTimeout(fallback);
controller.close();
}, { once: true });
},
}), {
status: 200,
headers: { 'content-type': 'text/event-stream' },
});
}) as unknown as typeof globalThis.fetch;
await server.listen({ host: '127.0.0.1', port: 0 });
const address = server.server.address();
if (!address || typeof address === 'string') throw new Error('Test server did not bind TCP');
const clientAbort = new AbortController();
const clientResponse = await originalFetch(
`http://127.0.0.1:${address.port}/v1/chat/completions`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
model,
messages: [{ role: 'user', content: 'test' }],
stream: true,
}),
signal: clientAbort.signal,
},
);
const clientReader = clientResponse.body?.getReader();
expect(clientReader).toBeDefined();
await clientReader!.read();
clientAbort.abort();
await vi.waitFor(() => {
expect(upstreamSignal?.aborted).toBe(true);
}, { timeout: 1_000 });
});
it('rejects a non-loopback Ollama endpoint before making an outbound request', async () => {
vi.stubEnv('OLLAMA_HOST', 'http://ollama.example.test');
server = createTestServer();
globalThis.fetch = vi.fn();
const res = await server.inject({
method: 'POST',
url: '/v1/chat/completions',
payload: {
model: 'ollama/qwen3:1.7b',
messages: [{ role: 'user', content: 'test' }],
stream: false,
},
});
expect(res.statusCode).toBe(409);
expect(res.json().error.code).toBe('OLLAMA_MODEL_NOT_LOCAL');
expect(globalThis.fetch).not.toHaveBeenCalled();
});
it.each([
{ model: 'qwen3:cloud', remoteHost: undefined },
{ model: 'qwen3:remote', remoteHost: 'https://ollama.com' },
]) (
'rejects Ollama remote alias $model without dispatching a completion',
async ({ model, remoteHost }) => {
vi.stubEnv('OLLAMA_HOST', 'http://127.0.0.1:11455');
server = createTestServer();
globalThis.fetch = vi.fn(async (url: string | URL | Request) => {
if (String(url).endsWith('/api/tags')) {
return new Response(JSON.stringify({
models: [{ name: model, ...(remoteHost ? { remote_host: remoteHost } : {}) }],
}), {
status: 200,
headers: { 'content-type': 'application/json' },
});
}
throw new Error('completion dispatch must not occur');
}) as unknown as typeof globalThis.fetch;
const res = await server.inject({
method: 'POST',
url: '/v1/chat/completions',
payload: {
model: `ollama/${model}`,
messages: [{ role: 'user', content: 'test' }],
},
});
expect(res.statusCode).toBe(409);
expect(res.json().error.code).toBe('OLLAMA_MODEL_NOT_LOCAL');
expect(globalThis.fetch).not.toHaveBeenCalledWith(
expect.stringContaining('/v1/chat/completions'),
expect.anything(),
);
},
);
it('forwards OpenAI-compatible models directly without LiteLLM', async () => {
server = createTestServer({
vaultProviders: { openai: { value: 'openai-vault-key' } },
});
globalThis.fetch = vi.fn(async () => new Response(JSON.stringify({
choices: [{ message: { role: 'assistant', content: 'Direct route works.' }, finish_reason: 'stop' }],
usage: {
prompt_tokens: 7_100,
completion_tokens: 20,
total_tokens: 7_120,
prompt_tokens_details: { cached_tokens: 5_000 },
},
model: 'gpt-5.4',
}), { status: 200, headers: { 'content-type': 'application/json' } })) as unknown as typeof globalThis.fetch;
const res = await server.inject({
method: 'POST',
url: '/v1/chat/completions',
payload: {
model: 'openai/gpt-5.4',
messages: [{ role: 'user', content: 'test' }],
tools: [{
type: 'function',
function: { name: 'read_file', description: 'Read', parameters: { type: 'object' } },
}],
stream: false,
},
});
expect(res.statusCode).toBe(200);
expect(res.json().choices[0].message.content).toBe('Direct route works.');
expect(res.json().usage).toEqual({
prompt_tokens: 7_100,
completion_tokens: 20,
total_tokens: 7_120,
prompt_tokens_details: { cached_tokens: 5_000 },
});
const [url, init] = vi.mocked(globalThis.fetch).mock.calls[0];
expect(String(url)).toBe('https://api.openai.com/v1/chat/completions');
expect((init?.headers as Record<string, string>).Authorization).toBe('Bearer openai-vault-key');
const outbound = JSON.parse(String(init?.body));
expect(outbound.model).toBe('gpt-5.4');
expect(outbound.tools[0].function.name).toBe('read_file');
});
it('preserves nested OpenRouter model ids and honors a configured compatible base URL', async () => {
server = createTestServer({
vaultProviders: {
openrouter: {
value: 'openrouter-vault-key',
metadata: { baseUrl: 'https://router.example.test/api/v1' },
},
},
});
globalThis.fetch = vi.fn(async () => new Response(JSON.stringify({
choices: [{ message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' }],
}), { status: 200, headers: { 'content-type': 'application/json' } })) as unknown as typeof globalThis.fetch;
const res = await server.inject({
method: 'POST',
url: '/v1/chat/completions',
payload: {
model: 'openrouter/anthropic/claude-opus-4.8',
messages: [{ role: 'user', content: 'test' }],
stream: false,
},
});
expect(res.statusCode).toBe(200);
const [url, init] = vi.mocked(globalThis.fetch).mock.calls[0];
expect(String(url)).toBe('https://router.example.test/api/v1/chat/completions');
expect(JSON.parse(String(init?.body)).model).toBe('anthropic/claude-opus-4.8');
});
it('uses the Gemini OpenAI-compatibility endpoint with bearer auth', async () => {
server = createTestServer({
vaultProviders: { google: { value: 'gemini-vault-key' } },
});
globalThis.fetch = vi.fn(async () => new Response(JSON.stringify({
choices: [{ message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' }],
}), { status: 200, headers: { 'content-type': 'application/json' } })) as unknown as typeof globalThis.fetch;
const res = await server.inject({
method: 'POST',
url: '/v1/chat/completions',
payload: {
model: 'google/gemini-3.5-flash',
messages: [{ role: 'user', content: 'test' }],
stream: false,
},
});
expect(res.statusCode).toBe(200);
const [url, init] = vi.mocked(globalThis.fetch).mock.calls[0];
expect(String(url)).toBe(
'https://generativelanguage.googleapis.com/v1beta/openai/chat/completions',
);
expect((init?.headers as Record<string, string>).Authorization).toBe('Bearer gemini-vault-key');
expect(JSON.parse(String(init?.body)).model).toBe('gemini-3.5-flash');
});
it('falls back from a stale Gemini alias to a working Google alias', async () => {
const priorGemini = process.env.GEMINI_API_KEY;
const priorGoogle = process.env.GOOGLE_API_KEY;
process.env.GEMINI_API_KEY = 'stale-gemini-key';
process.env.GOOGLE_API_KEY = 'working-google-key';
try {
server = createTestServer({ vaultProviders: {} });
globalThis.fetch = vi.fn(async (_url, init) => {
const authorization = (init?.headers as Record<string, string>).Authorization;
if (authorization === 'Bearer stale-gemini-key') {
return new Response(JSON.stringify({
error: { message: 'Please pass a valid API key.' },
}), { status: 400, headers: { 'content-type': 'application/json' } });
}
return new Response(JSON.stringify({
choices: [{ message: { role: 'assistant', content: 'fallback works' }, finish_reason: 'stop' }],
}), { status: 200, headers: { 'content-type': 'application/json' } });
}) as unknown as typeof globalThis.fetch;
const res = await server.inject({
method: 'POST',
url: '/v1/chat/completions',
payload: {
model: 'google/gemini-2.5-flash',
messages: [{ role: 'user', content: 'test' }],
stream: false,
},
});
expect(res.statusCode).toBe(200);
expect(res.json().choices[0].message.content).toBe('fallback works');
expect(vi.mocked(globalThis.fetch)).toHaveBeenCalledTimes(2);
expect(process.env.GEMINI_API_KEY).toBe('working-google-key');
expect(process.env.GOOGLE_API_KEY).toBe('working-google-key');
} finally {
if (priorGemini === undefined) delete process.env.GEMINI_API_KEY;
else process.env.GEMINI_API_KEY = priorGemini;
if (priorGoogle === undefined) delete process.env.GOOGLE_API_KEY;
else process.env.GOOGLE_API_KEY = priorGoogle;
}
});
it('passes through provider SSE without buffering it into JSON', async () => {
server = createTestServer({
vaultProviders: { deepseek: { value: 'deepseek-vault-key' } },
});
const upstream = 'data: {"choices":[{"delta":{"content":"Hi"}}]}\n\ndata: [DONE]\n\n';
globalThis.fetch = vi.fn(async () => new Response(upstream, {
status: 200,
headers: { 'content-type': 'text/event-stream' },
})) as unknown as typeof globalThis.fetch;
const res = await server.inject({
method: 'POST',
url: '/v1/chat/completions',
payload: {
model: 'deepseek/deepseek-chat',
messages: [{ role: 'user', content: 'test' }],
stream: true,
},
});
expect(res.statusCode).toBe(200);
expect(res.headers['content-type']).toContain('text/event-stream');
expect(res.body).toBe(upstream);
});
it('rejects unknown providers before making an outbound request', async () => {
server = createTestServer();
globalThis.fetch = vi.fn();
const res = await server.inject({
method: 'POST',
url: '/v1/chat/completions',
payload: {
model: 'unknown-provider/new-model',
messages: [{ role: 'user', content: 'test' }],
stream: false,
},
});
expect(res.statusCode).toBe(400);
expect(res.json().error.message).toContain('unknown-provider/new-model');
expect(globalThis.fetch).not.toHaveBeenCalled();
});
it('still forwards Claude models (with and without provider prefix)', async () => {
process.env.ANTHROPIC_API_KEY = 'test-key-model-guard-pass';
server = createTestServer();
const captures: string[] = [];
globalThis.fetch = vi.fn(async (_url: string | URL | Request, init?: RequestInit) => {
const body = init?.body ? JSON.parse(String(init.body)) : {};
captures.push(body.model);
return {
ok: true,
status: 200,
json: async () => ({
content: [{ type: 'text', text: 'ok' }],
model: body.model,
stop_reason: 'end_turn',
usage: { input_tokens: 1, output_tokens: 1 },
}),
};
}) as unknown as typeof globalThis.fetch;
for (const model of ['claude-fable-5', 'anthropic/claude-sonnet-5']) {
const res = await server.inject({
method: 'POST',
url: '/v1/chat/completions',
payload: {
model,
messages: [{ role: 'user', content: 'test' }],
stream: false,
},
});
expect(res.statusCode).toBe(200);
}
expect(captures).toEqual(['claude-fable-5', 'claude-sonnet-5']);
});
});
});