Skip to content

Commit 60abf37

Browse files
fix(opencode-go): green CI and address review nits (#172)
- Add missing opencodeGo translation keys across all 17 non-English locales (check-translations was failing). - Include the opencode-go entry in expected routerModels for the ClineProvider and webviewMessageHandler requestRouterModels suites (unit tests were failing). - Add a request timeout to the /models fetch so it can't hang. - Fall back to the default ModelInfo in useSelectedModel when the /models list is empty, keeping capability-driven UI working. - Validate the Opencode Go API key and add validate.spec + OpenCodeGo component tests.
1 parent 12bf8a1 commit 60abf37

25 files changed

Lines changed: 176 additions & 1 deletion

File tree

src/api/providers/fetchers/__tests__/opencode-go.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ describe("Opencode Go Fetchers", () => {
3535

3636
expect(mockedAxios.get).toHaveBeenCalledWith("https://opencode.ai/zen/go/v1/models", {
3737
headers: { Authorization: "Bearer test-key" },
38+
timeout: 10_000,
3839
})
3940

4041
expect(Object.keys(models).sort()).toEqual(["deepseek-v4-pro", "glm-5.1"])

src/api/providers/fetchers/opencode-go.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export async function getOpencodeGoModels(apiKey?: string): Promise<Record<strin
4343
try {
4444
const response = await axios.get(`${OPENCODE_GO_BASE_URL}/models`, {
4545
headers: apiKey ? { Authorization: `Bearer ${apiKey}` } : undefined,
46+
timeout: 10_000,
4647
})
4748

4849
const result = opencodeGoModelsResponseSchema.safeParse(response.data)

src/core/webview/__tests__/ClineProvider.spec.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2479,6 +2479,7 @@ describe("ClineProvider - Router Models", () => {
24792479
"vercel-ai-gateway": mockModels,
24802480
poe: {},
24812481
deepseek: {},
2482+
"opencode-go": {},
24822483
},
24832484
values: undefined,
24842485
})
@@ -2525,6 +2526,7 @@ describe("ClineProvider - Router Models", () => {
25252526
"vercel-ai-gateway": mockModels,
25262527
poe: {},
25272528
deepseek: {},
2529+
"opencode-go": {},
25282530
},
25292531
values: undefined,
25302532
})
@@ -2620,6 +2622,7 @@ describe("ClineProvider - Router Models", () => {
26202622
"vercel-ai-gateway": mockModels,
26212623
poe: {},
26222624
deepseek: {},
2625+
"opencode-go": {},
26232626
},
26242627
values: undefined,
26252628
})

src/core/webview/__tests__/webviewMessageHandler.spec.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,7 @@ describe("webviewMessageHandler - requestRouterModels", () => {
371371
"vercel-ai-gateway": mockModels,
372372
poe: {},
373373
deepseek: {},
374+
"opencode-go": {},
374375
},
375376
values: undefined,
376377
})
@@ -457,6 +458,7 @@ describe("webviewMessageHandler - requestRouterModels", () => {
457458
"vercel-ai-gateway": mockModels,
458459
poe: {},
459460
deepseek: {},
461+
"opencode-go": {},
460462
},
461463
values: undefined,
462464
})
@@ -512,6 +514,7 @@ describe("webviewMessageHandler - requestRouterModels", () => {
512514
"vercel-ai-gateway": mockModels,
513515
poe: {},
514516
deepseek: {},
517+
"opencode-go": {},
515518
},
516519
values: undefined,
517520
})
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { render, screen, fireEvent } from "@testing-library/react"
2+
3+
import type { ProviderSettings, OrganizationAllowList } from "@roo-code/types"
4+
import { opencodeGoDefaultModelId } from "@roo-code/types"
5+
6+
import { OpenCodeGo } from "../OpenCodeGo"
7+
8+
vi.mock("@vscode/webview-ui-toolkit/react", () => ({
9+
VSCodeTextField: ({ children, value, onInput, type }: any) => (
10+
<div>
11+
{children}
12+
<input type={type} value={value} onChange={(e) => onInput(e)} data-testid="api-key-input" />
13+
</div>
14+
),
15+
}))
16+
17+
vi.mock("@src/i18n/TranslationContext", () => ({
18+
useAppTranslation: () => ({ t: (key: string) => key }),
19+
}))
20+
21+
vi.mock("@src/components/common/VSCodeButtonLink", () => ({
22+
VSCodeButtonLink: ({ children, href }: any) => (
23+
<a href={href} data-testid="get-api-key-link">
24+
{children}
25+
</a>
26+
),
27+
}))
28+
29+
// Stub ModelPicker so we can assert the props it receives without pulling in its hooks.
30+
vi.mock("../../ModelPicker", () => ({
31+
ModelPicker: ({ defaultModelId, modelIdKey, serviceName }: any) => (
32+
<div
33+
data-testid="model-picker"
34+
data-default-model-id={defaultModelId}
35+
data-model-id-key={modelIdKey}
36+
data-service-name={serviceName}
37+
/>
38+
),
39+
}))
40+
41+
describe("OpenCodeGo", () => {
42+
const organizationAllowList: OrganizationAllowList = { allowAll: true, providers: {} }
43+
const mockSetApiConfigurationField = vi.fn()
44+
45+
const renderComponent = (apiConfiguration: ProviderSettings) =>
46+
render(
47+
<OpenCodeGo
48+
apiConfiguration={apiConfiguration}
49+
setApiConfigurationField={mockSetApiConfigurationField}
50+
organizationAllowList={organizationAllowList}
51+
/>,
52+
)
53+
54+
beforeEach(() => {
55+
vi.clearAllMocks()
56+
})
57+
58+
it("updates the API key via setApiConfigurationField on input", () => {
59+
renderComponent({ opencodeGoApiKey: "" })
60+
61+
fireEvent.change(screen.getByTestId("api-key-input"), { target: { value: "secret-key" } })
62+
63+
expect(mockSetApiConfigurationField).toHaveBeenCalledWith("opencodeGoApiKey", "secret-key")
64+
})
65+
66+
it("shows the get-API-key CTA only when no API key is set", () => {
67+
const { rerender } = renderComponent({ opencodeGoApiKey: "" })
68+
const link = screen.getByTestId("get-api-key-link")
69+
expect(link).toBeInTheDocument()
70+
expect(link).toHaveAttribute("href", "https://opencode.ai/docs/go/")
71+
72+
rerender(
73+
<OpenCodeGo
74+
apiConfiguration={{ opencodeGoApiKey: "already-set" }}
75+
setApiConfigurationField={mockSetApiConfigurationField}
76+
organizationAllowList={organizationAllowList}
77+
/>,
78+
)
79+
expect(screen.queryByTestId("get-api-key-link")).not.toBeInTheDocument()
80+
})
81+
82+
it("wires the ModelPicker with the Opencode Go defaults", () => {
83+
renderComponent({ opencodeGoApiKey: "key" })
84+
85+
const picker = screen.getByTestId("model-picker")
86+
expect(picker).toHaveAttribute("data-default-model-id", opencodeGoDefaultModelId)
87+
expect(picker).toHaveAttribute("data-model-id-key", "opencodeGoModelId")
88+
expect(picker).toHaveAttribute("data-service-name", "Opencode Go")
89+
})
90+
})

webview-ui/src/components/ui/hooks/useSelectedModel.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
qwenCodeModels,
2828
litellmDefaultModelInfo,
2929
lMStudioDefaultModelInfo,
30+
opencodeGoDefaultModelInfo,
3031
BEDROCK_1M_CONTEXT_MODEL_IDS,
3132
VERTEX_1M_CONTEXT_MODEL_IDS,
3233
isDynamicProvider,
@@ -352,7 +353,9 @@ function getSelectedModel({
352353
routerModels["opencode-go"],
353354
defaultModelId,
354355
)
355-
const info = routerModels["opencode-go"]?.[id]
356+
// Fall back to the provider's default ModelInfo so capability-driven UI
357+
// keeps working when the /models list is empty or unavailable.
358+
const info = routerModels["opencode-go"]?.[id] ?? opencodeGoDefaultModelInfo
356359
return { id, info }
357360
}
358361
// case "anthropic":

webview-ui/src/i18n/locales/ca/settings.json

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

webview-ui/src/i18n/locales/de/settings.json

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

webview-ui/src/i18n/locales/es/settings.json

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

webview-ui/src/i18n/locales/fr/settings.json

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)