/** Host-only proxy catalog, provider transports, authentication, and quoted token prices. */ import { createModels } from '@earendil-works/pi-ai/models'; import { openaiProvider } from '@earendil-works/pi-ai/providers/openai'; import { anthropicProvider } from '@earendil-works/pi-ai/providers/anthropic'; export async function proxyModels(proxy: {base_url: string, api_key: string}, readProxy = async () => proxy) { const models = createModels(); const base = proxy.base_url.replace(/\/$/, ''); let catalog: any[] = []; try { if (proxy.base_url && proxy.api_key) { const response = await fetch(`${base}/v1/models`, { headers: {Authorization: `Bearer ${proxy.api_key}`}, signal: AbortSignal.timeout(5000), }); if (response.ok) catalog = (await response.json()).data ?? []; } } catch { /* An unavailable catalog must not prevent durable recovery. */ } for (const [provider, api, path, fallback] of [ [openaiProvider(), 'openai-responses', '/v1/responses', 'gpt-6.1-sol'], [anthropicProvider(), 'anthropic-messages', '/apis/anthropic/v1/messages', 'claude-opus-5-5'], ] as const) { const rows = catalog.filter(row => row.creator === provider.id && row.endpoints?.some((endpoint: any) => endpoint.api === api)); const entries = (rows.length ? rows : [{id: fallback}]).map(row => { const endpoint = row.endpoints?.find((endpoint: any) => endpoint.api === api)?.path ?? path; // pi-ai appends /responses or /v1/messages, respectively. const suffix = api === 'anthropic-messages' ? '/v1/messages' : '/responses'; const baseUrl = `${base}${endpoint.slice(0, -suffix.length)}`; const price = row.providers?.[0]?.pricing; const scale = price?.currency === 'USD' ? 1000000 / price.unit_tokens : 0; return { id: row.id, name: row.name ?? row.id, provider: provider.id, api, baseUrl, reasoning: row.capabilities?.reasoning ?? true, input: ['text'], thinkingLevelMap: {off: null, minimal: null, ...Object.fromEntries((row.capabilities?.reasoning_options?.find((option: any) => option.type === 'effort')?.values ?? []).map((level: string) => [level, level]))}, compat: api === 'anthropic-messages' ? {forceAdaptiveThinking: row.capabilities?.reasoning_options?.some((option: any) => option.type === 'effort') ?? true} : undefined, cost: {input: (price?.input ?? 0) * scale, output: (price?.output ?? 0) * scale, cacheRead: (price?.cached_input ?? 0) * scale, cacheWrite: (price?.cache_write ?? 0) * scale}, contextWindow: row.limits?.context_window ?? 200000, maxTokens: 4096, }; }); models.setProvider({...provider, baseUrl: entries[0].baseUrl, getModels: () => entries as any, auth: {apiKey: {name: 'Sausage proxy', resolve: async () => { const current = await readProxy(); if (!current.base_url || !current.api_key) throw new Error('Set proxy.base_url and proxy.api_key in /etc/sausage/config.yaml before a model request'); return {auth:{apiKey:current.api_key, baseUrl:current.base_url.replace(/\/$/,'') + entries[0].baseUrl.slice(base.length), headers:{'Shopify-Usage-Tag':JSON.stringify(['pi','sausage'])}},source:'/etc/sausage/config.yaml'}; }}}, }); } return models; }