Skip to content

applyCaching called too freely, causes LiteLLM to fail #6473

Closed

Description

@RingOfStorms

Description

Calls to models with the string claude or anthropic in the id fail when they are not actual anthropic upstream providers.

I have this custom provider in my config:

  "provider": {
    "custom": {
      "models": {
        "claude-opus-4.1": {
          "name": "claude-opus-4.1"
        },
      },
      "npm": "@ai-sdk/openai-compatible",
      "options": {
        "apiKey": "na",
        "baseURL": "https://custom.com"
      }
    }
  },

Chatting with this model fails with

litellm.BadRequestError: Vertex_aiException BadRequestError - b'{"type":"error","error":{"type":"invalid_request_error","message":"invalid beta flag"},"request_id":"req_vrtx_011CWdZLV7UDvRZBkvyfXcKJ"}'. Received Model Group=claude-opus-4.1
Available Model Group Fallbacks=None

Because of this logic https://github.com/sst/opencode/blob/3fe5d91372fdf859e09ed5a2aefe359e0648ed10/packages/opencode/src/provider/transform.ts#L198-L208

Even though my model has claude in the name I am explicitly saying it is an openai provider with "npm": "@ai-sdk/openai-compatible". In this case I am using LiteLLM proxy to front all LLM access and it does not support the added "cache_control" key this injects to every message.

Some possible solutions that could be done:

  • config option to disable the applyCaching functionality, either per model or provider
  • (... existing ors) && model.api.npm !== "@ai-sdk/openai-compatible" be added to the if statement logic there. Though I am not sure if that is the best approach or if it should apply to other providers as well.

OpenCode version

1.0.212

Steps to reproduce

  1. Setup LiteLLM in front of Anthropic models where model id includes claude or anthropic
  2. Setup a custom provider in opencode using LiteLLM as a @ai-sdk/openai-compatible provider
  3. call on a model and it fails due to injected cache keys in messages array.

Screenshot and/or share link

Image

Operating System

NixOS/macos

Terminal

Kitty/Iterm2

