feat(proxy): favourite-index tool presentation #86

Merged
michal merged 4 commits from feat/favourite-index-presentation into main 2026-07-23 21:28:23 +00:00
2 changed files with 60 additions and 0 deletions
Showing only changes of commit 366b505c00 - Show all commits

View File

@@ -101,6 +101,15 @@ export class AnthropicProvider implements LlmProvider {
res.on('data', (chunk: Buffer) => chunks.push(chunk));
res.on('end', () => {
const raw = Buffer.concat(chunks).toString('utf-8');
// Surface transport failures (e.g. a 404 "model not found" or a 401
// for an OAuth-gated key) instead of parsing the error body as a
// completion — otherwise it slips through as an empty completion and
// the gate reports the misleading "did not contain valid JSON".
const status = res.statusCode ?? 0;
if (status >= 400) {
reject(new Error(`Anthropic HTTP ${status}: ${raw.slice(0, 300)}`));
return;
}
try {
resolve(JSON.parse(raw));
} catch {

View File

@@ -0,0 +1,51 @@
/**
* AnthropicProvider — transport-error surfacing (parity with OpenAiProvider).
*
* A 404 "model not found" (OAuth-gated key on an unavailable model) or a 401
* must reject with the status + body, not parse the error body as an empty
* completion (which the gate then misreports as "did not contain valid JSON").
*/
import { describe, it, expect, beforeEach, vi } from 'vitest';
let mockStatus = 200;
let mockBody = JSON.stringify({ content: [{ type: 'text', text: 'ok' }], usage: {}, stop_reason: 'end_turn' });
vi.mock('node:https', () => {
const request = vi.fn((_opts: unknown, cb?: unknown) => {
const res = {
statusCode: mockStatus,
on: (event: string, handler: (d?: unknown) => void) => {
if (event === 'data') handler(Buffer.from(mockBody));
if (event === 'end') handler();
return res;
},
};
if (typeof cb === 'function') (cb as (r: unknown) => void)(res);
return { on: vi.fn().mockReturnThis(), write: vi.fn(), end: vi.fn() };
});
return { default: { request }, request };
});
const { AnthropicProvider } = await import('../src/providers/anthropic.js');
describe('AnthropicProvider transport handling', () => {
beforeEach(() => {
mockStatus = 200;
mockBody = JSON.stringify({ content: [{ type: 'text', text: 'ok' }], usage: {}, stop_reason: 'end_turn' });
});
it('rejects on HTTP 404 (model not found) instead of masking it as bad JSON', async () => {
mockStatus = 404;
mockBody = JSON.stringify({ type: 'error', error: { type: 'not_found_error', message: 'model: claude-opus-4' } });
const p = new AnthropicProvider({ apiKey: 'sk-ant-oat01-x' });
await expect(
p.complete({ messages: [{ role: 'user', content: 'hi' }], maxTokens: 8 }),
).rejects.toThrow(/Anthropic HTTP 404/);
});
it('still returns the completion on 200', async () => {
const p = new AnthropicProvider({ apiKey: 'sk-ant-oat01-x' });
const r = await p.complete({ messages: [{ role: 'user', content: 'hi' }], maxTokens: 8 });
expect(r.content).toBe('ok');
});
});