Messages Native Vllm

Routes native Anthropic Messages API traffic (/v1/messages and /v1/messages/count_tokens) to a vLLM backend that natively serves the Anthropic Messages API, WITHOUT any request or response body translation

Category: Setup-dependent integration
Task: Routes native Anthropic Messages API traffic (/v1/messages and /v1/messages/count_tokens) to a vLLM backend that natively serves the Anthropic Messages API, WITHOUT any request or response body translation

Prerequisites: The external service, credentials, or certificates referenced by this configuration.

This configuration comes from the selected release. The example has not been run here; external services are not bundled.

Download the source file.

Companion resources from the same snapshot:

# Anthropic Messages -> Native vLLM Passthrough
#
# Routes native Anthropic Messages API traffic (`/v1/messages` and
# `/v1/messages/count_tokens`) to a vLLM backend that natively serves
# the Anthropic Messages API, WITHOUT any request or response body
# translation. This is the config a real Claude Code client uses to
# reach a vLLM backend through Praxis while every byte of the Anthropic
# wire format is preserved end to end.
#
# Two first-class client paths share this one chain:
#   Claude Code -> POST /v1/messages         -> vLLM (native Anthropic Messages)
#   OpenCode    -> POST /v1/chat/completions -> vLLM (native OpenAI Chat Completions)
# Neither body is translated in either direction. vLLM serves both surfaces,
# so one gateway backs both clients. See
# `docs/developing/cli-vllm-through-praxis.md` for the client-side setup.
#
# Contrast with `messages-to-openai.yaml`, which TRANSLATES Anthropic
# Messages into OpenAI Chat Completions for Chat-Completions-only
# backends. This config deliberately omits the
# `anthropic_messages_to_chat_completions[_stream]` translation filters:
# vLLM speaks `/v1/messages` natively, so translation would be lossy and
# unnecessary.
#
# Credential isolation (three distinct credentials, each at its own boundary):
#   - `basic_auth` authenticates the *client* to this gateway. A trusted
#     caller (Claude Code via `ANTHROPIC_CUSTOM_HEADERS`, OpenCode via its
#     `praxis-auth.ts` plugin or a per-model `headers` entry) presents an
#     `Authorization: Basic ...` gateway credential; `basic_auth` verifies
#     it and, with `strip_authorization: true`, removes it so it never
#     reaches the backend.
#   - The `headers` filter removes the client's `x-api-key` (the native
#     Anthropic auth header) so a caller's Anthropic key never reaches
#     vLLM either.
#   - `credential_injection` then injects the backend's own distinct Bearer
#     token from `VLLM_API_KEY` toward the cluster.
#   Together these guarantee the client cannot smuggle credentials to the
#   backend, only authenticated callers are upgraded with the backend
#   credential, and the backend only ever sees the server-owned token.
#
# The gateway password is resolved from `GATEWAY_AUTH_PASSWORD` at pipeline
# build time; set it (and `VLLM_API_KEY`) in the proxy's environment. See
# `credential-injection.yaml` for alternative client gates (mTLS, IP ACL).

listeners:
  - name: anthropic-gateway
    address: "127.0.0.1:8080"
    filter_chains: [anthropic-native-vllm]

filter_chains:
  - name: anthropic-native-vllm
    filters:
      # Authenticate the client to the gateway BEFORE anything else, and
      # strip the gateway `Authorization` so it never reaches the backend.
      # Real callers present it via a custom `Authorization: Basic ...`
      # header (Claude Code: `ANTHROPIC_CUSTOM_HEADERS`).
      - filter: basic_auth
        realm: "praxis-native-vllm-gateway"
        strip_authorization: true
        credentials:
          - username: gateway
            env_var: GATEWAY_AUTH_PASSWORD

      # Classify from the request head, before any body is read. This is what
      # installs the per-protocol error formatter, so a proxy-generated failure
      # reaches each client in the envelope its SDK parses. The request
      # processor below only covers the Anthropic Messages surface; without
      # this filter an OpenCode `/v1/chat/completions` request would get
      # RFC 9457 problem details that an OpenAI client cannot parse.
      - filter: ai_operation

      # Process the create-message body once: validate the envelope and
      # promote routing facts to internal `x-praxis-ai-*` headers.
      # `on_invalid: reject` refuses a malformed body at the gateway, which is
      # what the separate validator here used to do. No path condition is
      # needed — the operation keys the filter, so bodyless probes such as
      # `GET /v1/models` are released untouched.
      - filter: anthropic_messages_request
        on_invalid: reject

      # Supply a default `anthropic-version` header for internal or
      # non-SDK callers that omit it. Real Claude Code always sends one,
      # so this is a no-op for it. Scoped to `/v1/messages` for the same
      # reason `anthropic_validate` is: `anthropic-version` is meaningless
      # on the OpenAI Chat Completions path OpenCode uses, and injecting it
      # there would put a wrong header on the wire.
      - filter: anthropic_messages_protocol
        default_version: "2023-06-01"
        conditions:
          - when:
              path_prefix: "/v1/messages"

      # Strip the client's native Anthropic auth header so it never
      # reaches the backend. `credential_injection` below handles
      # `Authorization`.
      - filter: headers
        request_remove:
          - x-api-key

      - filter: router
        routes:
          - path: "/v1/messages"
            cluster: "vllm-backend"
          - path: "/v1/messages/count_tokens"
            cluster: "vllm-backend"
          - path: "/v1/models"
            cluster: "vllm-backend"
          # OpenCode (and any other OpenAI-compatible client) posts native
          # Chat Completions here. vLLM serves this endpoint natively, so
          # it is the same untranslated passthrough as `/v1/messages`:
          # Praxis authenticates the caller, scrubs client credentials,
          # injects the backend credential, and routes. No body translation.
          - path: "/v1/chat/completions"
            cluster: "vllm-backend"
          # Any other native endpoint (health checks and documented startup
          # probes) is proxied unchanged to the same backend.
          - path_prefix: "/"
            cluster: "vllm-backend"

      # Inject the backend's own credential after routing selects the
      # cluster. `strip_client_credential` (default true) removes any
      # client-supplied `Authorization` before injection.
      - filter: credential_injection
        clusters:
          - name: vllm-backend
            header: Authorization
            env_var: VLLM_API_KEY
            header_prefix: "Bearer "
            strip_client_credential: true

      - filter: load_balancer
        clusters:
          - name: "vllm-backend"
            endpoints:
              - "127.0.0.1:8000"

insecure_options:
  allow_private_endpoints: true # example proxies to a local vLLM backend