Просмотр исходного кода

feat: add Kilo as a native provider (#13765)

RAMA 1 месяц назад
Родитель
Сommit
ad92181fa7

+ 12 - 0
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

+ 187 - 0
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")
+    },
+  })
+})