Skip to content

baseURL ending in /v1 causes /v1/models fetch to hit /v1/v1/models (HTTP 404) #3

Description

@MciG-ggg

baseURL ending in /v1 causes /v1/models fetch to hit /v1/v1/models (HTTP 404)

Repro

Settings (legacy single-connection form):

llm-newapi:
  baseURL: https://www.dogapi.cc/v1     # ← ending in /v1 is the trigger
  apiKeyEnv: DOGAPI_API_KEY
  flavor: openai-compatible
  catalogMode: v1

Boot the profile and try to enumerate the gateway's models. Catalog fetch throws:

LlmError: newapi model list failed (HTTP 404)
  at .../dsh-gateway-provider/lib/catalog.js:84 (fetchV1Models)

Root cause

Trace through the legacy single-connection path:

  1. index.js:262baseURL.replace(/\/+$/, "") keeps the trailing /v1 (it only strips trailing slashes, not the version segment).
  2. index.js:211catalogBase: proto !== undefined ? (proto.catalogBase ?? (ownBase || null)) : baseURL. For the legacy form proto is undefined, so catalogBase = baseURL = "https://www.dogapi.cc/v1".
  3. lib/catalog.js:132discoveryBase() returns catalogBase verbatim: "https://www.dogapi.cc/v1".
  4. lib/catalog.js:81fetch(\${baseURL}/v1/models`) → **GET https://www.dogapi.cc/v1/v1/models`** → dogapi.cc returns {"error":{"message":"Invalid URL (GET /v1/v1/models)","type":"invalid_request_error"}} → 404.

In other words, the plugin treats baseURL as a bare host when assembling the SDK base URL (pi-provider.js:50-56 appends /v1 if missing), but treats it as an already-versioned base when assembling the catalog URL (catalog.js always appends /v1/models). Two different conventions for the same field, no overlap handling.

The dual-convention appears intentional for apiBases["openai-completions"] etc. — protocols.js:85 does strip /v\d+$ when deriving catalogBase for the multi-protocol form. The legacy form doesn't go through that derivation, so it leaks a versioned baseURL straight into the catalog path.

Suggested fix

In lib/catalog.js, strip the version segment from discoveryBase() output before the catalog appends its suffixes (mirroring what protocols.js:85 already does for the multi-protocol form):

function stripVersion(baseURL) {
  return String(baseURL ?? "").replace(/\/+$/, "").replace(/\/v\d+$/, "");
}

function discoveryBase(connection) {
  const base = connection.catalogBase !== undefined ? connection.catalogBase : connection.baseURL;
  return typeof base === "string" && base.length > 0 ? stripVersion(base) : null;
}

Same trim should apply to the management-API call (fetchManagementModels, line 104) — otherwise catalogMode: management with a /v1 baseURL hits /v1/api/user/models and 404s the same way.

Workaround for users on 1.0.8 today

Drop /v1 from baseURL:

llm-newapi:
  baseURL: https://www.dogapi.cc      # ← bare host; pi-provider.js appends /v1 for SDK calls

The plugin's chat path (pi-provider.js:sdkBaseURL) already handles appending /v1 for the OpenAI protocols, so this is the canonical form (matches the public default https://api.newapi.ai at index.js:53).

Environment

  • dsh 0.1.2-rc.1
  • dsh-gateway-provider 1.0.8
  • dogapi.cc (self-hosted newapi fork): /v1/models returns 401 with a valid request, /v1/v1/models returns 404
  • Node 26.8.1, pnpm 11.22.0, macOS

Separate but related: this is bug #2 in a series

Bug #1 (still open in pi-bridge.js): imports CallId from @deepseek-ai/dsh-llm, renamed to ToolCallId in 0.1.2-rc.1 — see #2 (the issue number is for the other bug, title starts with "pi-bridge.js imports CallId"). Once that's fixed upstream the plugin tree will at least load, and then this /v1/v1/models 404 surfaces immediately as the next regression.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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