Files
sausage/host/js/proxy-models.ts
T

48 lines
2.8 KiB
TypeScript

/** 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}) {
const models = createModels();
const base = proxy.base_url.replace(/\/$/, '');
let catalog: any[] = [];
try {
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 () => ({
auth: {apiKey: proxy.api_key, headers: {'Shopify-Usage-Tag': JSON.stringify(['pi', 'sausage'])}},
source: 'llm-proxy',
})}},
});
}
return models;
}