Unify agent flavor definitions

This commit is contained in:
weishu
2026-05-21 10:23:57 +08:00
parent 15113668cc
commit 9698aa9f4c
11 changed files with 27 additions and 18 deletions
+3 -1
View File
@@ -1,10 +1,12 @@
import type { AgentFlavor } from '@hapi/protocol'
export interface SpawnSessionOptions { export interface SpawnSessionOptions {
machineId?: string machineId?: string
directory: string directory: string
sessionId?: string sessionId?: string
resumeSessionId?: string resumeSessionId?: string
approvedNewDirectoryCreation?: boolean approvedNewDirectoryCreation?: boolean
agent?: 'claude' | 'codex' | 'cursor' | 'gemini' | 'opencode' agent?: AgentFlavor
model?: string model?: string
effort?: string effort?: string
modelReasoningEffort?: string modelReasoningEffort?: string
+2 -2
View File
@@ -1,4 +1,4 @@
import type { CodexCollaborationMode, PermissionMode } from '@hapi/protocol/types' import type { AgentFlavor, CodexCollaborationMode, PermissionMode } from '@hapi/protocol/types'
import type { Server } from 'socket.io' import type { Server } from 'socket.io'
import type { RpcRegistry } from '../socket/rpcRegistry' import type { RpcRegistry } from '../socket/rpcRegistry'
@@ -150,7 +150,7 @@ export class RpcGateway {
async spawnSession( async spawnSession(
machineId: string, machineId: string,
directory: string, directory: string,
agent: 'claude' | 'codex' | 'cursor' | 'gemini' | 'opencode' = 'claude', agent: AgentFlavor = 'claude',
model?: string, model?: string,
modelReasoningEffort?: string, modelReasoningEffort?: string,
yolo?: boolean, yolo?: boolean,
+3 -5
View File
@@ -7,7 +7,7 @@
* - No E2E encryption; data is stored as JSON in SQLite * - No E2E encryption; data is stored as JSON in SQLite
*/ */
import type { LocalResumeTarget, ResumableSession } from '@hapi/protocol' import { isKnownFlavor, type LocalResumeTarget, type ResumableSession } from '@hapi/protocol'
import type { AgentFlavor, CodexCollaborationMode, DecryptedMessage, PermissionMode, Session, SyncEvent } from '@hapi/protocol/types' import type { AgentFlavor, CodexCollaborationMode, DecryptedMessage, PermissionMode, Session, SyncEvent } from '@hapi/protocol/types'
import { unwrapRoleWrappedRecordEnvelope } from '@hapi/protocol/messages' import { unwrapRoleWrappedRecordEnvelope } from '@hapi/protocol/messages'
import type { Server } from 'socket.io' import type { Server } from 'socket.io'
@@ -459,7 +459,7 @@ export class SyncEngine {
async spawnSession( async spawnSession(
machineId: string, machineId: string,
directory: string, directory: string,
agent: 'claude' | 'codex' | 'cursor' | 'gemini' | 'opencode' = 'claude', agent: AgentFlavor = 'claude',
model?: string, model?: string,
modelReasoningEffort?: string, modelReasoningEffort?: string,
yolo?: boolean, yolo?: boolean,
@@ -486,9 +486,7 @@ export class SyncEngine {
private resolveFlavor(session: Session): AgentFlavor { private resolveFlavor(session: Session): AgentFlavor {
const flavor = session.metadata?.flavor const flavor = session.metadata?.flavor
return flavor === 'codex' || flavor === 'gemini' || flavor === 'opencode' || flavor === 'cursor' return isKnownFlavor(flavor) ? flavor : 'claude'
? flavor
: 'claude'
} }
private resolveAgentResumeId(session: Session, namespace: string): string | null { private resolveAgentResumeId(session: Session, namespace: string): string | null {
+2 -1
View File
@@ -1,3 +1,4 @@
import { AgentFlavorSchema } from '@hapi/protocol'
import { Hono } from 'hono' import { Hono } from 'hono'
import { z } from 'zod' import { z } from 'zod'
import type { SyncEngine } from '../../sync/syncEngine' import type { SyncEngine } from '../../sync/syncEngine'
@@ -6,7 +7,7 @@ import { requireMachine } from './guards'
const spawnBodySchema = z.object({ const spawnBodySchema = z.object({
directory: z.string().min(1), directory: z.string().min(1),
agent: z.enum(['claude', 'codex', 'cursor', 'gemini', 'opencode']).optional(), agent: AgentFlavorSchema.optional(),
model: z.string().optional(), model: z.string().optional(),
effort: z.string().optional(), effort: z.string().optional(),
modelReasoningEffort: z.string().optional(), modelReasoningEffort: z.string().optional(),
+6 -2
View File
@@ -1,3 +1,5 @@
import { z } from 'zod'
/** /**
* @description The legacy payload type identifier used for all generic agent messages. * @description The legacy payload type identifier used for all generic agent messages.
* Changing this value will affect the communication schema between CLI, Hub, and Web. * Changing this value will affect the communication schema between CLI, Hub, and Web.
@@ -5,6 +7,10 @@
*/ */
export const AGENT_MESSAGE_PAYLOAD_TYPE = 'codex' as const export const AGENT_MESSAGE_PAYLOAD_TYPE = 'codex' as const
export const AGENT_FLAVORS = ['claude', 'codex', 'cursor', 'gemini', 'opencode'] as const
export type AgentFlavor = typeof AGENT_FLAVORS[number]
export const AgentFlavorSchema = z.enum(AGENT_FLAVORS)
export const CLAUDE_PERMISSION_MODES = ['default', 'acceptEdits', 'bypassPermissions', 'plan'] as const export const CLAUDE_PERMISSION_MODES = ['default', 'acceptEdits', 'bypassPermissions', 'plan'] as const
export type ClaudePermissionMode = typeof CLAUDE_PERMISSION_MODES[number] export type ClaudePermissionMode = typeof CLAUDE_PERMISSION_MODES[number]
@@ -35,8 +41,6 @@ export const PERMISSION_MODES = [
] as const ] as const
export type PermissionMode = typeof PERMISSION_MODES[number] export type PermissionMode = typeof PERMISSION_MODES[number]
export type AgentFlavor = 'claude' | 'codex' | 'gemini' | 'opencode' | 'cursor'
export const PERMISSION_MODE_LABELS: Record<PermissionMode, string> = { export const PERMISSION_MODE_LABELS: Record<PermissionMode, string> = {
default: 'Default', default: 'Default',
acceptEdits: 'Accept Edits', acceptEdits: 'Accept Edits',
+1 -2
View File
@@ -1,7 +1,6 @@
import { z } from 'zod' import { z } from 'zod'
import { CodexCollaborationModeSchema, PermissionModeSchema } from './schemas' import { CodexCollaborationModeSchema, PermissionModeSchema } from './schemas'
import { AgentFlavorSchema } from './modes'
export const AgentFlavorSchema = z.enum(['claude', 'codex', 'gemini', 'opencode', 'cursor'])
export const LocalResumeTargetSchema = z.object({ export const LocalResumeTargetSchema = z.object({
sessionId: z.string().min(1), sessionId: z.string().min(1),
+2 -1
View File
@@ -25,6 +25,7 @@ import type {
SessionResponse, SessionResponse,
SessionsResponse SessionsResponse
} from '@/types/api' } from '@/types/api'
import type { AgentFlavor } from '@hapi/protocol'
import type { CancelMessageResponse } from '@hapi/protocol/schemas' import type { CancelMessageResponse } from '@hapi/protocol/schemas'
type ApiClientOptions = { type ApiClientOptions = {
@@ -459,7 +460,7 @@ export class ApiClient {
async spawnSession( async spawnSession(
machineId: string, machineId: string,
directory: string, directory: string,
agent?: 'claude' | 'codex' | 'cursor' | 'gemini' | 'opencode', agent?: AgentFlavor,
model?: string, model?: string,
modelReasoningEffort?: string, modelReasoningEffort?: string,
yolo?: boolean, yolo?: boolean,
@@ -1,3 +1,4 @@
import { AGENT_FLAVORS } from '@hapi/protocol'
import type { AgentType } from './types' import type { AgentType } from './types'
import { useTranslation } from '@/lib/use-translation' import { useTranslation } from '@/lib/use-translation'
@@ -14,7 +15,7 @@ export function AgentSelector(props: {
{t('newSession.agent')} {t('newSession.agent')}
</label> </label>
<div className="flex flex-wrap gap-x-3 gap-y-2"> <div className="flex flex-wrap gap-x-3 gap-y-2">
{(['claude', 'codex', 'cursor', 'gemini', 'opencode'] as const).map((agentType) => ( {AGENT_FLAVORS.map((agentType) => (
<label <label
key={agentType} key={agentType}
className="flex items-center gap-1.5 cursor-pointer" className="flex items-center gap-1.5 cursor-pointer"
+2 -1
View File
@@ -1,9 +1,10 @@
import { AGENT_FLAVORS } from '@hapi/protocol'
import type { AgentType } from './types' import type { AgentType } from './types'
const AGENT_STORAGE_KEY = 'hapi:newSession:agent' const AGENT_STORAGE_KEY = 'hapi:newSession:agent'
const YOLO_STORAGE_KEY = 'hapi:newSession:yolo' const YOLO_STORAGE_KEY = 'hapi:newSession:yolo'
const VALID_AGENTS: AgentType[] = ['claude', 'codex', 'cursor', 'gemini', 'opencode'] const VALID_AGENTS = AGENT_FLAVORS
export function loadPreferredAgent(): AgentType { export function loadPreferredAgent(): AgentType {
try { try {
+2 -1
View File
@@ -1,6 +1,7 @@
import { GEMINI_MODEL_PRESETS, GEMINI_MODEL_LABELS } from '@hapi/protocol' import { GEMINI_MODEL_PRESETS, GEMINI_MODEL_LABELS } from '@hapi/protocol'
import type { AgentFlavor } from '@hapi/protocol'
export type AgentType = 'claude' | 'codex' | 'cursor' | 'gemini' | 'opencode' export type AgentType = AgentFlavor
export type SessionType = 'simple' | 'worktree' export type SessionType = 'simple' | 'worktree'
export type CodexReasoningEffort = 'default' | 'low' | 'medium' | 'high' | 'xhigh' export type CodexReasoningEffort = 'default' | 'low' | 'medium' | 'high' | 'xhigh'
export type ClaudeEffort = 'auto' | 'medium' | 'high' | 'max' export type ClaudeEffort = 'auto' | 'medium' | 'high' | 'max'
+2 -1
View File
@@ -1,4 +1,5 @@
import { useMutation, useQueryClient } from '@tanstack/react-query' import { useMutation, useQueryClient } from '@tanstack/react-query'
import type { AgentFlavor } from '@hapi/protocol'
import type { ApiClient } from '@/api/client' import type { ApiClient } from '@/api/client'
import type { SpawnResponse } from '@/types/api' import type { SpawnResponse } from '@/types/api'
import { queryKeys } from '@/lib/query-keys' import { queryKeys } from '@/lib/query-keys'
@@ -6,7 +7,7 @@ import { queryKeys } from '@/lib/query-keys'
type SpawnInput = { type SpawnInput = {
machineId: string machineId: string
directory: string directory: string
agent?: 'claude' | 'codex' | 'cursor' | 'gemini' | 'opencode' agent?: AgentFlavor
model?: string model?: string
effort?: string effort?: string
modelReasoningEffort?: string modelReasoningEffort?: string