| import type { KimiConfig, ModelAlias } from '#/config/index'; |
| import { |
| catalogBaseUrl, |
| catalogProviderModels, |
| inferWireType, |
| resolveCatalogImport, |
| type Catalog, |
| type CatalogImportInvalidReason, |
| type CatalogImportResolution, |
| type CatalogModel, |
| type CatalogProviderEntry, |
| type ModelCapability, |
| type ProviderType, |
| } from '@moonshot-ai/kosong'; |
|
|
| export { catalogBaseUrl, catalogProviderModels, inferWireType, resolveCatalogImport }; |
| export type { CatalogImportInvalidReason, CatalogImportResolution }; |
| export type { Catalog, CatalogModel, CatalogProviderEntry }; |
|
|
| export const DEFAULT_CATALOG_URL = 'https://models.dev/api.json'; |
|
|
| export class CatalogFetchError extends Error { |
| readonly status: number; |
| constructor(message: string, status: number) { |
| super(message); |
| this.status = status; |
| } |
| } |
|
|
| export interface FetchCatalogOptions { |
| readonly signal?: AbortSignal; |
| readonly fetchImpl?: typeof fetch; |
| readonly userAgent?: string; |
| } |
|
|
| |
| |
| |
| |
| |
| export async function fetchCatalog( |
| url: string, |
| options: FetchCatalogOptions = {}, |
| ): Promise<Catalog> { |
| const { signal, fetchImpl = fetch, userAgent } = options; |
| const headers: Record<string, string> = { Accept: 'application/json' }; |
| if (userAgent !== undefined) headers['User-Agent'] = userAgent; |
| const res = await fetchImpl(url, { headers, signal }); |
| if (!res.ok) { |
| throw new CatalogFetchError(`Failed to fetch catalog (HTTP ${res.status}).`, res.status); |
| } |
| const payload: unknown = await res.json(); |
| if (typeof payload !== 'object' || payload === null || Array.isArray(payload)) { |
| throw new Error(`Unexpected catalog response from ${url}.`); |
| } |
| return payload as Catalog; |
| } |
|
|
| function capabilityToStrings(capability: ModelCapability): string[] | undefined { |
| const caps: string[] = []; |
| if (capability.image_in) caps.push('image_in'); |
| if (capability.video_in) caps.push('video_in'); |
| if (capability.audio_in) caps.push('audio_in'); |
| if (capability.thinking) caps.push('thinking'); |
| if (capability.tool_use) caps.push('tool_use'); |
| if (capability.dynamically_loaded_tools === true) caps.push('dynamically_loaded_tools'); |
| return caps.length > 0 ? caps : undefined; |
| } |
|
|
| |
| export function catalogModelToAlias(providerId: string, model: CatalogModel): ModelAlias { |
| const caps = capabilityToStrings(model.capability); |
| return { |
| provider: providerId, |
| model: model.id, |
| maxContextSize: model.capability.max_context_tokens, |
| maxInputSize: model.capability.max_input_tokens, |
| maxOutputSize: model.maxOutputSize, |
| |
| |
| capabilities: |
| model.alwaysThinking === true |
| ? caps?.map((cap) => (cap === 'thinking' ? 'always_thinking' : cap)) |
| : caps, |
| displayName: model.name, |
| reasoningKey: model.reasoningKey, |
| supportEfforts: model.supportEfforts === undefined ? undefined : [...model.supportEfforts], |
| offEffort: model.offEffort, |
| protocol: model.protocol, |
| baseUrl: model.baseUrl, |
| }; |
| } |
|
|
| export interface ApplyCatalogProviderOptions { |
| readonly providerId: string; |
| readonly wire: ProviderType; |
| readonly baseUrl?: string; |
| readonly apiKey: string; |
| readonly models: readonly CatalogModel[]; |
| readonly selectedModelId: string; |
| readonly thinking: boolean; |
| } |
|
|
| |
| |
| |
| |
| |
| export function loadBuiltInCatalog(text?: string): Catalog | undefined { |
| if (typeof text !== 'string' || text.length === 0) return undefined; |
| try { |
| return JSON.parse(text) as Catalog; |
| } catch { |
| return undefined; |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export function applyCatalogProvider( |
| config: KimiConfig, |
| options: ApplyCatalogProviderOptions, |
| ): { defaultModel: string } { |
| config.providers[options.providerId] = { |
| type: options.wire, |
| baseUrl: options.baseUrl, |
| apiKey: options.apiKey, |
| }; |
|
|
| const models = config.models ?? {}; |
| for (const [key, alias] of Object.entries(models)) { |
| if (alias.provider === options.providerId) delete models[key]; |
| } |
| for (const model of options.models) { |
| models[`${options.providerId}/${model.id}`] = catalogModelToAlias(options.providerId, model); |
| } |
| config.models = models; |
|
|
| const defaultModel = `${options.providerId}/${options.selectedModelId}`; |
| config.defaultModel = defaultModel; |
| config.thinking = { ...config.thinking, enabled: options.thinking }; |
| return { defaultModel }; |
| } |
|
|