import { mkdirSync } from 'node:fs'; import { homedir } from 'node:os'; import { join } from 'pathe'; import type { KimiHostIdentity } from '@moonshot-ai/kimi-code-oauth'; import { SyncDescriptor } from '#/_base/di/descriptors'; import { createDecorator, type ServiceIdentifier } from '#/_base/di/instantiation'; import { createAppScope, type Scope, type ScopeSeed } from '#/_base/di/scope'; import { IFileSystemStorageService, } from '#/persistence/interface/storage'; import { FileStorageService } from '#/persistence/backends/node-fs/fileStorageService'; import { FileSkillDiscovery } from '#/features/skill/catalog/fileSkillDiscovery'; import { ISkillDiscovery } from '#/features/skill/catalog/skillDiscovery'; export type HostUiCapability = 'update_panel'; export interface HostArgs { readonly agentFiles?: readonly string[]; readonly skillDirs?: readonly string[]; readonly requestHeaders: Readonly>; readonly displayName?: string; readonly replyStyleGuide?: string; readonly nonInteractive?: boolean; readonly uiCapabilities?: readonly HostUiCapability[]; } export interface HostArgsInput { readonly agentFiles?: readonly string[]; readonly skillDirs?: readonly string[]; readonly requestHeaders?: Readonly>; readonly displayName?: string; readonly replyStyleGuide?: string; readonly nonInteractive?: boolean; readonly uiCapabilities?: readonly HostUiCapability[]; } export function resolveHostArgs(input: HostArgsInput | undefined): HostArgs { return { agentFiles: input?.agentFiles, skillDirs: input?.skillDirs, requestHeaders: input?.requestHeaders ?? {}, displayName: input?.displayName, replyStyleGuide: input?.replyStyleGuide, nonInteractive: input?.nonInteractive, uiCapabilities: input?.uiCapabilities, }; } export interface IBootstrapOptions { readonly homeDir: string; readonly configPath: string; readonly osHomeDir: string; readonly platform: NodeJS.Platform; readonly arch: string; readonly cwd: string; readonly env: NodeJS.ProcessEnv; readonly clientIdentity: KimiHostIdentity; readonly args: HostArgs; } export const IBootstrapOptions: ServiceIdentifier = createDecorator('bootstrapOptions'); export type PersistenceScopeName = | 'config' | 'sessions' | 'blobs' | 'store' | 'logs' | 'cache' | 'credentials'; export interface IBootstrapService { readonly _serviceBrand: undefined; readonly platform: NodeJS.Platform; readonly arch: string; readonly cwd: string; readonly osHomeDir: string; readonly homeDir: string; readonly configPath: string; readonly clientIdentity: KimiHostIdentity; readonly args: HostArgs; readonly sessionsDir: string; readonly blobsDir: string; readonly storeDir: string; readonly cacheDir: string; readonly logsDir: string; getEnv(name: string): string | undefined; scope(name: PersistenceScopeName): string; readonly configKey: string; } export const IBootstrapService: ServiceIdentifier = createDecorator('bootstrapService'); export interface BootstrapInput { readonly homeDir?: string; readonly configPath?: string; readonly env?: NodeJS.ProcessEnv; readonly osHomeDir?: string; readonly platform?: NodeJS.Platform; readonly arch?: string; readonly cwd?: string; readonly clientIdentity: KimiHostIdentity; readonly args?: HostArgsInput; } export function resolveBootstrapOptions(input: BootstrapInput): IBootstrapOptions { const env = input.env ?? process.env; const osHomeDir = input.osHomeDir ?? homedir(); const homeDir = resolveKimiHome(input.homeDir, env, osHomeDir); const configPath = input.configPath ?? join(homeDir, 'config.toml'); return { homeDir, configPath, osHomeDir, platform: input.platform ?? process.platform, arch: input.arch ?? process.arch, cwd: input.cwd ?? process.cwd(), env, clientIdentity: input.clientIdentity, args: resolveHostArgs(input.args), }; } export function bootstrapSeed(input: BootstrapInput): ScopeSeed { return [ [ IBootstrapOptions as ServiceIdentifier, resolveBootstrapOptions(input), ], ]; } export interface BootstrapResult { readonly app: Scope; } export function bootstrap(input: BootstrapInput, extraSeeds: ScopeSeed = []): BootstrapResult { const options = resolveBootstrapOptions(input); const app = createAppScope({ seeds: [...bootstrapSeed(input), ...storageSeed(options), ...skillSeed(), ...extraSeeds], }); return { app }; } function storageSeed(options: IBootstrapOptions): ScopeSeed { const file = (): SyncDescriptor => new SyncDescriptor(FileStorageService, [options.homeDir, 0o700, 0o600]); return [ [IFileSystemStorageService as ServiceIdentifier, file()], ]; } function skillSeed(): ScopeSeed { return [ [ ISkillDiscovery as ServiceIdentifier, new SyncDescriptor(FileSkillDiscovery, []), ], ]; } export function resolveKimiHome( homeDir?: string, env: NodeJS.ProcessEnv = process.env, osHomeDir: string = homedir(), ): string { return homeDir ?? env['KIMI_CODE_HOME'] ?? join(osHomeDir, '.kimi-code'); } export function resolveConfigPath(input: { readonly homeDir?: string; readonly configPath?: string; }): string { return input.configPath ?? join(resolveKimiHome(input.homeDir), 'config.toml'); } export function ensureKimiHome(homeDir: string): void { mkdirSync(homeDir, { recursive: true, mode: 0o700 }); }