Activity

  1. github-actions commented on Dec 30, 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 Dec 30, 2025

    @rekram1-node
    Contributor

    I think litellm has a flag to drop unsupported parameters, dmed person that wrote that code originally

  3. RingOfStorms commented on Dec 31, 2025

    @RingOfStorms
    Author

    For anyone else hitting this, this plugin will work (for now at least) since the model.api.id is used primarily before this event hook is called so if we change it during this stage the applyLogic comes after.

    This could break something in the future but it works as a patch for me now.

    import type { Plugin } from "@opencode-ai/plugin";
    
    export const PatchCachingBug: Plugin = async () => {
      return {
        async "chat.params"(input) {
          const model = input.model;
          // Only touch the specific bad combo: OpenAI-compatible / gateway model
          // whose api.id contains "claude" but is NOT actually Anthropic.
          if (
            model.providerID !== "anthropic" &&
            model.api.npm !== "@ai-sdk/anthropic" &&
            typeof model.api.id === "string" &&
            (model.api.id.includes("claude") || model.api.id.includes("anthropic"))
          ) {
            // Strip the "claude" substring so ProviderTransform.message
            // does not enter the Anthropic caching branch.
            model.api.id = model.api.id.replace(/claude/gi, "cl4ude");
          }
        },
      };
    };

    I think litellm has a flag to drop unsupported parameters, dmed person that wrote that code originally

    I don't have full control over this particular LiteLLM instance myself so I can't easily get this option added yet.

  4. RingOfStorms commented on Jan 22, 2026

    @RingOfStorms
    Author

    Well the above plugin no longer works, either the execution order changed or something. Pretty annoying, don't feel like trying to fix it again.

    Upstream litellm has not fixed this yet either with drop params. I've resorted to running a full litellm proxy locally just to alias the names of the models that cause issues. Litellm team said they added this to their backlog so just waiting for it to get fixed there.

  5. rekram1-node commented on Jan 22, 2026

    @rekram1-node
    Contributor

    Ah, the plugin still runs it's the code that changed internally that makes it not take affect

  6. hendem commented on Jan 22, 2026

    @hendem

    I'm seeing the same issue: any custom model whose id contains "claude"/"anthropic" triggers applyCaching and injects cache_control into messages, which LiteLLM/OpenAI-compatible proxies reject (400 invalid_request_error / LiteLLM General Error).\n\nThis also makes a plugin workaround impossible now because the caching injection happens inside ProviderTransform.message() before any accessible plugin hooks run (chat.params runs later and experimental.chat.messages.transform can't affect the model id). So a plugin can't prevent applyCaching or strip cache_control early enough.\n\nNet: LiteLLM/OpenAI-compatible usage is broken for these model ids unless you rename/alias the model or patch core.

  7. rekram1-node commented on Jan 22, 2026

    @rekram1-node
    Contributor

    Yeah I think we need some better approach internally, note that a plugin can still strip them out, it's kinda weird but you can override the fetch implementation for any provider and it could strip out the things, example fetch being overriden:

    async fetch(requestInput: RequestInfo | URL, init?: RequestInit) {

    Ofc this isnt ideal solution just providing work around

  8. hendem commented on Jan 22, 2026

    @hendem

    Thanks for the suggestion — that makes sense. I'll try the fetch-override approach and report back.

  9. hendem commented on Jan 22, 2026

    @hendem

    Hey @rekram1-node, thanks for the fetch-override suggestion — it worked for me.

    I had to hook the provider ID that actually owns the OpenAI-compatible/LiteLLM proxy (redacted below), and make sure auth exists for that provider so the loader runs. After that, stripping cache_control before the request goes out fixed the 400s.

    Here’s the working plugin (provider name redacted):

    import type { Plugin } from "@opencode-ai/plugin";
    
    const STRIP_KEYS = new Set(["cache_control", "cacheControl", "promptCacheKey"]);
    
    function scrub(value: any): void {
      if (!value || typeof value !== "object") return;
      if (Array.isArray(value)) {
        for (const item of value) scrub(item);
        return;
      }
    
      for (const key of Object.keys(value)) {
        if (STRIP_KEYS.has(key)) {
          delete value[key];
          continue;
        }
        scrub(value[key]);
      }
    }
    
    function decodeBody(body: RequestInit["body"]): string | null {
      if (typeof body === "string") return body;
      if (body instanceof Uint8Array) return new TextDecoder().decode(body);
      if (body instanceof ArrayBuffer) return new TextDecoder().decode(new Uint8Array(body));
      return null;
    }
    
    export const LiteLLMFetchScrub: Plugin = async () => {
      return {
        auth: {
          provider: "redacted-classified-company",
          async loader(getAuth) {
            const auth = await getAuth();
            if (!auth) return {};
    
            return {
              async fetch(requestInput: RequestInfo | URL, init?: RequestInit) {
                if (!init?.body) return fetch(requestInput, init);
    
                const bodyText = decodeBody(init.body);
                if (!bodyText) return fetch(requestInput, init);
    
                try {
                  const payload = JSON.parse(bodyText);
                  scrub(payload);
                  const nextInit: RequestInit = {
                    ...init,
                    body: JSON.stringify(payload),
                  };
                  return fetch(requestInput, nextInit);
                } catch {
                  return fetch(requestInput, init);
                }
              },
            };
          },
          methods: [],
        },
      };
    };
    
    export default LiteLLMFetchScrub;
  10. ririnto commented on Feb 4, 2026

    @ririnto

    use other model id and re-mapping in config.yaml(litellm).

  11. RingOfStorms commented on Mar 3, 2026

    @RingOfStorms
    Author

    I will close this out for now as I agree it is sort of not an opencode issue directly and is more an issue with so many different providers and proxies handling calls and payloads differently

  12. added a commit that references this issue on Mar 13, 2026
    2d82524
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions