File size: 20,508 Bytes
1dbc34b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
/**
 * SDK Options Factory - Centralized configuration for Claude Agent SDK
 *
 * Provides presets for common use cases:
 * - Spec generation: Long-running analysis with read-only tools
 * - Feature generation: Quick JSON generation from specs
 * - Feature building: Autonomous feature implementation with full tool access
 * - Suggestions: Analysis with read-only tools
 * - Chat: Full tool access for interactive coding
 *
 * Uses model-resolver for consistent model handling across the application.
 *
 * SECURITY: All factory functions validate the working directory (cwd) against
 * ALLOWED_ROOT_DIRECTORY before returning options. This provides a centralized
 * security check that applies to ALL AI model invocations, regardless of provider.
 */

import type { Options } from '@anthropic-ai/claude-agent-sdk';
import path from 'path';
import { resolveModelString } from '@automaker/model-resolver';
import { createLogger } from '@automaker/utils';

const logger = createLogger('SdkOptions');
import {
  DEFAULT_MODELS,
  CLAUDE_MODEL_MAP,
  type McpServerConfig,
  type ThinkingLevel,
  getThinkingTokenBudget,
} from '@automaker/types';
import { isPathAllowed, PathNotAllowedError, getAllowedRootDirectory } from '@automaker/platform';

/**
 * Result of sandbox compatibility check
 */
export interface SandboxCompatibilityResult {
  /** Whether sandbox mode can be enabled for this path */
  enabled: boolean;
  /** Optional message explaining why sandbox is disabled */
  message?: string;
}

/**
 * Check if a working directory is compatible with sandbox mode.
 * Some paths (like cloud storage mounts) may not work with sandboxed execution.
 *
 * @param cwd - The working directory to check
 * @param sandboxRequested - Whether sandbox mode was requested by settings
 * @returns Object indicating if sandbox can be enabled and why not if disabled
 */
export function checkSandboxCompatibility(
  cwd: string,
  sandboxRequested: boolean
): SandboxCompatibilityResult {
  if (!sandboxRequested) {
    return { enabled: false };
  }

  const resolvedCwd = path.resolve(cwd);

  // Check for cloud storage paths that may not be compatible with sandbox
  const cloudStoragePatterns = [
    // macOS mounted volumes
    /^\/Volumes\/GoogleDrive/i,
    /^\/Volumes\/Dropbox/i,
    /^\/Volumes\/OneDrive/i,
    /^\/Volumes\/iCloud/i,
    // macOS home directory
    /^\/Users\/[^/]+\/Google Drive/i,
    /^\/Users\/[^/]+\/Dropbox/i,
    /^\/Users\/[^/]+\/OneDrive/i,
    /^\/Users\/[^/]+\/Library\/Mobile Documents/i, // iCloud
    // Linux home directory
    /^\/home\/[^/]+\/Google Drive/i,
    /^\/home\/[^/]+\/Dropbox/i,
    /^\/home\/[^/]+\/OneDrive/i,
    // Windows
    /^C:\\Users\\[^\\]+\\Google Drive/i,
    /^C:\\Users\\[^\\]+\\Dropbox/i,
    /^C:\\Users\\[^\\]+\\OneDrive/i,
  ];

  for (const pattern of cloudStoragePatterns) {
    if (pattern.test(resolvedCwd)) {
      return {
        enabled: false,
        message: `Sandbox disabled: Cloud storage path detected (${resolvedCwd}). Sandbox mode may not work correctly with cloud-synced directories.`,
      };
    }
  }

  return { enabled: true };
}

/**
 * Validate that a working directory is allowed by ALLOWED_ROOT_DIRECTORY.
 * This is the centralized security check for ALL AI model invocations.
 *
 * @param cwd - The working directory to validate
 * @throws PathNotAllowedError if the directory is not within ALLOWED_ROOT_DIRECTORY
 *
 * This function is called by all create*Options() factory functions to ensure
 * that AI models can only operate within allowed directories. This applies to:
 * - All current models (Claude, future models)
 * - All invocation types (chat, auto-mode, spec generation, etc.)
 */
export function validateWorkingDirectory(cwd: string): void {
  const resolvedCwd = path.resolve(cwd);

  if (!isPathAllowed(resolvedCwd)) {
    const allowedRoot = getAllowedRootDirectory();
    throw new PathNotAllowedError(
      `Working directory "${cwd}" (resolved: ${resolvedCwd}) is not allowed. ` +
        (allowedRoot
          ? `Must be within ALLOWED_ROOT_DIRECTORY: ${allowedRoot}`
          : 'ALLOWED_ROOT_DIRECTORY is configured but path is not within allowed directories.')
    );
  }
}

/**
 * Tool presets for different use cases
 */
export const TOOL_PRESETS = {
  /** Read-only tools for analysis */
  readOnly: ['Read', 'Glob', 'Grep'] as const,

  /** Tools for spec generation that needs to read the codebase */
  specGeneration: ['Read', 'Glob', 'Grep'] as const,

  /** Full tool access for feature implementation */
  fullAccess: [
    'Read',
    'Write',
    'Edit',
    'MultiEdit',
    'Glob',
    'Grep',
    'LS',
    'Bash',
    'WebSearch',
    'WebFetch',
    'TodoWrite',
    'Task',
    'Skill',
  ] as const,

  /** Tools for chat/interactive mode */
  chat: [
    'Read',
    'Write',
    'Edit',
    'MultiEdit',
    'Glob',
    'Grep',
    'LS',
    'Bash',
    'WebSearch',
    'WebFetch',
    'TodoWrite',
    'Task',
    'Skill',
  ] as const,
} as const;

/**
 * Max turns presets for different use cases
 */
export const MAX_TURNS = {
  /** Quick operations that shouldn't need many iterations */
  quick: 50,

  /** Standard operations */
  standard: 100,

  /** Long-running operations like full spec generation */
  extended: 250,

  /** Very long operations that may require extensive exploration */
  maximum: 1000,
} as const;

/**
 * Model presets for different use cases
 *
 * These can be overridden via environment variables:
 * - AUTOMAKER_MODEL_SPEC: Model for spec generation
 * - AUTOMAKER_MODEL_FEATURES: Model for feature generation
 * - AUTOMAKER_MODEL_SUGGESTIONS: Model for suggestions
 * - AUTOMAKER_MODEL_CHAT: Model for chat
 * - AUTOMAKER_MODEL_DEFAULT: Fallback model for all operations
 */
export function getModelForUseCase(
  useCase: 'spec' | 'features' | 'suggestions' | 'chat' | 'auto' | 'default',
  explicitModel?: string
): string {
  // Explicit model takes precedence
  if (explicitModel) {
    return resolveModelString(explicitModel);
  }

  // Check environment variable override for this use case
  const envVarMap: Record<string, string | undefined> = {
    spec: process.env.AUTOMAKER_MODEL_SPEC,
    features: process.env.AUTOMAKER_MODEL_FEATURES,
    suggestions: process.env.AUTOMAKER_MODEL_SUGGESTIONS,
    chat: process.env.AUTOMAKER_MODEL_CHAT,
    auto: process.env.AUTOMAKER_MODEL_AUTO,
    default: process.env.AUTOMAKER_MODEL_DEFAULT,
  };

  const envModel = envVarMap[useCase] || envVarMap.default;
  if (envModel) {
    return resolveModelString(envModel);
  }

  const defaultModels: Record<string, string> = {
    spec: CLAUDE_MODEL_MAP['haiku'], // used to generate app specs
    features: CLAUDE_MODEL_MAP['haiku'], // used to generate features from app specs
    suggestions: CLAUDE_MODEL_MAP['haiku'], // used for suggestions
    chat: CLAUDE_MODEL_MAP['haiku'], // used for chat
    auto: CLAUDE_MODEL_MAP['opus'], // used to implement kanban cards
    default: CLAUDE_MODEL_MAP['opus'],
  };

  return resolveModelString(defaultModels[useCase] || DEFAULT_MODELS.claude);
}

/**
 * Base options that apply to all SDK calls
 * AUTONOMOUS MODE: Always bypass permissions for fully autonomous operation
 */
function getBaseOptions(): Partial<Options> {
  return {
    permissionMode: 'bypassPermissions',
    allowDangerouslySkipPermissions: true,
  };
}

/**
 * MCP options result
 */
interface McpOptions {
  /** Options to spread for MCP servers */
  mcpServerOptions: Partial<Options>;
}

/**
 * Build MCP-related options based on configuration.
 *
 * @param config - The SDK options config
 * @returns Object with MCP server settings to spread into final options
 */
function buildMcpOptions(config: CreateSdkOptionsConfig): McpOptions {
  return {
    // Include MCP servers if configured
    mcpServerOptions: config.mcpServers ? { mcpServers: config.mcpServers } : {},
  };
}

