Skip to content

Commit f761431

Browse files
committed
Add Novita AI provider
1 parent 0f1054e commit f761431

46 files changed

Lines changed: 791 additions & 6 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ 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
5960
- **OpenCode-Go improvements** — native model parameters, Anthropic-format routing, and a context-token fix for more reliable responses
6061
- **Tool-writer mode** — a new specialized mode for writing and maintaining tool definitions, now available in the Marketplace
6162
- **LiteLLM session header** — forward taskId as X-Zoo-Session-ID request header for better request tracing

apps/cli/README.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ 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=...
7981

8082
roo "What is this project?" -w ~/Documents/my-project
8183
```
@@ -160,7 +162,7 @@ If you never used Roo Code Router, you can ignore this section entirely.
160162
| `-d, --debug` | Enable debug output (includes detailed debug information, prompts, paths, etc) | `false` |
161163
| `-a, --require-approval` | Require manual approval before actions execute | `false` |
162164
| `-k, --api-key <key>` | API key for the LLM provider | From env var |
163-
| `--provider <provider>` | API provider (anthropic, openai-native, gemini, openrouter, vercel-ai-gateway) | `openrouter` |
165+
| `--provider <provider>` | API provider (anthropic, openai-native, gemini, openrouter, novita, vercel-ai-gateway) | `openrouter` |
164166
| `-m, --model <model>` | Model to use | `anthropic/claude-opus-4.6` |
165167
| `--mode <mode>` | Mode to start in (code, architect, ask, debug, etc.) | `code` |
166168
| `--terminal-shell <path>` | Absolute shell path for inline terminal command execution | Auto-detected shell |
@@ -186,6 +188,7 @@ The CLI will look for API keys in environment variables if not provided via `--a
186188
| anthropic | `ANTHROPIC_API_KEY` |
187189
| openai-native | `OPENAI_API_KEY` |
188190
| openrouter | `OPENROUTER_API_KEY` |
191+
| novita | `NOVITA_API_KEY` |
189192
| gemini | `GOOGLE_API_KEY` |
190193
| vercel-ai-gateway | `VERCEL_AI_GATEWAY_API_KEY` |
191194

@@ -234,6 +237,10 @@ The CLI will look for API keys in environment variables if not provided via `--a
234237
# Run directly from source (no build required)
235238
pnpm dev --provider openrouter --api-key $OPENROUTER_API_KEY --print "Hello"
236239

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+
pnpm dev --provider novita --api-key $NOVITA_API_KEY --model moonshotai/kimi-k2.7-code --print "Hello"
243+
237244
# Run tests
238245
pnpm test
239246

apps/cli/src/lib/utils/__tests__/provider.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ describe("getApiKeyFromEnv", () => {
2222
expect(getApiKeyFromEnv("openrouter")).toBe("test-openrouter-key")
2323
})
2424

25+
it("should return API key from environment variable for novita", () => {
26+
process.env.NOVITA_API_KEY = "test-novita-key"
27+
expect(getApiKeyFromEnv("novita")).toBe("test-novita-key")
28+
})
29+
2530
it("should return API key from environment variable for openai", () => {
2631
process.env.OPENAI_API_KEY = "test-openai-key"
2732
expect(getApiKeyFromEnv("openai-native")).toBe("test-openai-key")

apps/cli/src/lib/utils/provider.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const envVarMap: Record<SupportedProvider, string> = {
77
"openai-native": "OPENAI_API_KEY",
88
gemini: "GOOGLE_API_KEY",
99
openrouter: "OPENROUTER_API_KEY",
10+
novita: "NOVITA_API_KEY",
1011
"vercel-ai-gateway": "VERCEL_AI_GATEWAY_API_KEY",
1112
}
1213

@@ -43,6 +44,10 @@ export function getProviderSettings(
4344
if (apiKey) config.openRouterApiKey = apiKey
4445
if (model) config.openRouterModelId = model
4546
break
47+
case "novita":
48+
if (apiKey) config.novitaApiKey = apiKey
49+
if (model) config.apiModelId = model
50+
break
4651
case "vercel-ai-gateway":
4752
if (apiKey) config.vercelAiGatewayApiKey = apiKey
4853
if (model) config.vercelAiGatewayModelId = model

apps/cli/src/types/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export const supportedProviders = [
66
"openai-native",
77
"gemini",
88
"openrouter",
9+
"novita",
910
"vercel-ai-gateway",
1011
] as const satisfies ProviderName[]
1112

apps/vscode-e2e/AGENTS.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,25 @@ After converting the generated `openai-*.json` files into stable named fixtures,
229229
USE_MOCK=true TEST_FILE=deepseek-v4.test pnpm --filter @roo-code/vscode-e2e test:run
230230
```
231231
232+
### Novita AI (`suite/providers/novita.test.ts`)
233+
234+
Novita exposes an OpenAI-compatible endpoint, so the suite redirects the provider through aimock with
235+
`novitaBaseUrl: ${AIMOCK_URL}/v1`. The default model is `moonshotai/kimi-k2.7-code`; override it with
236+
`NOVITA_MODEL_ID` only when refreshing matching fixtures.
237+
238+
Record Novita fixtures with the targeted file filter so aimock proxies OpenAI-compatible traffic to
239+
`https://api.novita.ai/openai`:
240+
241+
```sh
242+
NOVITA_API_KEY=<key> TEST_FILE=novita.test pnpm --filter @roo-code/vscode-e2e test:record
243+
```
244+
245+
After converting generated `openai-*.json` files into `fixtures/novita.json`, verify in mock mode:
246+
247+
```sh
248+
USE_MOCK=true TEST_FILE=novita.test pnpm --filter @roo-code/vscode-e2e test:run
249+
```
250+
232251
## Tests that use a non-default provider
233252
234253
If your test calls `api.setConfiguration({ apiProvider: "anthropic", ... })`, point aimock at the
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"fixtures": [
3+
{
4+
"match": {
5+
"model": "moonshotai/kimi-k2.7-code",
6+
"userMessage": "novita-e2e:tool-use",
7+
"sequenceIndex": 0
8+
},
9+
"response": {
10+
"toolCalls": [
11+
{
12+
"name": "read_file",
13+
"arguments": "{\"path\":\"novita-e2e-marker.txt\"}",
14+
"id": "call_novita_read"
15+
}
16+
]
17+
}
18+
},
19+
{
20+
"match": {
21+
"model": "moonshotai/kimi-k2.7-code",
22+
"toolCallId": "call_novita_read"
23+
},
24+
"response": {
25+
"toolCalls": [
26+
{
27+
"name": "attempt_completion",
28+
"arguments": "{\"result\":\"NOVITA_E2E_MARKER\"}",
29+
"id": "call_novita_done"
30+
}
31+
]
32+
}
33+
}
34+
]
35+
}

