summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorRAMA <[email protected]>2026-02-18 10:52:21 +1100
committerGitHub <[email protected]>2026-02-17 17:52:21 -0600
commitad92181fa7fad0d81bce055a2a601072af6b38a9 (patch)
tree89824afef8c7be6de5f0c0bcc607bdcdff8b1c3c
parentc56f4aa5d85df55f7c447821b07ee4b88d9b1d73 (diff)
downloadopencode-ad92181fa7fad0d81bce055a2a601072af6b38a9.tar.gz
opencode-ad92181fa7fad0d81bce055a2a601072af6b38a9.zip
feat: add Kilo as a native provider (#13765)
-rw-r--r--packages/opencode/src/provider/provider.ts12
-rw-r--r--packages/opencode/test/provider/provider.test.ts187
2 files changed, 199 insertions, 0 deletions
diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts
index cdbad6637..ecb932001 100644
--- a/packages/opencode/src/provider/provider.ts
+++ b/packages/opencode/src/provider/provider.ts
@@ -578,6 +578,18 @@ export namespace Provider {
},
}
},
+ kilo: async () => {
+ return {
+ autoload: true,
+ options: {
+ baseURL: "https://api.kilo.ai/api/gateway",
+ headers: {
+ "HTTP-Referer": "https://opencode.ai/",
+ "X-Title": "opencode",
+ },
+ },
+ }
+ },
}
export const Model = z
diff --git a/packages/opencode/test/provider/provider.test.ts b/packages/opencode/test/provider/provider.test.ts
index 0a5aa4151..c7f8a5ede 100644
--- a/packages/opencode/test/provider/provider.test.ts
+++ b/packages/opencode/test/provider/provider.test.ts
@@ -2218,3 +2218,190 @@ test("Google Vertex: supports OpenAI compatible models", async () => {
},
})
})
+
+test("kilo provider loaded from config with env var", async () => {
+ await using tmp = await tmpdir({
+ init: async (dir) => {
+ await Bun.write(
+ path.join(dir, "opencode.json"),
+ JSON.stringify({
+ $schema: "https://opencode.ai/config.json",
+ provider: {
+ kilo: {
+ name: "Kilo",
+ npm: "@ai-sdk/openai-compatible",
+ env: ["KILO_API_KEY"],
+ api: "https://api.kilo.ai/api/gateway",
+ models: {
+ "anthropic/claude-sonnet-4-20250514": {
+ name: "Claude Sonnet 4 (via Kilo)",
+ tool_call: true,
+ attachment: true,
+ temperature: true,
+ limit: { context: 200000, output: 16384 },
+ },
+ },
+ },
+ },
+ }),
+ )
+ },
+ })
+ await Instance.provide({
+ directory: tmp.path,
+ init: async () => {
+ Env.set("KILO_API_KEY", "test-kilo-key")
+ },
+ fn: async () => {
+ const providers = await Provider.list()
+ expect(providers["kilo"]).toBeDefined()
+ expect(providers["kilo"].source).toBe("config")
+ expect(providers["kilo"].options.baseURL).toBe(
+ "https://api.kilo.ai/api/gateway",
+ )
+ expect(providers["kilo"].options.headers).toBeDefined()
+ expect(providers["kilo"].options.headers["HTTP-Referer"]).toBe(
+ "https://opencode.ai/",
+ )
+ expect(providers["kilo"].options.headers["X-Title"]).toBe("opencode")
+ const model =
+ providers["kilo"].models["anthropic/claude-sonnet-4-20250514"]
+ expect(model).toBeDefined()
+ expect(model.name).toBe("Claude Sonnet 4 (via Kilo)")
+ },
+ })
+})
+
+test("kilo provider loaded from config without env var still has custom loader options", async () => {
+ await using tmp = await tmpdir({
+ init: async (dir) => {
+ await Bun.write(
+ path.join(dir, "opencode.json"),
+ JSON.stringify({
+ $schema: "https://opencode.ai/config.json",
+ provider: {
+ kilo: {
+ name: "Kilo",
+ npm: "@ai-sdk/openai-compatible",
+ env: ["KILO_API_KEY"],
+ api: "https://api.kilo.ai/api/gateway",
+ models: {
+ "anthropic/claude-sonnet-4-20250514": {
+ name: "Claude Sonnet 4 (via Kilo)",
+ tool_call: true,
+ attachment: true,
+ temperature: true,
+ limit: { context: 200000, output: 16384 },
+ },
+ },
+ },
+ },
+ }),
+ )
+ },
+ })
+ await Instance.provide({
+ directory: tmp.path,
+ fn: async () => {
+ const providers = await Provider.list()
+ expect(providers["kilo"]).toBeDefined()
+ expect(providers["kilo"].source).toBe("config")
+ expect(providers["kilo"].options.baseURL).toBe(
+ "https://api.kilo.ai/api/gateway",
+ )
+ expect(providers["kilo"].options.headers["HTTP-Referer"]).toBe(
+ "https://opencode.ai/",
+ )
+ expect(providers["kilo"].options.headers["X-Title"]).toBe("opencode")
+ },
+ })
+})
+
+test("kilo provider config options deeply merged with custom loader", async () => {
+ await using tmp = await tmpdir({
+ init: async (dir) => {
+ await Bun.write(
+ path.join(dir, "opencode.json"),
+ JSON.stringify({
+ $schema: "https://opencode.ai/config.json",
+ provider: {
+ kilo: {
+ name: "Kilo",
+ npm: "@ai-sdk/openai-compatible",
+ env: ["KILO_API_KEY"],
+ api: "https://api.kilo.ai/api/gateway",
+ options: {
+ apiKey: "custom-key-from-config",
+ },
+ models: {
+ "openai/gpt-4o": {
+ name: "GPT-4o (via Kilo)",
+ tool_call: true,
+ limit: { context: 128000, output: 16384 },
+ },
+ },
+ },
+ },
+ }),
+ )
+ },
+ })
+ await Instance.provide({
+ directory: tmp.path,
+ init: async () => {
+ Env.set("KILO_API_KEY", "test-kilo-key")
+ },
+ fn: async () => {
+ const providers = await Provider.list()
+ expect(providers["kilo"]).toBeDefined()
+ expect(providers["kilo"].options.headers["HTTP-Referer"]).toBe(
+ "https://opencode.ai/",
+ )
+ expect(providers["kilo"].options.apiKey).toBe("custom-key-from-config")
+ expect(providers["kilo"].models["openai/gpt-4o"]).toBeDefined()
+ expect(providers["kilo"].models["openai/gpt-4o"].name).toBe(
+ "GPT-4o (via Kilo)",
+ )
+ },
+ })
+})
+
+test("kilo provider with api key set via config apiKey", async () => {
+ await using tmp = await tmpdir({
+ init: async (dir) => {
+ await Bun.write(
+ path.join(dir, "opencode.json"),
+ JSON.stringify({
+ $schema: "https://opencode.ai/config.json",
+ provider: {
+ kilo: {
+ name: "Kilo",
+ npm: "@ai-sdk/openai-compatible",
+ env: ["KILO_API_KEY"],
+ api: "https://api.kilo.ai/api/gateway",
+ options: {
+ apiKey: "config-api-key",
+ },
+ models: {
+ "anthropic/claude-sonnet-4-20250514": {
+ name: "Claude Sonnet 4 (via Kilo)",
+ tool_call: true,
+ limit: { context: 200000, output: 16384 },
+ },
+ },
+ },
+ },
+ }),
+ )
+ },
+ })
+ await Instance.provide({
+ directory: tmp.path,
+ fn: async () => {
+ const providers = await Provider.list()
+ expect(providers["kilo"]).toBeDefined()
+ expect(providers["kilo"].source).toBe("config")
+ expect(providers["kilo"].options.apiKey).toBe("config-api-key")
+ },
+ })
+})