Skip to content

Instantly share code, notes, and snippets.

@Kiryous
Created July 16, 2026 11:24
Show Gist options
  • Select an option

  • Save Kiryous/3d427f817b35c0a8ce95122bde8abf32 to your computer and use it in GitHub Desktop.

Select an option

Save Kiryous/3d427f817b35c0a8ce95122bde8abf32 to your computer and use it in GitHub Desktop.
OpenAI Codex + Claude Code with Elastic's LiteLLM Gateway (strip-proxy for Azure param issues)

Using OpenAI Codex + Claude Code with Elastic's LiteLLM Gateway

Elastic's LiteLLM gateway routes some models (e.g. GPT-5.6 Sol) through Azure, which rejects parameters like tool_choice and client_metadata. This setup runs a tiny local Node.js proxy that strips those params before forwarding.

Prerequisites

  • Node.js (any recent version)
  • LITELLM_API_KEY env var set to your Elastic LiteLLM key

1. Save the proxy script

Save this as ~/.local/bin/litellm-strip-proxy.cjs (or anywhere you like):

```js #!/usr/bin/env node const http = require("http"); const https = require("https");

const PORT = process.env.PROXY_PORT || 4000; const UPSTREAM = process.env.LITELLM_BASE_URL || "https://elastic.litellm-prod.ai/v1"; const API_KEY = process.env.LITELLM_API_KEY; const STRIP_PARAMS = new Set(["tool_choice", "client_metadata"]); const MODEL_PREFIX = "llm-gateway/";

const upstream = new URL(UPSTREAM);

const server = http.createServer((req, res) => { const chunks = []; req.on("data", (c) => chunks.push(c)); req.on("end", () => { let body = Buffer.concat(chunks);

if (body.length > 0) {
  try {
    const json = JSON.parse(body);
    const stripped = [];
    for (const key of Object.keys(json)) {
      if (STRIP_PARAMS.has(key)) {
        delete json[key];
        stripped.push(key);
      }
    }
    process.stderr.write(\`→ \${req.method} \${req.url} model=\${json.model || "?"}\`);
    if (stripped.length > 0) {
      process.stderr.write(\` [stripped: \${stripped.join(", ")}]\`);
    }
    process.stderr.write("\\n");
    body = Buffer.from(JSON.stringify(json));
  } catch {}
}

const opts = {
  hostname: upstream.hostname,
  port: upstream.port || 443,
  path: upstream.pathname.replace(/\/+$/, "") + req.url,
  method: req.method,
  headers: {
    ...req.headers,
    host: upstream.hostname,
    "content-length": body.length,
    authorization: \`Bearer \${API_KEY}\`,
  },
};

const proxy = https.request(opts, (upRes) => {
  const isStream = (upRes.headers["content-type"] || "").includes("text/event-stream");
  if (isStream) {
    res.writeHead(upRes.statusCode, upRes.headers);
    upRes.on("data", (chunk) => {
      let text = chunk.toString();
      text = text.replaceAll(\`"model":"\${MODEL_PREFIX}\`, '"model":"');
      res.write(text);
    });
    upRes.on("end", () => res.end());
  } else {
    const rChunks = [];
    upRes.on("data", (c) => rChunks.push(c));
    upRes.on("end", () => {
      let rBody = Buffer.concat(rChunks).toString();
      rBody = rBody.replaceAll(\`"model":"\${MODEL_PREFIX}\`, '"model":"');
      const h = { ...upRes.headers };
      h["content-length"] = Buffer.byteLength(rBody);
      res.writeHead(upRes.statusCode, h);
      res.end(rBody);
    });
  }
});
proxy.on("error", (e) => {
  res.writeHead(502);
  res.end(JSON.stringify({ error: e.message }));
});
proxy.end(body);

}); });

server.listen(PORT, () => { console.log(`strip-proxy listening on :${PORT} -> ${UPSTREAM}`); }); ```

2. Shell functions

Add to your `~/.zshrc` or equivalent:

```bash

Start the local strip-proxy (terminal 1)

start-litellm-local() { local port="${1:-4000}" echo "strip-proxy -> localhost:$port (forwarding to elastic.litellm-prod.ai)" PROXY_PORT="$port" node ~/.local/bin/litellm-strip-proxy.cjs }

Run Codex through the local proxy (terminal 2)

codex-litellm() { if [ -z "$LITELLM_API_KEY" ]; then echo "LITELLM_API_KEY is not set" >&2 return 1 fi local base_url="http://localhost:4000/v1" local model="${LITELLM_MODEL:-llm-gateway/gpt-5.6-sol}" local cfg="$HOME/.codex/config.toml" mkdir -p "$HOME/.codex" if [ -f "$cfg" ] && ! grep -q '# codex-litellm managed' "$cfg"; then cp "$cfg" "$cfg.bak.$(date +%s)" echo "Backed up existing $cfg" fi cat > "$cfg" <<EOF

codex-litellm managed

model = "$model" model_provider = "local_litellm"

[model_providers.local_litellm] name = "Local LiteLLM" base_url = "$base_url" env_key = "LITELLM_API_KEY" wire_api = "responses" EOF echo "codex -> $base_url ($model)" codex "$@" }

Run Claude Code through Elastic's LiteLLM (no local proxy needed)

claude-litellm() { ANTHROPIC_DEFAULT_OPUS_MODEL="llm-gateway/claude-opus-4-7" \ ANTHROPIC_DEFAULT_SONNET_MODEL="llm-gateway/claude-sonnet-4-6" \ ANTHROPIC_DEFAULT_HAIKU_MODEL="llm-gateway/claude-haiku-4-5" \ CLAUDE_CODE_SUBAGENT_MODEL="llm-gateway/claude-opus-4-6" \ ANTHROPIC_BASE_URL=${LITELLM_BASE_URL:-https://elastic.litellm-prod.ai/v1} \ ANTHROPIC_AUTH_TOKEN="$LITELLM_API_KEY" \ ANTHROPIC_API_KEY="" \ claude } ```

Usage

```bash

Terminal 1 — start the proxy (keep running)

start-litellm-local

Terminal 2 — use codex or claude

codex-litellm # OpenAI Codex via local proxy → Elastic gateway claude-litellm # Claude Code directly via Elastic gateway (no proxy needed) ```

Why?

  • Codex needs the local proxy because Elastic's gateway routes GPT models through Azure, which rejects `tool_choice` and `client_metadata` params. The proxy strips them before forwarding.
  • Claude Code talks the Anthropic API, not OpenAI — it connects directly to Elastic's gateway without the param issues.
  • The proxy also strips the `llm-gateway/` prefix from model names in responses to avoid the codex metadata warning (codex#14276).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment