diff --git a/packages/app/src/hooks/use-providers.ts b/packages/app/src/hooks/use-providers.ts index 95260fc766d..33897db54f8 100644 --- a/packages/app/src/hooks/use-providers.ts +++ b/packages/app/src/hooks/use-providers.ts @@ -4,7 +4,16 @@ import { useParams } from "@solidjs/router" import { createMemo } from "solid-js" // kilocode_change start - Preferred providers list (order determines display priority) -export const popularProviders = ["kilo", "anthropic", "github-copilot", "openai", "google", "openrouter", "vercel"] +export const popularProviders = [ + "kilo", + "anthropic", + "github-copilot", + "openai", + "google", + "openrouter", + "fastrouter", + "vercel", +] // kilocode_change end const popularProviderSet = new Set(popularProviders) diff --git a/packages/kilo-docs/lib/nav/ai-providers.ts b/packages/kilo-docs/lib/nav/ai-providers.ts index bb9f109ae75..e6809e87c22 100644 --- a/packages/kilo-docs/lib/nav/ai-providers.ts +++ b/packages/kilo-docs/lib/nav/ai-providers.ts @@ -28,6 +28,7 @@ export const AiProvidersNav: NavSection[] = [ title: "AI Gateways", links: [ { href: "/ai-providers/openrouter", children: "OpenRouter" }, + { href: "/ai-providers/fastrouter", children: "FastRouter" }, { href: "/ai-providers/glama", children: "Glama" }, { href: "/ai-providers/requesty", children: "Requesty" }, { href: "/ai-providers/unbound", children: "Unbound" }, diff --git a/packages/kilo-docs/lychee.toml b/packages/kilo-docs/lychee.toml index f86612c02b0..d91cc78a2ed 100644 --- a/packages/kilo-docs/lychee.toml +++ b/packages/kilo-docs/lychee.toml @@ -34,6 +34,7 @@ exclude = [ '^https?://vercel\.link/', # API base URL, returns 404 when fetched directly '^https?://api\.apertis\.ai/v1/?$', + '^https?://go\.fastrouter\.ai/api/v1/?$', # Redirects to authenticated Google Cloud console '^https?://console\.cloud\.google\.com', # Consistently times out in CI diff --git a/packages/kilo-docs/pages/ai-providers/fastrouter.md b/packages/kilo-docs/pages/ai-providers/fastrouter.md new file mode 100644 index 00000000000..1e0bf3042de --- /dev/null +++ b/packages/kilo-docs/pages/ai-providers/fastrouter.md @@ -0,0 +1,50 @@ +--- +sidebar_label: FastRouter +--- + +# Using FastRouter With Kilo Code + +FastRouter is an LLM gateway that provides access to 150+ language models from providers like Anthropic, OpenAI, Google, and more — all through a single OpenAI-compatible API. It adds smart routing, observability, cost tracking, and fallback handling on top of the underlying providers. + +**Website:** [https://fastrouter.ai/](https://fastrouter.ai/) + +## Getting an API Key + +1. **Sign Up/Sign In:** Go to the [FastRouter website](https://fastrouter.ai/) and create an account. +2. **Get an API Key:** Navigate to your dashboard and generate a new API key. +3. **Copy the Key:** Copy the displayed API key. + +## Supported Models + +Kilo Code automatically fetches the full list of available models from the FastRouter API. FastRouter provides access to models from: + +- **Anthropic:** Claude 4, Claude 3.5 Sonnet, Claude Haiku, and more +- **OpenAI:** GPT-4o, GPT-4.1, o3, o4-mini, and more +- **Google:** Gemini 2.5 Pro, Gemini 2.5 Flash, and more +- **Meta, Mistral, DeepSeek, and many other open-source and proprietary models** + +Refer to the [FastRouter models page](https://go.fastrouter.ai/api/v1/models) for the full up-to-date list. + +## Configuration in Kilo Code + +1. **Open Kilo Code Settings:** Click the gear icon ({% codicon name="gear" /%}) in the Kilo Code panel. +2. **Select Provider:** Choose "FastRouter" from the "API Provider" dropdown. +3. **Enter API Key:** Paste your FastRouter API key into the "FastRouter API Key" field. +4. **Select Model:** Choose your desired model from the "Model" dropdown. Models are grouped by the underlying provider. + +Alternatively, set the `FASTROUTER_API_KEY` environment variable before launching Kilo Code and the provider will be configured automatically. + +## Smart Routing + +FastRouter routes each request to the best available inference backend based on availability, latency, and cost. This means: + +- **Automatic failover** — if one backend is down, FastRouter retries on another +- **Lower latency** — requests are routed to the fastest available provider for the chosen model +- **Cost optimization** — routing can be tuned to prefer lower-cost backends + +## Tips and Notes + +- **Model IDs:** FastRouter uses the format `provider/model-name` (e.g., `anthropic/claude-haiku-4.5`, `openai/gpt-4o`), the same convention as OpenRouter. +- **Pricing:** Pricing follows the underlying model's cost. See the [FastRouter documentation](https://docs.fastrouter.ai) for details. +- **Multimodal support:** Many FastRouter models support image, audio, and video inputs in addition to text. Model capabilities are detected automatically. +- **Streaming:** FastRouter fully supports streaming responses, which Kilo Code uses by default. diff --git a/packages/kilo-docs/pages/ai-providers/index.md b/packages/kilo-docs/pages/ai-providers/index.md index f1598b785e5..80e3bfd3c7a 100644 --- a/packages/kilo-docs/pages/ai-providers/index.md +++ b/packages/kilo-docs/pages/ai-providers/index.md @@ -38,6 +38,7 @@ Run models on your own hardware for privacy and offline use: Route requests through unified APIs with additional features: - **[OpenRouter](/docs/ai-providers/openrouter)** - Access multiple providers through one API +- **[FastRouter](/docs/ai-providers/fastrouter)** - LLM gateway with smart routing and observability - **[Glama](/docs/ai-providers/glama)** - Enterprise AI gateway - **[Requesty](/docs/ai-providers/requesty)** - Smart routing and fallbacks diff --git a/packages/kilo-docs/source-links.md b/packages/kilo-docs/source-links.md index ab260f2cfa9..8789ca5bd28 100644 --- a/packages/kilo-docs/source-links.md +++ b/packages/kilo-docs/source-links.md @@ -1,7 +1,7 @@ # Source Code Links - + - @@ -65,6 +65,10 @@ - +- + +- + - - diff --git a/packages/opencode/src/kilocode/cli/cmd/tui/component/dialog-provider.tsx b/packages/opencode/src/kilocode/cli/cmd/tui/component/dialog-provider.tsx index 7f26418a121..cb35092ecf8 100644 --- a/packages/opencode/src/kilocode/cli/cmd/tui/component/dialog-provider.tsx +++ b/packages/opencode/src/kilocode/cli/cmd/tui/component/dialog-provider.tsx @@ -21,6 +21,8 @@ export const PROVIDER_PRIORITY: Record = { "github-copilot": 1, openai: 2, google: 3, + openrouter: 4, + fastrouter: 5, } // --------------------------------------------------------------------------- diff --git a/packages/opencode/src/kilocode/provider/provider.ts b/packages/opencode/src/kilocode/provider/provider.ts index da6ea19b1fe..2498b941f2e 100644 --- a/packages/opencode/src/kilocode/provider/provider.ts +++ b/packages/opencode/src/kilocode/provider/provider.ts @@ -111,6 +111,24 @@ export function kiloCustomLoaders(dep: CustomDep): Record options: {}, }), + fastrouter: Effect.fnUntraced(function* (input: any) { + const env = Env.all() + const apiKey = yield* Effect.gen(function* () { + const envKey = input.env.map((e: string) => env[e]).find(Boolean) + if (envKey) return envKey + const auth = yield* dep.auth(input.id) + if (auth?.type === "api") return auth.key + return (yield* dep.config()).provider?.["fastrouter"]?.options?.apiKey + }) + return { + autoload: !!apiKey && Object.keys(input.models).length > 0, + options: { + ...(apiKey ? { apiKey } : {}), + headers: DEFAULT_HEADERS, + }, + } + }), + kilo: Effect.fnUntraced(function* (input: any) { const env = Env.all() const hasKey = yield* Effect.gen(function* () { @@ -182,6 +200,7 @@ export function patchCustomLoaderResult(providerID: string, result: { options?: break } case "openrouter": + case "fastrouter": case "vercel": case "zenmux": result.options.headers = { ...result.options.headers, ...DEFAULT_HEADERS } diff --git a/packages/opencode/src/provider/model-cache.ts b/packages/opencode/src/provider/model-cache.ts index bd39b227ad2..fd518289139 100644 --- a/packages/opencode/src/provider/model-cache.ts +++ b/packages/opencode/src/provider/model-cache.ts @@ -168,6 +168,9 @@ export namespace ModelCache { if (providerID === "apertis") { return fetchApertisModels(options) } + if (providerID === "fastrouter") { + return fetchFastRouterModels() + } // kilocode_change end // Other providers not implemented yet @@ -225,6 +228,52 @@ export namespace ModelCache { return models } + + async function fetchFastRouterModels(): Promise> { + const resp = await globalThis + .fetch("https://go.fastrouter.ai/api/v1/models", { + signal: AbortSignal.timeout(10 * 1000), + }) + .catch((err) => { + log.error("fastrouter models fetch failed", { err }) + return undefined + }) + if (!resp) return {} + if (!resp.ok) { + log.error("fastrouter models fetch failed", { status: resp.status }) + return {} + } + const data = await resp.json() + const models: Record = {} + for (const m of data.data ?? []) { + if (!m.id) continue + const modality: string = m.architecture?.modality ?? "text->text" + const [rawInput = "text", rawOutput = "text"] = modality.split("->") + const allowed = ["text", "image", "audio", "video", "pdf"] + const inputs = rawInput.split("+").filter((p: string) => allowed.includes(p)) + const outputs = rawOutput.split("+").filter((p: string) => allowed.includes(p)) + models[m.id] = { + id: m.id, + name: m.name || m.id, + release_date: "", + attachment: inputs.includes("image"), + reasoning: false, + temperature: true, + tool_call: true, + cost: { + input: parseFloat(m.pricing?.prompt || "0") * 1_000_000, + output: parseFloat(m.pricing?.completion || "0") * 1_000_000, + }, + limit: { + context: m.context_length || 128_000, + output: m.max_completion_tokens || 16_384, + }, + modalities: { input: inputs, output: outputs }, + options: {}, + } + } + return models + } // kilocode_change end /** diff --git a/packages/opencode/src/provider/models.ts b/packages/opencode/src/provider/models.ts index 703ea772b24..e6dab0d72ef 100644 --- a/packages/opencode/src/provider/models.ts +++ b/packages/opencode/src/provider/models.ts @@ -253,6 +253,29 @@ export namespace ModelsDev { } } + // kilocode_change start + const frAllowed = (!enabled || enabled.has("fastrouter")) && !disabled.has("fastrouter") + if (frAllowed) { + const priorFr = providers["fastrouter"] + const snapshotModels = priorFr?.models && Object.keys(priorFr.models).length > 0 ? priorFr.models : {} + let frModels = await ModelCache.fetch("fastrouter").catch(() => ({})) + if (Object.keys(frModels).length === 0) { + frModels = await ModelCache.refresh("fastrouter").catch(() => ({})) + } + const frModelsFinal = Object.keys(frModels).length > 0 ? frModels : snapshotModels + providers["fastrouter"] = { + id: "fastrouter", + name: "FastRouter", + env: ["FASTROUTER_API_KEY"], + api: "https://go.fastrouter.ai/api/v1/", + npm: "@openrouter/ai-sdk-provider", + models: frModelsFinal, + } + } else if (providers["fastrouter"]) { + delete providers["fastrouter"] + } + // kilocode_change end + return providers // kilocode_change end } diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts index 72deb39d045..ddde56d787c 100644 --- a/packages/opencode/src/provider/transform.ts +++ b/packages/opencode/src/provider/transform.ts @@ -921,14 +921,18 @@ export namespace ProviderTransform { } return { thinkingConfig: { thinkingBudget: 0 } } } - if (model.providerID === "openrouter" || model.api.npm === "@kilocode/kilo-gateway") { - // kilocode_change - add Kilo Gateway support + // kilocode_change start + if ( + model.providerID === "openrouter" || + model.providerID === "fastrouter" || + model.api.npm === "@kilocode/kilo-gateway" + ) { if (model.api.id.includes("google")) { return { reasoning: { enabled: false } } } - // Other models use reasoningEffort (AI SDK format) return { reasoningEffort: "minimal" } } + // kilocode_change end if (model.providerID === "venice") { return { veniceParameters: { disableThinking: true } }