| import type { SwarmModeTrigger } from '@moonshot-ai/agent-core-v2/features/swarm/agent/swarm'; |
|
|
| import type { AgentContextData } from '#/context'; |
| import { ErrorCodes, KimiError, type KimiErrorCode } from '#/errors'; |
|
|
| import { type ApprovalHandler, type Event, type QuestionHandler } from '#/events'; |
| import type { SDKRpcClientBase } from '#/rpc'; |
| import type { |
| AddAdditionalDirOptions, |
| AddAdditionalDirResult, |
| AgentCommandInfo, |
| AgentRuntimeBinding, |
| BackgroundTaskInfo, |
| CapabilityStatus, |
| CompactOptions, |
| CreateGoalInput, |
| GetCronTasksResult, |
| GoalSnapshot, |
| GoalToolResult, |
| JsonObject, |
| McpServerConfig, |
| McpServerInfo, |
| McpStartupMetrics, |
| PermissionMode, |
| PluginInfo, |
| PluginSummary, |
| PromptInput, |
| PromptSkillActivation, |
| ReloadSessionOptions, |
| ReloadSummary, |
| ResumedSessionState, |
| ResumedSessionSummary, |
| SessionPlan, |
| SessionStatus, |
| SessionSummary, |
| SessionTodoItem, |
| SessionUsage, |
| SkillSummary, |
| PluginCommandDef, |
| ThinkingEffort, |
| Unsubscribe, |
| } from '#/types'; |
|
|
| const MAIN_AGENT_ID = 'main'; |
|
|
| export interface SessionOptions { |
| readonly id: string; |
| readonly workDir: string; |
| readonly summary?: SessionSummary | undefined; |
| readonly resumeState?: ResumedSessionState | undefined; |
| readonly rpc: SDKRpcClientBase; |
| readonly onClose?: (() => void | Promise<void>) | undefined; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| interface CapabilityRpcSurface { |
| listCapabilities(): Promise<readonly CapabilityStatus[]>; |
| getCapability(id: string): Promise<CapabilityStatus>; |
| installCapability(id: string): Promise<CapabilityStatus>; |
| } |
|
|
| export function capabilityRpc(rpc: SDKRpcClientBase): CapabilityRpcSurface { |
| const candidate = rpc as Partial<CapabilityRpcSurface>; |
| if ( |
| typeof candidate.listCapabilities !== 'function' || |
| typeof candidate.getCapability !== 'function' || |
| typeof candidate.installCapability !== 'function' |
| ) { |
| throw new TypeError('The capability surface is unavailable on this engine (requires v2).'); |
| } |
| return candidate as CapabilityRpcSurface; |
| } |
|
|
| export class Session { |
| readonly id: string; |
| readonly workDir: string; |
| summary?: SessionSummary | undefined; |
| private resumeState: ResumedSessionState | undefined; |
|
|
| private readonly rpc: SDKRpcClientBase; |
| private readonly onClose?: (() => void | Promise<void>) | undefined; |
| private closed = false; |
|
|
| constructor(options: SessionOptions) { |
| this.id = options.id; |
| this.workDir = options.workDir; |
| this.summary = options.summary; |
| this.resumeState = options.resumeState ?? resumeStateFromSummary(options.summary); |
| this.rpc = options.rpc; |
| this.onClose = options.onClose; |
| } |
|
|
| |
| get isClosed(): boolean { |
| return this.closed; |
| } |
|
|
| getResumeState(): ResumedSessionState | undefined { |
| this.ensureOpen(); |
| return this.resumeState; |
| } |
|
|
| async reloadSession(options?: ReloadSessionOptions): Promise<ResumedSessionSummary> { |
| this.ensureOpen(); |
| const summary = await this.rpc.reloadSession({ |
| sessionId: this.id, |
| forcePluginSessionStartReminder: options?.forcePluginSessionStartReminder, |
| }); |
| this.summary = summary; |
| this.resumeState = resumeStateFromSummary(summary); |
| return summary; |
| } |
|
|
| onEvent(listener: (event: Event) => void): Unsubscribe { |
| this.ensureOpen(); |
| return this.rpc.onEvent((event) => { |
| if (event.sessionId === this.id) { |
| listener(event); |
| } |
| }); |
| } |
|
|
| setApprovalHandler(handler: ApprovalHandler | undefined): void { |
| this.ensureOpen(); |
| this.rpc.setApprovalHandler(this.id, handler); |
| } |
|
|
| setQuestionHandler(handler: QuestionHandler | undefined): void { |
| this.ensureOpen(); |
| this.rpc.setQuestionHandler(this.id, handler); |
| } |
|
|
| async prompt(input: string | PromptInput, options?: { promptId?: string }): Promise<void> { |
| this.ensureOpen(); |
| if (options?.promptId !== undefined && options.promptId.length === 0) { |
| throw new TypeError('promptId must not be empty'); |
| } |
| await this.rpc.prompt({ |
| sessionId: this.id, |
| input: normalizePromptInput(input), |
| promptId: options?.promptId, |
| }); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| async promptWithSkills( |
| input: string | PromptInput, |
| skills: readonly PromptSkillActivation[], |
| ): Promise<void> { |
| this.ensureOpen(); |
| await this.rpc.promptWithSkills({ |
| sessionId: this.id, |
| input: normalizePromptInput(input), |
| skills, |
| }); |
| } |
|
|
| |
| |
| |
| async runShellCommand( |
| command: string, |
| options?: { commandId?: string }, |
| ): Promise<{ stdout: string; stderr: string; isError?: boolean; backgrounded?: boolean }> { |
| this.ensureOpen(); |
| return this.rpc.runShellCommand({ |
| sessionId: this.id, |
| command, |
| commandId: options?.commandId, |
| }); |
| } |
|
|
| |
| async cancelShellCommand(commandId: string): Promise<void> { |
| this.ensureOpen(); |
| return this.rpc.cancelShellCommand({ sessionId: this.id, commandId }); |
| } |
|
|
| async steer(input: string | PromptInput): Promise<void> { |
| this.ensureOpen(); |
| await this.rpc.steer({ |
| sessionId: this.id, |
| input: normalizePromptInput(input), |
| }); |
| } |
|
|
| async swarm(input: string | PromptInput): Promise<void> { |
| this.ensureOpen(); |
| await this.rpc.swarm({ |
| sessionId: this.id, |
| input: normalizePromptInput(input), |
| }); |
| } |
|
|
| async init(): Promise<void> { |
| this.ensureOpen(); |
| await this.rpc.generateAgentsMd({ sessionId: this.id }); |
| } |
|
|
| async getSessionWarnings() { |
| this.ensureOpen(); |
| return this.rpc.getSessionWarnings({ sessionId: this.id }); |
| } |
|
|
| async addAdditionalDir( |
| path: string, |
| options?: AddAdditionalDirOptions, |
| ): Promise<AddAdditionalDirResult> { |
| this.ensureOpen(); |
| const normalized = normalizeRequiredString( |
| path, |
| 'Additional directory cannot be empty', |
| ErrorCodes.REQUEST_INVALID, |
| ); |
| const result = await this.rpc.addAdditionalDir({ |
| id: this.id, |
| path: normalized, |
| persist: options?.persist ?? true, |
| }); |
| this.summary = { ...this.requireSummary(), additionalDirs: result.additionalDirs }; |
| return result; |
| } |
|
|
| async startBtw(): Promise<string> { |
| this.ensureOpen(); |
| return this.rpc.startBtw({ sessionId: this.id }); |
| } |
|
|
| async cancel(): Promise<void> { |
| this.ensureOpen(); |
| await this.rpc.cancel({ sessionId: this.id }); |
| } |
|
|
| async setModel(model: string): Promise<void> { |
| this.ensureOpen(); |
| const normalized = normalizeRequiredString( |
| model, |
| 'Session model cannot be empty', |
| ErrorCodes.SESSION_MODEL_EMPTY, |
| ); |
| await this.rpc.setModel({ sessionId: this.id, model: normalized }); |
| } |
|
|
| async getRuntime(): Promise<AgentRuntimeBinding> { |
| this.ensureOpen(); |
| return this.rpc.getRuntime({ sessionId: this.id }); |
| } |
|
|
| async switchRuntime(runtimeId: string): Promise<AgentRuntimeBinding> { |
| this.ensureOpen(); |
| const normalized = normalizeRequiredString( |
| runtimeId, |
| 'Session runtime cannot be empty', |
| ErrorCodes.REQUEST_INVALID, |
| ); |
| return this.rpc.switchRuntime({ sessionId: this.id, runtimeId: normalized }); |
| } |
|
|
| async setThinking(effort: ThinkingEffort): Promise<void> { |
| this.ensureOpen(); |
| const normalized = normalizeRequiredString( |
| effort, |
| 'Session thinking effort cannot be empty', |
| ErrorCodes.SESSION_THINKING_EMPTY, |
| ); |
| await this.rpc.setThinking({ sessionId: this.id, effort: normalized }); |
| } |
|
|
| async setPermission(mode: PermissionMode): Promise<void> { |
| this.ensureOpen(); |
| if (!isPermissionMode(mode)) { |
| throw new KimiError( |
| ErrorCodes.SESSION_PERMISSION_MODE_INVALID, |
| 'Session permission mode must be yolo, manual, or auto', |
| ); |
| } |
| await this.rpc.setPermission({ sessionId: this.id, mode }); |
| } |
|
|
| |
| async updateMetadata(patch: JsonObject): Promise<void> { |
| this.ensureOpen(); |
| if (Object.hasOwn(patch, 'goal')) { |
| throw new KimiError( |
| ErrorCodes.GOAL_METADATA_RESERVED, |
| 'Session metadata key "goal" is reserved for the goal lifecycle', |
| ); |
| } |
| const summary = this.requireSummary(); |
| await this.rpc.updateSessionMetadata({ sessionId: this.id, metadata: patch }); |
| const metadata = { ...summary.metadata, ...patch }; |
| this.summary = { ...summary, metadata }; |
| if (this.resumeState !== undefined) { |
| this.resumeState = { |
| ...this.resumeState, |
| sessionMetadata: { |
| ...this.resumeState.sessionMetadata, |
| custom: { ...this.resumeState.sessionMetadata.custom, ...patch }, |
| }, |
| }; |
| } |
| } |
|
|
| async setPlanMode(enabled: boolean): Promise<void> { |
| this.ensureOpen(); |
| if (typeof enabled !== 'boolean') { |
| throw new KimiError( |
| ErrorCodes.SESSION_PLAN_MODE_INVALID, |
| 'Session plan mode must be a boolean', |
| ); |
| } |
| await this.rpc.setPlanMode({ sessionId: this.id, enabled }); |
| } |
|
|
| async setSwarmMode(enabled: boolean, trigger: SwarmModeTrigger): Promise<void> { |
| this.ensureOpen(); |
| if (typeof enabled !== 'boolean') { |
| throw new KimiError( |
| ErrorCodes.REQUEST_INVALID, |
| 'Session swarm mode must be a boolean', |
| ); |
| } |
| if (enabled) { |
| await this.rpc.setSwarmMode({ sessionId: this.id, enabled: true, trigger }); |
| } else { |
| await this.rpc.setSwarmMode({ sessionId: this.id, enabled: false }); |
| } |
| } |
|
|
| async setTowerMode(enabled: boolean, base?: string): Promise<void> { |
| this.ensureOpen(); |
| if (typeof enabled !== 'boolean') { |
| throw new KimiError( |
| ErrorCodes.REQUEST_INVALID, |
| 'Session tower mode must be a boolean', |
| ); |
| } |
| if (base !== undefined && typeof base !== 'string') { |
| throw new KimiError( |
| ErrorCodes.REQUEST_INVALID, |
| 'Session tower mode base must be a string', |
| ); |
| } |
| await this.rpc.setTowerMode({ sessionId: this.id, enabled, base }); |
| } |
|
|
| async getPlan(): Promise<SessionPlan> { |
| this.ensureOpen(); |
| return this.rpc.getPlan({ sessionId: this.id }); |
| } |
|
|
| async clearPlan(): Promise<void> { |
| this.ensureOpen(); |
| await this.rpc.clearPlan({ sessionId: this.id }); |
| } |
|
|
| async compact(options: CompactOptions = {}): Promise<void> { |
| this.ensureOpen(); |
| const instruction = normalizeOptionalString(options.instruction); |
| await this.rpc.compact({ |
| sessionId: this.id, |
| ...(instruction !== undefined ? { instruction } : {}), |
| }); |
| } |
|
|
| async cancelCompaction(): Promise<void> { |
| this.ensureOpen(); |
| await this.rpc.cancelCompaction({ sessionId: this.id }); |
| } |
|
|
| async undoHistory(count: number = 1): Promise<void> { |
| this.ensureOpen(); |
| await this.rpc.undoHistory({ sessionId: this.id, count }); |
| } |
|
|
| async getTodos(): Promise<readonly SessionTodoItem[]> { |
| this.ensureOpen(); |
| return this.rpc.getTodos({ sessionId: this.id }); |
| } |
|
|
| |
| async clearContext(): Promise<void> { |
| this.ensureOpen(); |
| await this.rpc.clearContext({ sessionId: this.id }); |
| } |
|
|
| |
| async importContext(content: string, source: string): Promise<void> { |
| this.ensureOpen(); |
| await this.rpc.importContext({ sessionId: this.id, content, source }); |
| } |
|
|
| async getContext(): Promise<AgentContextData> { |
| this.ensureOpen(); |
| return this.rpc.getContext({ sessionId: this.id }); |
| } |
|
|
| async getUsage(): Promise<SessionUsage> { |
| this.ensureOpen(); |
| return this.rpc.getUsage({ sessionId: this.id }); |
| } |
|
|
| async getStatus(): Promise<SessionStatus> { |
| this.ensureOpen(); |
| return this.rpc.getStatus({ sessionId: this.id }); |
| } |
|
|
| async listSkills(): Promise<readonly SkillSummary[]> { |
| this.ensureOpen(); |
| return this.rpc.listSkills({ sessionId: this.id }); |
| } |
|
|
| async listPluginCommands(): Promise<readonly PluginCommandDef[]> { |
| this.ensureOpen(); |
| return this.rpc.listPluginCommands({ sessionId: this.id }); |
| } |
|
|
| |
| |
| |
| |
| async listCommands(): Promise<readonly AgentCommandInfo[]> { |
| this.ensureOpen(); |
| return this.rpc.listCommands({ sessionId: this.id }); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| async listBackgroundTasks( |
| options: { activeOnly?: boolean; limit?: number } = {}, |
| ): Promise<readonly BackgroundTaskInfo[]> { |
| this.ensureOpen(); |
| return this.rpc.listBackgroundTasks({ |
| sessionId: this.id, |
| activeOnly: options.activeOnly, |
| limit: options.limit, |
| }); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| async getBackgroundTaskOutput( |
| taskId: string, |
| options: { tail?: number } = {}, |
| ): Promise<string> { |
| this.ensureOpen(); |
| const trimmedTaskId = normalizeRequiredString( |
| taskId, |
| 'Task id cannot be empty', |
| ErrorCodes.BACKGROUND_TASK_ID_EMPTY, |
| ); |
| return this.rpc.getBackgroundTaskOutput({ |
| sessionId: this.id, |
| taskId: trimmedTaskId, |
| tail: options.tail, |
| }); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| async stopBackgroundTask( |
| taskId: string, |
| options: { reason?: string } = {}, |
| ): Promise<void> { |
| this.ensureOpen(); |
| const trimmedTaskId = normalizeRequiredString( |
| taskId, |
| 'Task id cannot be empty', |
| ErrorCodes.BACKGROUND_TASK_ID_EMPTY, |
| ); |
| await this.rpc.stopBackgroundTask({ |
| sessionId: this.id, |
| taskId: trimmedTaskId, |
| reason: options.reason, |
| }); |
| } |
|
|
| |
| |
| |
| |
| async detachBackgroundTask(taskId: string): Promise<BackgroundTaskInfo | undefined> { |
| this.ensureOpen(); |
| const trimmedTaskId = normalizeRequiredString( |
| taskId, |
| 'Task id cannot be empty', |
| ErrorCodes.BACKGROUND_TASK_ID_EMPTY, |
| ); |
| return this.rpc.detachBackgroundTask({ |
| sessionId: this.id, |
| taskId: trimmedTaskId, |
| }); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| async waitForBackgroundTasksOnPrint(): Promise<void> { |
| this.ensureOpen(); |
| await this.rpc.waitForBackgroundTasksOnPrint({ sessionId: this.id }); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| async handlePrintMainTurnCompleted(): Promise<'finish' | 'continue'> { |
| this.ensureOpen(); |
| return this.rpc.handlePrintMainTurnCompleted({ sessionId: this.id }); |
| } |
|
|
| |
| |
| |
| |
| |
|
|
| async createGoal(input: CreateGoalInput): Promise<GoalSnapshot> { |
| this.ensureOpen(); |
| return this.rpc.createGoal({ sessionId: this.id, ...input }); |
| } |
|
|
| async getGoal(): Promise<GoalToolResult> { |
| this.ensureOpen(); |
| return this.rpc.getGoal({ sessionId: this.id }); |
| } |
|
|
| async pauseGoal(): Promise<GoalSnapshot> { |
| this.ensureOpen(); |
| return this.rpc.pauseGoal({ sessionId: this.id }); |
| } |
|
|
| async resumeGoal(): Promise<GoalSnapshot> { |
| this.ensureOpen(); |
| return this.rpc.resumeGoal({ sessionId: this.id }); |
| } |
|
|
| async cancelGoal(): Promise<GoalSnapshot> { |
| this.ensureOpen(); |
| return this.rpc.cancelGoal({ sessionId: this.id }); |
| } |
|
|
| |
| |
| |
| |
| |
| async getCronTasks(): Promise<GetCronTasksResult> { |
| this.ensureOpen(); |
| return this.rpc.getCronTasks({ sessionId: this.id }); |
| } |
|
|
| async listMcpServers(): Promise<readonly McpServerInfo[]> { |
| this.ensureOpen(); |
| return this.rpc.listMcpServers({ sessionId: this.id }); |
| } |
|
|
| async getMcpStartupMetrics(): Promise<McpStartupMetrics> { |
| this.ensureOpen(); |
| return this.rpc.getMcpStartupMetrics({ sessionId: this.id }); |
| } |
|
|
| |
| |
| |
| |
| |
| async addMcpServer( |
| server: McpServerConfig, |
| options: { readonly persist?: boolean } = {}, |
| ): Promise<McpServerInfo> { |
| this.ensureOpen(); |
| return this.rpc.addSessionMcpServer({ |
| sessionId: this.id, |
| server, |
| persist: options.persist, |
| }); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| async reconnectMcpServer(name: string, config?: McpServerConfig): Promise<void> { |
| this.ensureOpen(); |
| await this.rpc.reconnectMcpServer({ sessionId: this.id, name, config }); |
| } |
|
|
| async listPlugins(): Promise<readonly PluginSummary[]> { |
| this.ensureOpen(); |
| return this.rpc.listPlugins(); |
| } |
|
|
| async installPlugin(source: string): Promise<PluginSummary> { |
| this.ensureOpen(); |
| return this.rpc.installPlugin(source); |
| } |
|
|
| async setPluginEnabled(id: string, enabled: boolean): Promise<void> { |
| this.ensureOpen(); |
| await this.rpc.setPluginEnabled(id, enabled); |
| } |
|
|
| |
| async listCapabilities(): Promise<readonly CapabilityStatus[]> { |
| this.ensureOpen(); |
| return capabilityRpc(this.rpc).listCapabilities(); |
| } |
|
|
| |
| async getCapability(id: string): Promise<CapabilityStatus> { |
| this.ensureOpen(); |
| return capabilityRpc(this.rpc).getCapability(id); |
| } |
|
|
| |
| |
| |
| |
| async installCapability(id: string): Promise<CapabilityStatus> { |
| this.ensureOpen(); |
| return capabilityRpc(this.rpc).installCapability(id); |
| } |
|
|
| async setPluginMcpServerEnabled( |
| id: string, |
| server: string, |
| enabled: boolean, |
| ): Promise<void> { |
| this.ensureOpen(); |
| await this.rpc.setPluginMcpServerEnabled(id, server, enabled); |
| } |
|
|
| async removePlugin(id: string): Promise<void> { |
| this.ensureOpen(); |
| await this.rpc.removePlugin(id); |
| } |
|
|
| async reloadPlugins(): Promise<ReloadSummary> { |
| this.ensureOpen(); |
| return this.rpc.reloadPlugins(); |
| } |
|
|
| async getPluginInfo(id: string): Promise<PluginInfo> { |
| this.ensureOpen(); |
| return this.rpc.getPluginInfo(id); |
| } |
|
|
| async activateSkill(name: string, args?: string | undefined): Promise<void> { |
| this.ensureOpen(); |
| const skillName = normalizeRequiredString( |
| name, |
| 'Skill name cannot be empty', |
| ErrorCodes.SKILL_NAME_EMPTY, |
| ); |
| const skillArgs = normalizeOptionalString(args); |
| await this.rpc.activateSkill({ |
| sessionId: this.id, |
| name: skillName, |
| ...(skillArgs !== undefined ? { args: skillArgs } : {}), |
| }); |
| } |
|
|
| async activatePluginCommand( |
| pluginId: string, |
| commandName: string, |
| args?: string | undefined, |
| ): Promise<void> { |
| this.ensureOpen(); |
| const normalizedPluginId = pluginId.trim(); |
| const normalizedCommandName = commandName.trim(); |
| if (normalizedPluginId.length === 0 || normalizedCommandName.length === 0) { |
| throw new KimiError( |
| ErrorCodes.REQUEST_INVALID, |
| 'Plugin id and command name cannot be empty', |
| ); |
| } |
| const commandArgs = normalizeOptionalString(args); |
| await this.rpc.activatePluginCommand({ |
| sessionId: this.id, |
| pluginId: normalizedPluginId, |
| commandName: normalizedCommandName, |
| ...(commandArgs !== undefined ? { args: commandArgs } : {}), |
| }); |
| } |
|
|
| |
| |
| |
| |
| |
| async runCommand(name: string, args?: string): Promise<void> { |
| this.ensureOpen(); |
| const commandName = name.trim(); |
| if (commandName.length === 0) { |
| throw new KimiError(ErrorCodes.REQUEST_INVALID, 'Command name cannot be empty'); |
| } |
| await this.rpc.runCommand({ |
| sessionId: this.id, |
| name: commandName, |
| args: normalizeOptionalString(args), |
| }); |
| } |
|
|
| async close(): Promise<void> { |
| if (this.closed) return; |
| this.closed = true; |
| try { |
| await this.rpc.closeSession({ sessionId: this.id }); |
| } finally { |
| this.rpc.clearSessionHandlers(this.id); |
| await this.onClose?.(); |
| } |
| } |
|
|
| |
| emitMetaUpdated(patch: { readonly title?: string; readonly isCustomTitle?: boolean }): void { |
| this.emit({ |
| type: 'session.meta.updated', |
| sessionId: this.id, |
| agentId: MAIN_AGENT_ID, |
| title: patch.title, |
| patch, |
| }); |
| } |
|
|
| private emit(event: Event): void { |
| this.rpc.receiveEvent(event); |
| } |
|
|
| private ensureOpen(): void { |
| if (this.closed) { |
| throw new KimiError(ErrorCodes.SESSION_CLOSED, 'Session is closed'); |
| } |
| } |
|
|
| private requireSummary(): SessionSummary { |
| if (this.summary === undefined) { |
| throw new KimiError(ErrorCodes.SESSION_STATE_INVALID, 'Session summary is unavailable'); |
| } |
| return this.summary; |
| } |
| } |
|
|
| function normalizePromptInput(input: string | PromptInput): PromptInput { |
| if (typeof input === 'string') { |
| if (input.trim().length === 0) { |
| throw new KimiError(ErrorCodes.REQUEST_PROMPT_INPUT_EMPTY, 'Prompt input cannot be empty'); |
| } |
| return [{ type: 'text', text: input }]; |
| } |
|
|
| if (input.length === 0) { |
| throw new KimiError(ErrorCodes.REQUEST_PROMPT_INPUT_EMPTY, 'Prompt input cannot be empty'); |
| } |
|
|
| for (const part of input) { |
| switch (part.type) { |
| case 'text': |
| if (part.text.trim().length === 0) { |
| throw new KimiError( |
| ErrorCodes.REQUEST_PROMPT_INPUT_EMPTY, |
| 'Prompt input cannot contain empty text parts', |
| ); |
| } |
| break; |
| case 'image_url': |
| if (part.imageUrl.url.trim().length === 0) { |
| throw new KimiError( |
| ErrorCodes.REQUEST_PROMPT_INPUT_EMPTY, |
| 'Prompt input cannot contain empty image URLs', |
| ); |
| } |
| break; |
| case 'video_url': |
| if (part.videoUrl.url.trim().length === 0) { |
| throw new KimiError( |
| ErrorCodes.REQUEST_PROMPT_INPUT_EMPTY, |
| 'Prompt input cannot contain empty video URLs', |
| ); |
| } |
| break; |
| } |
| } |
| return input; |
| } |
|
|
| function normalizeRequiredString( |
| value: string, |
| message: string, |
| code: KimiErrorCode, |
| ): string { |
| const normalized = value.trim(); |
| if (normalized.length === 0) { |
| throw new KimiError(code, message); |
| } |
| return normalized; |
| } |
|
|
| function normalizeOptionalString(value: string | undefined): string | undefined { |
| if (value === undefined) return undefined; |
| const normalized = value.trim(); |
| return normalized.length > 0 ? normalized : undefined; |
| } |
|
|
| function isPermissionMode(value: unknown): value is PermissionMode { |
| return value === 'yolo' || value === 'manual' || value === 'auto'; |
| } |
|
|
| function resumeStateFromSummary( |
| summary: SessionSummary | undefined, |
| ): ResumedSessionState | undefined { |
| if (!hasResumeState(summary)) return undefined; |
| return { |
| sessionMetadata: summary.sessionMetadata, |
| agents: summary.agents, |
| warning: summary.warning, |
| }; |
| } |
|
|
| function hasResumeState( |
| summary: SessionSummary | undefined, |
| ): summary is SessionSummary & ResumedSessionState { |
| return ( |
| summary !== undefined && |
| typeof (summary as { readonly sessionMetadata?: unknown }).sessionMetadata === 'object' && |
| (summary as { readonly sessionMetadata?: unknown }).sessionMetadata !== null && |
| typeof (summary as { readonly agents?: unknown }).agents === 'object' && |
| (summary as { readonly agents?: unknown }).agents !== null |
| ); |
| } |
|
|