apps/vscode-e2e/src/runTest.ts

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,14 @@ function isDeepSeekTargetedRun(testFile?: string, testGrep?: string) {
2828
return testGrep?.toLowerCase().includes("deepseek") ?? false
2929
}
3030

31+
function isNovitaTargetedRun(testFile?: string, testGrep?: string) {
32+
if (testFile?.toLowerCase().includes("novita.test")) {
33+
return true
34+
}
35+
36+
return testGrep?.toLowerCase().includes("novita") ?? false
37+
}
38+
3139
function isBedrockTargetedRun(testFile?: string, testGrep?: string) {
3240
if (testFile?.toLowerCase().includes("bedrock.test")) {
3341
return true
@@ -41,28 +49,35 @@ async function main() {
4149
const testGrep = getCliFlagValue("--grep") || process.env.TEST_GREP
4250
const testFile = getCliFlagValue("--file") || process.env.TEST_FILE
4351
const isDeepSeekTest = isDeepSeekTargetedRun(testFile, testGrep)
52+
const isNovitaTest = isNovitaTargetedRun(testFile, testGrep)
4453
const isGeminiTest = testFile?.toLowerCase().includes("gemini.test") ?? false
4554
const isBedrockTest = isBedrockTargetedRun(testFile, testGrep)
4655

4756
if (isRecord && isDeepSeekTest && !process.env.DEEPSEEK_API_KEY) {
4857
throw new Error("AIMOCK_RECORD=true requires DEEPSEEK_API_KEY to record DeepSeek fixtures")
4958
}
5059

60+
if (isRecord && isNovitaTest && !process.env.NOVITA_API_KEY) {
61+
throw new Error("AIMOCK_RECORD=true requires NOVITA_API_KEY to record Novita fixtures")
62+
}
63+
5164
if (isRecord && isGeminiTest && !process.env.GEMINI_API_KEY && !process.env.GOOGLE_API_KEY) {
5265
throw new Error("AIMOCK_RECORD=true requires GEMINI_API_KEY to record Gemini fixtures")
5366
}
5467

55-
if (isRecord && !isDeepSeekTest && !isGeminiTest && !process.env.OPENROUTER_API_KEY) {
68+
if (isRecord && !isDeepSeekTest && !isNovitaTest && !isGeminiTest && !process.env.OPENROUTER_API_KEY) {
5669
throw new Error("AIMOCK_RECORD=true requires OPENROUTER_API_KEY to record fixtures")
5770
}
5871

5972
// Record mode always needs aimock running (to capture traffic).
6073
// Replay mode starts aimock when no real API key is present or USE_MOCK is forced.
6174
const hasRealApiKey = isDeepSeekTest
6275
? !!process.env.DEEPSEEK_API_KEY
63-
: isBedrockTest
64-
? true // Bedrock test starts its own binary-event-stream mock server when no real token
65-
: !!(process.env.OPENROUTER_API_KEY || process.env.ANTHROPIC_API_KEY)
76+
: isNovitaTest
77+
? !!process.env.NOVITA_API_KEY
78+
: isBedrockTest
79+
? true // Bedrock test starts its own binary-event-stream mock server when no real token
80+
: !!(process.env.OPENROUTER_API_KEY || process.env.ANTHROPIC_API_KEY)
6681
const useMock = isRecord || !hasRealApiKey || process.env.USE_MOCK === "true"
6782

6883
let mock: InstanceType<typeof LLMock> | undefined
@@ -93,7 +108,11 @@ async function main() {
93108
// Use /api (not /api/v1) — aimock appends the request path (/v1/chat/completions)
94109
// so including /v1 here would produce a doubled /v1/v1 upstream URL.
95110
providers: {
96-
openai: isDeepSeekTest ? "https://api.deepseek.com" : "https://openrouter.ai/api",
111+
openai: isDeepSeekTest
112+
? "https://api.deepseek.com"
113+
: isNovitaTest
114+
? "https://api.novita.ai/openai"
115+
: "https://openrouter.ai/api",
97116
// aimock forwards the x-api-key header from the Anthropic SDK to the real API.
98117
anthropic: "https://api.anthropic.com",
99118
// aimock forwards the x-goog-api-key header from the Google AI SDK.

0 commit comments

Comments
 (0)