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