diff --git a/apps/server/tests/unit/providers/opencode-provider.test.ts b/apps/server/tests/unit/providers/opencode-provider.test.ts index fc3791e7..aeecd83b 100644 --- a/apps/server/tests/unit/providers/opencode-provider.test.ts +++ b/apps/server/tests/unit/providers/opencode-provider.test.ts @@ -51,63 +51,38 @@ describe('opencode-provider.ts', () => { }); describe('getAvailableModels', () => { - it('should return 10 models', () => { + it('should return 5 models', () => { const models = provider.getAvailableModels(); - expect(models).toHaveLength(10); + expect(models).toHaveLength(5); }); - it('should include Claude Sonnet 4.5 (Bedrock) as default', () => { - const models = provider.getAvailableModels(); - const sonnet = models.find( - (m) => m.id === 'amazon-bedrock/anthropic.claude-sonnet-4-5-20250929-v1:0' - ); - - expect(sonnet).toBeDefined(); - expect(sonnet?.name).toBe('Claude Sonnet 4.5 (Bedrock)'); - expect(sonnet?.provider).toBe('opencode'); - expect(sonnet?.default).toBe(true); - expect(sonnet?.modelString).toBe('amazon-bedrock/anthropic.claude-sonnet-4-5-20250929-v1:0'); - }); - - it('should include Claude Opus 4.5 (Bedrock)', () => { - const models = provider.getAvailableModels(); - const opus = models.find( - (m) => m.id === 'amazon-bedrock/anthropic.claude-opus-4-5-20251101-v1:0' - ); - - expect(opus).toBeDefined(); - expect(opus?.name).toBe('Claude Opus 4.5 (Bedrock)'); - expect(opus?.modelString).toBe('amazon-bedrock/anthropic.claude-opus-4-5-20251101-v1:0'); - }); - - it('should include Claude Haiku 4.5 (Bedrock)', () => { - const models = provider.getAvailableModels(); - const haiku = models.find( - (m) => m.id === 'amazon-bedrock/anthropic.claude-haiku-4-5-20251001-v1:0' - ); - - expect(haiku).toBeDefined(); - expect(haiku?.name).toBe('Claude Haiku 4.5 (Bedrock)'); - expect(haiku?.tier).toBe('standard'); - }); - - it('should include free tier Big Pickle model', () => { + it('should include Big Pickle as default', () => { const models = provider.getAvailableModels(); const bigPickle = models.find((m) => m.id === 'opencode/big-pickle'); expect(bigPickle).toBeDefined(); expect(bigPickle?.name).toBe('Big Pickle (Free)'); + expect(bigPickle?.provider).toBe('opencode'); + expect(bigPickle?.default).toBe(true); expect(bigPickle?.modelString).toBe('opencode/big-pickle'); - expect(bigPickle?.tier).toBe('basic'); }); - it('should include DeepSeek R1 (Bedrock)', () => { + it('should include free tier GLM model', () => { const models = provider.getAvailableModels(); - const deepseek = models.find((m) => m.id === 'amazon-bedrock/deepseek.r1-v1:0'); + const glm = models.find((m) => m.id === 'opencode/glm-4.7-free'); - expect(deepseek).toBeDefined(); - expect(deepseek?.name).toBe('DeepSeek R1 (Bedrock)'); - expect(deepseek?.tier).toBe('premium'); + expect(glm).toBeDefined(); + expect(glm?.name).toBe('GLM 4.7 Free'); + expect(glm?.tier).toBe('basic'); + }); + + it('should include free tier MiniMax model', () => { + const models = provider.getAvailableModels(); + const minimax = models.find((m) => m.id === 'opencode/minimax-m2.1-free'); + + expect(minimax).toBeDefined(); + expect(minimax?.name).toBe('MiniMax M2.1 Free'); + expect(minimax?.tier).toBe('basic'); }); it('should have all models support tools', () => { @@ -130,18 +105,14 @@ describe('opencode-provider.ts', () => { describe('parseModelsOutput', () => { it('should parse nested provider model IDs', () => { - const output = [ - 'openrouter/anthropic/claude-3.5-sonnet', - 'openai/gpt-4o', - 'amazon-bedrock/anthropic.claude-sonnet-4-5-20250929-v1:0', - ].join('\n'); + const output = ['openrouter/anthropic/claude-3.5-sonnet', 'openai/gpt-4o'].join('\n'); const parseModelsOutput = ( provider as unknown as { parseModelsOutput: (output: string) => ModelDefinition[] } ).parseModelsOutput; const models = parseModelsOutput(output); - expect(models).toHaveLength(3); + expect(models).toHaveLength(2); const openrouterModel = models.find((model) => model.id.startsWith('openrouter/')); expect(openrouterModel).toBeDefined(); @@ -1265,7 +1236,7 @@ describe('opencode-provider.ts', () => { const defaultModels = models.filter((m) => m.default === true); expect(defaultModels).toHaveLength(1); - expect(defaultModels[0].id).toBe('amazon-bedrock/anthropic.claude-sonnet-4-5-20250929-v1:0'); + expect(defaultModels[0].id).toBe('opencode/big-pickle'); }); it('should have valid tier values for all models', () => { diff --git a/apps/ui/src/components/views/settings-view/providers/opencode-settings-tab.tsx b/apps/ui/src/components/views/settings-view/providers/opencode-settings-tab.tsx index 97adc645..2bf20d82 100644 --- a/apps/ui/src/components/views/settings-view/providers/opencode-settings-tab.tsx +++ b/apps/ui/src/components/views/settings-view/providers/opencode-settings-tab.tsx @@ -11,12 +11,8 @@ import type { OpencodeAuthStatus, OpenCodeProviderInfo } from '../cli-status/ope const logger = createLogger('OpencodeSettings'); const OPENCODE_PROVIDER_ID = 'opencode'; -const OPENCODE_BEDROCK_PROVIDER_ID = 'amazon-bedrock'; const OPENCODE_PROVIDER_SIGNATURE_SEPARATOR = '|'; -const OPENCODE_STATIC_MODEL_PROVIDERS = new Set([ - OPENCODE_PROVIDER_ID, - OPENCODE_BEDROCK_PROVIDER_ID, -]); +const OPENCODE_STATIC_MODEL_PROVIDERS = new Set([OPENCODE_PROVIDER_ID]); export function OpencodeSettingsTab() { const { diff --git a/apps/ui/src/components/views/setup-view/steps/opencode-setup-step.tsx b/apps/ui/src/components/views/setup-view/steps/opencode-setup-step.tsx index afb40b6d..5e7e29c0 100644 --- a/apps/ui/src/components/views/setup-view/steps/opencode-setup-step.tsx +++ b/apps/ui/src/components/views/setup-view/steps/opencode-setup-step.tsx @@ -182,7 +182,7 @@ export function OpencodeSetupStep({ onNext, onBack, onSkip }: OpencodeSetupStepP

This step is optional

- Configure OpenCode CLI for access to free tier models and AWS Bedrock models. You + Configure OpenCode CLI for access to free tier models and connected providers. You can skip this and use other providers, or configure it later in Settings.

@@ -241,7 +241,7 @@ export function OpencodeSetupStep({ onNext, onBack, onSkip }: OpencodeSetupStepP

OpenCode CLI not found

- Install the OpenCode CLI to use free tier and AWS Bedrock models. + Install the OpenCode CLI to use free tier models and connected providers.

diff --git a/apps/ui/src/components/views/setup-view/steps/providers-setup-step.tsx b/apps/ui/src/components/views/setup-view/steps/providers-setup-step.tsx index ca2b1759..b9ad3263 100644 --- a/apps/ui/src/components/views/setup-view/steps/providers-setup-step.tsx +++ b/apps/ui/src/components/views/setup-view/steps/providers-setup-step.tsx @@ -1119,7 +1119,7 @@ function OpencodeContent() {

OpenCode CLI not found

- Install the OpenCode CLI for free tier and AWS Bedrock models. + Install the OpenCode CLI for free tier models and connected providers.

diff --git a/apps/ui/src/hooks/use-settings-migration.ts b/apps/ui/src/hooks/use-settings-migration.ts index 2e271d51..ed236de8 100644 --- a/apps/ui/src/hooks/use-settings-migration.ts +++ b/apps/ui/src/hooks/use-settings-migration.ts @@ -28,7 +28,11 @@ import { getHttpApiClient, waitForApiKeyInit } from '@/lib/http-api-client'; import { getItem, setItem } from '@/lib/storage'; import { useAppStore, THEME_STORAGE_KEY } from '@/store/app-store'; import { useSetupStore } from '@/store/setup-store'; -import type { GlobalSettings } from '@automaker/types'; +import { + DEFAULT_OPENCODE_MODEL, + getAllOpencodeModelIds, + type GlobalSettings, +} from '@automaker/types'; const logger = createLogger('SettingsMigration'); @@ -497,6 +501,21 @@ export function useSettingsMigration(): MigrationState { */ export function hydrateStoreFromSettings(settings: GlobalSettings): void { const current = useAppStore.getState(); + const validOpencodeModelIds = new Set(getAllOpencodeModelIds()); + const incomingEnabledOpencodeModels = + settings.enabledOpencodeModels ?? current.enabledOpencodeModels; + const sanitizedOpencodeDefaultModel = validOpencodeModelIds.has( + settings.opencodeDefaultModel ?? current.opencodeDefaultModel + ) + ? (settings.opencodeDefaultModel ?? current.opencodeDefaultModel) + : DEFAULT_OPENCODE_MODEL; + const sanitizedEnabledOpencodeModels = Array.from( + new Set(incomingEnabledOpencodeModels.filter((modelId) => validOpencodeModelIds.has(modelId))) + ); + + if (!sanitizedEnabledOpencodeModels.includes(sanitizedOpencodeDefaultModel)) { + sanitizedEnabledOpencodeModels.push(sanitizedOpencodeDefaultModel); + } // Convert ProjectRef[] to Project[] (minimal data, features will be loaded separately) const projects = (settings.projects ?? []).map((ref) => ({ @@ -541,8 +560,8 @@ export function hydrateStoreFromSettings(settings: GlobalSettings): void { phaseModels: settings.phaseModels ?? current.phaseModels, enabledCursorModels: settings.enabledCursorModels ?? current.enabledCursorModels, cursorDefaultModel: settings.cursorDefaultModel ?? 'auto', - enabledOpencodeModels: settings.enabledOpencodeModels ?? current.enabledOpencodeModels, - opencodeDefaultModel: settings.opencodeDefaultModel ?? current.opencodeDefaultModel, + enabledOpencodeModels: sanitizedEnabledOpencodeModels, + opencodeDefaultModel: sanitizedOpencodeDefaultModel, autoLoadClaudeMd: settings.autoLoadClaudeMd ?? false, skipSandboxWarning: settings.skipSandboxWarning ?? false, keyboardShortcuts: { diff --git a/apps/ui/src/store/app-store.ts b/apps/ui/src/store/app-store.ts index 2076221b..0490738e 100644 --- a/apps/ui/src/store/app-store.ts +++ b/apps/ui/src/store/app-store.ts @@ -34,6 +34,8 @@ import { } from '@automaker/types'; const logger = createLogger('AppStore'); +const OPENCODE_BEDROCK_PROVIDER_ID = 'amazon-bedrock'; +const OPENCODE_BEDROCK_MODEL_PREFIX = `${OPENCODE_BEDROCK_PROVIDER_ID}/`; // Re-export types for convenience export type { @@ -1237,7 +1239,7 @@ const initialState: AppState = { codexEnableWebSearch: false, // Default to disabled codexEnableImages: false, // Default to disabled enabledOpencodeModels: getAllOpencodeModelIds(), // All OpenCode models enabled by default - opencodeDefaultModel: DEFAULT_OPENCODE_MODEL, // Default to Claude Sonnet 4.5 + opencodeDefaultModel: DEFAULT_OPENCODE_MODEL, // Default to OpenCode free tier dynamicOpencodeModels: [], // Empty until fetched from OpenCode CLI enabledDynamicModelIds: [], // All dynamic models enabled by default (populated when models are fetched) cachedOpencodeProviders: [], // Empty until fetched from OpenCode CLI @@ -2042,15 +2044,20 @@ export const useAppStore = create()((set, get) => ({ // Dynamic models are session-only (not persisted to server) because they depend on // current CLI authentication state and are re-discovered each session // When setting dynamic models, auto-enable all of them if enabledDynamicModelIds is empty + const filteredModels = models.filter( + (model) => + model.provider !== OPENCODE_BEDROCK_PROVIDER_ID && + !model.id.startsWith(OPENCODE_BEDROCK_MODEL_PREFIX) + ); const currentEnabled = get().enabledDynamicModelIds; - const newModelIds = models.map((m) => m.id); + const newModelIds = filteredModels.map((m) => m.id); // If no models were previously enabled, enable all new ones if (currentEnabled.length === 0) { - set({ dynamicOpencodeModels: models, enabledDynamicModelIds: newModelIds }); + set({ dynamicOpencodeModels: filteredModels, enabledDynamicModelIds: newModelIds }); } else { // Keep existing enabled state, just update the models list - set({ dynamicOpencodeModels: models }); + set({ dynamicOpencodeModels: filteredModels }); } }, setEnabledDynamicModelIds: (ids) => set({ enabledDynamicModelIds: ids }), @@ -2060,7 +2067,12 @@ export const useAppStore = create()((set, get) => ({ ? [...state.enabledDynamicModelIds, modelId] : state.enabledDynamicModelIds.filter((id) => id !== modelId), })), - setCachedOpencodeProviders: (providers) => set({ cachedOpencodeProviders: providers }), + setCachedOpencodeProviders: (providers) => + set({ + cachedOpencodeProviders: providers.filter( + (provider) => provider.id !== OPENCODE_BEDROCK_PROVIDER_ID + ), + }), // Claude Agent SDK Settings actions setAutoLoadClaudeMd: async (enabled) => { diff --git a/libs/types/src/opencode-models.ts b/libs/types/src/opencode-models.ts index 246f8770..21d5a652 100644 --- a/libs/types/src/opencode-models.ts +++ b/libs/types/src/opencode-models.ts @@ -8,43 +8,12 @@ export type OpencodeModelId = | 'opencode/glm-4.7-free' | 'opencode/gpt-5-nano' | 'opencode/grok-code' - | 'opencode/minimax-m2.1-free' - // Amazon Bedrock - Claude Models - | 'amazon-bedrock/anthropic.claude-sonnet-4-5-20250929-v1:0' - | 'amazon-bedrock/anthropic.claude-opus-4-5-20251101-v1:0' - | 'amazon-bedrock/anthropic.claude-haiku-4-5-20251001-v1:0' - | 'amazon-bedrock/anthropic.claude-sonnet-4-20250514-v1:0' - | 'amazon-bedrock/anthropic.claude-opus-4-20250514-v1:0' - | 'amazon-bedrock/anthropic.claude-3-7-sonnet-20250219-v1:0' - | 'amazon-bedrock/anthropic.claude-3-5-sonnet-20241022-v2:0' - | 'amazon-bedrock/anthropic.claude-3-opus-20240229-v1:0' - // Amazon Bedrock - DeepSeek Models - | 'amazon-bedrock/deepseek.r1-v1:0' - | 'amazon-bedrock/deepseek.v3-v1:0' - // Amazon Bedrock - Amazon Nova Models - | 'amazon-bedrock/amazon.nova-premier-v1:0' - | 'amazon-bedrock/amazon.nova-pro-v1:0' - | 'amazon-bedrock/amazon.nova-lite-v1:0' - // Amazon Bedrock - Meta Llama Models - | 'amazon-bedrock/meta.llama4-maverick-17b-instruct-v1:0' - | 'amazon-bedrock/meta.llama3-3-70b-instruct-v1:0' - // Amazon Bedrock - Mistral Models - | 'amazon-bedrock/mistral.mistral-large-2402-v1:0' - // Amazon Bedrock - Qwen Models - | 'amazon-bedrock/qwen.qwen3-coder-480b-a35b-v1:0' - | 'amazon-bedrock/qwen.qwen3-235b-a22b-2507-v1:0'; + | 'opencode/minimax-m2.1-free'; /** * Provider type for OpenCode models */ -export type OpencodeProvider = - | 'opencode' - | 'amazon-bedrock-anthropic' - | 'amazon-bedrock-deepseek' - | 'amazon-bedrock-amazon' - | 'amazon-bedrock-meta' - | 'amazon-bedrock-mistral' - | 'amazon-bedrock-qwen'; +export type OpencodeProvider = 'opencode'; /** * Friendly aliases mapped to full model IDs @@ -59,36 +28,6 @@ export const OPENCODE_MODEL_MAP: Record = { 'grok-code': 'opencode/grok-code', grok: 'opencode/grok-code', minimax: 'opencode/minimax-m2.1-free', - - // Claude aliases (via Bedrock) - 'claude-sonnet-4.5': 'amazon-bedrock/anthropic.claude-sonnet-4-5-20250929-v1:0', - 'sonnet-4.5': 'amazon-bedrock/anthropic.claude-sonnet-4-5-20250929-v1:0', - sonnet: 'amazon-bedrock/anthropic.claude-sonnet-4-5-20250929-v1:0', - 'claude-opus-4.5': 'amazon-bedrock/anthropic.claude-opus-4-5-20251101-v1:0', - 'opus-4.5': 'amazon-bedrock/anthropic.claude-opus-4-5-20251101-v1:0', - opus: 'amazon-bedrock/anthropic.claude-opus-4-5-20251101-v1:0', - 'claude-haiku-4.5': 'amazon-bedrock/anthropic.claude-haiku-4-5-20251001-v1:0', - 'haiku-4.5': 'amazon-bedrock/anthropic.claude-haiku-4-5-20251001-v1:0', - haiku: 'amazon-bedrock/anthropic.claude-haiku-4-5-20251001-v1:0', - - // DeepSeek aliases - 'deepseek-r1': 'amazon-bedrock/deepseek.r1-v1:0', - r1: 'amazon-bedrock/deepseek.r1-v1:0', - 'deepseek-v3': 'amazon-bedrock/deepseek.v3-v1:0', - - // Nova aliases - 'nova-premier': 'amazon-bedrock/amazon.nova-premier-v1:0', - 'nova-pro': 'amazon-bedrock/amazon.nova-pro-v1:0', - nova: 'amazon-bedrock/amazon.nova-pro-v1:0', - - // Llama aliases - llama4: 'amazon-bedrock/meta.llama4-maverick-17b-instruct-v1:0', - 'llama-4': 'amazon-bedrock/meta.llama4-maverick-17b-instruct-v1:0', - llama3: 'amazon-bedrock/meta.llama3-3-70b-instruct-v1:0', - - // Qwen aliases - qwen: 'amazon-bedrock/qwen.qwen3-coder-480b-a35b-v1:0', - 'qwen-coder': 'amazon-bedrock/qwen.qwen3-coder-480b-a35b-v1:0', } as const; /** @@ -148,162 +87,6 @@ export const OPENCODE_MODELS: OpencodeModelConfig[] = [ provider: 'opencode', tier: 'free', }, - - // Amazon Bedrock - Claude Models - { - id: 'amazon-bedrock/anthropic.claude-sonnet-4-5-20250929-v1:0', - label: 'Claude Sonnet 4.5 (Bedrock)', - description: 'Latest Claude Sonnet via AWS Bedrock - fast and intelligent (default)', - supportsVision: true, - provider: 'amazon-bedrock-anthropic', - tier: 'premium', - }, - { - id: 'amazon-bedrock/anthropic.claude-opus-4-5-20251101-v1:0', - label: 'Claude Opus 4.5 (Bedrock)', - description: 'Most capable Claude model via AWS Bedrock', - supportsVision: true, - provider: 'amazon-bedrock-anthropic', - tier: 'premium', - }, - { - id: 'amazon-bedrock/anthropic.claude-haiku-4-5-20251001-v1:0', - label: 'Claude Haiku 4.5 (Bedrock)', - description: 'Fastest Claude model via AWS Bedrock', - supportsVision: true, - provider: 'amazon-bedrock-anthropic', - tier: 'standard', - }, - { - id: 'amazon-bedrock/anthropic.claude-sonnet-4-20250514-v1:0', - label: 'Claude Sonnet 4 (Bedrock)', - description: 'Claude Sonnet 4 via AWS Bedrock', - supportsVision: true, - provider: 'amazon-bedrock-anthropic', - tier: 'premium', - }, - { - id: 'amazon-bedrock/anthropic.claude-opus-4-20250514-v1:0', - label: 'Claude Opus 4 (Bedrock)', - description: 'Claude Opus 4 via AWS Bedrock', - supportsVision: true, - provider: 'amazon-bedrock-anthropic', - tier: 'premium', - }, - { - id: 'amazon-bedrock/anthropic.claude-3-7-sonnet-20250219-v1:0', - label: 'Claude 3.7 Sonnet (Bedrock)', - description: 'Claude 3.7 Sonnet via AWS Bedrock', - supportsVision: true, - provider: 'amazon-bedrock-anthropic', - tier: 'standard', - }, - { - id: 'amazon-bedrock/anthropic.claude-3-5-sonnet-20241022-v2:0', - label: 'Claude 3.5 Sonnet (Bedrock)', - description: 'Claude 3.5 Sonnet v2 via AWS Bedrock', - supportsVision: true, - provider: 'amazon-bedrock-anthropic', - tier: 'standard', - }, - { - id: 'amazon-bedrock/anthropic.claude-3-opus-20240229-v1:0', - label: 'Claude 3 Opus (Bedrock)', - description: 'Claude 3 Opus via AWS Bedrock', - supportsVision: true, - provider: 'amazon-bedrock-anthropic', - tier: 'premium', - }, - - // Amazon Bedrock - DeepSeek Models - { - id: 'amazon-bedrock/deepseek.r1-v1:0', - label: 'DeepSeek R1 (Bedrock)', - description: 'DeepSeek R1 reasoning model via AWS Bedrock - excellent for coding', - supportsVision: false, - provider: 'amazon-bedrock-deepseek', - tier: 'premium', - }, - { - id: 'amazon-bedrock/deepseek.v3-v1:0', - label: 'DeepSeek V3 (Bedrock)', - description: 'DeepSeek V3 via AWS Bedrock', - supportsVision: false, - provider: 'amazon-bedrock-deepseek', - tier: 'standard', - }, - - // Amazon Bedrock - Amazon Nova Models - { - id: 'amazon-bedrock/amazon.nova-premier-v1:0', - label: 'Amazon Nova Premier (Bedrock)', - description: 'Amazon Nova Premier - most capable Nova model', - supportsVision: true, - provider: 'amazon-bedrock-amazon', - tier: 'premium', - }, - { - id: 'amazon-bedrock/amazon.nova-pro-v1:0', - label: 'Amazon Nova Pro (Bedrock)', - description: 'Amazon Nova Pro - balanced performance', - supportsVision: true, - provider: 'amazon-bedrock-amazon', - tier: 'standard', - }, - { - id: 'amazon-bedrock/amazon.nova-lite-v1:0', - label: 'Amazon Nova Lite (Bedrock)', - description: 'Amazon Nova Lite - fast and efficient', - supportsVision: true, - provider: 'amazon-bedrock-amazon', - tier: 'standard', - }, - - // Amazon Bedrock - Meta Llama Models - { - id: 'amazon-bedrock/meta.llama4-maverick-17b-instruct-v1:0', - label: 'Llama 4 Maverick 17B (Bedrock)', - description: 'Meta Llama 4 Maverick via AWS Bedrock', - supportsVision: false, - provider: 'amazon-bedrock-meta', - tier: 'standard', - }, - { - id: 'amazon-bedrock/meta.llama3-3-70b-instruct-v1:0', - label: 'Llama 3.3 70B (Bedrock)', - description: 'Meta Llama 3.3 70B via AWS Bedrock', - supportsVision: false, - provider: 'amazon-bedrock-meta', - tier: 'standard', - }, - - // Amazon Bedrock - Mistral Models - { - id: 'amazon-bedrock/mistral.mistral-large-2402-v1:0', - label: 'Mistral Large (Bedrock)', - description: 'Mistral Large via AWS Bedrock', - supportsVision: false, - provider: 'amazon-bedrock-mistral', - tier: 'standard', - }, - - // Amazon Bedrock - Qwen Models - { - id: 'amazon-bedrock/qwen.qwen3-coder-480b-a35b-v1:0', - label: 'Qwen3 Coder 480B (Bedrock)', - description: 'Qwen3 Coder 480B via AWS Bedrock - excellent for coding', - supportsVision: false, - provider: 'amazon-bedrock-qwen', - tier: 'premium', - }, - { - id: 'amazon-bedrock/qwen.qwen3-235b-a22b-2507-v1:0', - label: 'Qwen3 235B (Bedrock)', - description: 'Qwen3 235B via AWS Bedrock', - supportsVision: false, - provider: 'amazon-bedrock-qwen', - tier: 'premium', - }, ]; /** @@ -319,10 +102,9 @@ export const OPENCODE_MODEL_CONFIG_MAP: Record