Files
hapi/shared/src/apiTypes.ts
T

559 lines
16 KiB
TypeScript

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<typeof CreateOrLoadMachineRequestSchema>
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<typeof CreateOrLoadSessionRequestSchema>
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<typeof CliMessagesResponseSchema>
export const CreateSessionResponseSchema = z.object({
session: SessionSchema
})
export type CreateSessionResponse = z.infer<typeof CreateSessionResponseSchema>
export const CreateMachineResponseSchema = z.object({
machine: MachineSchema
})
export type CreateMachineResponse = z.infer<typeof CreateMachineResponseSchema>
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<typeof SessionPermissionModeRequestSchema>
export const ResumeSessionRequestSchema = z.object({
permissionMode: PermissionModeSchema.optional()
})
export type ResumeSessionRequest = z.infer<typeof ResumeSessionRequestSchema>
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<typeof ReopenSessionResponseSchema>
export const ReopenSessionMissingMetadataResponseSchema = z.object({
error: z.string(),
missing: z.array(z.string()).nonempty()
})
export type ReopenSessionMissingMetadataResponse = z.infer<typeof ReopenSessionMissingMetadataResponseSchema>
export const CursorChatStoreStatusSchema = z.object({
onDisk: z.boolean(),
store: z.enum(['legacy', 'acp']).nullable()
})
export type CursorChatStoreStatus = z.infer<typeof CursorChatStoreStatusSchema>
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<typeof ListCodexSessionsRpcRequestSchema>
export type ListCodexSessionsRpcResponse = z.infer<typeof ListCodexSessionsRpcResponseSchema>
export type ArchiveCodexSessionRpcRequest = z.infer<typeof ArchiveCodexSessionRpcRequestSchema>
export type ArchiveCodexSessionRpcResponse = z.infer<typeof ArchiveCodexSessionRpcResponseSchema>
export const SessionCollaborationModeRequestSchema = z.object({
mode: CodexCollaborationModeSchema
})
export type SessionCollaborationModeRequest = z.infer<typeof SessionCollaborationModeRequestSchema>
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<typeof SessionModelRequestSchema>
export const SessionModelReasoningEffortRequestSchema = z.object({
modelReasoningEffort: z.string().trim().min(1).nullable()
})
export type SessionModelReasoningEffortRequest = z.infer<typeof SessionModelReasoningEffortRequestSchema>
export const SessionEffortRequestSchema = z.object({
effort: z.string().trim().min(1).nullable()
})
export type SessionEffortRequest = z.infer<typeof SessionEffortRequestSchema>
// 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<typeof SessionServiceTierRequestSchema>
export const RenameSessionRequestSchema = z.object({
name: z.string().min(1).max(255)
})
export type RenameSessionRequest = z.infer<typeof RenameSessionRequestSchema>
/** 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<typeof CursorMigrateToAcpRequestSchema>
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<typeof UploadFileRequestSchema>
export const DeleteUploadRequestSchema = z.object({
path: z.string().min(1)
})
export type DeleteUploadRequest = z.infer<typeof DeleteUploadRequestSchema>
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<typeof MessagesQuerySchema>
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<typeof SendMessageRequestSchema>
export const QueuedStateRequestSchema = z.object({
localIds: z.array(z.string().min(1)).max(1000)
})
export type QueuedStateRequest = z.infer<typeof QueuedStateRequestSchema>
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<typeof SpawnSessionRequestSchema>
export const MachineListDirectoryRequestSchema = z.object({
path: z.string().min(1)
})
export type MachineListDirectoryRequest = z.infer<typeof MachineListDirectoryRequestSchema>
export const MachinePathsExistsRequestSchema = z.object({
paths: z.array(z.string().min(1)).max(1000)
})
export type MachinePathsExistsRequest = z.infer<typeof MachinePathsExistsRequestSchema>
export const AuthRequestSchema = z.union([
z.object({ initData: z.string() }),
z.object({ accessToken: z.string() })
])
export type AuthRequest = z.infer<typeof AuthRequestSchema>
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<string, boolean>
}
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<Record<string, string | null>>
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
}