import {
assertNever,
hostFromHttpsUrl,
secretNameFor,
PROVIDER_IDS,
type ProviderId,
type SecretPrefix,
} from './validation.ts'
export { PROVIDER_IDS }
export type ProviderAuth = 'bearer' | 'anthropic'
export type ProviderCatalogEntry = {
id: ProviderId
label: string
secretPrefix: SecretPrefix
hosts: Array<string>
defaultModel: string
auth: ProviderAuth
docs: string
console: string
}
export const PROVIDERS: Record<ProviderId, ProviderCatalogEntry> = {
openai: {
id: 'openai',
label: 'OpenAI',
secretPrefix: 'openaiApiKey',
hosts: ['api.openai.com'],
defaultModel: 'gpt-4o-mini',
auth: 'bearer',
docs: 'https://platform.openai.com/docs/api-reference/chat',
console: 'https://platform.openai.com/api-keys',
},
anthropic: {
id: 'anthropic',
label: 'Anthropic',
secretPrefix: 'anthropicApiKey',
hosts: ['api.anthropic.com'],
defaultModel: 'claude-sonnet-4-0',
auth: 'anthropic',
docs: 'https://docs.anthropic.com/en/api/messages',
console: 'https://console.anthropic.com/settings/keys',
},
groq: {
id: 'groq',
label: 'Groq',
secretPrefix: 'groqApiKey',
hosts: ['api.groq.com'],
defaultModel: 'llama-3.1-8b-instant',
auth: 'bearer',
docs: 'https://console.groq.com/docs/openai',
console: 'https://console.groq.com/keys',
},
cloudflare: {
id: 'cloudflare',
label: 'Cloudflare Workers AI',
secretPrefix: 'cloudflareApiToken',
hosts: ['api.cloudflare.com', 'gateway.ai.cloudflare.com'],
defaultModel: '@cf/meta/llama-3.1-8b-instruct',
auth: 'bearer',
docs: 'https://developers.cloudflare.com/workers-ai/configuration/open-ai-compatibility/',
console: 'https://dash.cloudflare.com/profile/api-tokens',
},
'openai-compatible': {
id: 'openai-compatible',
label: 'OpenAI-compatible',
secretPrefix: 'openaiCompatibleApiKey',
hosts: [],
defaultModel: '',
auth: 'bearer',
docs: 'https://platform.openai.com/docs/api-reference/chat',
console: '',
},
}
export function providerCatalog(id: ProviderId): ProviderCatalogEntry {
const entry = PROVIDERS[id]
if (!entry) return assertNever(id, 'provider')
return entry
}
export function defaultSecretName(
provider: ProviderId,
account?: string,
override?: string,
): string {
return secretNameFor(providerCatalog(provider).secretPrefix, account, override)
}
export function secretSetupUrl(
secretName: string,
hosts: Array<string>,
description: string,
): string {
const params = new URLSearchParams({
name: secretName,
description,
scope: 'user',
})
if (hosts.length > 0) params.set('allowedHosts', hosts.join(','))
return 'https://kody.codes/account/secrets/new?' + params.toString()
}
export function providerSecretUrl(
provider: ProviderId,
secretName?: string,
compatibleHost?: string,
): string {
const entry = providerCatalog(provider)
const name = secretName ?? defaultSecretName(provider)
const hosts =
provider === 'openai-compatible' && compatibleHost
? [compatibleHost]
: entry.hosts
return secretSetupUrl(
name,
hosts,
entry.label + ' API key for Kody AI helpers',
)
}
export function chatUrl(input: {
provider: ProviderId
baseUrl?: string
cloudflareAccountId?: string
cloudflareAiGatewayId?: string
}): { href: string; host: string } {
switch (input.provider) {
case 'openai':
return {
href: 'https://api.openai.com/v1/chat/completions',
host: 'api.openai.com',
}
case 'anthropic':
return {
href: 'https://api.anthropic.com/v1/messages',
host: 'api.anthropic.com',
}
case 'groq':
return {
href: 'https://api.groq.com/openai/v1/chat/completions',
host: 'api.groq.com',
}
case 'cloudflare': {
const accountId = input.cloudflareAccountId
if (!accountId) {
throw new Error(
'cloudflareAccountId is required for Cloudflare Workers AI. Fork this package and save it with ./settings (confirm: true). This package never bakes in an account id.',
)
}
if (input.cloudflareAiGatewayId) {
return {
href:
'https://gateway.ai.cloudflare.com/v1/' +
accountId +
'/' +
input.cloudflareAiGatewayId +
'/workers-ai/v1/chat/completions',
host: 'gateway.ai.cloudflare.com',
}
}
return {
href:
'https://api.cloudflare.com/client/v4/accounts/' +
accountId +
'/ai/v1/chat/completions',
host: 'api.cloudflare.com',
}
}
case 'openai-compatible': {
if (!input.baseUrl) {
throw new Error(
'baseUrl is required for openai-compatible. Pass an https origin+path (no credentials) or store it in ./settings on your fork.',
)
}
const href = input.baseUrl.endsWith('/chat/completions')
? input.baseUrl
: input.baseUrl.replace(/\/$/, '') + '/chat/completions'
return { href, host: hostFromHttpsUrl(href) }
}
default:
return assertNever(input.provider, 'provider')
}
}
export function modelsUrl(input: {
provider: ProviderId
baseUrl?: string
cloudflareAccountId?: string
}): { href: string; host: string } | null {
switch (input.provider) {
case 'openai':
return { href: 'https://api.openai.com/v1/models', host: 'api.openai.com' }
case 'anthropic':
return { href: 'https://api.anthropic.com/v1/models', host: 'api.anthropic.com' }
case 'groq':
return { href: 'https://api.groq.com/openai/v1/models', host: 'api.groq.com' }
case 'cloudflare': {
if (!input.cloudflareAccountId) return null
return {
href:
'https://api.cloudflare.com/client/v4/accounts/' +
input.cloudflareAccountId +
'/ai/models/search',
host: 'api.cloudflare.com',
}
}
case 'openai-compatible': {
if (!input.baseUrl) return null
const root = input.baseUrl.replace(/\/chat\/completions$/, '').replace(/\/$/, '')
const href = root.endsWith('/models') ? root : root + '/models'
return { href, host: hostFromHttpsUrl(href) }
}
default:
return assertNever(input.provider, 'provider')
}
}
export function requestHeaders(
provider: ProviderId,
secretName: string,
): Record<string, string> {
const placeholder = '{{secret:' + secretName + '|scope=user}}'
const headers: Record<string, string> = {
Accept: 'application/json',
'Content-Type': 'application/json',
}
switch (provider) {
case 'openai':
case 'groq':
case 'cloudflare':
case 'openai-compatible':
headers.Authorization = 'Bearer ' + placeholder
return headers
case 'anthropic':
headers['x-api-key'] = placeholder
headers['anthropic-version'] = '2023-06-01'
return headers
default:
return assertNever(provider, 'provider')
}
}