Download packages/oauth/src/managed-kimi-code.ts from SaylorTwift/kimi-code: direct link, hf CLI and curl.
- Browser
- Download file 31 kB
-
https://huggingface.co/SaylorTwift/kimi-code/resolve/main/packages/oauth/src/managed-kimi-code.ts
- Command line
-
hf download hf://SaylorTwift/kimi-code/packages/oauth/src/managed-kimi-code.ts
-
curl -L -o managed-kimi-code.ts https://huggingface.co/SaylorTwift/kimi-code/resolve/main/packages/oauth/src/managed-kimi-code.ts
31 kB
| import { createHash } from 'node:crypto'; | |
| import { readApiErrorMessage } from './api-error'; | |
| import { DEFAULT_KIMI_CODE_OAUTH_HOST } from './constants'; | |
| import { OAuthUnauthorizedError } from './errors'; | |
| import { parseKimiCodeCustomHeaders } from './identity'; | |
| import { DEFAULT_KIMI_CODE_BASE_URL, kimiCodeBaseUrl } from './managed-usage'; | |
| import { MANAGED_KIMI_MODEL_FIELDS, mergeRefreshedModelAlias } from './model-alias-merge'; | |
| import { isRecord } from './utils'; | |
| export const KIMI_CODE_PLATFORM_ID = 'kimi-code'; | |
| export const KIMI_CODE_PROVIDER_NAME = 'managed:kimi-code'; | |
| export const KIMI_CODE_OAUTH_KEY = 'oauth/kimi-code'; | |
| const KIMI_CODE_SCOPED_OAUTH_KEY_PREFIX = 'oauth/kimi-code-env-'; | |
| export type ManagedKimiCodeProtocol = 'kimi' | 'anthropic' | 'openai_responses'; | |
| export function parseModelProtocol(value: unknown): ManagedKimiCodeProtocol | undefined { | |
| if (value === 'anthropic') return 'anthropic'; | |
| if (value === 'response') return 'openai_responses'; | |
| return undefined; | |
| } | |
| /** | |
| * Server-declared thinking toggle support from `/models`: | |
| * - 'only' — thinking cannot be turned off (always-thinking) | |
| * - 'no' — thinking is not supported at all | |
| * - 'both' — thinking can be toggled on and off | |
| * Absent on older servers — callers fall back to `supportsReasoning`. | |
| */ | |
| export type SupportsThinkingType = 'only' | 'no' | 'both'; | |
| export interface ManagedKimiCodeModelInfo { | |
| readonly id: string; | |
| readonly contextLength: number; | |
| readonly supportsReasoning: boolean; | |
| readonly supportsImageIn: boolean; | |
| readonly supportsVideoIn: boolean; | |
| readonly supportsToolUse?: boolean; | |
| readonly supportsDynamicTools?: boolean; | |
| readonly supportsThinkingType?: SupportsThinkingType; | |
| readonly supportEfforts?: readonly string[]; | |
| readonly defaultEffort?: string; | |
| readonly displayName?: string | undefined; | |
| readonly protocol?: ManagedKimiCodeProtocol | undefined; | |
| } | |
| export interface ManagedKimiCodeProvisionResult { | |
| readonly providerName: typeof KIMI_CODE_PROVIDER_NAME; | |
| readonly defaultModel: string; | |
| readonly defaultThinking: boolean; | |
| readonly models: readonly ManagedKimiCodeModelInfo[]; | |
| readonly configPath?: string | undefined; | |
| } | |
| export interface FetchManagedKimiCodeModelsOptions { | |
| readonly accessToken: string; | |
| readonly baseUrl?: string | undefined; | |
| readonly fetchImpl?: typeof fetch | undefined; | |
| readonly headers?: Record<string, string> | undefined; | |
| /** | |
| * What `accessToken` actually is; only affects auth-error wording. Defaults | |
| * to 'oauth' (the managed login flow); api-key providers refreshed against | |
| * the managed endpoint pass 'apiKey' so a 401 doesn't tell the user their | |
| * "OAuth credentials" were rejected. | |
| */ | |
| readonly credentialKind?: 'oauth' | 'apiKey' | undefined; | |
| } | |
| export interface ManagedKimiCodeApplyResult { | |
| readonly defaultModel: string; | |
| readonly defaultThinking: boolean; | |
| } | |
| export interface ManagedKimiCodeCleanupResult { | |
| readonly providerName: typeof KIMI_CODE_PROVIDER_NAME; | |
| readonly removedProvider: boolean; | |
| readonly removedModels: readonly string[]; | |
| readonly defaultModelCleared: boolean; | |
| readonly removedServices: readonly string[]; | |
| } | |
| export interface ManagedKimiOAuthRef { | |
| readonly storage: 'file' | 'keyring'; | |
| readonly key: string; | |
| readonly oauthHost?: string | undefined; | |
| } | |
| export interface ManagedKimiOAuthRefInput { | |
| readonly storage?: 'file' | 'keyring' | undefined; | |
| readonly key?: string | undefined; | |
| readonly oauthHost?: string | undefined; | |
| } | |
| export interface ManagedKimiRuntimeAuth { | |
| readonly baseUrl?: string | undefined; | |
| readonly oauthRef: ManagedKimiOAuthRef; | |
| } | |
| export interface ManagedKimiLoginAuth { | |
| readonly baseUrl?: string | undefined; | |
| readonly oauthHost?: string | undefined; | |
| readonly oauthRef?: ManagedKimiOAuthRef | undefined; | |
| } | |
| export interface ManagedKimiEnv { | |
| readonly KIMI_CODE_BASE_URL?: string | undefined; | |
| readonly KIMI_CODE_OAUTH_HOST?: string | undefined; | |
| readonly KIMI_OAUTH_HOST?: string | undefined; | |
| } | |
| export class ManagedKimiCodeModelsAuthError extends OAuthUnauthorizedError { | |
| readonly status: number; | |
| readonly baseUrl: string; | |
| constructor(options: { | |
| readonly status: number; | |
| readonly baseUrl: string; | |
| readonly message: string; | |
| readonly credentialKind?: 'oauth' | 'apiKey' | undefined; | |
| }) { | |
| super( | |
| `Kimi Code models endpoint ${options.baseUrl} rejected ${ | |
| options.credentialKind === 'apiKey' ? 'the API key' : 'OAuth credentials' | |
| }: ${options.message}`, | |
| ); | |
| this.name = 'ManagedKimiCodeModelsAuthError'; | |
| this.status = options.status; | |
| this.baseUrl = options.baseUrl; | |
| } | |
| } | |
| export interface ManagedKimiProviderConfig { | |
| type: ManagedKimiCodeProtocol; | |
| baseUrl?: string | undefined; | |
| apiKey?: string | undefined; | |
| oauth?: ManagedKimiOAuthRef | undefined; | |
| readonly [key: string]: unknown; | |
| } | |
| export interface ManagedKimiModelAliasOverrides { | |
| maxContextSize?: number | undefined; | |
| maxOutputSize?: number | undefined; | |
| capabilities?: string[] | undefined; | |
| displayName?: string | undefined; | |
| reasoningKey?: string | undefined; | |
| adaptiveThinking?: boolean | undefined; | |
| supportEfforts?: readonly string[] | undefined; | |
| defaultEffort?: string | undefined; | |
| readonly [key: string]: unknown; | |
| } | |
| export interface ManagedKimiModelAlias { | |
| provider: string; | |
| model: string; | |
| maxContextSize: number; | |
| capabilities?: string[] | undefined; | |
| supportEfforts?: readonly string[] | undefined; | |
| defaultEffort?: string | undefined; | |
| displayName?: string | undefined; | |
| protocol?: ManagedKimiCodeProtocol; | |
| betaApi?: boolean; | |
| adaptiveThinking?: boolean | undefined; | |
| overrides?: ManagedKimiModelAliasOverrides | undefined; | |
| readonly [key: string]: unknown; | |
| } | |
| export interface ManagedKimiServiceConfig { | |
| baseUrl?: string | undefined; | |
| apiKey?: string | undefined; | |
| oauth?: ManagedKimiOAuthRef | undefined; | |
| } | |
| export interface ManagedKimiServicesConfig { | |
| moonshotSearch?: ManagedKimiServiceConfig | undefined; | |
| moonshotFetch?: ManagedKimiServiceConfig | undefined; | |
| readonly [key: string]: unknown; | |
| } | |
| export interface ManagedKimiThinkingShape { | |
| enabled?: boolean | undefined; | |
| effort?: string | undefined; | |
| [key: string]: unknown; | |
| } | |
| export interface ManagedKimiConfigShape { | |
| providers: Record<string, ManagedKimiProviderConfig | Record<string, unknown>>; | |
| models?: Record<string, ManagedKimiModelAlias | Record<string, unknown>> | undefined; | |
| defaultModel?: string | undefined; | |
| thinking?: ManagedKimiThinkingShape | undefined; | |
| services?: ManagedKimiServicesConfig | undefined; | |
| [key: string]: unknown; | |
| } | |
| export interface ManagedKimiConfigAdapter<TConfig> { | |
| read(): Promise<TConfig> | TConfig; | |
| write(config: TConfig): Promise<void> | void; | |
| apply( | |
| config: TConfig, | |
| input: { | |
| readonly models: readonly ManagedKimiCodeModelInfo[]; | |
| readonly baseUrl?: string | undefined; | |
| readonly oauthKey?: string | undefined; | |
| readonly oauthHost?: string | undefined; | |
| readonly preserveDefaultModel?: boolean | undefined; | |
| }, | |
| ): ManagedKimiCodeApplyResult; | |
| remove?(config: TConfig): void; | |
| readonly configPath?: string | undefined; | |
| } | |
| export interface ProvisionManagedKimiCodeConfigOptions<TConfig> { | |
| readonly adapter: ManagedKimiConfigAdapter<TConfig>; | |
| readonly accessToken: string; | |
| readonly baseUrl?: string | undefined; | |
| readonly oauthKey?: string | undefined; | |
| readonly oauthHost?: string | undefined; | |
| readonly preserveDefaultModel?: boolean | undefined; | |
| readonly fetchImpl?: typeof fetch | undefined; | |
| readonly headers?: Record<string, string> | undefined; | |
| } | |
| function managedModelKey(modelId: string): string { | |
| return `${KIMI_CODE_PLATFORM_ID}/${modelId}`; | |
| } | |
| interface SelectedDefaultModel { | |
| readonly modelKey: string; | |
| readonly thinking: boolean; | |
| } | |
| function capabilitiesForModel(model: ManagedKimiCodeModelInfo): string[] | undefined { | |
| const caps = new Set<string>(); | |
| // supports_thinking_type is the full three-state declaration and wins over | |
| // the legacy supports_reasoning boolean; absent (older servers) falls back. | |
| switch (model.supportsThinkingType) { | |
| case 'only': | |
| caps.add('thinking'); | |
| caps.add('always_thinking'); | |
| break; | |
| case 'both': | |
| caps.add('thinking'); | |
| break; | |
| case 'no': | |
| break; | |
| case undefined: | |
| if (model.supportsReasoning) caps.add('thinking'); | |
| break; | |
| } | |
| if (model.supportsImageIn) caps.add('image_in'); | |
| if (model.supportsVideoIn) caps.add('video_in'); | |
| if (model.supportsToolUse ?? true) caps.add('tool_use'); | |
| if (model.supportsDynamicTools === true) caps.add('dynamically_loaded_tools'); | |
| return caps.size > 0 ? [...caps] : undefined; | |
| } | |
| function defaultBaseUrl(baseUrl: string | undefined): string { | |
| return (baseUrl ?? kimiCodeBaseUrl()).replace(/\/+$/, ''); | |
| } | |
| function normalizeBaseUrl(baseUrl: string): string { | |
| return baseUrl.replace(/\/+$/, ''); | |
| } | |
| function normalizeEndpoint(value: string): string { | |
| return value.trim().replace(/\/+$/, ''); | |
| } | |
| function persistedOAuthHost(options: { | |
| readonly key: string; | |
| readonly oauthHost?: string | undefined; | |
| }): string | undefined { | |
| const oauthHost = options.oauthHost; | |
| const normalized = normalizeEndpoint(oauthHost ?? DEFAULT_KIMI_CODE_OAUTH_HOST); | |
| if ( | |
| options.key === KIMI_CODE_OAUTH_KEY && | |
| normalized === normalizeEndpoint(DEFAULT_KIMI_CODE_OAUTH_HOST) | |
| ) { | |
| return undefined; | |
| } | |
| return normalized; | |
| } | |
| function managedOAuthRef(options: { | |
| readonly key: string; | |
| readonly oauthHost?: string | undefined; | |
| readonly storage?: 'file' | 'keyring' | undefined; | |
| }): ManagedKimiOAuthRef { | |
| const oauthHost = persistedOAuthHost(options); | |
| return { | |
| storage: options.storage ?? 'file', | |
| key: options.key, | |
| oauthHost, | |
| }; | |
| } | |
| function configuredOAuthRef( | |
| oauthRef: ManagedKimiOAuthRefInput | undefined, | |
| ): ManagedKimiOAuthRef | undefined { | |
| if (oauthRef === undefined) return undefined; | |
| const key = oauthRef.key; | |
| if (key === undefined) return undefined; | |
| return managedOAuthRef({ | |
| storage: oauthRef.storage, | |
| key, | |
| oauthHost: oauthRef.oauthHost, | |
| }); | |
| } | |
| export function kimiCodeEnvBaseUrl(env: ManagedKimiEnv = process.env): string | undefined { | |
| return env.KIMI_CODE_BASE_URL; | |
| } | |
| export function kimiCodeEnvOAuthHost(env: ManagedKimiEnv = process.env): string | undefined { | |
| return env.KIMI_CODE_OAUTH_HOST ?? env.KIMI_OAUTH_HOST; | |
| } | |
| // Base URLs that share the default `oauth/kimi-code` credential slot. | |
| const SHARED_DEFAULT_BASE_URLS: readonly string[] = [ | |
| normalizeEndpoint(DEFAULT_KIMI_CODE_BASE_URL), | |
| ]; | |
| export function resolveKimiCodeOAuthKey(options: { | |
| readonly oauthHost?: string | undefined; | |
| readonly baseUrl?: string | undefined; | |
| }): string { | |
| const oauthHost = normalizeEndpoint(options.oauthHost ?? DEFAULT_KIMI_CODE_OAUTH_HOST); | |
| const baseUrl = defaultBaseUrl(options.baseUrl); | |
| const defaultOauthHost = normalizeEndpoint(DEFAULT_KIMI_CODE_OAUTH_HOST); | |
| if (oauthHost === defaultOauthHost && SHARED_DEFAULT_BASE_URLS.includes(baseUrl)) { | |
| return KIMI_CODE_OAUTH_KEY; | |
| } | |
| const digest = createHash('sha256') | |
| .update(JSON.stringify({ oauthHost, baseUrl })) | |
| .digest('hex') | |
| .slice(0, 16); | |
| return `${KIMI_CODE_SCOPED_OAUTH_KEY_PREFIX}${digest}`; | |
| } | |
| /** | |
| * Resolve the full managed-Kimi-Code OAuth ref (credential storage key + | |
| * persisted host) for an (oauthHost, baseUrl) environment. | |
| * | |
| * Single source of truth for "which credential slot does this environment map | |
| * to". Login, provisioning, and the runtime provider all derive their ref | |
| * through here, so the slot a token is written to always matches the slot it | |
| * is later read from — preventing the env-mismatch credential mix-ups this | |
| * scoping is meant to fix. | |
| */ | |
| export function resolveKimiCodeOAuthRef(options: { | |
| readonly oauthHost?: string | undefined; | |
| readonly baseUrl?: string | undefined; | |
| }): ManagedKimiOAuthRef { | |
| return managedOAuthRef({ | |
| key: resolveKimiCodeOAuthKey(options), | |
| oauthHost: options.oauthHost, | |
| }); | |
| } | |
| export function resolveKimiCodeRuntimeAuth(options: { | |
| readonly configuredBaseUrl?: string | undefined; | |
| readonly configuredOAuthRef?: ManagedKimiOAuthRefInput | undefined; | |
| readonly env?: ManagedKimiEnv | undefined; | |
| }): ManagedKimiRuntimeAuth { | |
| const env = options.env ?? process.env; | |
| const envBaseUrl = kimiCodeEnvBaseUrl(env); | |
| const envOAuthHost = kimiCodeEnvOAuthHost(env); | |
| const hasEnvOverride = envBaseUrl !== undefined || envOAuthHost !== undefined; | |
| const baseUrl = | |
| envBaseUrl !== undefined ? normalizeBaseUrl(envBaseUrl) : options.configuredBaseUrl; | |
| const expected = resolveKimiCodeOAuthRef({ | |
| oauthHost: hasEnvOverride ? envOAuthHost : options.configuredOAuthRef?.oauthHost, | |
| baseUrl, | |
| }); | |
| const configured = configuredOAuthRef(options.configuredOAuthRef); | |
| if (configured === undefined) return { baseUrl, oauthRef: expected }; | |
| if (hasEnvOverride) return { baseUrl, oauthRef: expected }; | |
| if (configured.key !== expected.key) return { baseUrl, oauthRef: expected }; | |
| return { baseUrl, oauthRef: configured }; | |
| } | |
| export function resolveKimiCodeLoginAuth(options: { | |
| readonly configuredBaseUrl?: string | undefined; | |
| readonly configuredOAuthRef?: ManagedKimiOAuthRefInput | undefined; | |
| readonly requestedBaseUrl?: string | undefined; | |
| readonly requestedOAuthHost?: string | undefined; | |
| readonly env?: ManagedKimiEnv | undefined; | |
| }): ManagedKimiLoginAuth { | |
| const env = options.env ?? process.env; | |
| const envBaseUrl = kimiCodeEnvBaseUrl(env); | |
| const envOAuthHost = kimiCodeEnvOAuthHost(env); | |
| const hasOverride = | |
| options.requestedBaseUrl !== undefined || | |
| options.requestedOAuthHost !== undefined || | |
| envBaseUrl !== undefined || | |
| envOAuthHost !== undefined; | |
| const baseUrl = | |
| options.requestedBaseUrl !== undefined | |
| ? normalizeBaseUrl(options.requestedBaseUrl) | |
| : envBaseUrl !== undefined | |
| ? normalizeBaseUrl(envBaseUrl) | |
| : options.configuredBaseUrl; | |
| const oauthHost = options.requestedOAuthHost ?? envOAuthHost; | |
| if (hasOverride) return { baseUrl, oauthHost }; | |
| const configured = configuredOAuthRef(options.configuredOAuthRef); | |
| if (configured === undefined) return { baseUrl, oauthHost }; | |
| const expectedKey = resolveKimiCodeOAuthKey({ | |
| oauthHost: configured.oauthHost, | |
| baseUrl, | |
| }); | |
| return configured.key === expectedKey | |
| ? { baseUrl, oauthHost, oauthRef: configured } | |
| : { baseUrl, oauthHost }; | |
| } | |
| function toModelInfo(item: unknown): ManagedKimiCodeModelInfo | undefined { | |
| if (!isRecord(item) || typeof item['id'] !== 'string' || item['id'].length === 0) { | |
| return undefined; | |
| } | |
| const contextLength = Number(item['context_length']); | |
| if (!Number.isInteger(contextLength) || contextLength <= 0) { | |
| throw new Error(`Kimi Code model "${item['id']}" must include a positive context_length.`); | |
| } | |
| const displayName = item['display_name']; | |
| const normalizedDisplayName = | |
| typeof displayName === 'string' && displayName.length > 0 ? displayName : undefined; | |
| const supportsToolUse = Object.hasOwn(item, 'supports_tool_use') | |
| ? Boolean(item['supports_tool_use']) | |
| : true; | |
| // Effort levels come from the nested `think_efforts` object | |
| // ({ support, valid_efforts, default_effort }) returned by /models. | |
| const thinkEfforts = parseThinkEfforts(item['think_efforts']); | |
| return { | |
| id: item['id'], | |
| contextLength, | |
| supportsReasoning: Boolean(item['supports_reasoning']), | |
| supportsImageIn: Boolean(item['supports_image_in']), | |
| supportsVideoIn: Boolean(item['supports_video_in']), | |
| supportsToolUse, | |
| supportsDynamicTools: item['supports_dynamic_tools'] === true, | |
| supportsThinkingType: parseSupportsThinkingType(item['supports_thinking_type']), | |
| supportEfforts: thinkEfforts.supportEfforts, | |
| defaultEffort: thinkEfforts.defaultEffort, | |
| displayName: normalizedDisplayName, | |
| protocol: parseModelProtocol(item['protocol']), | |
| }; | |
| } | |
| export function parseStringArray(value: unknown): readonly string[] | undefined { | |
| if (!Array.isArray(value)) return undefined; | |
| const out = value.filter((v): v is string => typeof v === 'string' && v.length > 0); | |
| return out.length > 0 ? out : undefined; | |
| } | |
| // Unknown or missing values resolve to undefined so callers fall back to the | |
| // legacy supports_reasoning boolean instead of guessing. | |
| export function parseSupportsThinkingType(value: unknown): SupportsThinkingType | undefined { | |
| return value === 'only' || value === 'no' || value === 'both' ? value : undefined; | |
| } | |
| /** | |
| * Parse the nested `think_efforts` object from `/models`: | |
| * { "support": true, "valid_efforts": ["low", "high", "max"], "default_effort": "high" } | |
| * Returns the effort list and default effort, or undefineds when absent so | |
| * callers can fall back to the legacy flat `support_efforts` / `default_effort` | |
| * fields on older servers. | |
| */ | |
| export function parseThinkEfforts(value: unknown): { | |
| supportEfforts: readonly string[] | undefined; | |
| defaultEffort: string | undefined; | |
| } { | |
| if (value === null || typeof value !== 'object') { | |
| return { supportEfforts: undefined, defaultEffort: undefined }; | |
| } | |
| const record = value as Record<string, unknown>; | |
| // `support` gates the whole object: when it is not true, ignore | |
| // valid_efforts / default_effort entirely. | |
| if (record['support'] !== true) { | |
| return { supportEfforts: undefined, defaultEffort: undefined }; | |
| } | |
| const rawDefault = record['default_effort']; | |
| return { | |
| supportEfforts: parseStringArray(record['valid_efforts']), | |
| defaultEffort: | |
| typeof rawDefault === 'string' && rawDefault.length > 0 ? rawDefault : undefined, | |
| }; | |
| } | |
| export async function fetchManagedKimiCodeModels( | |
| options: FetchManagedKimiCodeModelsOptions, | |
| ): Promise<ManagedKimiCodeModelInfo[]> { | |
| const fetchImpl = options.fetchImpl ?? fetch; | |
| const baseUrl = defaultBaseUrl(options.baseUrl); | |
| const response = await fetchImpl(`${baseUrl}/models`, { | |
| headers: { | |
| ...parseKimiCodeCustomHeaders(), | |
| ...options.headers, | |
| Authorization: `Bearer ${options.accessToken}`, | |
| Accept: 'application/json', | |
| }, | |
| }); | |
| if (!response.ok) { | |
| const message = await readApiErrorMessage( | |
| response, | |
| `Failed to list Kimi Code models (HTTP ${response.status}).`, | |
| ); | |
| if (response.status === 401 || response.status === 402 || response.status === 403) { | |
| throw new ManagedKimiCodeModelsAuthError({ | |
| status: response.status, | |
| baseUrl, | |
| message, | |
| credentialKind: options.credentialKind, | |
| }); | |
| } | |
| throw new Error(message); | |
| } | |
| const payload: unknown = await response.json(); | |
| if (!isRecord(payload) || !Array.isArray(payload['data'])) { | |
| throw new Error(`Unexpected models response for ${baseUrl}.`); | |
| } | |
| return payload['data'] | |
| .map((item) => toModelInfo(item)) | |
| .filter((item): item is ManagedKimiCodeModelInfo => item !== undefined); | |
| } | |
| /** | |
| * Builds the upstream-owned alias record for one `/models` entry. Shared by | |
| * `applyManagedKimiCodeConfig` (OAuth provisioning/refresh) and | |
| * `applyManagedApiKeyProviderModels` (api-key provider refresh) so both write | |
| * identical upstream-owned fields for the same model payload. | |
| */ | |
| export function toManagedModelAlias( | |
| providerId: string, | |
| model: ManagedKimiCodeModelInfo, | |
| ): ManagedKimiModelAlias { | |
| const capabilities = capabilitiesForModel(model); | |
| // Kimi's Anthropic-compatible endpoint only accepts adaptive thinking | |
| // (`thinking: { type: 'adaptive' }`); the kosong adapter otherwise infers | |
| // budget-based thinking from the model name, which fails for Kimi model ids. | |
| // Restrict the override to thinking-capable models: the UI treats | |
| // `adaptiveThinking === true` as "supports a thinking toggle", so marking a | |
| // non-thinking model would misrepresent it. | |
| const supportsAdaptiveThinking = | |
| model.protocol === 'anthropic' && | |
| (capabilities?.includes('thinking') === true || | |
| capabilities?.includes('always_thinking') === true); | |
| return { | |
| provider: providerId, | |
| model: model.id, | |
| maxContextSize: model.contextLength, | |
| capabilities, | |
| ...(model.displayName !== undefined ? { displayName: model.displayName } : {}), | |
| ...(model.supportEfforts !== undefined ? { supportEfforts: model.supportEfforts } : {}), | |
| ...(model.defaultEffort !== undefined ? { defaultEffort: model.defaultEffort } : {}), | |
| protocol: model.protocol, | |
| // Kimi's anthropic-compatible endpoint is served behind the beta Messages | |
| // API (`/v1/messages?beta=true`), so route anthropic-protocol models | |
| // through `client.beta.messages.create`. Cleared on refresh when the | |
| // server stops declaring anthropic so stale routing never lingers. | |
| betaApi: model.protocol === 'anthropic' ? true : undefined, | |
| adaptiveThinking: supportsAdaptiveThinking ? true : undefined, | |
| }; | |
| } | |
| export function applyManagedKimiCodeConfig( | |
| config: ManagedKimiConfigShape, | |
| options: { | |
| readonly models: readonly ManagedKimiCodeModelInfo[]; | |
| readonly baseUrl?: string | undefined; | |
| readonly oauthKey?: string | undefined; | |
| readonly oauthHost?: string | undefined; | |
| readonly preserveDefaultModel?: boolean | undefined; | |
| }, | |
| ): ManagedKimiCodeApplyResult { | |
| if (options.models.length === 0) { | |
| throw new Error('No models available for Kimi Code.'); | |
| } | |
| for (const model of options.models) { | |
| assertPositiveContextLength(model); | |
| } | |
| const baseUrl = defaultBaseUrl(options.baseUrl); | |
| const oauth = | |
| options.oauthKey !== undefined | |
| ? managedOAuthRef({ key: options.oauthKey, oauthHost: options.oauthHost }) | |
| : resolveKimiCodeOAuthRef({ baseUrl, oauthHost: options.oauthHost }); | |
| const existingModels = config.models ?? {}; | |
| const selectedDefault = selectDefaultModel(config, options.models, { | |
| preserveExisting: options.preserveDefaultModel === true, | |
| }); | |
| config.providers[KIMI_CODE_PROVIDER_NAME] = { | |
| type: 'kimi', | |
| baseUrl, | |
| apiKey: '', | |
| oauth, | |
| }; | |
| // Selectively merge upstream models into the existing config so any fields | |
| // the user added by hand (or that upstream does not declare) survive a | |
| // refresh. Managed models that upstream no longer lists are removed; the | |
| // rest are merged field-by-field — upstream-owned fields are overwritten, | |
| // everything else is preserved. | |
| const upstreamKeys = new Set(options.models.map((m) => managedModelKey(m.id))); | |
| for (const [key, model] of Object.entries(existingModels)) { | |
| if ( | |
| isRecord(model) && | |
| model['provider'] === KIMI_CODE_PROVIDER_NAME && | |
| !upstreamKeys.has(key) | |
| ) { | |
| delete existingModels[key]; | |
| } | |
| } | |
| for (const model of options.models) { | |
| const key = managedModelKey(model.id); | |
| const existing = isRecord(existingModels[key]) ? existingModels[key] : {}; | |
| existingModels[key] = mergeRefreshedModelAlias( | |
| existing, | |
| toManagedModelAlias(KIMI_CODE_PROVIDER_NAME, model), | |
| MANAGED_KIMI_MODEL_FIELDS, | |
| ); | |
| } | |
| config.models = existingModels; | |
| config.defaultModel = selectedDefault.modelKey; | |
| config.thinking = { ...config.thinking, enabled: selectedDefault.thinking }; | |
| config.services = { | |
| moonshotSearch: { | |
| baseUrl: `${baseUrl}/search`, | |
| apiKey: '', | |
| oauth, | |
| }, | |
| moonshotFetch: { | |
| baseUrl: `${baseUrl}/fetch`, | |
| apiKey: '', | |
| oauth, | |
| }, | |
| }; | |
| return { | |
| defaultModel: selectedDefault.modelKey, | |
| defaultThinking: selectedDefault.thinking, | |
| }; | |
| } | |
| /** | |
| * Merge refreshed `/models` entries into the aliases of an api-key provider | |
| * pointing at the managed Kimi Code endpoint (a hand-configured provider using | |
| * a distributed API key instead of OAuth). Unlike `applyManagedKimiCodeConfig` | |
| * this touches ONLY `config.models`: the provider record (type / baseUrl / | |
| * apiKey and any hand-written extras), `services`, `defaultModel`, and | |
| * `thinking` are all left untouched — the provider is user-owned, only its | |
| * model catalog is upstream-owned. | |
| * | |
| * `aliasPrefix` scopes which aliases count as refresh-generated: | |
| * `${providerId}/` for ordinary providers, `kimi-code/` for a hand-written | |
| * `managed:kimi-code` entry so its aliases line up with the OAuth provisioned | |
| * shape. Aliases outside the prefix are the caller's responsibility (the | |
| * refresh orchestrator preserves them via `preserveUserProviderAliases`). | |
| */ | |
| export function applyManagedApiKeyProviderModels( | |
| config: ManagedKimiConfigShape, | |
| providerId: string, | |
| models: readonly ManagedKimiCodeModelInfo[], | |
| aliasPrefix: string, | |
| ): void { | |
| for (const model of models) { | |
| assertPositiveContextLength(model); | |
| } | |
| const existingModels = config.models ?? {}; | |
| // Same merge contract as `applyManagedKimiCodeConfig`: upstream-owned fields | |
| // are overwritten, hand-written extras survive, and aliases upstream no | |
| // longer lists are removed (the orchestrator restores non-prefix ones). | |
| const upstreamKeys = new Set(models.map((m) => `${aliasPrefix}${m.id}`)); | |
| for (const [key, model] of Object.entries(existingModels)) { | |
| if (isRecord(model) && model['provider'] === providerId && !upstreamKeys.has(key)) { | |
| delete existingModels[key]; | |
| } | |
| } | |
| for (const model of models) { | |
| const key = `${aliasPrefix}${model.id}`; | |
| const existing = isRecord(existingModels[key]) ? existingModels[key] : {}; | |
| existingModels[key] = mergeRefreshedModelAlias( | |
| existing, | |
| toManagedModelAlias(providerId, model), | |
| MANAGED_KIMI_MODEL_FIELDS, | |
| ); | |
| } | |
| config.models = existingModels; | |
| } | |
| export function applyManagedKimiCodeLogoutConfig(config: ManagedKimiConfigShape): void { | |
| delete config.providers[KIMI_CODE_PROVIDER_NAME]; | |
| let removedDefaultModel = false; | |
| const existingModels = config.models ?? {}; | |
| for (const [key, model] of Object.entries(existingModels)) { | |
| if (!isRecord(model) || model['provider'] !== KIMI_CODE_PROVIDER_NAME) continue; | |
| delete existingModels[key]; | |
| if (config.defaultModel === key) removedDefaultModel = true; | |
| } | |
| config.models = existingModels; | |
| if (removedDefaultModel) { | |
| config.defaultModel = undefined; | |
| } | |
| if (config['defaultProvider'] === KIMI_CODE_PROVIDER_NAME) { | |
| config['defaultProvider'] = undefined; | |
| } | |
| if (config.services !== undefined) { | |
| delete config.services.moonshotSearch; | |
| delete config.services.moonshotFetch; | |
| if (Object.keys(config.services).length === 0) { | |
| config.services = undefined; | |
| } | |
| } | |
| } | |
| // The server's three-state declaration overrides any stale thinking.enabled | |
| // being preserved from an earlier config: an always-thinking model ('only') | |
| // must never end up with thinking off, and a non-thinking model ('no') must | |
| // never end up with thinking on. | |
| function forcedThinking( | |
| model: ManagedKimiCodeModelInfo | undefined, | |
| fallback: boolean, | |
| ): boolean { | |
| if (model?.supportsThinkingType === 'only') return true; | |
| if (model?.supportsThinkingType === 'no') return false; | |
| return fallback; | |
| } | |
| function selectDefaultModel( | |
| config: ManagedKimiConfigShape, | |
| models: readonly ManagedKimiCodeModelInfo[], | |
| options: { readonly preserveExisting: boolean }, | |
| ): SelectedDefaultModel { | |
| const firstModel = models[0]; | |
| if (firstModel === undefined) { | |
| throw new Error('No models available for Kimi Code.'); | |
| } | |
| const managedModels = new Map(models.map((model) => [managedModelKey(model.id), model])); | |
| const existingModels = config.models ?? {}; | |
| const currentDefault = | |
| typeof config.defaultModel === 'string' && config.defaultModel.length > 0 | |
| ? config.defaultModel | |
| : undefined; | |
| if ( | |
| options.preserveExisting && | |
| currentDefault !== undefined && | |
| canPreserveDefaultModel(existingModels, currentDefault, managedModels) | |
| ) { | |
| const preservedModel = managedModels.get(currentDefault); | |
| return { | |
| modelKey: currentDefault, | |
| thinking: forcedThinking( | |
| preservedModel, | |
| config.thinking?.enabled ?? preservedModel?.supportsReasoning ?? false, | |
| ), | |
| }; | |
| } | |
| return { | |
| modelKey: managedModelKey(firstModel.id), | |
| thinking: forcedThinking(firstModel, config.thinking?.enabled ?? firstModel.supportsReasoning), | |
| }; | |
| } | |
| function canPreserveDefaultModel( | |
| existingModels: Record<string, ManagedKimiModelAlias | Record<string, unknown>>, | |
| defaultModel: string, | |
| managedModels: ReadonlyMap<string, ManagedKimiCodeModelInfo>, | |
| ): boolean { | |
| if (managedModels.has(defaultModel)) return true; | |
| const existing = existingModels[defaultModel]; | |
| return isRecord(existing) && existing['provider'] !== KIMI_CODE_PROVIDER_NAME; | |
| } | |
| export function clearManagedKimiCodeConfig( | |
| config: ManagedKimiConfigShape, | |
| ): ManagedKimiCodeCleanupResult { | |
| const removedProvider = Object.hasOwn(config.providers, KIMI_CODE_PROVIDER_NAME); | |
| delete config.providers[KIMI_CODE_PROVIDER_NAME]; | |
| const removedModels: string[] = []; | |
| const models = config.models; | |
| if (models !== undefined) { | |
| for (const [key, model] of Object.entries(models)) { | |
| if (!isRecord(model) || model['provider'] !== KIMI_CODE_PROVIDER_NAME) continue; | |
| delete models[key]; | |
| removedModels.push(key); | |
| } | |
| } | |
| let defaultModelCleared = false; | |
| if (typeof config.defaultModel === 'string' && removedModels.includes(config.defaultModel)) { | |
| config.defaultModel = undefined; | |
| defaultModelCleared = true; | |
| } | |
| const removedServices: string[] = []; | |
| if (config.services?.moonshotSearch !== undefined) { | |
| delete config.services.moonshotSearch; | |
| removedServices.push('moonshotSearch'); | |
| } | |
| if (config.services?.moonshotFetch !== undefined) { | |
| delete config.services.moonshotFetch; | |
| removedServices.push('moonshotFetch'); | |
| } | |
| if (config.services !== undefined && Object.keys(config.services).length === 0) { | |
| config.services = undefined; | |
| } | |
| return { | |
| providerName: KIMI_CODE_PROVIDER_NAME, | |
| removedProvider, | |
| removedModels, | |
| defaultModelCleared, | |
| removedServices, | |
| }; | |
| } | |
| function assertPositiveContextLength(model: ManagedKimiCodeModelInfo): void { | |
| if (!Number.isInteger(model.contextLength) || model.contextLength <= 0) { | |
| throw new Error(`Kimi Code model "${model.id}" must include a positive context_length.`); | |
| } | |
| } | |
| export async function provisionManagedKimiCodeConfigAfterLogin( | |
| options: ProvisionManagedKimiCodeConfigOptions<ManagedKimiConfigShape>, | |
| ): Promise<ManagedKimiCodeProvisionResult> { | |
| return provisionManagedKimiCodeConfig(options); | |
| } | |
| export async function provisionManagedKimiCodeConfig<TConfig>( | |
| options: ProvisionManagedKimiCodeConfigOptions<TConfig>, | |
| ): Promise<ManagedKimiCodeProvisionResult> { | |
| const models = await fetchManagedKimiCodeModels(options); | |
| const config = await options.adapter.read(); | |
| const applied = options.adapter.apply(config, { | |
| models, | |
| baseUrl: options.baseUrl, | |
| oauthKey: options.oauthKey, | |
| oauthHost: options.oauthHost, | |
| preserveDefaultModel: options.preserveDefaultModel, | |
| }); | |
| await options.adapter.write(config); | |
| return { | |
| providerName: KIMI_CODE_PROVIDER_NAME, | |
| defaultModel: applied.defaultModel, | |
| defaultThinking: applied.defaultThinking, | |
| models, | |
| configPath: options.adapter.configPath, | |
| }; | |
| } | |