diff --git a/packages/server/src/server/agent/agent-manager.ts b/packages/server/src/server/agent/agent-manager.ts index c6272dc57..9a10ac2a4 100644 --- a/packages/server/src/server/agent/agent-manager.ts +++ b/packages/server/src/server/agent/agent-manager.ts @@ -22,6 +22,8 @@ import type { } from "./agent-sdk-types.js"; import { resolveAgentModel } from "./model-resolver.js"; import type { AgentRegistry } from "./agent-registry.js"; +import { VALID_CLAUDE_MODES } from "./providers/claude-agent.js"; +import { VALID_CODEX_MODES } from "./providers/codex-agent.js"; export const AGENT_LIFECYCLE_STATUSES = [ "initializing", @@ -162,6 +164,27 @@ function isAgentBusy(status: AgentLifecycleStatus): boolean { return BUSY_STATUSES.includes(status); } +function getValidModesForProvider(provider: AgentProvider): Set { + switch (provider) { + case "claude": + return VALID_CLAUDE_MODES; + case "codex": + return VALID_CODEX_MODES; + default: + return new Set(); + } +} + +function validateMode(provider: AgentProvider, modeId: string): void { + const validModes = getValidModesForProvider(provider); + if (!validModes.has(modeId)) { + const validModesList = Array.from(validModes).join(", "); + throw new Error( + `Invalid mode '${modeId}' for provider '${provider}'. Valid modes: ${validModesList}` + ); + } +} + function createAbortError( signal: AbortSignal | undefined, fallbackMessage: string @@ -292,6 +315,12 @@ export class AgentManager { agentId?: string ): Promise { const normalizedConfig = await this.normalizeConfig(config); + + // Validate mode if provided + if (normalizedConfig.modeId) { + validateMode(normalizedConfig.provider, normalizedConfig.modeId); + } + const client = this.requireClient(normalizedConfig.provider); const session = await client.createSession(normalizedConfig); return this.registerSession( @@ -313,6 +342,12 @@ export class AgentManager { provider: handle.provider, } as AgentSessionConfig; const normalizedConfig = await this.normalizeConfig(mergedConfig); + + // Validate mode if provided + if (normalizedConfig.modeId) { + validateMode(handle.provider, normalizedConfig.modeId); + } + const resumeOverrides = normalizedConfig.model !== mergedConfig.model ? { ...overrides, model: normalizedConfig.model } @@ -374,6 +409,10 @@ export class AgentManager { async setAgentMode(agentId: string, modeId: string): Promise { const agent = this.requireAgent(agentId); + + // Validate mode before attempting to set it + validateMode(agent.provider, modeId); + await agent.session.setMode(modeId); agent.currentModeId = modeId; this.emitState(agent); diff --git a/packages/server/src/server/agent/providers/claude-agent.ts b/packages/server/src/server/agent/providers/claude-agent.ts index 815a4acfe..0a1470136 100644 --- a/packages/server/src/server/agent/providers/claude-agent.ts +++ b/packages/server/src/server/agent/providers/claude-agent.ts @@ -72,6 +72,10 @@ const DEFAULT_MODES: AgentMode[] = [ }, ]; +export const VALID_CLAUDE_MODES = new Set( + DEFAULT_MODES.map((mode) => mode.id) +); + type ClaudeAgentConfig = AgentSessionConfig & { provider: "claude" }; export type ClaudeContentChunk = { type: string; [key: string]: any }; diff --git a/packages/server/src/server/agent/providers/codex-agent.ts b/packages/server/src/server/agent/providers/codex-agent.ts index 43aedd1bc..29a01aafa 100644 --- a/packages/server/src/server/agent/providers/codex-agent.ts +++ b/packages/server/src/server/agent/providers/codex-agent.ts @@ -69,6 +69,10 @@ const CODEX_MODES: AgentMode[] = [ }, ]; +export const VALID_CODEX_MODES = new Set( + CODEX_MODES.map((mode) => mode.id) +); + const DEFAULT_CODEX_MODE_ID = "auto"; const MODE_PRESETS: Record<