feat(cli): add LiteLLM as a first-class provider

- Add "litellm" to supportedProviders in types.ts
- Add litellmBaseUrl to FlagOptions type
- Add LITELLM_API_KEY to environment variable mapping
- Add litellm case to getProviderSettings function
- Add --litellm-base-url CLI flag with default http://localhost:4000
- Add litellmBaseUrl to ExtensionHostOptions interface
- Add support for LITELLM_BASE_URL environment variable
- Add tests for LiteLLM provider functionality
- Update README with LiteLLM documentation

Closes #10839
This commit is contained in:
Roo Code 2026-01-19 20:55:09 +00:00
parent 1a1827d806
commit 103d436a42
7 changed files with 84 additions and 3 deletions

View file

@ -156,12 +156,13 @@ Tokens are valid for 90 days. The CLI will prompt you to re-authenticate when yo
| `-x, --exit-on-complete` | Exit the process when task completes (useful for testing) | `false` |
| `-y, --yes` | Non-interactive mode: auto-approve all actions | `false` |
| `-k, --api-key <key>` | API key for the LLM provider | From env var |
| `-p, --provider <provider>` | API provider (anthropic, openai, openrouter, etc.) | `openrouter` |
| `-p, --provider <provider>` | API provider (anthropic, openai, openrouter, litellm, etc.) | `openrouter` |
| `-m, --model <model>` | Model to use | `anthropic/claude-sonnet-4.5` |
| `-M, --mode <mode>` | Mode to start in (code, architect, ask, debug, etc.) | `code` |
| `-r, --reasoning-effort <effort>` | Reasoning effort level (unspecified, disabled, none, minimal, low, medium, high, xhigh) | `medium` |
| `--ephemeral` | Run without persisting state (uses temporary storage) | `false` |
| `--no-tui` | Disable TUI, use plain text output | `false` |
| `--litellm-base-url <url>` | Base URL for LiteLLM endpoint | `http://localhost:4000` |
## Auth Commands
@ -181,8 +182,16 @@ The CLI will look for API keys in environment variables if not provided via `--a
| openai | `OPENAI_API_KEY` |
| openrouter | `OPENROUTER_API_KEY` |
| google/gemini | `GOOGLE_API_KEY` |
| litellm | `LITELLM_API_KEY` |
| ... | ... |
**LiteLLM Environment Variables:**
| Variable | Description |
| ------------------ | ---------------------------------------------------------------- |
| `LITELLM_API_KEY` | API key for LiteLLM |
| `LITELLM_BASE_URL` | Base URL for LiteLLM endpoint (default: `http://localhost:4000`) |
**Authentication Environment Variables:**
| Variable | Description |

View file

@ -74,6 +74,10 @@ export interface ExtensionHostOptions {
* running in an integration test and we want to see the output.
*/
integrationTest?: boolean
/**
* Base URL for LiteLLM endpoint (only used when provider is "litellm").
*/
litellmBaseUrl?: string
}
interface ExtensionModule {
@ -191,7 +195,12 @@ export class ExtensionHost extends EventEmitter implements ExtensionHostInterfac
commandExecutionTimeout: 30,
browserToolEnabled: false,
enableCheckpoints: false,
...getProviderSettings(this.options.provider, this.options.apiKey, this.options.model),
...getProviderSettings(
this.options.provider,
this.options.apiKey,
this.options.model,
this.options.litellmBaseUrl,
),
}
this.initialSettings = this.options.nonInteractive

View file

