• Issue: OpenRouter requests failed with HTTP 400 because the models[] array exceeded size constraints.

Bug Analysis & Root Cause

  • Observed Error: "'models' array must have 3 items or fewer."
  • Failure Path: UI generation -> src/scripts/api.js buildApiPayload(...) -> POST /api/llm -> OpenRouter.
  • Root Cause: OpenRouter fallback routing was built as [primaryModel, ...fallbackModels] without size checks. 1 primary plus 3 fallback models produced an array of length 4, exceeding OpenRouter’s maximum limit of 3 items.

Proposed Fix

  • Client Payload Cap and Deduplication (src/scripts/api.js):
    • Cap routing models: const OPENROUTER_MAX_ROUTING_MODELS = 3;
    • Normalize keys to deduplicate standard and :online variants.
    • Filter candidates against synced model catalog.
    const candidates = [primaryModel, ...providerConfig.fallbackModels]
      .map((model) => String(model || '').trim())
      .filter((model) => isModelAvailableInCatalog(settings.provider, model))
      .filter(Boolean);
     
    const deduped = [];
    const seen = new Set();
    candidates.forEach((model) => {
      const key = String(model || '').trim().replace(/:online$/i, '');
      if (!key || seen.has(key)) return;
      seen.add(key);
      deduped.push(model);
    });
     
    const limited = deduped.slice(0, OPENROUTER_MAX_ROUTING_MODELS);
    if (limited.length > 1) {
      payload.models = limited;
    }
  • Server-Side Defensive Clamp (server.py):
    • Intercept OpenRouter payload proxying.
    • Parse, deduplicate standard/online variants, and clamp array length to 3 elements.
    if provider == "openrouter":
        models = payload.get("models")
        if isinstance(models, list):
            deduped_models = []
            seen = set()
            for model in models:
                model_id = str(model or "").strip()
                if not model_id:
                    continue
                routing_key = model_id.replace(":online", "")
                if not routing_key or routing_key in seen:
                    continue
                seen.add(routing_key)
                deduped_models.append(model_id)
                if len(deduped_models) >= 3:
                    break
            if len(deduped_models) > 1:
                payload["models"] = deduped_models
            else:
                payload.pop("models", None)

Explanation of Fix

  • Client Safety: Guarantees the payload array respects the maximum size constraints.
  • Server Enforcement: Guards against any legacy or malformed client calls.
  • Key Normalization: Avoids duplicate routing options for similar models.

Verification Steps

  1. Run Backend: python3 server.py.
  2. Trigger Call: Generate tree using OpenRouter inside client.
  3. Verify API Code: Confirm HTTP 200 is returned.
  4. Endpoint Mocking: Send payload with 4 models to proxy to verify clamping.
  5. Syntax Checks:
    • node --check src/scripts/api.js
    • python3 -m py_compile server.py