/**
 * Build thinking options for SDK configuration.
 * Converts ThinkingLevel to maxThinkingTokens for the Claude SDK.
 * For adaptive thinking (Opus 4.6), omits maxThinkingTokens to let the model
 * decide its own reasoning depth.
 *
 * @param thinkingLevel - The thinking level to convert
 * @returns Object with maxThinkingTokens if thinking is enabled with a budget
 */
function buildThinkingOptions(thinkingLevel?: ThinkingLevel): Partial<Options> {
  if (!thinkingLevel || thinkingLevel === 'none') {
    return {};
  }

  // Adaptive thinking (Opus 4.6): don't set maxThinkingTokens
  // The model will use adaptive thinking by default
  if (thinkingLevel === 'adaptive') {
    logger.debug(
      `buildThinkingOptions: thinkingLevel="adaptive" -> no maxThinkingTokens (model decides)`
    );
    return {};
  }

  // Manual budget-based thinking for Haiku/Sonnet
  const maxThinkingTokens = getThinkingTokenBudget(thinkingLevel);
  logger.debug(
    `buildThinkingOptions: thinkingLevel="${thinkingLevel}" -> maxThinkingTokens=${maxThinkingTokens}`
  );
  return maxThinkingTokens ? { maxThinkingTokens } : {};
}

/**
 * Build system prompt and settingSources based on two independent settings:
 * - useClaudeCodeSystemPrompt: controls whether to use the 'claude_code' preset as the base prompt
 * - autoLoadClaudeMd: controls whether to add settingSources for SDK to load CLAUDE.md files
 *
 * These combine independently (4 possible states):
 * 1. Both ON: preset + settingSources (full Claude Code experience)
 * 2. useClaudeCodeSystemPrompt ON, autoLoadClaudeMd OFF: preset only (no CLAUDE.md auto-loading)
 * 3. useClaudeCodeSystemPrompt OFF, autoLoadClaudeMd ON: plain string + settingSources
 * 4. Both OFF: plain string only
 *
 * @param config - The SDK options config
 * @returns Object with systemPrompt and settingSources for SDK options
 */
function buildClaudeMdOptions(config: CreateSdkOptionsConfig): {
  systemPrompt?: string | SystemPromptConfig;
  settingSources?: Array<'user' | 'project' | 'local'>;
} {
  const result: {
    systemPrompt?: string | SystemPromptConfig;
    settingSources?: Array<'user' | 'project' | 'local'>;
  } = {};

  // Determine system prompt format based on useClaudeCodeSystemPrompt
  if (config.useClaudeCodeSystemPrompt) {
    // Use Claude Code's built-in system prompt as the base
    const presetConfig: SystemPromptConfig = {
      type: 'preset',
      preset: 'claude_code',
    };
    // If there's a custom system prompt, append it to the preset
    if (config.systemPrompt) {
      presetConfig.append = config.systemPrompt;
    }
    result.systemPrompt = presetConfig;
  } else {
    // Standard mode - just pass through the system prompt as-is
    if (config.systemPrompt) {
      result.systemPrompt = config.systemPrompt;
    }
  }

  // Determine settingSources based on autoLoadClaudeMd
  if (config.autoLoadClaudeMd) {
    // Load both user (~/.claude/CLAUDE.md) and project (.claude/CLAUDE.md) settings
    result.settingSources = ['user', 'project'];
  }

  return result;
}

/**
 * System prompt configuration for SDK options
 * The 'claude_code' preset provides the system prompt only — it does NOT auto-load
 * CLAUDE.md files. CLAUDE.md auto-loading is controlled independently by
 * settingSources (set via autoLoadClaudeMd). These two settings are orthogonal.
 */
export interface SystemPromptConfig {
  /** Use preset mode to select the base system prompt */
  type: 'preset';
  /** The preset to use - 'claude_code' uses the Claude Code system prompt */
  preset: 'claude_code';
  /** Optional additional prompt to append to the preset */
  append?: string;
}

/**
 * Options configuration for creating SDK options
 */
export interface CreateSdkOptionsConfig {
  /** Working directory for the agent */
  cwd: string;

  /** Optional explicit model override */
  model?: string;

  /** Optional session model (used as fallback if explicit model not provided) */
  sessionModel?: string;

  /** Optional system prompt */
  systemPrompt?: string;

  /** Optional abort controller for cancellation */
  abortController?: AbortController;

  /** Optional output format for structured outputs */
  outputFormat?: {
    type: 'json_schema';
    schema: Record<string, unknown>;
  };

  /** Enable auto-loading of CLAUDE.md files via SDK's settingSources */
  autoLoadClaudeMd?: boolean;

  /** Use Claude Code's built-in system prompt (claude_code preset) as the base prompt */
  useClaudeCodeSystemPrompt?: boolean;

  /** MCP servers to make available to the agent */
  mcpServers?: Record<string, McpServerConfig>;

  /** Extended thinking level for Claude models */
  thinkingLevel?: ThinkingLevel;

  /** Optional user-configured max turns override (from settings).
   * When provided, overrides the preset MAX_TURNS for the use case.
   * Range: 1-2000. */
  maxTurns?: number;
}

// Re-export MCP types from @automaker/types for convenience
export type {
  McpServerConfig,
  McpStdioServerConfig,
  McpSSEServerConfig,
  McpHttpServerConfig,
} from '@automaker/types';

/**
 * Create SDK options for spec generation
 *
 * Configuration:
 * - Uses read-only tools for codebase analysis
 * - Extended turns for thorough exploration
 * - Opus model by default (can be overridden)
 * - When autoLoadClaudeMd is true, uses preset mode and settingSources for CLAUDE.md loading
 */
export function createSpecGenerationOptions(config: CreateSdkOptionsConfig): Options {
  // Validate working directory before creating options
  validateWorkingDirectory(config.cwd);

  // Build CLAUDE.md auto-loading options if enabled
  const claudeMdOptions = buildClaudeMdOptions(config);

  // Build thinking options
  const thinkingOptions = buildThinkingOptions(config.thinkingLevel);

  return {
    ...getBaseOptions(),
    // Override permissionMode - spec generation only needs read-only tools
    // Using "acceptEdits" can cause Claude to write files to unexpected locations
    // See: https://github.com/AutoMaker-Org/automaker/issues/149
    permissionMode: 'default',
    model: getModelForUseCase('spec', config.model),
    maxTurns: config.maxTurns ?? MAX_TURNS.maximum,
    cwd: config.cwd,
    allowedTools: [...TOOL_PRESETS.specGeneration],
    ...claudeMdOptions,
    ...thinkingOptions,
    ...(config.abortController && { abortController: config.abortController }),
    ...(config.outputFormat && { outputFormat: config.outputFormat }),
  };
}

/**
 * Create SDK options for feature generation from specs
 *
 * Configuration:
 * - Uses read-only tools (just needs to read the spec)
 * - Quick turns since it's mostly JSON generation
 * - Sonnet model by default for speed
 * - When autoLoadClaudeMd is true, uses preset mode and settingSources for CLAUDE.md loading
 */
export function createFeatureGenerationOptions(config: CreateSdkOptionsConfig): Options {
  // Validate working directory before creating options
  validateWorkingDirectory(config.cwd);

  // Build CLAUDE.md auto-loading options if enabled
  const claudeMdOptions = buildClaudeMdOptions(config);

  // Build thinking options
  const thinkingOptions = buildThinkingOptions(config.thinkingLevel);

  return {
    ...getBaseOptions(),
    // Override permissionMode - feature generation only needs read-only tools
    permissionMode: 'default',
    model: getModelForUseCase('features', config.model),
    maxTurns: config.maxTurns ?? MAX_TURNS.quick,
    cwd: config.cwd,
    allowedTools: [...TOOL_PRESETS.readOnly],
    ...claudeMdOptions,
    ...thinkingOptions,
    ...(config.abortController && { abortController: config.abortController }),
  };
}

/**
 * Create SDK options for generating suggestions
 *
 * Configuration:
 * - Uses read-only tools for analysis
 * - Standard turns to allow thorough codebase exploration and structured output generation
 * - Opus model by default for thorough analysis
 * - When autoLoadClaudeMd is true, uses preset mode and settingSources for CLAUDE.md loading
 */
export function createSuggestionsOptions(config: CreateSdkOptionsConfig): Options {
  // Validate working directory before creating options
  validateWorkingDirectory(config.cwd);

  // Build CLAUDE.md auto-loading options if enabled
  const claudeMdOptions = buildClaudeMdOptions(config);

  // Build thinking options
  const thinkingOptions = buildThinkingOptions(config.thinkingLevel);

  return {
    ...getBaseOptions(),
    model: getModelForUseCase('suggestions', config.model),
    maxTurns: config.maxTurns ?? MAX_TURNS.extended,
    cwd: config.cwd,
    allowedTools: [...TOOL_PRESETS.readOnly],
    ...claudeMdOptions,
    ...thinkingOptions,
    ...(config.abortController && { abortController: config.abortController }),
    ...(config.outputFormat && { outputFormat: config.outputFormat }),
  };
}

/**
 * Create SDK options for chat/interactive mode
 *
 * Configuration:
 * - Full tool access for code modification
 * - Standard turns for interactive sessions
 * - Model priority: explicit model > session model > chat default
 * - When autoLoadClaudeMd is true, uses preset mode and settingSources for CLAUDE.md loading
 */
export function createChatOptions(config: CreateSdkOptionsConfig): Options {
  // Validate working directory before creating options
  validateWorkingDirectory(config.cwd);

  // Model priority: explicit model > session model > chat default
  const effectiveModel = config.model || config.sessionModel;

  // Build CLAUDE.md auto-loading options if enabled
  const claudeMdOptions = buildClaudeMdOptions(config);

  // Build MCP-related options
  const mcpOptions = buildMcpOptions(config);

  // Build thinking options
  const thinkingOptions = buildThinkingOptions(config.thinkingLevel);

  return {
    ...getBaseOptions(),
    model: getModelForUseCase('chat', effectiveModel),
    maxTurns: config.maxTurns ?? MAX_TURNS.standard,
    cwd: config.cwd,
    allowedTools: [...TOOL_PRESETS.chat],
    ...claudeMdOptions,
    ...thinkingOptions,
    ...(config.abortController && { abortController: config.abortController }),
    ...mcpOptions.mcpServerOptions,
  };
}

/**
 * Create SDK options for autonomous feature building/implementation
 *
 * Configuration:
 * - Full tool access for code modification and implementation
 * - Extended turns for thorough feature implementation
 * - Uses default model (can be overridden)
 * - When autoLoadClaudeMd is true, uses preset mode and settingSources for CLAUDE.md loading
 */
export function createAutoModeOptions(config: CreateSdkOptionsConfig): Options {
  // Validate working directory before creating options
  validateWorkingDirectory(config.cwd);

  // Build CLAUDE.md auto-loading options if enabled
  const claudeMdOptions = buildClaudeMdOptions(config);

  // Build MCP-related options
  const mcpOptions = buildMcpOptions(config);

  // Build thinking options
  const thinkingOptions = buildThinkingOptions(config.thinkingLevel);

  return {
    ...getBaseOptions(),
    model: getModelForUseCase('auto', config.model),
    maxTurns: config.maxTurns ?? MAX_TURNS.maximum,
    cwd: config.cwd,
    allowedTools: [...TOOL_PRESETS.fullAccess],
    ...claudeMdOptions,
    ...thinkingOptions,
    ...(config.abortController && { abortController: config.abortController }),
    ...mcpOptions.mcpServerOptions,
  };
}

/**
 * Create custom SDK options with explicit configuration
 *
 * Use this when the preset options don't fit your use case.
 * When autoLoadClaudeMd is true, uses preset mode and settingSources for CLAUDE.md loading
 */
export function createCustomOptions(
  config: CreateSdkOptionsConfig & {
    maxTurns?: number;
    allowedTools?: readonly string[];
  }
): Options {
  // Validate working directory before creating options
  validateWorkingDirectory(config.cwd);

  // Build CLAUDE.md auto-loading options if enabled
  const claudeMdOptions = buildClaudeMdOptions(config);

  // Build MCP-related options
  const mcpOptions = buildMcpOptions(config);

  // Build thinking options
  const thinkingOptions = buildThinkingOptions(config.thinkingLevel);

  // For custom options: use explicit allowedTools if provided, otherwise default to readOnly
  const effectiveAllowedTools = config.allowedTools
    ? [...config.allowedTools]
    : [...TOOL_PRESETS.readOnly];

  return {
    ...getBaseOptions(),
    model: getModelForUseCase('default', config.model),
    maxTurns: config.maxTurns ?? MAX_TURNS.maximum,
    cwd: config.cwd,
    allowedTools: effectiveAllowedTools,
    ...claudeMdOptions,
    ...thinkingOptions,
    ...(config.abortController && { abortController: config.abortController }),
    ...mcpOptions.mcpServerOptions,
  };
}