Skip to content
1 change: 1 addition & 0 deletions packages/types/src/global-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ export const globalSettingsSchema = z.object({
customSupportPrompts: customSupportPromptsSchema.optional(),
enhancementApiConfigId: z.string().optional(),
includeTaskHistoryInEnhance: z.boolean().optional(),
commitMessageApiConfigId: z.string().optional(),
historyPreviewCollapsed: z.boolean().optional(),
reasoningBlockCollapsed: z.boolean().optional(),
/**
Expand Down
1 change: 1 addition & 0 deletions packages/types/src/vscode-extension-host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,7 @@ export type ExtensionState = Pick<
| "customModePrompts"
| "customSupportPrompts"
| "enhancementApiConfigId"
| "commitMessageApiConfigId"
| "customCondensingPrompt"
| "codebaseIndexConfig"
| "codebaseIndexModels"
Expand Down
3 changes: 3 additions & 0 deletions src/core/webview/ClineProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2461,6 +2461,7 @@ export class ClineProvider
customModePrompts,
customSupportPrompts,
enhancementApiConfigId,
commitMessageApiConfigId,
Comment thread
Rafael-Silva-Oliveira marked this conversation as resolved.
autoApprovalEnabled,
customModes,
experiments,
Expand Down Expand Up @@ -2619,6 +2620,7 @@ export class ClineProvider
customModePrompts: customModePrompts ?? {},
customSupportPrompts: customSupportPrompts ?? {},
enhancementApiConfigId,
commitMessageApiConfigId,
autoApprovalEnabled: autoApprovalEnabled ?? false,
customModes,
experiments: experiments ?? experimentDefault,
Expand Down Expand Up @@ -2852,6 +2854,7 @@ export class ClineProvider
customModePrompts: stateValues.customModePrompts ?? {},
customSupportPrompts: stateValues.customSupportPrompts ?? {},
enhancementApiConfigId: stateValues.enhancementApiConfigId,
commitMessageApiConfigId: stateValues.commitMessageApiConfigId,
experiments: stateValues.experiments ?? experimentDefault,
autoApprovalEnabled: stateValues.autoApprovalEnabled ?? false,
customModes,
Expand Down
41 changes: 41 additions & 0 deletions src/core/webview/__tests__/ClineProvider.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1226,6 +1226,47 @@ describe("ClineProvider", () => {
})
})

describe("commit message model selection is included in state", () => {
// Both paths matter: the webview reads the posted state to show the current selection, and
// the generator reads getState() to pick a profile. Dropping either one makes a saved
// selection look like it reverted.
it("getStateToPostToWebview returns the saved commitMessageApiConfigId", async () => {
await provider.resolveWebviewView(mockWebviewView)
await provider.contextProxy.setValue("commitMessageApiConfigId", "config-2")

const state = await provider.getStateToPostToWebview()

expect(state.commitMessageApiConfigId).toBe("config-2")
})

it("getStateToPostToWebview leaves commitMessageApiConfigId unset when no profile is chosen", async () => {
await provider.resolveWebviewView(mockWebviewView)
await provider.contextProxy.setValue("commitMessageApiConfigId", undefined)

const state = await provider.getStateToPostToWebview()

expect(state.commitMessageApiConfigId).toBeUndefined()
})

it("getState returns the saved commitMessageApiConfigId", async () => {
await provider.resolveWebviewView(mockWebviewView)
await provider.contextProxy.setValue("commitMessageApiConfigId", "config-2")

const state = await provider.getState()

expect(state.commitMessageApiConfigId).toBe("config-2")
})

it("getState leaves commitMessageApiConfigId unset when no profile is chosen", async () => {
await provider.resolveWebviewView(mockWebviewView)
await provider.contextProxy.setValue("commitMessageApiConfigId", undefined)

const state = await provider.getState()

expect(state.commitMessageApiConfigId).toBeUndefined()
})
})

it("getStateToPostToWebview passes through defined diffFuzzyThreshold value", async () => {
await provider.resolveWebviewView(mockWebviewView)
await provider.contextProxy.setValue("diffFuzzyThreshold", 0.5)
Expand Down
1 change: 1 addition & 0 deletions src/i18n/locales/ca/common.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/i18n/locales/de/common.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/i18n/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"update_support_prompt": "Failed to update support prompt",
"reset_support_prompt": "Failed to reset support prompt",
"enhance_prompt": "Failed to enhance prompt",
"commit_message_empty_response": "The model returned an empty commit message.",
"get_system_prompt": "Failed to get system prompt",
"search_commits": "Failed to search commits",
"save_api_config": "Failed to save api configuration",
Expand Down
1 change: 1 addition & 0 deletions src/i18n/locales/es/common.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/i18n/locales/fr/common.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/i18n/locales/hi/common.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/i18n/locales/id/common.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/i18n/locales/it/common.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/i18n/locales/ja/common.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/i18n/locales/ko/common.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/i18n/locales/nl/common.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/i18n/locales/pl/common.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/i18n/locales/pt-BR/common.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/i18n/locales/ru/common.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/i18n/locales/tr/common.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/i18n/locales/vi/common.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/i18n/locales/zh-CN/common.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/i18n/locales/zh-TW/common.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

92 changes: 92 additions & 0 deletions src/services/commit-message/__tests__/config.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import type { ProviderSettings } from "@roo-code/types"

import { getCommitMessageSettings } from "../config"
import type { ClineProvider } from "../../../core/webview/ClineProvider"

describe("getCommitMessageSettings", () => {
const apiConfiguration: ProviderSettings = { apiProvider: "openai", apiKey: "key", apiModelId: "gpt-4" }

const listApiConfigMeta = [
{ id: "config1", name: "Config 1" },
{ id: "config2", name: "Config 2" },
]

const commitProfile = {
name: "Commit Config",
apiProvider: "anthropic" as const,
apiKey: "commit-key",
apiModelId: "claude-3",
}

let getProfile: ReturnType<typeof vi.fn>

// `ClineProvider` is a large concrete class, and constructing one would drag in the extension
// host. This reads the two members the function actually touches, so the double assertion is
// the narrowest way to stand in for it - widening to `unknown` first because the stub is not
// structurally assignable to the full class.
const makeProvider = (commitMessageApiConfigId?: string) =>
({
getState: vi.fn().mockResolvedValue({
apiConfiguration,
listApiConfigMeta,
customSupportPrompts: { COMMIT_MESSAGE: "custom" },
commitMessageApiConfigId,
}),
providerSettingsManager: { getProfile },
}) as unknown as ClineProvider

beforeEach(() => {
vi.clearAllMocks()
getProfile = vi.fn().mockResolvedValue(commitProfile)
})

it("uses the active configuration when no dedicated profile is chosen", async () => {
const settings = await getCommitMessageSettings(makeProvider())

expect(settings.apiConfiguration).toBe(apiConfiguration)
expect(getProfile).not.toHaveBeenCalled()
})

it("uses the dedicated profile when one is configured", async () => {
const settings = await getCommitMessageSettings(makeProvider("config2"))

expect(getProfile).toHaveBeenCalledWith({ id: "config2" })
expect(settings.apiConfiguration).toEqual({
apiProvider: "anthropic",
apiKey: "commit-key",
apiModelId: "claude-3",
})
})

it("carries the customized prompt through", async () => {
const settings = await getCommitMessageSettings(makeProvider())

expect(settings.customSupportPrompts).toEqual({ COMMIT_MESSAGE: "custom" })
})

it("falls back when the saved id is not in the known profiles", async () => {
const settings = await getCommitMessageSettings(makeProvider("deleted-config"))

expect(getProfile).not.toHaveBeenCalled()
expect(settings.apiConfiguration).toBe(apiConfiguration)
})

// The metadata check is not enough on its own: a profile can be deleted between reading the
// state and looking it up, and stale metadata points at profiles that are already gone.
it("falls back when the profile disappears between the state read and the lookup", async () => {
getProfile = vi.fn().mockRejectedValue(new Error("Profile not found"))

const settings = await getCommitMessageSettings(makeProvider("config2"))

expect(getProfile).toHaveBeenCalledWith({ id: "config2" })
expect(settings.apiConfiguration).toBe(apiConfiguration)
})

it("falls back when the saved profile has no provider configured", async () => {
getProfile = vi.fn().mockResolvedValue({ name: "Empty Config" })

const settings = await getCommitMessageSettings(makeProvider("config2"))

expect(settings.apiConfiguration).toBe(apiConfiguration)
})
})
Loading
Loading