Skip to content

Commit becbd04

Browse files
committed
Address Novita provider review feedback
1 parent dbb482a commit becbd04

9 files changed

Lines changed: 74 additions & 22 deletions

File tree

README.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@ for this exact support, so if you are having problems or if you have question, j
5656
## What's New in v3.62.0
5757

5858
- **GLM-5.2 support** — the latest GLM model is now available in your provider settings
59-
- **Novita AI support** — The AI-Native Cloud for Builders and Agents: run models, scale GPUs, and build AI agents, all on one platform
6059
- **OpenCode-Go improvements** — native model parameters, Anthropic-format routing, and a context-token fix for more reliable responses
6160
- **Tool-writer mode** — a new specialized mode for writing and maintaining tool definitions, now available in the Marketplace
6261
- **LiteLLM session header** — forward taskId as X-Zoo-Session-ID request header for better request tracing
@@ -88,7 +87,7 @@ for this exact support, so if you are having problems or if you have question, j
8887
- [简体中文](locales/zh-CN/README.md)
8988
- [繁體中文](locales/zh-TW/README.md)
9089
- ...
91-
</details>
90+
</details>
9291

9392
---
9493

apps/cli/README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,11 @@ By default, the CLI auto-approves actions and runs in interactive TUI mode:
7676

7777
```bash
7878
export OPENROUTER_API_KEY=sk-or-v1-...
79-
# or use Novita AI, the AI-native cloud for builders and agents:
80-
export NOVITA_API_KEY=...
8179

80+
roo "What is this project?" -w ~/Documents/my-project
81+
82+
# Or use Novita AI explicitly:
83+
export NOVITA_API_KEY=...
8284
roo "What is this project?" --provider novita -w ~/Documents/my-project
8385
```
8486

