import { AsyncLocalStorage } from 'node:async_hooks'; import { ErrorCodes, makeErrorPayload, type AgentContextData, type ApprovalRequest, type ApprovalResponse, type CoreAPI, type Event, type ExperimentalFeatureState, type QuestionRequest, type QuestionResult, type RPCMethods, type SDKAPI, type ToolCallRequest, type ToolCallResponse, type SwarmModeTrigger, } from '@moonshot-ai/agent-core'; import type { Kaos } from '@moonshot-ai/kaos'; import type { ApprovalHandler, QuestionHandler } from '#/events'; import type { AddAdditionalDirInput, AddAdditionalDirResult, BackgroundTaskInfo, ConfigDiagnostics, CreateSessionOptions, ExportSessionInput, ExportSessionResult, CreateGoalInput, ForkSessionInput, GetConfigOptions, GoalSnapshot, GoalToolResult, KimiConfig, KimiConfigPatch, ListSessionsOptions, McpServerInfo, McpStartupMetrics, PermissionMode, PluginInfo, PluginSummary, ReloadSummary, CompactOptions, SessionPlan, SessionStatus, SessionUsage, PromptInput, RenameSessionInput, ResumeSessionInput, ResumedSessionSummary, SessionSummary, SkillSummary, PluginCommandDef, Unsubscribe, } from '#/types'; const MAIN_AGENT_ID = 'main'; export interface SessionPromptRpcInput { readonly sessionId: string; readonly input: PromptInput; } export interface SessionIdRpcInput { readonly sessionId: string; } export interface ReloadSessionRpcInput extends SessionIdRpcInput { readonly forcePluginSessionStartReminder?: boolean; } export interface SetSessionModelRpcInput extends SessionIdRpcInput { readonly model: string; } export interface SetSessionModelRpcResult { readonly model: string; readonly providerName?: string | undefined; } export interface SetSessionThinkingRpcInput extends SessionIdRpcInput { readonly effort: string; } export interface SetSessionPermissionRpcInput extends SessionIdRpcInput { readonly mode: PermissionMode; } export interface SetSessionPlanModeRpcInput extends SessionIdRpcInput { readonly enabled: boolean; } export type SetSessionSwarmModeRpcInput = | (SessionIdRpcInput & { readonly enabled: true; readonly trigger: SwarmModeTrigger }) | (SessionIdRpcInput & { readonly enabled: false }); export interface ActivateSkillRpcInput extends SessionIdRpcInput { readonly name: string; readonly args?: string | undefined; } export interface ActivatePluginCommandRpcInput extends SessionIdRpcInput { readonly pluginId: string; readonly commandName: string; readonly args?: string | undefined; } export interface ReconnectMcpServerRpcInput extends SessionIdRpcInput { readonly name: string; } type ResolvedCoreAPI = RPCMethods; export abstract class SDKRpcClientBase { private readonly interactiveAgentScope = new AsyncLocalStorage(); private readonly eventListeners = new Set<(event: Event) => void>(); private readonly approvalHandlers = new Map(); private readonly questionHandlers = new Map(); get interactiveAgentId(): string { return this.interactiveAgentScope.getStore() ?? MAIN_AGENT_ID; } withInteractiveAgent(agentId: string, fn: () => T): T { return this.interactiveAgentScope.run(agentId, fn); } protected abstract getRpc(): Promise; async createSession(input: CreateSessionOptions): Promise { const rpc = await this.getRpc(); const { planMode, ...coreInput } = input; void planMode; return rpc.createSession(coreInput); } async createSessionWithKaos( input: CreateSessionOptions, kaos: Kaos, persistenceKaos?: Kaos, ): Promise { void kaos; void persistenceKaos; return this.createSession(input); } async resumeSession(input: ResumeSessionInput): Promise { const rpc = await this.getRpc(); return rpc.resumeSession({ ...input, sessionId: input.id }); } async resumeSessionWithKaos( input: ResumeSessionInput, kaos: Kaos, persistenceKaos?: Kaos, ): Promise { void kaos; void persistenceKaos; return this.resumeSession(input); } async reloadSession(input: ReloadSessionRpcInput): Promise { const rpc = await this.getRpc(); return rpc.reloadSession({ sessionId: input.sessionId, forcePluginSessionStartReminder: input.forcePluginSessionStartReminder, }); } async forkSession(input: ForkSessionInput): Promise { const rpc = await this.getRpc(); return rpc.forkSession({ sessionId: input.id, id: input.forkId, title: input.title, metadata: input.metadata, }); } async closeSession(input: SessionIdRpcInput): Promise { const rpc = await this.getRpc(); return rpc.closeSession({ sessionId: input.sessionId }); } async listSessions(input: ListSessionsOptions = {}): Promise { const rpc = await this.getRpc(); return rpc.listSessions(input); } async renameSession(input: RenameSessionInput): Promise { const rpc = await this.getRpc(); return rpc.renameSession({ sessionId: input.id, title: input.title, }); } async exportSession(input: ExportSessionInput): Promise { const rpc = await this.getRpc(); return rpc.exportSession({ sessionId: input.id, outputPath: input.outputPath, includeGlobalLog: input.includeGlobalLog, version: input.version, installSource: input.installSource, shellEnv: input.shellEnv, }); } async getConfig(input?: GetConfigOptions): Promise { const rpc = await this.getRpc(); return rpc.getKimiConfig(input ?? {}); } async getConfigDiagnostics(): Promise { const rpc = await this.getRpc(); return rpc.getConfigDiagnostics({}); } async getExperimentalFeatures(): Promise { const rpc = await this.getRpc(); return rpc.getExperimentalFeatures({}); } async setConfig(input: KimiConfigPatch): Promise { const rpc = await this.getRpc(); return rpc.setKimiConfig(input); } async removeProvider(providerId: string): Promise { const rpc = await this.getRpc(); return rpc.removeKimiProvider({ providerId }); } async prompt(input: SessionPromptRpcInput): Promise { const agentId = this.interactiveAgentId; const rpc = await this.getRpc(); return rpc.prompt({ sessionId: input.sessionId, agentId, input: input.input, }); } async runShellCommand(input: { sessionId: string; command: string; commandId?: string; }): Promise<{ stdout: string; stderr: string; isError?: boolean; backgrounded?: boolean }> { const agentId = this.interactiveAgentId; const rpc = await this.getRpc(); return rpc.runShellCommand({ sessionId: input.sessionId, agentId, command: input.command, commandId: input.commandId, }); } async cancelShellCommand(input: { sessionId: string; commandId: string }): Promise { const agentId = this.interactiveAgentId; const rpc = await this.getRpc(); return rpc.cancelShellCommand({ sessionId: input.sessionId, agentId, commandId: input.commandId, }); } async steer(input: SessionPromptRpcInput): Promise { const agentId = this.interactiveAgentId; const rpc = await this.getRpc(); return rpc.steer({ sessionId: input.sessionId, agentId, input: input.input, }); } async generateAgentsMd(input: SessionIdRpcInput): Promise { const rpc = await this.getRpc(); return rpc.generateAgentsMd({ sessionId: input.sessionId }); } async getSessionWarnings(input: SessionIdRpcInput) { const rpc = await this.getRpc(); return rpc.getSessionWarnings({ sessionId: input.sessionId }); } async addAdditionalDir(input: AddAdditionalDirInput): Promise { const rpc = await this.getRpc(); return rpc.addAdditionalDir({ sessionId: input.id, path: input.path, persist: input.persist }); } async startBtw(input: SessionIdRpcInput): Promise { const agentId = this.interactiveAgentId; const rpc = await this.getRpc(); return rpc.startBtw({ sessionId: input.sessionId, agentId, }); } async cancel(input: SessionIdRpcInput): Promise { const agentId = this.interactiveAgentId; const rpc = await this.getRpc(); return rpc.cancel({ sessionId: input.sessionId, agentId, }); } async setModel(input: SetSessionModelRpcInput): Promise { const rpc = await this.getRpc(); return rpc.setModel({ sessionId: input.sessionId, agentId: this.interactiveAgentId, model: input.model, }); } async setThinking(input: SetSessionThinkingRpcInput): Promise { const rpc = await this.getRpc(); return rpc.setThinking({ sessionId: input.sessionId, agentId: this.interactiveAgentId, effort: input.effort, }); } async setPermission(input: SetSessionPermissionRpcInput): Promise { const rpc = await this.getRpc(); return rpc.setPermission({ sessionId: input.sessionId, agentId: this.interactiveAgentId, mode: input.mode, }); } async setPlanMode(input: SetSessionPlanModeRpcInput): Promise { const rpc = await this.getRpc(); if (!input.enabled) { return rpc.cancelPlan({ sessionId: input.sessionId, agentId: this.interactiveAgentId, }); } return rpc.enterPlan({ sessionId: input.sessionId, agentId: this.interactiveAgentId, }); } async setSwarmMode(input: SetSessionSwarmModeRpcInput): Promise { if (input.enabled) return this.enterSwarmMode(input); return this.exitSwarmMode(input); } async swarm(input: SessionPromptRpcInput): Promise { await this.enterSwarmMode({ sessionId: input.sessionId, trigger: 'task' }); return this.prompt(input); } private async enterSwarmMode( input: SessionIdRpcInput & { readonly trigger: SwarmModeTrigger }, ): Promise { const rpc = await this.getRpc(); return rpc.enterSwarm({ sessionId: input.sessionId, agentId: this.interactiveAgentId, trigger: input.trigger, }); } private async exitSwarmMode(input: SessionIdRpcInput): Promise { const rpc = await this.getRpc(); return rpc.exitSwarm({ sessionId: input.sessionId, agentId: this.interactiveAgentId, }); } async getPlan(input: SessionIdRpcInput): Promise { const rpc = await this.getRpc(); return rpc.getPlan({ sessionId: input.sessionId, agentId: this.interactiveAgentId, }); } async clearPlan(input: SessionIdRpcInput): Promise { const rpc = await this.getRpc(); await rpc.clearPlan({ sessionId: input.sessionId, agentId: this.interactiveAgentId, }); } async compact(input: SessionIdRpcInput & CompactOptions): Promise { const rpc = await this.getRpc(); return rpc.beginCompaction({ sessionId: input.sessionId, agentId: this.interactiveAgentId, ...(input.instruction !== undefined ? { instruction: input.instruction } : {}), }); } async cancelCompaction(input: SessionIdRpcInput): Promise { const rpc = await this.getRpc(); return rpc.cancelCompaction({ sessionId: input.sessionId, agentId: this.interactiveAgentId, }); } async undoHistory(input: SessionIdRpcInput & { count: number }): Promise { const rpc = await this.getRpc(); return rpc.undoHistory({ sessionId: input.sessionId, agentId: this.interactiveAgentId, count: input.count, }); } async getContext(input: SessionIdRpcInput): Promise { const rpc = await this.getRpc(); return rpc.getContext({ sessionId: input.sessionId, agentId: this.interactiveAgentId, }); } async getUsage(input: SessionIdRpcInput): Promise { const rpc = await this.getRpc(); return rpc.getUsage({ sessionId: input.sessionId, agentId: this.interactiveAgentId, }); } async getStatus(input: SessionIdRpcInput): Promise { const rpc = await this.getRpc(); const agentId = this.interactiveAgentId; const config = await rpc.getConfig({ sessionId: input.sessionId, agentId, }); const context = await rpc.getContext({ sessionId: input.sessionId, agentId, }); const permission = await rpc.getPermission({ sessionId: input.sessionId, agentId, }); const plan = await rpc.getPlan({ sessionId: input.sessionId, agentId, }); const swarmMode = await rpc.getSwarmMode({ sessionId: input.sessionId, agentId, }); const usage = await rpc.getUsage({ sessionId: input.sessionId, agentId, }); const maxContextTokens = config.modelCapabilities?.max_context_tokens ?? 0; const contextTokens = context.tokenCount; const contextUsage = maxContextTokens > 0 ? contextTokens / maxContextTokens : 0; const hasUsage = usage.byModel !== undefined || usage.total !== undefined || usage.currentTurn !== undefined; return { model: config.modelAlias ?? config.provider?.model, thinkingEffort: config.thinkingEffort, permission: permission.mode, planMode: plan !== null, swarmMode, contextTokens, maxContextTokens, contextUsage, usage: hasUsage ? usage : undefined, }; } async listSkills(input: SessionIdRpcInput): Promise { const rpc = await this.getRpc(); return rpc.listSkills({ sessionId: input.sessionId }); } async listPluginCommands(input: SessionIdRpcInput): Promise { const rpc = await this.getRpc(); return rpc.listPluginCommands({ sessionId: input.sessionId }); } async listBackgroundTasks( input: SessionIdRpcInput & { activeOnly?: boolean; limit?: number }, ): Promise { const rpc = await this.getRpc(); return rpc.getBackground({ sessionId: input.sessionId, agentId: this.interactiveAgentId, activeOnly: input.activeOnly, limit: input.limit, }); } async getBackgroundTaskOutput( input: SessionIdRpcInput & { taskId: string; tail?: number }, ): Promise { const rpc = await this.getRpc(); return rpc.getBackgroundOutput({ sessionId: input.sessionId, agentId: this.interactiveAgentId, taskId: input.taskId, tail: input.tail, }); } async stopBackgroundTask( input: SessionIdRpcInput & { taskId: string; reason?: string }, ): Promise { const rpc = await this.getRpc(); return rpc.stopBackground({ sessionId: input.sessionId, agentId: this.interactiveAgentId, taskId: input.taskId, reason: input.reason, }); } async detachBackgroundTask( input: SessionIdRpcInput & { taskId: string }, ): Promise { const rpc = await this.getRpc(); return rpc.detachBackground({ sessionId: input.sessionId, agentId: this.interactiveAgentId, taskId: input.taskId, }); } async createGoal(input: SessionIdRpcInput & CreateGoalInput): Promise { const rpc = await this.getRpc(); return rpc.createGoal({ sessionId: input.sessionId, agentId: this.interactiveAgentId, objective: input.objective, replace: input.replace, }); } async getGoal(input: SessionIdRpcInput): Promise { const rpc = await this.getRpc(); return rpc.getGoal({ sessionId: input.sessionId, agentId: this.interactiveAgentId }); } async pauseGoal(input: SessionIdRpcInput): Promise { const rpc = await this.getRpc(); return rpc.pauseGoal({ sessionId: input.sessionId, agentId: this.interactiveAgentId, }); } async resumeGoal(input: SessionIdRpcInput): Promise { const rpc = await this.getRpc(); return rpc.resumeGoal({ sessionId: input.sessionId, agentId: this.interactiveAgentId, }); } async cancelGoal(input: SessionIdRpcInput): Promise { const rpc = await this.getRpc(); return rpc.cancelGoal({ sessionId: input.sessionId, agentId: this.interactiveAgentId, }); } async listMcpServers(input: SessionIdRpcInput): Promise { const rpc = await this.getRpc(); return rpc.listMcpServers({ sessionId: input.sessionId }); } async getMcpStartupMetrics(input: SessionIdRpcInput): Promise { const rpc = await this.getRpc(); return rpc.getMcpStartupMetrics({ sessionId: input.sessionId }); } async reconnectMcpServer(input: ReconnectMcpServerRpcInput): Promise { const rpc = await this.getRpc(); return rpc.reconnectMcpServer({ sessionId: input.sessionId, name: input.name }); } async listPlugins(): Promise { const rpc = await this.getRpc(); return rpc.listPlugins({}); } async installPlugin(source: string): Promise { const rpc = await this.getRpc(); return rpc.installPlugin({ source }); } async setPluginEnabled(id: string, enabled: boolean): Promise { const rpc = await this.getRpc(); return rpc.setPluginEnabled({ id, enabled }); } async setPluginMcpServerEnabled( id: string, server: string, enabled: boolean, ): Promise { const rpc = await this.getRpc(); return rpc.setPluginMcpServerEnabled({ id, server, enabled }); } async removePlugin(id: string): Promise { const rpc = await this.getRpc(); return rpc.removePlugin({ id }); } async reloadPlugins(): Promise { const rpc = await this.getRpc(); return rpc.reloadPlugins({}); } async getPluginInfo(id: string): Promise { const rpc = await this.getRpc(); return rpc.getPluginInfo({ id }); } async activateSkill(input: ActivateSkillRpcInput): Promise { const rpc = await this.getRpc(); return rpc.activateSkill({ sessionId: input.sessionId, agentId: this.interactiveAgentId, name: input.name, args: input.args, }); } async activatePluginCommand(input: ActivatePluginCommandRpcInput): Promise { const rpc = await this.getRpc(); return rpc.activatePluginCommand({ sessionId: input.sessionId, agentId: this.interactiveAgentId, pluginId: input.pluginId, commandName: input.commandName, args: input.args, }); } onEvent(listener: (event: Event) => void): Unsubscribe { this.eventListeners.add(listener); return () => { this.eventListeners.delete(listener); }; } receiveEvent(event: Event): void { for (const listener of this.eventListeners) { listener(event); } } setApprovalHandler(sessionId: string, handler: ApprovalHandler | undefined): void { if (handler === undefined) { this.approvalHandlers.delete(sessionId); return; } this.approvalHandlers.set(sessionId, handler); } setQuestionHandler(sessionId: string, handler: QuestionHandler | undefined): void { if (handler === undefined) { this.questionHandlers.delete(sessionId); return; } this.questionHandlers.set(sessionId, handler); } clearSessionHandlers(sessionId: string): void { this.approvalHandlers.delete(sessionId); this.questionHandlers.delete(sessionId); } async requestApproval( request: ApprovalRequest & { sessionId: string; agentId: string }, ): Promise { const handler = this.approvalHandlers.get(request.sessionId); if (handler === undefined) { return { decision: 'cancelled', feedback: 'No approval handler registered.', }; } try { return await handler(request); } catch (error) { this.receiveEvent({ type: 'error', sessionId: request.sessionId, agentId: request.agentId, ...makeErrorPayload(ErrorCodes.SESSION_APPROVAL_HANDLER_ERROR, errorMessage(error)), }); return { decision: 'cancelled', feedback: 'Approval handler failed.', }; } } async requestQuestion( request: QuestionRequest & { sessionId: string; agentId: string }, ): Promise { const handler = this.questionHandlers.get(request.sessionId); if (handler === undefined) return null; try { return await handler(request); } catch (error) { this.receiveEvent({ type: 'error', sessionId: request.sessionId, agentId: request.agentId, ...makeErrorPayload(ErrorCodes.SESSION_QUESTION_HANDLER_ERROR, errorMessage(error)), }); return null; } } async toolCall(request: ToolCallRequest): Promise { return { output: `SDK custom tool calls are not supported: ${request.toolCallId}`, isError: true, }; } } export class ClientAPI implements SDKAPI { constructor(readonly client: SDKRpcClientBase) {} emitEvent(event: Event): void { this.client.receiveEvent(event); } requestApproval( request: ApprovalRequest & { sessionId: string; agentId: string }, ): Promise { return this.client.requestApproval(request); } requestQuestion( request: QuestionRequest & { sessionId: string; agentId: string }, ): Promise { return this.client.requestQuestion(request); } toolCall(request: ToolCallRequest): Promise { return this.client.toolCall(request); } } function errorMessage(error: unknown): string { return error instanceof Error ? error.message : String(error); }