@ -69,6 +69,12 @@ export async function run(promptArg: string | undefined, flagOptions: FlagOption
flagOptions.yes || flagOptions.dangerouslySkipPermissions || settings.dangerouslySkipPermissions || false
const effectiveExitOnComplete = flagOptions.print || flagOptions.oneshot || settings.oneshot || false
// Determine effective LiteLLM base URL: CLI flag > env var > default (when using litellm provider)
let effectiveLitellmBaseUrl: string | undefined = flagOptions.litellmBaseUrl || process.env.LITELLM_BASE_URL
if (effectiveProvider === "litellm" && !effectiveLitellmBaseUrl) {
effectiveLitellmBaseUrl = "http://localhost:4000"
}
const extensionHostOptions: ExtensionHostOptions = {
mode: effectiveMode,
reasoningEffort: effectiveReasoningEffort === "unspecified" ? undefined : effectiveReasoningEffort,
@ -81,6 +87,7 @@ export async function run(promptArg: string | undefined, flagOptions: FlagOption
ephemeral: flagOptions.ephemeral,
debug: flagOptions.debug,
exitOnComplete: effectiveExitOnComplete,
litellmBaseUrl: effectiveLitellmBaseUrl,
}
// Roo Code Cloud Authentication

View file

@ -35,6 +35,10 @@ program
'Output format (only works with --print): "text" (default), "json" (single result), or "stream-json" (realtime streaming)',
"text",
)
.option(
"--litellm-base-url <url>",
"Base URL for LiteLLM endpoint (default: http://localhost:4000 when using litellm provider)",
)
.action(run)
const authCommand = program.command("auth").description("Manage authentication for Roo Code Cloud")

View file

@ -1,4 +1,4 @@
import { getApiKeyFromEnv } from "../provider.js"
import { getApiKeyFromEnv, getProviderSettings } from "../provider.js"
describe("getApiKeyFromEnv", () => {
const originalEnv = process.env
@ -27,8 +27,51 @@ describe("getApiKeyFromEnv", () => {
expect(getApiKeyFromEnv("openai-native")).toBe("test-openai-key")
})
it("should return API key from environment variable for litellm", () => {
process.env.LITELLM_API_KEY = "test-litellm-key"
expect(getApiKeyFromEnv("litellm")).toBe("test-litellm-key")
})
it("should return undefined when API key is not set", () => {
delete process.env.ANTHROPIC_API_KEY
expect(getApiKeyFromEnv("anthropic")).toBeUndefined()
})
})
describe("getProviderSettings", () => {
it("should return LiteLLM settings with API key, model, and base URL", () => {
const settings = getProviderSettings("litellm", "test-api-key", "claude-3-sonnet", "http://localhost:4000")
expect(settings).toEqual({
apiProvider: "litellm",
litellmApiKey: "test-api-key",
litellmModelId: "claude-3-sonnet",
litellmBaseUrl: "http://localhost:4000",
})
})
it("should return LiteLLM settings without base URL when not provided", () => {
const settings = getProviderSettings("litellm", "test-api-key", "claude-3-sonnet")
expect(settings).toEqual({
apiProvider: "litellm",
litellmApiKey: "test-api-key",
litellmModelId: "claude-3-sonnet",
})
})
it("should return LiteLLM settings with only API key when model is not provided", () => {
const settings = getProviderSettings("litellm", "test-api-key", undefined)
expect(settings).toEqual({
apiProvider: "litellm",
litellmApiKey: "test-api-key",
})
})
it("should return anthropic settings correctly", () => {
const settings = getProviderSettings("anthropic", "test-api-key", "claude-3-opus")
expect(settings).toEqual({
apiProvider: "anthropic",
apiKey: "test-api-key",
apiModelId: "claude-3-opus",
})
})
})

View file

@ -9,6 +9,7 @@ const envVarMap: Record<SupportedProvider, string> = {
openrouter: "OPENROUTER_API_KEY",
"vercel-ai-gateway": "VERCEL_AI_GATEWAY_API_KEY",
roo: "ROO_API_KEY",
litellm: "LITELLM_API_KEY",
}
export function getEnvVarName(provider: SupportedProvider): string {
@ -24,6 +25,7 @@ export function getProviderSettings(
provider: SupportedProvider,
apiKey: string | undefined,
model: string | undefined,
baseUrl?: string,
): RooCodeSettings {
const config: RooCodeSettings = { apiProvider: provider }
@ -52,6 +54,11 @@ export function getProviderSettings(
if (apiKey) config.rooApiKey = apiKey
if (model) config.apiModelId = model
break
case "litellm":
if (apiKey) config.litellmApiKey = apiKey
if (model) config.litellmModelId = model
if (baseUrl) config.litellmBaseUrl = baseUrl
break
default:
if (apiKey) config.apiKey = apiKey
if (model) config.apiModelId = model

View file

@ -8,6 +8,7 @@ export const supportedProviders = [
"openrouter",
"vercel-ai-gateway",
"roo",
"litellm",
] as const satisfies ProviderName[]
export type SupportedProvider = (typeof supportedProviders)[number]
@ -34,6 +35,7 @@ export type FlagOptions = {
ephemeral: boolean
oneshot: boolean
outputFormat?: OutputFormat
litellmBaseUrl?: string
}
export enum OnboardingProviderChoice {