import { z } from 'zod' import { AttachmentMetadataSchema, CodexCollaborationModeSchema, DecryptedMessageSchema, MachineSchema, PermissionModeSchema, SessionSchema } from './schemas' import { AgentFlavorSchema } from './modes' import type { DecryptedMessage, Machine, Session } from './schemas' import type { SessionSummary } from './sessionSummary' export const CreateOrLoadMachineRequestSchema = z.object({ id: z.string().min(1), metadata: z.unknown(), runnerState: z.unknown().nullable().optional() }) export type CreateOrLoadMachineRequest = z.infer export const CreateOrLoadSessionRequestSchema = z.object({ id: z.string().uuid().optional(), tag: z.string().min(1), metadata: z.unknown(), agentState: z.unknown().nullable().optional(), model: z.string().optional(), modelReasoningEffort: z.string().optional(), effort: z.string().optional(), machine: CreateOrLoadMachineRequestSchema.optional() }) export type CreateOrLoadSessionRequest = z.infer export const CliMessagesResponseSchema = z.object({ messages: z.array(z.object({ id: z.string(), seq: z.number(), createdAt: z.number(), localId: z.string().nullable().optional(), content: z.unknown() })) }) export type CliMessagesResponse = z.infer export const CreateSessionResponseSchema = z.object({ session: SessionSchema }) export type CreateSessionResponse = z.infer export const CreateMachineResponseSchema = z.object({ machine: MachineSchema }) export type CreateMachineResponse = z.infer export const GetSessionResponseSchema = CreateSessionResponseSchema export type GetSessionResponse = CreateSessionResponse export type AuthResponse = { token: string user: { id: number username?: string firstName?: string lastName?: string } } export type SessionsResponse = { sessions: SessionSummary[] } export type SessionResponse = { session: Session } export type MessagesResponse = { messages: DecryptedMessage[] page: { limit: number nextBeforeSeq: number | null nextBeforeAt: number | null hasMore: boolean } } export type MachinesResponse = { machines: Machine[] } export type SpawnResponse = | { type: 'success'; sessionId: string } | { type: 'error'; message: string } export const SessionPermissionModeRequestSchema = z.object({ mode: PermissionModeSchema }) export type SessionPermissionModeRequest = z.infer export const ResumeSessionRequestSchema = z.object({ permissionMode: PermissionModeSchema.optional() }) export type ResumeSessionRequest = z.infer export const ReopenSessionResponseSchema = z.object({ ok: z.literal(true), sessionId: z.string(), resumed: z.boolean(), cursorSessionProtocol: z.enum(['acp', 'stream-json']).optional() }) export type ReopenSessionResponse = z.infer export const ReopenSessionMissingMetadataResponseSchema = z.object({ error: z.string(), missing: z.array(z.string()).nonempty() }) export type ReopenSessionMissingMetadataResponse = z.infer export const CursorChatStoreStatusSchema = z.object({ onDisk: z.boolean(), store: z.enum(['legacy', 'acp']).nullable() }) export type CursorChatStoreStatus = z.infer export const CodexImportedMessageSchema = z.union([ z.object({ role: z.literal('user'), content: z.object({ type: z.literal('text'), text: z.string() }), meta: z.object({ sentFrom: z.literal('cli') }) }), z.object({ role: z.literal('agent'), content: z.object({ type: z.literal('codex'), data: z.unknown() }), meta: z.object({ sentFrom: z.literal('cli') }) }) ]) export const CodexLocalSessionSummarySchema = z.object({ id: z.string().min(1), title: z.string(), lastUserMessage: z.string().nullable().optional(), cwd: z.string().nullable().optional(), file: z.string().min(1), modifiedAt: z.number(), originator: z.string().nullable().optional(), cliVersion: z.string().nullable().optional(), source: z.string().nullable().optional(), threadSource: z.string().nullable().optional(), forkedFromId: z.string().nullable().optional() }) export const CodexLocalSessionWithMessagesSchema = CodexLocalSessionSummarySchema.extend({ messages: z.array(CodexImportedMessageSchema) }) export const ListCodexSessionsRpcRequestSchema = z.object({ cwd: z.string().nullable().optional(), sessionIds: z.array(z.string().min(1)).optional() }) export const ListCodexSessionsRpcResponseSchema = z.union([ z.object({ success: z.literal(true), sessions: z.array(z.union([CodexLocalSessionWithMessagesSchema, CodexLocalSessionSummarySchema])) }), z.object({ success: z.literal(false), error: z.string() }) ]) export const ArchiveCodexSessionRpcRequestSchema = z.object({ sessionId: z.string().min(1) }) export const ArchiveCodexSessionRpcResponseSchema = z.union([ z.object({ success: z.literal(true), archivedPath: z.string() }), z.object({ success: z.literal(false), error: z.string() }) ]) export type ListCodexSessionsRpcRequest = z.infer export type ListCodexSessionsRpcResponse = z.infer export type ArchiveCodexSessionRpcRequest = z.infer export type ArchiveCodexSessionRpcResponse = z.infer export const SessionCollaborationModeRequestSchema = z.object({ mode: CodexCollaborationModeSchema }) export type SessionCollaborationModeRequest = z.infer export const SessionModelRequestSchema = z.object({ model: z.union([ z.string().trim().min(1), z.object({ provider: z.string().trim().min(1), modelId: z.string().trim().min(1), }), ]).nullable() }) export type SessionModelRequest = z.infer export const SessionModelReasoningEffortRequestSchema = z.object({ modelReasoningEffort: z.string().trim().min(1).nullable() }) export type SessionModelReasoningEffortRequest = z.infer export const SessionEffortRequestSchema = z.object({ effort: z.string().trim().min(1).nullable() }) export type SessionEffortRequest = z.infer // Fast mode is an explicit two-way choice. `'standard'` (not `null`) is the // stored sentinel for an explicit Fast-off so it stays distinct from // "untouched" and survives restart/resume. Reject anything else so stray tier // strings are never forwarded to the Codex app-server. export const SessionServiceTierRequestSchema = z.object({ serviceTier: z.enum(['fast', 'standard']) }) export type SessionServiceTierRequest = z.infer export const RenameSessionRequestSchema = z.object({ name: z.string().min(1).max(255) }) export type RenameSessionRequest = z.infer /** Per-session legacy stream-json → ACP migrator request. See tiann/hapi#824. */ export const CursorMigrateToAcpRequestSchema = z.object({ /** Skip removing the legacy ~/.cursor/chats source store.db even after verify passes. */ keepSource: z.boolean().optional(), /** Allow migrating a session whose lifecycleState === 'running' by archiving it first. */ forceArchiveRunning: z.boolean().optional(), /** Skip the verify-by-prompt step (session/load alone is run). */ skipVerify: z.boolean().optional() }) export type CursorMigrateToAcpRequest = z.infer export type CursorMigrateOutcome = | { ok: true; sessionId: string; acpSessionId: string; replayNotifications: number; durationMs: number; lastUsedModelPreserved: string | null; sourceRemoved: boolean } | { ok: false; sessionId: string; reason: CursorMigrateRefusalReason; message: string; durationMs: number } export type CursorMigrateRefusalReason = | 'not_cursor_session' | 'already_acp' | 'running_refused' | 'no_cursor_session_id' | 'no_legacy_store_on_disk' | 'target_already_exists' | 'verify_load_failed' | 'verify_prompt_failed' | 'metadata_write_failed' | 'archive_failed' | 'lock_release_timeout' | 'acp_transport_active' | 'session_resumed_during_migrate' | 'legacy_store_modified_during_migrate' | 'cross_host_session' | 'ambiguous_legacy_store' | 'size_mismatch' | 'internal_error' export const UploadFileRequestSchema = z.object({ filename: z.string().min(1).max(255), content: z.string().min(1), mimeType: z.string().min(1).max(255) }) export type UploadFileRequest = z.infer export const DeleteUploadRequestSchema = z.object({ path: z.string().min(1) }) export type DeleteUploadRequest = z.infer export const MessagesQuerySchema = z.object({ limit: z.coerce.number().int().min(1).max(200).optional(), beforeSeq: z.coerce.number().int().min(1).optional(), beforeAt: z.coerce.number().int().min(0).optional(), }).refine((data) => (data.beforeAt === undefined) === (data.beforeSeq === undefined), { message: 'beforeAt and beforeSeq must be provided together', path: ['beforeAt'], }) export type MessagesQuery = z.infer export const SendMessageRequestSchema = z.object({ text: z.string(), localId: z.string().min(1).optional(), attachments: z.array(AttachmentMetadataSchema).optional(), scheduledAt: z.number().int().positive().nullable().optional() }).refine( (data) => data.scheduledAt == null || typeof data.localId === 'string', { message: 'scheduledAt requires localId', path: ['localId'] } ).refine( (data) => data.scheduledAt == null || data.scheduledAt <= Date.now() + 7 * 24 * 60 * 60 * 1000, { message: 'scheduledAt must be within 7 days from now', path: ['scheduledAt'] } ).refine( (data) => data.scheduledAt == null || !data.attachments?.length, { message: 'scheduled messages with attachments are not supported', path: ['attachments'] } ) export type SendMessageRequest = z.infer export const QueuedStateRequestSchema = z.object({ localIds: z.array(z.string().min(1)).max(1000) }) export type QueuedStateRequest = z.infer export type QueuedStateResponse = { queuedLocalIds: string[] invokedLocalMessages: Array<{ localId: string invokedAt: number }> } export const SpawnSessionRequestSchema = z.object({ directory: z.string().min(1), agent: AgentFlavorSchema.optional(), model: z.string().optional(), effort: z.string().optional(), modelReasoningEffort: z.string().optional(), yolo: z.boolean().optional(), permissionMode: PermissionModeSchema.optional(), sessionType: z.enum(['simple', 'worktree']).optional(), worktreeName: z.string().optional() }) export type SpawnSessionRequest = z.infer export const MachineListDirectoryRequestSchema = z.object({ path: z.string().min(1) }) export type MachineListDirectoryRequest = z.infer export const MachinePathsExistsRequestSchema = z.object({ paths: z.array(z.string().min(1)).max(1000) }) export type MachinePathsExistsRequest = z.infer export const AuthRequestSchema = z.union([ z.object({ initData: z.string() }), z.object({ accessToken: z.string() }) ]) export type AuthRequest = z.infer export type CommandResponse = { success: boolean stdout?: string stderr?: string exitCode?: number error?: string } export type GitCommandResponse = CommandResponse export type FileReadResponse = { success: boolean content?: string error?: string } export type GeneratedImageResponse = { success: boolean content?: string mimeType?: string fileName?: string error?: string } export type UploadFileResponse = { success: boolean path?: string error?: string } export type DeleteUploadResponse = { success: boolean error?: string } export type DirectoryEntry = { name: string type: 'file' | 'directory' | 'other' size?: number modified?: number } export type ListDirectoryResponse = { success: boolean entries?: DirectoryEntry[] error?: string } export type RpcListDirectoryResponse = ListDirectoryResponse export type FileMetadataEntry = { path: string size?: number modified?: number } export type StatFilesResponse = { success: boolean entries?: FileMetadataEntry[] error?: string } export type MachineDirectoryEntry = DirectoryEntry & { isGitRepo?: boolean } export type MachineListDirectoryResponse = { success: boolean entries?: MachineDirectoryEntry[] error?: string } export type PathExistsResponse = { exists: Record } export type MachinePathsExistsResponse = PathExistsResponse export type CodexModelSummary = { id: string displayName: string isDefault: boolean defaultReasoningEffort?: string | null supportedReasoningEfforts?: string[] /** Service tier ids advertised for this model in the current auth/plan context (e.g. 'fast'). */ serviceTiers?: string[] } export type CodexModelsResponse = { success: boolean models?: CodexModelSummary[] error?: string } export type ListCodexModelsResponse = CodexModelsResponse export type OpencodeModelSummary = { modelId: string name?: string } export type OpencodeModelsResponse = { success: boolean availableModels?: OpencodeModelSummary[] /** CLI `agent --list-models` skus grouped under ACP wire bases for variant pickers. */ cliModelSkus?: OpencodeModelSummary[] currentModelId?: string | null error?: string } export type ListOpencodeModelsResponse = OpencodeModelsResponse export type GrokModelSummary = { modelId: string name?: string reasoningEfforts?: GrokReasoningEffortOption[] } export type GrokReasoningEffortOption = { value: string name?: string isDefault?: boolean } export type GrokModelsResponse = { success: boolean availableModels?: GrokModelSummary[] currentModelId?: string | null autoPermissionModeSupported?: boolean error?: string } export type ListGrokModelsResponse = GrokModelsResponse export type GrokReasoningEffortResponse = { success: boolean options?: GrokReasoningEffortOption[] currentValue?: string | null error?: string } export type OpencodeReasoningEffortOption = { value: string name?: string } export type OpencodeReasoningEffortResponse = { success: boolean options?: OpencodeReasoningEffortOption[] currentValue?: string | null error?: string } export type CursorModelSummary = OpencodeModelSummary export type CursorModelsResponse = OpencodeModelsResponse export type ListCursorModelsResponse = CursorModelsResponse /** Maps thinking levels to provider-specific values. null = unsupported. */ export type PiThinkingLevelMap = Partial> export type PiModelSummary = { provider: string modelId: string name?: string contextWindow?: number /** Whether the model supports reasoning/thinking */ reasoning?: boolean /** Maps Pi thinking levels to provider values; null = unsupported level */ thinkingLevelMap?: PiThinkingLevelMap } export type PiModelsResponse = { success: boolean availableModels?: PiModelSummary[] currentModelId?: string | null error?: string } export type ListPiModelsResponse = PiModelsResponse export type PiCommandSummary = { name: string description?: string source: 'extension' | 'prompt' | 'skill' } export type PiCommandsResponse = { success: boolean commands?: PiCommandSummary[] error?: string } export type SlashCommand = { name: string description?: string source: 'builtin' | 'user' | 'plugin' | 'project' content?: string pluginName?: string } export type SlashCommandsResponse = { success: boolean commands?: SlashCommand[] error?: string }