Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1622,7 +1622,7 @@ program
.command('plan')
.description('Launch interactive planning session with Architect persona')
.argument('[prompt]', 'Initial planning prompt or topic')
.option('--model <model>', 'Model to use: opus, sonnet, haiku, opus[1m], sonnet[1m] (default: opus[1m])')
.option('--model <model>', 'Model to use: opus, sonnet, haiku, opus[1m], sonnet[1m], or full model ID (default: opus[1m])')
.addOption(
new Option('--one-shot <mode>', 'One-shot automation mode')
.choices(['default', 'noReview', 'bypassPermissions'])
Expand Down
8 changes: 4 additions & 4 deletions src/lib/AgentManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import fg from 'fast-glob'
import fs from 'fs-extra'
import { MarkdownAgentParser } from '../utils/MarkdownAgentParser.js'
import { logger } from '../utils/logger.js'
import { VALID_CLAUDE_MODELS, type IloomSettings } from './SettingsManager.js'
import { VALID_CLAUDE_MODELS, claudeModelSchema, type IloomSettings } from './SettingsManager.js'
import { PromptTemplateManager, TemplateVariables, buildReviewTemplateVariables } from './PromptTemplateManager.js'
import { isEffortLevel, type EffortLevel } from '../types/index.js'

Expand Down Expand Up @@ -212,11 +212,11 @@ export class AgentManager {
}

// Validate model and warn if non-standard
const validModels: readonly string[] = VALID_CLAUDE_MODELS
if (!validModels.includes(data.model)) {
const parseResult = claudeModelSchema.safeParse(data.model)
if (!parseResult.success) {
logger.warn(
`Agent ${data.name} uses model "${data.model}" which may not be recognized by Claude CLI, and your workflow may fail or produce unexpected results. ` +
`Valid values are: ${validModels.join(', ')}`
`Valid values are: ${VALID_CLAUDE_MODELS.join(', ')} or a full model ID starting with "claude-"`
)
}

Expand Down
23 changes: 22 additions & 1 deletion src/lib/SettingsManager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -688,7 +688,7 @@ describe('SettingsManager', () => {
}

expect(() => settingsManager['validateSettings'](invalidSettings as never)).toThrow(
/Invalid enum value.*Expected 'sonnet' \| 'opus' \| 'haiku' \| 'sonnet\[1m\]' \| 'opus\[1m\]'/,
/Full model IDs must start with "claude-"/,
)
})

Expand All @@ -708,6 +708,27 @@ describe('SettingsManager', () => {
})
})

it('should accept full Claude model IDs', () => {
const fullModelIds = [
'claude-opus-4-6[1m]',
'claude-sonnet-4-20250514',
'claude-haiku-4-5-20251001',
'claude-opus-4-6',
]

fullModelIds.forEach(model => {
const settings = {
agents: {
'test-agent': {
model,
},
},
}

expect(() => settingsManager['validateSettings'](settings as never)).not.toThrow()
})
})

it('should handle agent settings without model field', () => {
const settingsWithoutModel = {
agents: {
Expand Down
36 changes: 18 additions & 18 deletions src/lib/SettingsManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,22 @@ const mergeModeTransform = (val: string): MergeMode => {

// Valid Claude model shorthands: standard + 1M context window variants
export const VALID_CLAUDE_MODELS = ['sonnet', 'opus', 'haiku', 'sonnet[1m]', 'opus[1m]'] as const
export type ClaudeModel = (typeof VALID_CLAUDE_MODELS)[number]
export type ClaudeModel = (typeof VALID_CLAUDE_MODELS)[number] | (string & {})

// Zod schema that accepts shorthands or full model IDs (e.g. claude-opus-4-6[1m])
export const claudeModelSchema = z.union([
z.enum(VALID_CLAUDE_MODELS),
z.string().regex(/^claude-/, 'Full model IDs must start with "claude-"'),
])

/**
* Zod schema for base agent settings (without nested agents)
*/
export const BaseAgentSettingsSchema = z.object({
model: z
.enum(VALID_CLAUDE_MODELS)
model: claudeModelSchema
.optional()
.describe('Claude model shorthand: sonnet, opus, haiku, sonnet[1m], or opus[1m]'),
swarmModel: z
.enum(VALID_CLAUDE_MODELS)
.describe('Claude model shorthand (sonnet, opus, haiku, sonnet[1m], opus[1m]) or full model ID (e.g. claude-opus-4-6[1m])'),
swarmModel: claudeModelSchema
.optional()
.describe('Model to use for this agent in swarm mode. Overrides the base model when running inside swarm workers.'),
effort: z
Expand Down Expand Up @@ -72,12 +76,10 @@ export const AgentSettingsSchema = BaseAgentSettingsSchema
* Used for the spin orchestrator configuration
*/
export const SpinAgentSettingsSchema = z.object({
model: z
.enum(VALID_CLAUDE_MODELS)
model: claudeModelSchema
.default('opus')
.describe('Claude model shorthand for spin orchestrator'),
swarmModel: z
.enum(VALID_CLAUDE_MODELS)
swarmModel: claudeModelSchema
.optional()
.describe('Model for the spin orchestrator when running in swarm mode. Overrides spin.model for swarm workflows.'),
effort: z
Expand All @@ -99,8 +101,7 @@ export const SpinAgentSettingsSchema = z.object({
* Used for the plan command configuration
*/
export const PlanCommandSettingsSchema = z.object({
model: z
.enum(VALID_CLAUDE_MODELS)
model: claudeModelSchema
.default('opus[1m]')
.describe('Claude model shorthand for plan command'),
effort: z
Expand All @@ -126,8 +127,7 @@ export const PlanCommandSettingsSchema = z.object({
* Used for session summary generation configuration
*/
export const SummarySettingsSchema = z.object({
model: z
.enum(VALID_CLAUDE_MODELS)
model: claudeModelSchema
.default('sonnet')
.describe('Claude model shorthand for session summary generation'),
})
Expand Down Expand Up @@ -853,8 +853,8 @@ export const IloomSettingsSchemaNoDefaults = z.object({
),
spin: z
.object({
model: z.enum(VALID_CLAUDE_MODELS).optional(),
swarmModel: z.enum(VALID_CLAUDE_MODELS).optional(),
model: claudeModelSchema.optional(),
swarmModel: claudeModelSchema.optional(),
effort: z.enum(VALID_EFFORT_LEVELS).optional(),
swarmEffort: z.enum(VALID_EFFORT_LEVELS).optional(),
postSwarmReview: z.boolean().optional(),
Expand All @@ -863,7 +863,7 @@ export const IloomSettingsSchemaNoDefaults = z.object({
.describe('Spin orchestrator configuration'),
plan: z
.object({
model: z.enum(VALID_CLAUDE_MODELS).optional(),
model: claudeModelSchema.optional(),
effort: z.enum(VALID_EFFORT_LEVELS).optional(),
planner: z.enum(['claude', 'gemini', 'codex']).optional(),
reviewer: z.enum(['claude', 'gemini', 'codex', 'none']).optional(),
Expand All @@ -873,7 +873,7 @@ export const IloomSettingsSchemaNoDefaults = z.object({
.describe('Plan command configuration'),
summary: z
.object({
model: z.enum(VALID_CLAUDE_MODELS).optional(),
model: claudeModelSchema.optional(),
})
.optional()
.describe('Session summary generation configuration'),
Expand Down
Loading