Skip to content

POST /api/models/custom silently drops max_input_tokens / max_output_tokens; passthrough kr/* aliases hardcoded to 200K context #1294

@codename-zen

Description

@codename-zen

Summary

The "Add Custom Model" feature on the dashboard accepts max_input_tokens and max_output_tokens in the form payload, but the backend route discards both fields. Additionally, /v1/models does not expose context length metadata for any model (only returns id, object, owned_by), and passthrough kr/* aliases that aren't in the static registry fall back to a hardcoded 200K context in the internal resolver.

This means:

  • Users cannot override context limits via the dashboard custom model feature (fields are silently dropped).
  • OpenAI-compatible clients that read /v1/models to determine context window (Cursor, Continue, etc.) have no metadata to work with and must hardcode or guess.
  • /v1/models/info returns 404 for passthrough aliases, with the internal resolver defaulting to 200K even for models that support 1M (e.g., Kiro Pro Claude Opus 4.7).

Tested on 9router 0.4.55 (latest at time of writing).

Reproduction

  1. Login to dashboard, then:

    curl -X POST http://<host>:20128/api/models/custom \
      -H "Content-Type: application/json" \
      -H "Cookie: auth_token=<...>" \
      -d '{
        "providerAlias": "kr",
        "id": "claude-opus-4.7",
        "name": "Claude Opus 4.7",
        "category": "chat",
        "max_input_tokens": 1000000,
        "max_output_tokens": 64000
      }'

    Returns: {"success":true,"added":true}

  2. Verify what was actually saved:

    curl http://<host>:20128/api/models/custom \
      -H "Cookie: auth_token=<...>"

    Returns:

    {"models":[{"providerAlias":"kr","id":"claude-opus-4.7","type":"llm","name":"Claude Opus 4.7"}]}

    max_input_tokens and max_output_tokens are not persisted.

  3. Check /v1/models response shape:

    curl http://<host>:20128/v1/models -H "Authorization: Bearer <key>"

    Every entry only contains {"id": "...", "object": "model", "owned_by": "..."} — no context_length, max_input_tokens, or similar field.

  4. Check /v1/models/info for passthrough alias:

    curl "http://<host>:20128/v1/models/info?id=kr/claude-opus-4.7" \
      -H "Authorization: Bearer <key>"

    Returns 404. The internal token-limit resolver falls back to 2e5 (200K).

Source evidence

app/api/models/custom/route.js (POST handler, deobfuscated):

let { providerAlias: b, id: c, type: d, name: e } = await a.json();
if (!b || !c) return v.NextResponse.json({error:"providerAlias and id required"}, {status:400});
let f = await S8({providerAlias:b, id:c, type:d||"llm", name:e});

Only 4 fields are destructured. max_input_tokens / max_output_tokens are never read.

Hardcoded fallback (chunks/8515.js, Kiro alias resolver):

d = Number(a?.tokenLimits?.maxInputTokens) || 2e5

Expected behavior

  1. POST /api/models/custom should persist max_input_tokens / max_output_tokens when provided.
  2. /v1/models should include context metadata (e.g., context_length or max_input_tokens) in each model entry, consistent with how OpenRouter, Together, and other OpenAI-compatible aggregators expose this.
  3. The Kiro alias resolver / /v1/models/info should honor custom overrides when present, and ideally ship Opus 4.6/4.7 in the static kr: registry with their real 1M context limit (per https://kiro.dev/changelog/models/claude-opus-4-7-now-available/).

Workaround

Clients that support explicit context configuration (e.g., Hermes Agent's context_length field in config.yaml) can override on the client side. Clients that rely solely on /v1/models metadata have no workaround.

Environment

  • 9router 0.4.55 (npm-global on Windows; also reproduced on Docker decolua/9router:latest)
  • Provider: Kiro (Builder ID OAuth, Pro tier accounts)
  • Model: kr/claude-opus-4.7 (passthrough alias)

cc related: #1244 (Kiro Pro account detected as Free) — same provider area, may be worth fixing together.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions