import { isCustomAgentId } from './commit-message-agent-spec' import type { CommitMessageAiSettings } from './commit-message-ai-types' import { DEFAULT_SOURCE_CONTROL_ACTION_COMMAND_TEMPLATES, AI_ACTION_IDS, SOURCE_CONTROL_TEXT_ACTION_IDS, normalizeSourceControlAiActionDefaults, readSourceControlActionDefault } from './source-control-ai-actions' import { actionRecipeFromLegacyCommitMessageAi, commandTemplateFromOperationInstruction, isLegacyBranchInstructionTemplate } from './source-control-ai-command-template' import type { SourceControlAiPrCreationDefaults, SourceControlAiSettings } from './source-control-ai-types' export const DEFAULT_SOURCE_CONTROL_AI_PR_CREATION_DEFAULTS: Required = { draft: false, useTemplate: false, generateDetailsOnOpen: false, openAfterCreate: false } function copyRecord(value: T | undefined): T | undefined { return value === undefined ? undefined : structuredClone(value) } export function getDefaultSourceControlAiSettings(): SourceControlAiSettings { const actions: NonNullable = {} for (const actionId of AI_ACTION_IDS) { actions[actionId] = { commandInputTemplate: DEFAULT_SOURCE_CONTROL_ACTION_COMMAND_TEMPLATES[actionId] } } return { enabled: true, actions, agentId: null, selectedModelByAgent: {}, selectedModelByAgentByHost: {}, discoveredModelsByAgent: {}, discoveredModelsByAgentByHost: {}, selectedThinkingByModel: {}, customAgentCommand: '', instructionsByOperation: { commitMessage: '', pullRequest: '', branchName: '' }, prCreationDefaults: { ...DEFAULT_SOURCE_CONTROL_AI_PR_CREATION_DEFAULTS }, launchActionDefaults: {} } } export function sourceControlAiSettingsFromLegacy( legacy: CommitMessageAiSettings | null | undefined ): SourceControlAiSettings { const defaults = getDefaultSourceControlAiSettings() if (!legacy) { return defaults } const legacyActionRecipe = actionRecipeFromLegacyCommitMessageAi(legacy) return { ...defaults, enabled: legacy.enabled, agentId: legacy.agentId, selectedModelByAgent: { ...legacy.selectedModelByAgent }, selectedModelByAgentByHost: copyRecord(legacy.selectedModelByAgentByHost) ?? {}, discoveredModelsByAgent: copyRecord(legacy.discoveredModelsByAgent) ?? {}, discoveredModelsByAgentByHost: copyRecord(legacy.discoveredModelsByAgentByHost) ?? {}, selectedThinkingByModel: { ...legacy.selectedThinkingByModel }, customAgentCommand: legacy.customAgentCommand, instructionsByOperation: { commitMessage: legacy.customPrompt ?? '', pullRequest: '', branchName: legacy.customPrompt ?? '' }, actions: { ...defaults.actions, commitMessage: legacyActionRecipe, branchName: { ...legacyActionRecipe, commandInputTemplate: commandTemplateFromOperationInstruction( 'branchName', legacy.customPrompt ) } } } } export function normalizeSourceControlAiSettings( value: SourceControlAiSettings | null | undefined, legacy?: CommitMessageAiSettings | null ): SourceControlAiSettings { const base = value ?? sourceControlAiSettingsFromLegacy(legacy) const defaults = getDefaultSourceControlAiSettings() const normalizedLaunchActionDefaults = normalizeSourceControlAiActionDefaults( base.launchActionDefaults ) const normalizedActions = { ...normalizedLaunchActionDefaults, ...normalizeSourceControlAiActionDefaults(base.actions) } const migratedTextActions = Object.fromEntries( SOURCE_CONTROL_TEXT_ACTION_IDS.map((actionId) => { const existing = readSourceControlActionDefault(normalizedActions, actionId) const instruction = base.instructionsByOperation?.[actionId] const legacyInstruction = actionId === 'commitMessage' ? legacy?.customPrompt : undefined const resolvedInstruction = instruction ?? legacyInstruction const instructionTemplate = instruction || legacyInstruction ? commandTemplateFromOperationInstruction(actionId, resolvedInstruction) : undefined const shouldApplyInstructionTemplate = instructionTemplate !== undefined && (existing.commandInputTemplate === undefined || existing.commandInputTemplate === DEFAULT_SOURCE_CONTROL_ACTION_COMMAND_TEMPLATES[actionId] || isLegacyBranchInstructionTemplate( actionId, resolvedInstruction, existing.commandInputTemplate )) return [ actionId, { ...defaults.actions?.[actionId], ...(base.agentId && !isCustomAgentId(base.agentId) ? { agentId: base.agentId } : {}), ...existing, ...(shouldApplyInstructionTemplate ? { commandInputTemplate: instructionTemplate } : {}) } ] }) ) as SourceControlAiSettings['actions'] return { ...defaults, ...base, selectedModelByAgent: { ...defaults.selectedModelByAgent, ...base.selectedModelByAgent }, selectedModelByAgentByHost: copyRecord(base.selectedModelByAgentByHost) ?? defaults.selectedModelByAgentByHost, discoveredModelsByAgent: copyRecord(base.discoveredModelsByAgent) ?? defaults.discoveredModelsByAgent, discoveredModelsByAgentByHost: copyRecord(base.discoveredModelsByAgentByHost) ?? defaults.discoveredModelsByAgentByHost, selectedThinkingByModel: { ...defaults.selectedThinkingByModel, ...base.selectedThinkingByModel }, instructionsByOperation: { ...defaults.instructionsByOperation, ...base.instructionsByOperation }, modelOverridesByOperation: copyRecord(base.modelOverridesByOperation), prCreationDefaults: { ...defaults.prCreationDefaults, ...base.prCreationDefaults }, actions: { ...defaults.actions, ...normalizedActions, ...migratedTextActions }, launchActionDefaults: normalizedLaunchActionDefaults ?? defaults.launchActionDefaults } }