48 lines
2.8 KiB
TypeScript
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;
|
|
}
|