import { createKimiDefaultHeaders, KIMI_CODE_FLOW_CONFIG, KIMI_CODE_PROVIDER_NAME, KimiOAuthToolkit, kimiCodeBaseUrl, parseKimiCodeCustomHeaders, resolveKimiCodeOAuthRef, type KimiHostIdentity, type ManagedKimiOAuthRef, } from '@moonshot-ai/kimi-code-oauth'; import type { ProviderConfig as KosongProviderConfig, ProviderRequestAuth, } from '@moonshot-ai/kosong'; import { APIStatusError, UNKNOWN_CAPABILITY } from '@moonshot-ai/kosong'; import { resolveKimiHome } from '@moonshot-ai/agent-core-v2'; import { ErrorCodes, KimiError } from '#/errors'; import type { Logger } from '#/logging/index'; import type { ModelProvider, ResolvedRuntimeProvider } from '#/model-provider'; import { mapOAuthTokenError } from '#/oauth-error'; export interface KimiForCodingProviderOptions extends KimiHostIdentity { readonly homeDir?: string; readonly model?: string; readonly baseUrl?: string; readonly promptCacheKey?: string; readonly defaultHeaders?: Record; } export class KimiForCodingProvider implements ModelProvider { private readonly model: string; private readonly baseUrl: string; private readonly promptCacheKey: string | undefined; private readonly defaultHeaders: Record | undefined; private readonly toolkit: KimiOAuthToolkit; private readonly homeDir: string; private readonly identity: KimiHostIdentity; private readonly oauthRef: ManagedKimiOAuthRef; constructor(options: KimiForCodingProviderOptions) { this.model = options.model ?? 'kimi-for-coding'; this.baseUrl = options.baseUrl ?? kimiCodeBaseUrl(); this.promptCacheKey = options.promptCacheKey; this.defaultHeaders = options.defaultHeaders; this.homeDir = resolveKimiHome(options.homeDir); this.identity = { productName: options.productName, version: options.version, platform: options.platform, userAgentSuffix: options.userAgentSuffix, }; this.oauthRef = resolveKimiCodeOAuthRef({ oauthHost: KIMI_CODE_FLOW_CONFIG.oauthHost, baseUrl: this.baseUrl, }); this.toolkit = new KimiOAuthToolkit({ homeDir: this.homeDir, identity: this.identity, }); } get defaultModel(): string { return this.model; } resolveProviderConfig(model: string): ResolvedRuntimeProvider { if (model !== this.model) { throw new KimiError( ErrorCodes.CONFIG_INVALID, `Model "${model}" is not supported by KimiForCodingProvider.`, ); } const provider: KosongProviderConfig = { type: 'kimi', model: this.model, baseUrl: this.baseUrl, generationKwargs: this.promptCacheKey ? { prompt_cache_key: this.promptCacheKey } : undefined, defaultHeaders: { ...parseKimiCodeCustomHeaders(), ...createKimiDefaultHeaders({ homeDir: this.homeDir, ...this.identity, }), ...this.defaultHeaders, }, }; return { providerName: 'kimi-for-coding', provider, modelCapabilities: UNKNOWN_CAPABILITY, type: 'kimi', protocol: undefined, }; } resolveAuth(_model: string, _options?: { readonly log?: Logger }) { return async (request: (auth: ProviderRequestAuth) => Promise): Promise => { let auth = await this.buildAuth(false); for (let refreshed = false; ; refreshed = true) { try { return await request(auth); } catch (error) { const is401 = error instanceof APIStatusError && error.statusCode === 401; if (!is401) throw error; if (refreshed) { throw new KimiError( ErrorCodes.AUTH_LOGIN_REQUIRED, 'OAuth token was rejected after refresh. Run /login to re-authenticate.', { cause: error }, ); } auth = await this.buildAuth(true); } } }; } private async buildAuth(force: boolean): Promise { try { const apiKey = await this.toolkit.ensureFresh(KIMI_CODE_PROVIDER_NAME, { force, oauthRef: this.oauthRef, }); return { apiKey }; } catch (error) { // Classify OAuth token failures into the public KimiError protocol so the // turn surfaces `auth.login_required` / `provider.connection_error` // instead of collapsing everything to `internal`. Unrecognized errors are // rethrown raw (see mapOAuthTokenError). throw mapOAuthTokenError(error, KIMI_CODE_PROVIDER_NAME) ?? error; } } }