Skip to content

Add Provider : agentrouter.org #2784

Closed
Closed

Description

@ariefly

Agent router support GLM-4.5 but i cannot use it as provider

Activity

  1. github-actions commented on Sep 25, 2025

    @github-actions
    Contributor

    This issue might be a duplicate of existing issues. Please check:

    Feel free to ignore if none of these address your specific case.

  2. rekram1-node commented on Sep 25, 2025

    @rekram1-node
    Contributor

    We will need to add to models.dev, you should still be able to use it if you define custom provider tho: https://opencode.ai/docs/providers/#custom-provider

  3. santoshd commented on Oct 15, 2025

    @santoshd

    I tried working with Aiden on discord to add the service through a custom provider. I suspect there's some filtering on the part of Agentrouter that prevents non-approved tool use. They have listed codex, claude as supported. I have attempted another CLI tool as well which also fails. I've written to the Agentrouter team and they have not responded.

  4. alpgul commented on Nov 28, 2025

    @alpgul

    Temporary solution:

    "agentrouter": {
          "npm": "@ai-sdk/openai-compatible",
          "name": "agentrouter",
          "options": {
            "baseURL": "https://agentrouter.org/v1",
            "headers": {
              "Authorization": "Bearer apiKey",
              "User-Agent": "RooCode/3.34.8",
              "X-Title": "Roo Code",
              "HTTP-Referer": "https://github.com/RooVetGit/Roo-Cline",
              "X-Stainless-Runtime-Version": "v22.20.0",
              "X-Stainless-Runtime": "node",
              "X-Stainless-Arch": "x64",
              "X-Stainless-OS": "Linux",
              "X-Stainless-Lang": "js"
            }
          },
          "models": {
            "glm-4.6": {
              "name": "glm-4.6",
              "limit": {
                "context": 200000,
                "output": 65536
              }
            }
          }
        }
  5. gethoopp commented on Apr 23, 2026

    @gethoopp

    unauthorized client detected, contact support for assistance at https://discord.com/invite/V6kaP6Rg44

    got this when use api key from agent router

  6. Lypt1x commented on Apr 23, 2026

    @Lypt1x

    @gethoopp

    unauthorized client detected, contact support for assistance at https://discord.com/invite/V6kaP6Rg44

    got this when use api key from agent router

    Got this working on opencode 1.14.22. Three separate problems stacking on top of each other:

    1. AgentRouter fingerprints the HTTP client. Their backend is OneAPI-based (you can see X-Oneapi-Request-Id in response headers), and unless the request looks like Claude Code they reject it with unauthorized client detected. A spoofed User-Agent alone isn't enough, you also need the x-stainless-* set and anthropic-* headers that the real Claude Code CLI sends.

    2. opencode bug #22608: custom headers in opencode.json (especially User-Agent) are silently dropped before fetch. So any config-based approach (like the RooCode one posted above) can't work regardless of what you put in there.

    3. AgentRouter emits a malformed data: null SSE chunk inside streams, which makes the AI SDK's Zod validator throw Type validation failed: Value: null has to be filtered.

    Workaround: a small plugin that patches globalThis.fetch for agentrouter.org only while other providers are untouched.

    ~/.config/opencode/opencode.json

    {
      "$schema": "https://opencode.ai/config.json",
      "provider": {
        "agentrouter": {
          "npm": "@ai-sdk/openai-compatible",
          "name": "agentrouter",
          "options": {
            "baseURL": "https://agentrouter.org/v1",
            "apiKey": "sk-YOUR_KEY"
          },
          "models": {
            "glm-4.6":                   { "name": "glm-4.6",                   "limit": { "context": 200000, "output": 65536 } },
            "claude-opus-4-6":           { "name": "claude-opus-4-6",           "limit": { "context": 200000, "output": 65536 } },
            "claude-haiku-4-5-20251001": { "name": "claude-haiku-4-5-20251001", "limit": { "context": 200000, "output": 65536 } },
            "deepseek-v3.1":             { "name": "deepseek-v3.1",             "limit": { "context": 200000, "output": 65536 } }
          }
        }
      }
    }

    No headers block, the plugin does that. apiKey handles Authorization: Bearer automatically via the SDK.

    Notice: I may be wrong with the upstream limits. Needs to be checked.

    ~/.config/opencode/plugins/agentrouter-headers.js

    // Scoped strictly to agentrouter.org. Other providers unaffected.
    const AGENTROUTER_HOST = "agentrouter.org";
    
    const INJECTED_HEADERS = {
      "User-Agent": "claude-cli/1.0.108 (external, cli)",
      "anthropic-version": "2023-06-01",
      "anthropic-beta": "claude-code-20250219,oauth-2025-04-20",
      "anthropic-dangerous-direct-browser-access": "true",
      "x-app": "cli",
      "x-stainless-lang": "js",
      "x-stainless-package-version": "0.55.1",
      "x-stainless-os": "Windows",
      "x-stainless-arch": "x64",
      "x-stainless-runtime": "node",
      "x-stainless-runtime-version": "v22.0.0",
    };
    
    function isAgentRouterUrl(url) {
      try {
        const p = typeof url === "string" ? new URL(url) : new URL(url.url ?? url.href ?? String(url));
        return p.hostname.endsWith(AGENTROUTER_HOST);
      } catch { return false; }
    }
    
    // Strip malformed `data: null` SSE chunks that crash the AI SDK Zod validator.
    function sanitizeSseBody(body) {
      if (!body) return body;
      const dec = new TextDecoder();
      const enc = new TextEncoder();
      let carry = "";
      const t = new TransformStream({
        transform(chunk, controller) {
          const text = carry + dec.decode(chunk, { stream: true });
          const nl = text.lastIndexOf("\n");
          if (nl === -1) { carry = text; return; }
          const complete = text.slice(0, nl + 1);
          carry = text.slice(nl + 1);
          const filtered = complete.split("\n")
            .filter(l => l.trim() !== "data: null" && l.trim() !== "data:null")
            .join("\n");
          if (filtered.length) controller.enqueue(enc.encode(filtered));
        },
        flush(controller) {
          if (carry.trim() !== "data: null" && carry.trim() !== "data:null" && carry.length) {
            controller.enqueue(enc.encode(carry));
          }
        },
      });
      return body.pipeThrough(t);
    }
    
    function patchFetchOnce() {
      if (globalThis.__agentrouter_fetch_patched__) return;
      globalThis.__agentrouter_fetch_patched__ = true;
      const orig = globalThis.fetch.bind(globalThis);
    
      globalThis.fetch = async function (input, init) {
        try {
          const url = typeof input === "string" || input instanceof URL ? input : input?.url;
          if (url && isAgentRouterUrl(url)) {
            const headers = new Headers(init?.headers ?? (input && input.headers) ?? undefined);
            for (const [k, v] of Object.entries(INJECTED_HEADERS)) headers.set(k, v);
            const newInit = { ...(init ?? {}), headers };
            const res = input instanceof Request
              ? await orig(new Request(input, newInit))
              : await orig(input, newInit);
            const ct = res.headers.get("content-type") ?? "";
            if (res.ok && ct.includes("text/event-stream") && res.body) {
              return new Response(sanitizeSseBody(res.body), {
                status: res.status, statusText: res.statusText, headers: res.headers,
              });
            }
            return res;
          }
        } catch {}
        return orig(input, init);
      };
    }
    
    export const AgentRouterHeadersPlugin = async ({ client }) => {
      patchFetchOnce();
      try {
        await client.app.log({
          body: { service: "agentrouter-headers", level: "info", message: "fetch patched for agentrouter.org" },
        });
      } catch {}
      return {};
    };

    Fully quit and restart opencode after adding this (config + plugins load at startup).

    Heads up on title generation

    Opencode uses small_model for background title generation. When that goes through AgentRouter you'll occasionally get sensitive_words_detected from their Chineeese content filter on otherwise normal prompts, the actual chat works fine, only the auto-title fails.

    Workarounds:

    Route titles elsewhere:

    { "model": "agentrouter/claude-opus-4-6", "small_model": "anthropic/claude-haiku-4-5" }

    Or force titles onto the main model:

    { "model": "agentrouter/claude-opus-4-6", "small_model": "agentrouter/claude-opus-4-6" }

    Would obviously be nicer if opencode just fixed #22608 so you could configure the headers directly in opencode.json, but for now this does the job.

    Hope it helps, happy coding!

  7. sbeygi commented on Jun 26, 2026

    @sbeygi
    "agentrouter": {
      "npm": "@ai-sdk/openai-compatible",
      "name": "agentrouter",
      "options": {
        "baseURL": "https://agentrouter.org/v1",
        "apiKey": "YOUR-API-KEY"
      },
      "models": {
        "glm-5.2": {
          "name": "glm-5.2",
          "limit": { "context": 1000000, "output": 1000000 }
        },
        "claude-opus-4-6": {
          "name": "claude-opus-4-6",
          "limit": { "context": 1000000, "output": 1000000 }
        },
        "claude-opus-4-7": {
          "name": "claude-opus-4-7",
          "limit": { "context": 1000000, "output": 1000000 }
        },
        "claude-opus-4-8": {
          "name": "claude-opus-4-8",
          "limit": { "context": 1000000, "output": 1000000 }
        }
      }
    }
    

    The solution offered by Lypt1x works. This is just an updated JSON for opencode.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions