From c32b31c9d4288f81ddf35e276c29df5e32b1c99b Mon Sep 17 00:00:00 2001 From: Francesco Dainese Date: Fri, 11 Jul 2025 22:05:38 +0200 Subject: [PATCH 01/12] feat: add LLM Gateway support for Anthropic provider MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add support for ANTHROPIC_BASE_URL and ANTHROPIC_AUTH_TOKEN environment variables to enable LLM Gateway configuration. This allows users to route Anthropic requests through gateway services like LiteLLM for centralized authentication, usage tracking, and cost controls. 🤖 Generated with [opencode](https://opencode.ai) Co-Authored-By: opencode --- packages/opencode/src/provider/provider.ts | 29 ++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index 920db882c29b..5ca287c8f24e 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -39,6 +39,17 @@ export namespace Provider { const CUSTOM_LOADERS: Record = { async anthropic(provider) { + // Check for LLM Gateway configuration first + const baseUrl = process.env["ANTHROPIC_BASE_URL"] + const authToken = process.env["ANTHROPIC_AUTH_TOKEN"] + const apiKey = process.env["ANTHROPIC_API_KEY"] + + // If gateway configuration is present, use standard API key authentication + if (baseUrl || authToken || apiKey) { + return { autoload: false } // Let the env loader handle this + } + + // Otherwise, use OAuth authentication const access = await AuthAnthropic.access() if (!access) return { autoload: false } for (const model of Object.values(provider.models)) { @@ -298,6 +309,24 @@ export namespace Provider { // load env for (const [providerID, provider] of Object.entries(database)) { if (disabled.has(providerID)) continue + + // Handle Anthropic LLM Gateway configuration + if (providerID === "anthropic") { + const baseUrl = process.env["ANTHROPIC_BASE_URL"] + const authToken = process.env["ANTHROPIC_AUTH_TOKEN"] + const apiKey = process.env["ANTHROPIC_API_KEY"] + + if (baseUrl || authToken || apiKey) { + const options: Record = {} + if (baseUrl) options["baseURL"] = baseUrl + if (authToken) options["apiKey"] = authToken + else if (apiKey) options["apiKey"] = apiKey + + mergeProvider(providerID, options, "env") + continue + } + } + const apiKey = provider.env.map((item) => process.env[item]).at(0) if (!apiKey) continue mergeProvider( From c0c43b66c5bd13556c64b9602f57fc5311112b81 Mon Sep 17 00:00:00 2001 From: Francesco Dainese Date: Fri, 11 Jul 2025 22:13:31 +0200 Subject: [PATCH 02/12] fix: add LLM Gateway env vars to Anthropic provider env list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add ANTHROPIC_BASE_URL and ANTHROPIC_AUTH_TOKEN to the Anthropic provider's environment variables list to prevent validation errors when using LLM Gateway configuration. 🤖 Generated with [opencode](https://opencode.ai) Co-Authored-By: opencode --- flake.nix | 65 ++++++++++++++++++++++ packages/opencode/src/provider/provider.ts | 19 +++++-- 2 files changed, 78 insertions(+), 6 deletions(-) create mode 100644 flake.nix diff --git a/flake.nix b/flake.nix new file mode 100644 index 000000000000..85d4da2a6d5a --- /dev/null +++ b/flake.nix @@ -0,0 +1,65 @@ +{ + description = "Opencode development environment"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + flake-parts.url = "github:hercules-ci/flake-parts"; + systems.url = "github:nix-systems/default"; + }; + + outputs = inputs @ { flake-parts, ... }: + flake-parts.lib.mkFlake { inherit inputs; } { + systems = import inputs.systems; + + perSystem = { config, self', inputs', pkgs, system, ... }: { + devShells.default = pkgs.mkShell { + buildInputs = with pkgs; [ + # Node.js/Bun runtime and package management + bun + nodejs_22 + + # Go development + go_1_24 + + # Build tools and utilities + git + gnumake + gcc + + # Development tools + jq + ripgrep + curl + + # For building native modules if needed + python3 + pkg-config + + # Additional tools that might be useful + which + file + ]; + + shellHook = '' + echo "🚀 Opencode development environment" + echo "Node.js: $(node --version)" + echo "Bun: $(bun --version)" + echo "Go: $(go version)" + echo "" + echo "Available commands:" + echo " bun install - Install dependencies" + echo " bun run dev - Start development server" + echo " bun typecheck - Run type checking" + echo "" + ''; + + # Set environment variables + OPENCODE_DEV = "1"; + + # Ensure proper locale settings + LANG = "en_US.UTF-8"; + LC_ALL = "en_US.UTF-8"; + }; + }; + }; +} \ No newline at end of file diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index 5ca287c8f24e..1879311a0b63 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -43,12 +43,12 @@ export namespace Provider { const baseUrl = process.env["ANTHROPIC_BASE_URL"] const authToken = process.env["ANTHROPIC_AUTH_TOKEN"] const apiKey = process.env["ANTHROPIC_API_KEY"] - + // If gateway configuration is present, use standard API key authentication if (baseUrl || authToken || apiKey) { return { autoload: false } // Let the env loader handle this } - + // Otherwise, use OAuth authentication const access = await AuthAnthropic.access() if (!access) return { autoload: false } @@ -305,28 +305,35 @@ export namespace Provider { database[providerID] = parsed } + // Add LLM Gateway environment variables to Anthropic provider + if (database["anthropic"]) { + database["anthropic"].env = [ + ...new Set([...database["anthropic"].env, "ANTHROPIC_BASE_URL", "ANTHROPIC_AUTH_TOKEN"]), + ] + } + const disabled = await Config.get().then((cfg) => new Set(cfg.disabled_providers ?? [])) // load env for (const [providerID, provider] of Object.entries(database)) { if (disabled.has(providerID)) continue - + // Handle Anthropic LLM Gateway configuration if (providerID === "anthropic") { const baseUrl = process.env["ANTHROPIC_BASE_URL"] const authToken = process.env["ANTHROPIC_AUTH_TOKEN"] const apiKey = process.env["ANTHROPIC_API_KEY"] - + if (baseUrl || authToken || apiKey) { const options: Record = {} if (baseUrl) options["baseURL"] = baseUrl if (authToken) options["apiKey"] = authToken else if (apiKey) options["apiKey"] = apiKey - + mergeProvider(providerID, options, "env") continue } } - + const apiKey = provider.env.map((item) => process.env[item]).at(0) if (!apiKey) continue mergeProvider( From 2d1adf5bf123ec3073704405822ca54b85b631ae Mon Sep 17 00:00:00 2001 From: Francesco Dainese Date: Fri, 11 Jul 2025 22:18:50 +0200 Subject: [PATCH 03/12] fix: improve Anthropic LLM Gateway authentication handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add fallback API key for baseURL-only configuration - Improve environment variable detection logic - Ensure OAuth is skipped when any gateway/API config is present - Handle cases where gateway provides authentication separately 🤖 Generated with [opencode](https://opencode.ai) Co-Authored-By: opencode --- packages/opencode/src/provider/provider.ts | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index 1879311a0b63..f7c0473fd757 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -44,7 +44,7 @@ export namespace Provider { const authToken = process.env["ANTHROPIC_AUTH_TOKEN"] const apiKey = process.env["ANTHROPIC_API_KEY"] - // If gateway configuration is present, use standard API key authentication + // If any gateway/direct API configuration is present, skip OAuth if (baseUrl || authToken || apiKey) { return { autoload: false } // Let the env loader handle this } @@ -325,15 +325,27 @@ export namespace Provider { if (baseUrl || authToken || apiKey) { const options: Record = {} - if (baseUrl) options["baseURL"] = baseUrl - if (authToken) options["apiKey"] = authToken - else if (apiKey) options["apiKey"] = apiKey + + // Set base URL if provided + if (baseUrl) { + options["baseURL"] = baseUrl + } + + // Use auth token first, then API key, or placeholder if using baseURL without auth + if (authToken) { + options["apiKey"] = authToken + } else if (apiKey) { + options["apiKey"] = apiKey + } else if (baseUrl) { + // If only baseURL is set, provide a placeholder API key + // The gateway should handle authentication + options["apiKey"] = "gateway-auth" + } mergeProvider(providerID, options, "env") continue } } - const apiKey = provider.env.map((item) => process.env[item]).at(0) if (!apiKey) continue mergeProvider( From 6de6604f85f613fc48111e55eef8a3ed653d0cb8 Mon Sep 17 00:00:00 2001 From: Francesco Dainese Date: Fri, 11 Jul 2025 22:21:12 +0200 Subject: [PATCH 04/12] feat: add gateway-specific headers for LLM Gateway compatibility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add Proxy-Authorization and X-API-Key headers when using ANTHROPIC_AUTH_TOKEN to ensure compatibility with LLM gateway services like LiteLLM that expect these specific headers. 🤖 Generated with [opencode](https://opencode.ai) Co-Authored-By: opencode --- packages/opencode/src/provider/provider.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index f7c0473fd757..e051e614b6d2 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -334,6 +334,18 @@ export namespace Provider { // Use auth token first, then API key, or placeholder if using baseURL without auth if (authToken) { options["apiKey"] = authToken + // For LLM gateways, also add custom fetch to handle gateway-specific headers + options["fetch"] = async (input: any, init: any) => { + const headers = { + ...init.headers, + "Proxy-Authorization": `Bearer ${authToken}`, + "X-API-Key": authToken, + } + return fetch(input, { + ...init, + headers, + }) + } } else if (apiKey) { options["apiKey"] = apiKey } else if (baseUrl) { From f458314680e39141630f655e42b5f0764b9c622a Mon Sep 17 00:00:00 2001 From: Francesco Dainese Date: Fri, 11 Jul 2025 22:24:07 +0200 Subject: [PATCH 05/12] feat: add comprehensive debugging for Anthropic LLM Gateway MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add detailed debug logging to help troubleshoot LLM Gateway configuration: - Log environment variable detection - Log request URLs, headers, and responses - Log authentication method selection - Log provider configuration options 🤖 Generated with [opencode](https://opencode.ai) Co-Authored-By: opencode --- packages/opencode/src/provider/provider.ts | 69 ++++++++++++++++++++-- 1 file changed, 65 insertions(+), 4 deletions(-) diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index e051e614b6d2..3edef7826f70 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -46,12 +46,23 @@ export namespace Provider { // If any gateway/direct API configuration is present, skip OAuth if (baseUrl || authToken || apiKey) { + log.debug("skipping OAuth for Anthropic - using env vars", { + hasBaseUrl: !!baseUrl, + hasAuthToken: !!authToken, + hasApiKey: !!apiKey, + }) return { autoload: false } // Let the env loader handle this } // Otherwise, use OAuth authentication + log.debug("attempting OAuth authentication for Anthropic") const access = await AuthAnthropic.access() - if (!access) return { autoload: false } + if (!access) { + log.debug("OAuth access not available for Anthropic") + return { autoload: false } + } + + log.debug("OAuth access available for Anthropic") for (const model of Object.values(provider.models)) { model.cost = { input: 0, @@ -326,6 +337,13 @@ export namespace Provider { if (baseUrl || authToken || apiKey) { const options: Record = {} + log.debug("configuring Anthropic LLM Gateway", { + hasBaseUrl: !!baseUrl, + baseUrl: baseUrl || "not set", + hasAuthToken: !!authToken, + hasApiKey: !!apiKey, + }) + // Set base URL if provided if (baseUrl) { options["baseURL"] = baseUrl @@ -336,24 +354,67 @@ export namespace Provider { options["apiKey"] = authToken // For LLM gateways, also add custom fetch to handle gateway-specific headers options["fetch"] = async (input: any, init: any) => { + log.debug("making request to Anthropic gateway", { + url: typeof input === "string" ? input : input?.url, + method: init?.method || "GET", + hasBody: !!init?.body, + bodyLength: init?.body ? String(init.body).length : 0, + }) + const headers = { ...init.headers, "Proxy-Authorization": `Bearer ${authToken}`, "X-API-Key": authToken, } - return fetch(input, { - ...init, - headers, + + log.debug("request headers", { + headers: Object.fromEntries( + Object.entries(headers).map(([key, value]) => [ + key, + key.toLowerCase().includes("auth") || key.toLowerCase().includes("key") + ? `${String(value).substring(0, 10)}...` + : value, + ]), + ), }) + + try { + const response = await fetch(input, { + ...init, + headers, + }) + + log.debug("gateway response", { + status: response.status, + statusText: response.statusText, + headers: Object.fromEntries(response.headers.entries()), + }) + + return response + } catch (error) { + log.error("gateway request failed", { + error: error instanceof Error ? error.message : String(error), + stack: error instanceof Error ? error.stack : undefined, + }) + throw error + } } } else if (apiKey) { options["apiKey"] = apiKey + log.debug("using direct ANTHROPIC_API_KEY authentication") } else if (baseUrl) { // If only baseURL is set, provide a placeholder API key // The gateway should handle authentication options["apiKey"] = "gateway-auth" + log.debug("using baseURL-only configuration with placeholder auth") } + log.debug("final Anthropic provider options", { + baseURL: options["baseURL"], + hasApiKey: !!options["apiKey"], + hasFetch: !!options["fetch"], + }) + mergeProvider(providerID, options, "env") continue } From 1659fcf071c8abbe7c4edd0120fbb3184489dcbf Mon Sep 17 00:00:00 2001 From: Francesco Dainese Date: Fri, 11 Jul 2025 22:30:05 +0200 Subject: [PATCH 06/12] fix: update authentication for claude-nexus-proxy compatibility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Change from x-api-key to Authorization Bearer header format - Use dummy API key and override headers in custom fetch - Remove x-api-key header that AI SDK adds by default - Fix response.headers.entries() compatibility issue This matches the authentication format expected by claude-nexus-proxy. 🤖 Generated with [opencode](https://opencode.ai) Co-Authored-By: opencode --- packages/opencode/src/provider/provider.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index 3edef7826f70..794b15bfefda 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -351,8 +351,8 @@ export namespace Provider { // Use auth token first, then API key, or placeholder if using baseURL without auth if (authToken) { - options["apiKey"] = authToken - // For LLM gateways, also add custom fetch to handle gateway-specific headers + options["apiKey"] = "dummy" // The AI SDK requires an apiKey but we'll override the headers + // For claude-nexus-proxy, send auth token as Authorization Bearer header options["fetch"] = async (input: any, init: any) => { log.debug("making request to Anthropic gateway", { url: typeof input === "string" ? input : input?.url, @@ -363,10 +363,12 @@ export namespace Provider { const headers = { ...init.headers, - "Proxy-Authorization": `Bearer ${authToken}`, - "X-API-Key": authToken, + Authorization: `Bearer ${authToken}`, // claude-nexus-proxy expects this format } + // Remove the x-api-key header that the AI SDK adds by default + delete headers["x-api-key"] + log.debug("request headers", { headers: Object.fromEntries( Object.entries(headers).map(([key, value]) => [ @@ -387,7 +389,7 @@ export namespace Provider { log.debug("gateway response", { status: response.status, statusText: response.statusText, - headers: Object.fromEntries(response.headers.entries()), + headers: Object.fromEntries(Array.from(response.headers.entries())), }) return response From b0fb0140bc61f01c7a4b804c7687f31c534cbdd5 Mon Sep 17 00:00:00 2001 From: Francesco Dainese Date: Sat, 12 Jul 2025 10:22:01 +0200 Subject: [PATCH 07/12] fix: properly handle Anthropic LLM Gateway with /v1 endpoint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Auto-append /v1 to baseURL if not present for claude-nexus-proxy compatibility - Handle gateway configuration directly in custom loader instead of fallback - Use Bearer token authentication for gateway requests - Comprehensive debug logging for troubleshooting 🤖 Generated with [opencode](https://opencode.ai) Co-Authored-By: opencode --- packages/opencode/src/provider/provider.ts | 57 +++++++++++++++++++--- 1 file changed, 51 insertions(+), 6 deletions(-) diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index 794b15bfefda..6ede1626310f 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -43,14 +43,59 @@ export namespace Provider { const baseUrl = process.env["ANTHROPIC_BASE_URL"] const authToken = process.env["ANTHROPIC_AUTH_TOKEN"] const apiKey = process.env["ANTHROPIC_API_KEY"] - - // If any gateway/direct API configuration is present, skip OAuth - if (baseUrl || authToken || apiKey) { - log.debug("skipping OAuth for Anthropic - using env vars", { - hasBaseUrl: !!baseUrl, + // If gateway configuration is present, handle it directly + if (baseUrl && authToken) { + log.debug("configuring Anthropic with LLM Gateway", { + baseUrl, hasAuthToken: !!authToken, - hasApiKey: !!apiKey, }) + + // Ensure the baseURL includes /v1 for the Anthropic API + const normalizedBaseUrl = baseUrl.endsWith("/v1") ? baseUrl : `${baseUrl}/v1` + log.debug("using baseURL", { originalBaseUrl: baseUrl, normalizedBaseUrl }) + + return { + autoload: true, + options: { + apiKey: "dummy-key-for-gateway", // Required by SDK but overridden by fetch + baseURL: normalizedBaseUrl, + async fetch(input: any, init: any) { + log.debug("making request to Anthropic gateway", { + url: input, + method: init?.method || "GET", + hasBody: !!init?.body, + bodyLength: init?.body ? String(init.body).length : 0, + }) + + const headers = { + ...init.headers, + authorization: `Bearer ${authToken}`, + } + // Remove the default x-api-key header since we're using Authorization + delete headers["x-api-key"] + + log.debug("request headers", { headers }) + + const response = await fetch(input, { + ...init, + headers, + }) + + log.debug("gateway response", { + status: response.status, + statusText: response.statusText, + headers: Object.fromEntries(response.headers.entries()), + }) + + return response + }, + }, + } + } + + // If direct API key is present, use standard configuration + if (apiKey) { + log.debug("using direct Anthropic API key") return { autoload: false } // Let the env loader handle this } From dc4fb682442ba90088f39baafd7134ea5a79f44e Mon Sep 17 00:00:00 2001 From: Francesco Dainese Date: Sat, 12 Jul 2025 20:44:38 +0200 Subject: [PATCH 08/12] chore: remove debug logging from Anthropic LLM Gateway logic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Cleans up provider.ts to only include necessary gateway logic - No functional changes; only removes log.debug and log.error statements 🤖 Generated with [opencode](https://opencode.ai) Co-Authored-By: opencode --- packages/opencode/src/provider/provider.ts | 68 ---------------------- 1 file changed, 68 deletions(-) diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index 6ede1626310f..d8db1afdc38d 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -45,14 +45,8 @@ export namespace Provider { const apiKey = process.env["ANTHROPIC_API_KEY"] // If gateway configuration is present, handle it directly if (baseUrl && authToken) { - log.debug("configuring Anthropic with LLM Gateway", { - baseUrl, - hasAuthToken: !!authToken, - }) - // Ensure the baseURL includes /v1 for the Anthropic API const normalizedBaseUrl = baseUrl.endsWith("/v1") ? baseUrl : `${baseUrl}/v1` - log.debug("using baseURL", { originalBaseUrl: baseUrl, normalizedBaseUrl }) return { autoload: true, @@ -60,13 +54,6 @@ export namespace Provider { apiKey: "dummy-key-for-gateway", // Required by SDK but overridden by fetch baseURL: normalizedBaseUrl, async fetch(input: any, init: any) { - log.debug("making request to Anthropic gateway", { - url: input, - method: init?.method || "GET", - hasBody: !!init?.body, - bodyLength: init?.body ? String(init.body).length : 0, - }) - const headers = { ...init.headers, authorization: `Bearer ${authToken}`, @@ -74,19 +61,11 @@ export namespace Provider { // Remove the default x-api-key header since we're using Authorization delete headers["x-api-key"] - log.debug("request headers", { headers }) - const response = await fetch(input, { ...init, headers, }) - log.debug("gateway response", { - status: response.status, - statusText: response.statusText, - headers: Object.fromEntries(response.headers.entries()), - }) - return response }, }, @@ -95,19 +74,15 @@ export namespace Provider { // If direct API key is present, use standard configuration if (apiKey) { - log.debug("using direct Anthropic API key") return { autoload: false } // Let the env loader handle this } // Otherwise, use OAuth authentication - log.debug("attempting OAuth authentication for Anthropic") const access = await AuthAnthropic.access() if (!access) { - log.debug("OAuth access not available for Anthropic") return { autoload: false } } - log.debug("OAuth access available for Anthropic") for (const model of Object.values(provider.models)) { model.cost = { input: 0, @@ -382,13 +357,6 @@ export namespace Provider { if (baseUrl || authToken || apiKey) { const options: Record = {} - log.debug("configuring Anthropic LLM Gateway", { - hasBaseUrl: !!baseUrl, - baseUrl: baseUrl || "not set", - hasAuthToken: !!authToken, - hasApiKey: !!apiKey, - }) - // Set base URL if provided if (baseUrl) { options["baseURL"] = baseUrl @@ -399,13 +367,6 @@ export namespace Provider { options["apiKey"] = "dummy" // The AI SDK requires an apiKey but we'll override the headers // For claude-nexus-proxy, send auth token as Authorization Bearer header options["fetch"] = async (input: any, init: any) => { - log.debug("making request to Anthropic gateway", { - url: typeof input === "string" ? input : input?.url, - method: init?.method || "GET", - hasBody: !!init?.body, - bodyLength: init?.body ? String(init.body).length : 0, - }) - const headers = { ...init.headers, Authorization: `Bearer ${authToken}`, // claude-nexus-proxy expects this format @@ -414,54 +375,25 @@ export namespace Provider { // Remove the x-api-key header that the AI SDK adds by default delete headers["x-api-key"] - log.debug("request headers", { - headers: Object.fromEntries( - Object.entries(headers).map(([key, value]) => [ - key, - key.toLowerCase().includes("auth") || key.toLowerCase().includes("key") - ? `${String(value).substring(0, 10)}...` - : value, - ]), - ), - }) - try { const response = await fetch(input, { ...init, headers, }) - log.debug("gateway response", { - status: response.status, - statusText: response.statusText, - headers: Object.fromEntries(Array.from(response.headers.entries())), - }) - return response } catch (error) { - log.error("gateway request failed", { - error: error instanceof Error ? error.message : String(error), - stack: error instanceof Error ? error.stack : undefined, - }) throw error } } } else if (apiKey) { options["apiKey"] = apiKey - log.debug("using direct ANTHROPIC_API_KEY authentication") } else if (baseUrl) { // If only baseURL is set, provide a placeholder API key // The gateway should handle authentication options["apiKey"] = "gateway-auth" - log.debug("using baseURL-only configuration with placeholder auth") } - log.debug("final Anthropic provider options", { - baseURL: options["baseURL"], - hasApiKey: !!options["apiKey"], - hasFetch: !!options["fetch"], - }) - mergeProvider(providerID, options, "env") continue } From 3c8ae171d2a8923441e9c8493facd8ad24ab726a Mon Sep 17 00:00:00 2001 From: Francesco Dainese Date: Sat, 12 Jul 2025 20:56:03 +0200 Subject: [PATCH 09/12] chore: cleanup --- flake.nix | 65 ------------------------------------------------------- 1 file changed, 65 deletions(-) delete mode 100644 flake.nix diff --git a/flake.nix b/flake.nix deleted file mode 100644 index 85d4da2a6d5a..000000000000 --- a/flake.nix +++ /dev/null @@ -1,65 +0,0 @@ -{ - description = "Opencode development environment"; - - inputs = { - nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; - flake-parts.url = "github:hercules-ci/flake-parts"; - systems.url = "github:nix-systems/default"; - }; - - outputs = inputs @ { flake-parts, ... }: - flake-parts.lib.mkFlake { inherit inputs; } { - systems = import inputs.systems; - - perSystem = { config, self', inputs', pkgs, system, ... }: { - devShells.default = pkgs.mkShell { - buildInputs = with pkgs; [ - # Node.js/Bun runtime and package management - bun - nodejs_22 - - # Go development - go_1_24 - - # Build tools and utilities - git - gnumake - gcc - - # Development tools - jq - ripgrep - curl - - # For building native modules if needed - python3 - pkg-config - - # Additional tools that might be useful - which - file - ]; - - shellHook = '' - echo "🚀 Opencode development environment" - echo "Node.js: $(node --version)" - echo "Bun: $(bun --version)" - echo "Go: $(go version)" - echo "" - echo "Available commands:" - echo " bun install - Install dependencies" - echo " bun run dev - Start development server" - echo " bun typecheck - Run type checking" - echo "" - ''; - - # Set environment variables - OPENCODE_DEV = "1"; - - # Ensure proper locale settings - LANG = "en_US.UTF-8"; - LC_ALL = "en_US.UTF-8"; - }; - }; - }; -} \ No newline at end of file From 598caf3375516d108ffecfe319d643fdc063df51 Mon Sep 17 00:00:00 2001 From: Francesco Dainese Date: Sat, 12 Jul 2025 20:56:17 +0200 Subject: [PATCH 10/12] fix: support both Authorization and x-api-key headers for Anthropic LLM Gateway MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - If both ANTHROPIC_AUTH_TOKEN and ANTHROPIC_API_KEY are set, both headers are sent - Maximizes compatibility with gateways that require one or both 🤖 Generated with [opencode](https://opencode.ai) Co-Authored-By: opencode --- packages/opencode/src/provider/provider.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index d8db1afdc38d..646941c99a41 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -44,7 +44,7 @@ export namespace Provider { const authToken = process.env["ANTHROPIC_AUTH_TOKEN"] const apiKey = process.env["ANTHROPIC_API_KEY"] // If gateway configuration is present, handle it directly - if (baseUrl && authToken) { + if (baseUrl && (authToken || apiKey)) { // Ensure the baseURL includes /v1 for the Anthropic API const normalizedBaseUrl = baseUrl.endsWith("/v1") ? baseUrl : `${baseUrl}/v1` @@ -56,16 +56,17 @@ export namespace Provider { async fetch(input: any, init: any) { const headers = { ...init.headers, - authorization: `Bearer ${authToken}`, } - // Remove the default x-api-key header since we're using Authorization - delete headers["x-api-key"] - + if (authToken) { + headers["authorization"] = `Bearer ${authToken}` + } + if (apiKey) { + headers["x-api-key"] = apiKey + } const response = await fetch(input, { ...init, headers, }) - return response }, }, From 55245e33a613543a82762297d0fe0a848b549f69 Mon Sep 17 00:00:00 2001 From: Francesco Dainese Date: Sat, 12 Jul 2025 21:04:13 +0200 Subject: [PATCH 11/12] fix: always use real API key for Anthropic LLM Gateway MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - SDK config uses real API key if set, otherwise blank string - Removes all references to claude-nexus-proxy - Header logic: sends both Authorization and x-api-key if both env vars are set 🤖 Generated with [opencode](https://opencode.ai) Co-Authored-By: opencode --- packages/opencode/src/provider/provider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index 646941c99a41..ac7e74e63961 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -51,7 +51,7 @@ export namespace Provider { return { autoload: true, options: { - apiKey: "dummy-key-for-gateway", // Required by SDK but overridden by fetch + apiKey: apiKey ?? "", // Use real API key if set, otherwise blank string baseURL: normalizedBaseUrl, async fetch(input: any, init: any) { const headers = { From c75f4895da7c9fb4f4b48ce1e761f772cd63da43 Mon Sep 17 00:00:00 2001 From: Francesco Dainese Date: Sun, 13 Jul 2025 10:26:07 +0200 Subject: [PATCH 12/12] fix: error if neither auth token nor api key set --- packages/opencode/src/provider/provider.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index ac7e74e63961..8b5dd6ec690c 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -73,6 +73,14 @@ export namespace Provider { } } + // If base URL is set but no auth credentials, log helpful error + if (baseUrl && !authToken && !apiKey) { + log.error("Anthropic LLM Gateway configured but missing authentication", { + baseUrl, + message: "Set either ANTHROPIC_AUTH_TOKEN or ANTHROPIC_API_KEY environment variable", + }) + } + // If direct API key is present, use standard configuration if (apiKey) { return { autoload: false } // Let the env loader handle this