File size: 2,518 Bytes
391c43e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import type { ProviderConfig, ProviderId } from '@/lib/llm/providers/types';
import type { WireFormat } from '@/lib/llm/providers/wire-format';

/** Resolve the upstream endpoint for a request, by wire format. */
export function getApiEndpoint(
  provider: ProviderId,
  config: ProviderConfig,
  model?: string,
  options?: { apiKey?: string; stream?: boolean },
  overrideBaseUrl?: string,
  wireFormat?: WireFormat,
): string {
  const baseUrl = overrideBaseUrl || config.baseUrl || 'https://openrouter.ai/api/v1';
  if (wireFormat === 'anthropic') {
    return provider === 'anthropic' ? 'https://api.anthropic.com/v1/messages' : `${baseUrl}/messages`;
  } else if (provider === 'gemini') {
    const geminiModel = model || 'gemini-2.5-flash';
    const action = options?.stream ? 'streamGenerateContent?alt=sse' : 'generateContent';
    const key = options?.apiKey ? `${options.stream ? '&' : '?'}key=${options.apiKey}` : '';
    return `https://generativelanguage.googleapis.com/v1beta/models/${geminiModel}:${action}${key}`;
  } else {
    return `${baseUrl}/chat/completions`;
  }
}

/** Build request headers, by wire format. `referer` is the incoming request's referer (for OpenRouter attribution). */
export function buildHeaders(
  provider: ProviderId,
  apiKey: string | undefined,
  referer: string | null,
  config: ProviderConfig,
  wireFormat?: WireFormat,
): Record<string, string> {
  const headers: Record<string, string> = { 'Content-Type': 'application/json' };
  if (wireFormat === 'anthropic') {
    headers['x-api-key'] = apiKey || '';
    headers['anthropic-version'] = '2023-06-01';
    if (provider === 'anthropic' && config.supportsFunctions) {
      headers['anthropic-beta'] = 'tools-2024-04-04';
    }
  } else if (provider === 'gemini') {
    // Gemini uses query-param key auth; no auth headers needed
  } else {
    if (apiKey) headers['Authorization'] = `Bearer ${apiKey}`;
    if (provider === 'openrouter') {
      headers['HTTP-Referer'] = referer || 'http://localhost:3000';
      headers['X-Title'] = 'OSW-Studio';
    }
  }
  return headers;
}

/** Sampling temperature. A few models reject anything but 1: OpenAI's gpt-5-nano and
 *  Opencode Go's Moonshot/Kimi models. Everything else uses 0.7. */
export function resolveTemperature(provider: ProviderId, model: string): number {
  const m = (model || '').toLowerCase();
  if (provider === 'openai' && m.includes('gpt-5-nano')) return 1;
  if (provider === 'opencode-go' && m.startsWith('kimi-')) return 1;
  return 0.7;
}