Unify protocol types and modes

This commit is contained in:
weishu
2026-01-03 14:43:59 +08:00
parent bfca058967
commit e2028b6914
20 changed files with 360 additions and 393 deletions
+2
View File
@@ -0,0 +1,2 @@
export * from './modes'
export type * from './types'
+62
View File
@@ -0,0 +1,62 @@
export const CLAUDE_PERMISSION_MODES = ['default', 'acceptEdits', 'bypassPermissions', 'plan'] as const
export type ClaudePermissionMode = typeof CLAUDE_PERMISSION_MODES[number]
export const CODEX_PERMISSION_MODES = ['default', 'read-only', 'safe-yolo', 'yolo'] as const
export type CodexPermissionMode = typeof CODEX_PERMISSION_MODES[number]
export const PERMISSION_MODES = [
'default',
'acceptEdits',
'bypassPermissions',
'plan',
'read-only',
'safe-yolo',
'yolo'
] as const
export type PermissionMode = typeof PERMISSION_MODES[number]
export const MODEL_MODES = ['default', 'sonnet', 'opus'] as const
export type ModelMode = typeof MODEL_MODES[number]
export type AgentFlavor = 'claude' | 'codex' | 'gemini'
export const PERMISSION_MODE_LABELS: Record<PermissionMode, string> = {
default: 'Default',
acceptEdits: 'Accept Edits',
plan: 'Plan Mode',
bypassPermissions: 'Yolo',
'read-only': 'Read Only',
'safe-yolo': 'Safe Yolo',
yolo: 'Yolo'
}
export const MODEL_MODE_LABELS: Record<ModelMode, string> = {
default: 'Default',
sonnet: 'Sonnet',
opus: 'Opus'
}
export function getPermissionModesForFlavor(flavor?: string | null): readonly PermissionMode[] {
if (flavor === 'codex') {
return CODEX_PERMISSION_MODES
}
if (flavor === 'gemini') {
return []
}
return CLAUDE_PERMISSION_MODES
}
export function isPermissionModeAllowedForFlavor(mode: PermissionMode, flavor?: string | null): boolean {
return getPermissionModesForFlavor(flavor).includes(mode)
}
export function getModelModesForFlavor(flavor?: string | null): readonly ModelMode[] {
if (flavor === 'codex' || flavor === 'gemini') {
return []
}
return MODEL_MODES
}
export function isModelModeAllowedForFlavor(mode: ModelMode, flavor?: string | null): boolean {
return getModelModesForFlavor(flavor).includes(mode)
}
+165
View File
@@ -0,0 +1,165 @@
import { z } from 'zod'
import { MODEL_MODES, PERMISSION_MODES } from './modes'
export const PermissionModeSchema = z.enum(PERMISSION_MODES)
export const ModelModeSchema = z.enum(MODEL_MODES)
const MetadataSummarySchema = z.object({
text: z.string(),
updatedAt: z.number()
})
export const WorktreeMetadataSchema = z.object({
basePath: z.string(),
branch: z.string(),
name: z.string(),
worktreePath: z.string().optional(),
createdAt: z.number().optional()
}).passthrough()
export type WorktreeMetadata = z.infer<typeof WorktreeMetadataSchema>
export const MetadataSchema = z.object({
path: z.string(),
host: z.string(),
version: z.string().optional(),
name: z.string().optional(),
os: z.string().optional(),
summary: MetadataSummarySchema.optional(),
machineId: z.string().optional(),
claudeSessionId: z.string().optional(),
codexSessionId: z.string().optional(),
tools: z.array(z.string()).optional(),
slashCommands: z.array(z.string()).optional(),
homeDir: z.string().optional(),
happyHomeDir: z.string().optional(),
happyLibDir: z.string().optional(),
happyToolsDir: z.string().optional(),
startedFromDaemon: z.boolean().optional(),
hostPid: z.number().optional(),
startedBy: z.enum(['daemon', 'terminal']).optional(),
lifecycleState: z.string().optional(),
lifecycleStateSince: z.number().optional(),
archivedBy: z.string().optional(),
archiveReason: z.string().optional(),
flavor: z.string().nullish(),
worktree: WorktreeMetadataSchema.optional()
}).passthrough()
export type Metadata = z.infer<typeof MetadataSchema>
export const AgentStateRequestSchema = z.object({
tool: z.string(),
arguments: z.unknown(),
createdAt: z.number().nullish()
}).passthrough()
export type AgentStateRequest = z.infer<typeof AgentStateRequestSchema>
export const AgentStateCompletedRequestSchema = z.object({
tool: z.string(),
arguments: z.unknown(),
createdAt: z.number().nullish(),
completedAt: z.number().nullish(),
status: z.enum(['canceled', 'denied', 'approved']),
reason: z.string().optional(),
mode: z.string().optional(),
decision: z.enum(['approved', 'approved_for_session', 'denied', 'abort']).optional(),
allowTools: z.array(z.string()).optional(),
answers: z.record(z.string(), z.array(z.string())).optional()
}).passthrough()
export type AgentStateCompletedRequest = z.infer<typeof AgentStateCompletedRequestSchema>
export const AgentStateSchema = z.object({
controlledByUser: z.boolean().nullish(),
requests: z.record(z.string(), AgentStateRequestSchema).nullish(),
completedRequests: z.record(z.string(), AgentStateCompletedRequestSchema).nullish()
}).passthrough()
export type AgentState = z.infer<typeof AgentStateSchema>
export const TodoItemSchema = z.object({
content: z.string(),
status: z.enum(['pending', 'in_progress', 'completed']),
priority: z.enum(['high', 'medium', 'low']),
id: z.string()
}).passthrough()
export type TodoItem = z.infer<typeof TodoItemSchema>
export const TodosSchema = z.array(TodoItemSchema)
export const DecryptedMessageSchema = z.object({
id: z.string(),
seq: z.number().nullable(),
localId: z.string().nullable(),
content: z.unknown(),
createdAt: z.number()
}).passthrough()
export type DecryptedMessage = z.infer<typeof DecryptedMessageSchema>
export const SessionSchema = z.object({
id: z.string(),
namespace: z.string(),
seq: z.number(),
createdAt: z.number(),
updatedAt: z.number(),
active: z.boolean(),
activeAt: z.number(),
metadata: MetadataSchema.nullable(),
metadataVersion: z.number(),
agentState: AgentStateSchema.nullable(),
agentStateVersion: z.number(),
thinking: z.boolean(),
thinkingAt: z.number(),
todos: TodosSchema.optional(),
permissionMode: PermissionModeSchema.optional(),
modelMode: ModelModeSchema.optional()
}).passthrough()
export type Session = z.infer<typeof SessionSchema>
const SessionEventBaseSchema = z.object({
namespace: z.string().optional()
})
const SessionChangedSchema = SessionEventBaseSchema.extend({
sessionId: z.string()
})
const MachineChangedSchema = SessionEventBaseSchema.extend({
machineId: z.string()
})
export const SyncEventSchema = z.discriminatedUnion('type', [
SessionChangedSchema.extend({
type: z.literal('session-added'),
data: z.unknown().optional()
}),
SessionChangedSchema.extend({
type: z.literal('session-updated'),
data: z.unknown().optional()
}),
SessionEventBaseSchema.extend({
type: z.literal('session-removed'),
sessionId: z.string()
}),
SessionChangedSchema.extend({
type: z.literal('message-received'),
message: DecryptedMessageSchema
}),
MachineChangedSchema.extend({
type: z.literal('machine-updated'),
data: z.unknown().optional()
}),
SessionEventBaseSchema.extend({
type: z.literal('connection-changed'),
data: z.object({
status: z.string()
}).optional()
})
])
export type SyncEvent = z.infer<typeof SyncEventSchema>
+19
View File
@@ -0,0 +1,19 @@
export type {
AgentState,
AgentStateCompletedRequest,
AgentStateRequest,
DecryptedMessage,
Metadata,
Session,
SyncEvent,
TodoItem,
WorktreeMetadata
} from './schemas'
export type {
AgentFlavor,
ClaudePermissionMode,
CodexPermissionMode,
ModelMode,
PermissionMode
} from './modes'