@@ -237,8 +239,7 @@ The CLI will look for API keys in environment variables if not provided via `--a
237239
# Run directly from source (no build required)
238240
pnpm dev --provider openrouter --api-key $OPENROUTER_API_KEY --print "Hello"
239241

240-
# Novita AI: The AI-Native Cloud for Builders and Agents.
241-
# Run models, scale GPUs, and build AI agents, all on one platform.
242+
# Novita AI (OpenAI-compatible)
242243
pnpm dev --provider novita --api-key $NOVITA_API_KEY --model moonshotai/kimi-k2.7-code --print "Hello"
243244

244245
# Run tests

apps/vscode-e2e/src/suite/providers/novita.test.ts

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -181,9 +181,11 @@ async function runNovitaToolProbe(
181181
let taskId: string | undefined
182182

183183
try {
184+
const novitaApiKey = aimockUrl && !isRecord ? "mock-key" : NOVITA_API_KEY
185+
184186
await api.setConfiguration({
185187
apiProvider: "novita" as const,
186-
novitaApiKey: aimockUrl && !isRecord ? "mock-key" : NOVITA_API_KEY!,
188+
...(novitaApiKey && { novitaApiKey }),
187189
...(aimockUrl && { novitaBaseUrl: `${aimockUrl}/v1` }),
188190
apiModelId: modelId,
189191
})
@@ -244,9 +246,7 @@ async function runNovitaToolProbe(
244246
mistakeLimitReached,
245247
completionText,
246248
usedReadFile: usedReadFile || transcript.some((line) => line.includes(fileName)),
247-
requests: requests.filter(
248-
(request) => request.model === modelId && (!request.probeTag || request.probeTag === probeTag),
249-
),
249+
requests: requests.filter((request) => request.model === modelId && request.probeTag === probeTag),
250250
transcript,
251251
},
252252
}
@@ -275,7 +275,9 @@ suite("Novita provider", function () {
275275
const requests: CapturedNovitaRequest[] = []
276276

277277
setup(function () {
278-
if (!process.env.AIMOCK_URL && !NOVITA_API_KEY) {
278+
const needsRealNovitaKey = process.env.AIMOCK_RECORD === "true" || !process.env.AIMOCK_URL
279+
280+
if (needsRealNovitaKey && !NOVITA_API_KEY) {
279281
this.skip()
280282
}
281283
})
@@ -293,13 +295,15 @@ suite("Novita provider", function () {
293295

294296
const aimockUrl = process.env.AIMOCK_URL
295297
const isRecord = process.env.AIMOCK_RECORD === "true"
298+
const openRouterApiKey = aimockUrl
299+
? isRecord
300+
? (process.env.OPENROUTER_API_KEY ?? "mock-key")
301+
: "mock-key"
302+
: process.env.OPENROUTER_API_KEY
303+
296304
await globalThis.api.setConfiguration({
297305
apiProvider: "openrouter" as const,
298-
openRouterApiKey: aimockUrl
299-
? isRecord
300-
? (process.env.OPENROUTER_API_KEY ?? "mock-key")
301-
: "mock-key"
302-
: process.env.OPENROUTER_API_KEY!,
306+
...(openRouterApiKey && { openRouterApiKey }),
303307
openRouterModelId: "openai/gpt-4.1",
304308
...(aimockUrl && { openRouterBaseUrl: `${aimockUrl}/v1` }),
305309
})
@@ -320,9 +324,6 @@ suite("Novita provider", function () {
320324
assert.strictEqual(result.aborted, false, `Novita task should not abort.\n${diagnostics}`)
321325
assert.strictEqual(result.noToolErrors, 0, `Novita should not hit MODEL_NO_TOOLS_USED.\n${diagnostics}`)
322326
assert.ok(result.usedReadFile, `Novita should use read_file for the marker file.\n${diagnostics}`)
323-
assert.ok(
324-
!result.completionText || result.completionText === marker,
325-
`Novita should not return an incorrect marker.\n${diagnostics}`,
326-
)
327+
assert.strictEqual(result.completionText, marker, `Novita should return the exact marker.\n${diagnostics}`)
327328
})
328329
})

packages/types/src/__tests__/provider-settings.test.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { getApiProtocol } from "../provider-settings.js"
1+
import { getApiProtocol, providerSettingsSchema } from "../provider-settings.js"
22
import { getProviderDefaultModelId, novitaDefaultModelId } from "../providers/index.js"
33

44
describe("getApiProtocol", () => {
@@ -94,6 +94,29 @@ describe("getApiProtocol", () => {
9494
it("returns the Novita default model ID", () => {
9595
expect(getProviderDefaultModelId("novita")).toBe(novitaDefaultModelId)
9696
})
97+
98+
it("validates Novita base URLs", () => {
99+
expect(
100+
providerSettingsSchema.safeParse({
101+
apiProvider: "novita",
102+
novitaBaseUrl: "https://api.novita.ai/openai",
103+
}).success,
104+
).toBe(true)
105+
106+
expect(
107+
providerSettingsSchema.safeParse({
108+
apiProvider: "novita",
109+
novitaBaseUrl: "",
110+
}).data?.novitaBaseUrl,
111+
).toBeUndefined()
112+
113+
expect(
114+
providerSettingsSchema.safeParse({
115+
apiProvider: "novita",
116+
novitaBaseUrl: "not-a-url",
117+
}).success,
118+
).toBe(false)
119+
})
97120
})
98121

99122
describe("Edge cases", () => {

packages/types/src/provider-settings.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,7 @@ const moonshotSchema = apiModelIdProviderModelSchema.extend({
334334
})
335335

336336
const novitaSchema = apiModelIdProviderModelSchema.extend({
337-
novitaBaseUrl: z.string().optional(),
337+
novitaBaseUrl: z.preprocess((value) => (value === "" ? undefined : value), z.string().url().optional()),
338338
novitaApiKey: z.string().optional(),
339339
})
340340

src/api/providers/__tests__/novita.spec.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,16 @@ describe("NovitaHandler", () => {
5454
expect(handler.getModel().info).toBe(novitaModels[novitaDefaultModelId])
5555
})
5656

57+
it("uses a sentinel API key when no Novita API key is configured", () => {
58+
new NovitaHandler({})
59+
60+
expect(mockCreateOpenAICompatible).toHaveBeenCalledWith(
61+
expect.objectContaining({
62+
apiKey: "not-provided",
63+
}),
64+
)
65+
})
66+
5767
it("returns the requested model ID with default model info for unknown models", () => {
5868
const handler = new NovitaHandler({
5969
...mockOptions,
@@ -73,6 +83,7 @@ describe("NovitaHandler", () => {
7383
})
7484

7585
const model = handler.getModel()
86+
expect((handler as unknown as { config: { modelMaxTokens?: number } }).config.modelMaxTokens).toBe(2048)
7687
expect(model.temperature).toBe(0.3)
7788
})
7889
})

webview-ui/src/components/settings/providers/Novita.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export const Novita = ({ apiConfiguration, setApiConfigurationField }: NovitaPro
3232
<div>
3333
<VSCodeTextField
3434
value={apiConfiguration?.novitaBaseUrl || "https://api.novita.ai/openai"}
35+
type="url"
3536
onInput={handleInputChange("novitaBaseUrl")}
3637
placeholder="https://api.novita.ai/openai"
3738
className="w-full">

webview-ui/src/components/settings/providers/__tests__/Novita.spec.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@ describe("Novita provider settings", () => {
3434
render(<Novita apiConfiguration={{} as ProviderSettings} setApiConfigurationField={vi.fn()} />)
3535

3636
expect(screen.getByLabelText("settings:providers.novitaBaseUrl")).toHaveValue("https://api.novita.ai/openai")
37+
expect(screen.getByLabelText("settings:providers.novitaBaseUrl")).toHaveAttribute("type", "url")
3738
expect(screen.getByLabelText("settings:providers.novitaApiKey")).toHaveValue("")
39+
expect(screen.getByLabelText("settings:providers.novitaApiKey")).toHaveAttribute("type", "password")
3840
expect(screen.getByRole("link", { name: "settings:providers.getNovitaApiKey" })).toHaveAttribute(
3941
"href",
4042
"https://novita.ai/settings/key-management",

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -820,5 +820,19 @@ describe("useSelectedModel", () => {
820820
expect(result.current.id).toBe("minimax/minimax-m3")
821821
expect(result.current.info).toEqual(novitaModels["minimax/minimax-m3"])
822822
})
823+
824+
it("should return undefined model info for unknown Novita model IDs", () => {
825+
const apiConfiguration: ProviderSettings = {
826+
apiProvider: "novita",
827+
apiModelId: "provider/new-model",
828+
}
829+
830+
const wrapper = createWrapper()
831+
const { result } = renderHook(() => useSelectedModel(apiConfiguration), { wrapper })
832+
833+
expect(result.current.provider).toBe("novita")
834+
expect(result.current.id).toBe("provider/new-model")
835+
expect(result.current.info).toBeUndefined()
836+
})
823837
})
824838
})

0 commit comments

Comments
 (0)