mirror of
https://github.com/teamchong/pxpipe.git
synced 2026-07-22 02:02:51 +02:00
47b5e412b3
Collapse old conversation history into rendered PNG sections so the model reads a compact image instead of re-billed text, while preserving prompt caching and tool-call behavior. Measures real vs compressed token/cost. Core: - GPT history collapse (openai-history.ts): append-only, o200k token-length sectioning. Sections seal only at a tool-closed boundary (open call-id set empty), so a function_call and its function_call_output never split across the collapse cut. Fixes the OpenAI 400 "No tool call found for function call output with call_id ..." that hit long Responses-API sessions. - Anthropic cache contract (history.ts): append-only per-chunk rendering; cache_control markers are preserved/moved, never added; chunk boundaries align with caller marker seams for byte-stable prefix caching. - GPT image budget (openai.ts): detail:'original' for gpt-5.x, flagship vision-multiplier fix, patch cap; schema-strip preserves real descriptions. - Savings accounting (openai-savings.ts): cached_tokens + vision-token basis. Model scope (applicability.ts): - Default imaged scope = claude-fable-5 + gpt-5.6. - gpt-5.5 and claude-opus-4-8 stay opt-in: same pipeline, but they degrade reading dense imaged history (gist drift), so silently imaging them by default is wrong. Promotion is gated on an OCR/recall threshold. Dashboard: GPT + Anthropic rendering, per-family model toggles, persisted metrics, thumbnail-expired session UI, reflow/newline handling. Tests: cache-alignment (GPT + Anthropic), history sectioning + tool-boundary invariants, savings, dashboard, sessions/restart-restore. 452 passing.
212 lines
7.9 KiB
TypeScript
212 lines
7.9 KiB
TypeScript
/**
|
|
* Cloudflare AI Gateway provider mode. All URLs and tokens here are fake —
|
|
* the suite never touches the network (global fetch is stubbed).
|
|
*/
|
|
import { afterEach, describe, expect, it } from 'vitest';
|
|
import { createProxy, parseGatewayHeaders, resolveUpstreams } from '../src/core/proxy.js';
|
|
|
|
const FAKE_BASE = 'https://gateway.example.test/v1/acct_fake/gw_fake';
|
|
const FAKE_TOKEN = 'Bearer fake-gateway-token';
|
|
|
|
const realFetch = globalThis.fetch;
|
|
afterEach(() => {
|
|
globalThis.fetch = realFetch;
|
|
});
|
|
|
|
describe('resolveUpstreams', () => {
|
|
it('defaults to direct upstreams without a provider', () => {
|
|
expect(resolveUpstreams({})).toEqual({
|
|
anthropic: 'https://api.anthropic.com',
|
|
openai: 'https://api.openai.com',
|
|
stripOpenAIV1: false,
|
|
});
|
|
});
|
|
|
|
it('derives both family routes from one gateway base', () => {
|
|
expect(resolveUpstreams({ provider: 'cloudflare-ai-gateway', gatewayBaseUrl: FAKE_BASE + '/' }))
|
|
.toEqual({
|
|
anthropic: `${FAKE_BASE}/anthropic`,
|
|
openai: `${FAKE_BASE}/openai`,
|
|
stripOpenAIV1: true,
|
|
});
|
|
});
|
|
|
|
it('requires gatewayBaseUrl in provider mode', () => {
|
|
expect(() => resolveUpstreams({ provider: 'cloudflare-ai-gateway' })).toThrow(
|
|
/gatewayBaseUrl/,
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('parseGatewayHeaders', () => {
|
|
it('parses JSON object form', () => {
|
|
expect(parseGatewayHeaders('{"cf-aig-authorization": "Bearer x", "x-extra": "1"}')).toEqual({
|
|
'cf-aig-authorization': 'Bearer x',
|
|
'x-extra': '1',
|
|
});
|
|
});
|
|
|
|
it('parses k=v;k2=v2 form (values may contain =)', () => {
|
|
expect(parseGatewayHeaders('cf-aig-authorization=Bearer a=b; x-extra=1')).toEqual({
|
|
'cf-aig-authorization': 'Bearer a=b',
|
|
'x-extra': '1',
|
|
});
|
|
});
|
|
|
|
it('returns empty for unset', () => {
|
|
expect(parseGatewayHeaders(undefined)).toEqual({});
|
|
expect(parseGatewayHeaders('')).toEqual({});
|
|
});
|
|
});
|
|
|
|
function stubFetch(capture: { url?: string; headers?: Headers }) {
|
|
globalThis.fetch = (async (input: RequestInfo | URL, init?: RequestInit) => {
|
|
const url = String(input);
|
|
if (url.endsWith('/count_tokens')) {
|
|
return new Response(JSON.stringify({ input_tokens: 1 }), {
|
|
headers: { 'content-type': 'application/json' },
|
|
});
|
|
}
|
|
capture.url = url;
|
|
capture.headers = new Headers(init?.headers);
|
|
return new Response(
|
|
JSON.stringify({ type: 'message', content: [], usage: { input_tokens: 1, output_tokens: 1 } }),
|
|
{ headers: { 'content-type': 'application/json' } },
|
|
);
|
|
}) as typeof fetch;
|
|
}
|
|
|
|
describe('gateway end-to-end routing (stubbed fetch)', () => {
|
|
const proxy = () =>
|
|
createProxy({
|
|
provider: 'cloudflare-ai-gateway',
|
|
gatewayBaseUrl: FAKE_BASE,
|
|
gatewayHeaders: { 'cf-aig-authorization': FAKE_TOKEN },
|
|
});
|
|
|
|
it('routes Anthropic /v1/messages to {base}/anthropic/v1/messages with injected headers', async () => {
|
|
const cap: { url?: string; headers?: Headers } = {};
|
|
stubFetch(cap);
|
|
const res = await proxy()(
|
|
new Request('http://localhost/v1/messages', {
|
|
method: 'POST',
|
|
headers: { 'content-type': 'application/json', 'x-api-key': 'fake-anthropic-key' },
|
|
body: JSON.stringify({ model: 'claude-fable-5', max_tokens: 1, messages: [{ role: 'user', content: 'hi' }] }),
|
|
}),
|
|
);
|
|
expect(res.status).toBe(200);
|
|
expect(cap.url).toBe(`${FAKE_BASE}/anthropic/v1/messages`);
|
|
expect(cap.headers?.get('cf-aig-authorization')).toBe(FAKE_TOKEN);
|
|
expect(cap.headers?.get('x-api-key')).toBe('fake-anthropic-key');
|
|
});
|
|
|
|
it('routes OpenAI /v1/chat/completions to {base}/openai/chat/completions', async () => {
|
|
const cap: { url?: string; headers?: Headers } = {};
|
|
stubFetch(cap);
|
|
await proxy()(
|
|
new Request('http://localhost/v1/chat/completions', {
|
|
method: 'POST',
|
|
headers: { 'content-type': 'application/json', authorization: 'Bearer fake-openai-key' },
|
|
body: JSON.stringify({ model: 'gpt-fake', messages: [{ role: 'user', content: 'hi' }] }),
|
|
}),
|
|
);
|
|
expect(cap.url).toBe(`${FAKE_BASE}/openai/chat/completions`);
|
|
expect(cap.headers?.get('cf-aig-authorization')).toBe(FAKE_TOKEN);
|
|
expect(cap.headers?.get('authorization')).toBe('Bearer fake-openai-key');
|
|
});
|
|
|
|
it('routes OpenAI /v1/responses to {base}/openai/responses', async () => {
|
|
const cap: { url?: string; headers?: Headers } = {};
|
|
stubFetch(cap);
|
|
await proxy()(
|
|
new Request('http://localhost/v1/responses', {
|
|
method: 'POST',
|
|
headers: { 'content-type': 'application/json', authorization: 'Bearer fake-openai-key' },
|
|
body: JSON.stringify({ model: 'gpt-fake', input: 'hi' }),
|
|
}),
|
|
);
|
|
expect(cap.url).toBe(`${FAKE_BASE}/openai/responses`);
|
|
});
|
|
|
|
it('passes unrecognized Anthropic-family paths through untouched', async () => {
|
|
const cap: { url?: string; headers?: Headers } = {};
|
|
stubFetch(cap);
|
|
await proxy()(
|
|
new Request('http://localhost/v1/some/unknown?x=1', {
|
|
method: 'GET',
|
|
headers: { 'x-api-key': 'fake-anthropic-key' },
|
|
}),
|
|
);
|
|
expect(cap.url).toBe(`${FAKE_BASE}/anthropic/v1/some/unknown?x=1`);
|
|
expect(cap.headers?.get('cf-aig-authorization')).toBe(FAKE_TOKEN);
|
|
});
|
|
|
|
it('preserves streaming responses byte-for-byte', async () => {
|
|
const sse = 'event: message_start\ndata: {}\n\nevent: message_stop\ndata: {}\n\n';
|
|
globalThis.fetch = (async (input: RequestInfo | URL) => {
|
|
if (String(input).endsWith('/count_tokens')) {
|
|
return new Response(JSON.stringify({ input_tokens: 1 }), {
|
|
headers: { 'content-type': 'application/json' },
|
|
});
|
|
}
|
|
return new Response(
|
|
new ReadableStream<Uint8Array>({
|
|
start(c) {
|
|
c.enqueue(new TextEncoder().encode(sse));
|
|
c.close();
|
|
},
|
|
}),
|
|
{ headers: { 'content-type': 'text/event-stream' } },
|
|
);
|
|
}) as typeof fetch;
|
|
const res = await proxy()(
|
|
new Request('http://localhost/v1/messages', {
|
|
method: 'POST',
|
|
headers: { 'content-type': 'application/json', 'x-api-key': 'fake-anthropic-key' },
|
|
body: JSON.stringify({ model: 'claude-fable-5', max_tokens: 1, stream: true, messages: [{ role: 'user', content: 'hi' }] }),
|
|
}),
|
|
);
|
|
expect(await res.text()).toBe(sse);
|
|
expect(res.headers.get('content-type')).toBe('text/event-stream');
|
|
});
|
|
});
|
|
|
|
describe('provider-prefixed passthrough routing', () => {
|
|
it('forwards non-Anthropic provider prefixes to the generic upstream', async () => {
|
|
const cap: { url?: string; headers?: Headers } = {};
|
|
stubFetch(cap);
|
|
await createProxy({
|
|
upstream: 'http://ocproxy.test',
|
|
openAIUpstream: 'http://openai.test',
|
|
})(
|
|
new Request('http://localhost/google-ai-studio/v1beta/models/gemini-2.5:generateContent?alt=sse', {
|
|
method: 'POST',
|
|
headers: { 'content-type': 'application/json', authorization: 'Bearer local-token' },
|
|
body: JSON.stringify({ contents: [{ role: 'user', parts: [{ text: 'hi' }] }] }),
|
|
}),
|
|
);
|
|
|
|
expect(cap.url).toBe('http://ocproxy.test/google-ai-studio/v1beta/models/gemini-2.5:generateContent?alt=sse');
|
|
expect(cap.headers?.get('authorization')).toBe('Bearer local-token');
|
|
});
|
|
|
|
it('does not inject the Anthropic API key into non-Anthropic provider prefixes', async () => {
|
|
const cap: { url?: string; headers?: Headers } = {};
|
|
stubFetch(cap);
|
|
await createProxy({
|
|
upstream: 'http://ocproxy.test',
|
|
apiKey: 'sk-anthropic-test',
|
|
})(
|
|
new Request('http://localhost/compat/v1/chat/completions', {
|
|
method: 'POST',
|
|
headers: { 'content-type': 'application/json', authorization: 'Bearer local-token' },
|
|
body: JSON.stringify({ model: '@cf/test/model', messages: [{ role: 'user', content: 'hi' }] }),
|
|
}),
|
|
);
|
|
|
|
expect(cap.url).toBe('http://ocproxy.test/compat/v1/chat/completions');
|
|
expect(cap.headers?.get('authorization')).toBe('Bearer local-token');
|
|
expect(cap.headers?.get('x-api-key')).toBeNull();
|
|
});
|
|
});
|