Skip to content

fix(provider): treat a scheme-less loopback base URL as local - #837

Open
ANIRUDDHA ADAK (aniruddhaadak80) wants to merge 1 commit into
synthetic-sciences:mainfrom
aniruddhaadak80:fix/inference-schemeless-local
Open

ANIRUDDHA ADAK (aniruddhaadak80) wants to merge 1 commit into
synthetic-sciences:mainfrom
aniruddhaadak80:fix/inference-schemeless-local

Conversation

@aniruddhaadak80

Copy link
Copy Markdown
Contributor

What

Inference.local reads the host of a base URL with new URL(value).hostname, but a scheme-less base URL does not throw here — it parses successfully with the host absorbed as the scheme.

const host = new URL(value).hostname.toLowerCase().replace(/^\[|\]$/g, "")
return host === "localhost" || host === "127.0.0.1" || host === "0.0.0.0" || host === "::1"

new URL("localhost:11434/v1") is valid per the URL spec: localhost: becomes the scheme, so hostname is the empty string and every comparison fails.

Why it matters

This form is legal here and accepted everywhere else. LocalProvider.normalizeBaseURL (src/provider/local.ts:125) explicitly prepends http:// when the scheme is missing, so the two helpers disagree on the same string:

LocalProvider.isLocalBaseURL("localhost:11434/v1")   // true
Inference.classify({ providerID: "ollama", providerSource: "config", baseURL: "localhost:11434/v1" })
// -> "byok", not "local"

classify is the route authority and its result is what usage-logging records, so a user's own Ollama configured the ordinary way is attributed as billable remote spend rather than local. The http://localhost:11434/v1 spelling of the same endpoint classifies correctly, which is why this reads as intermittent rather than systematic.

Verification

The existing test only covered the http:// spelling. One new case fails before, passes after:

(fail) a base URL without a scheme is still a local endpoint
  Expected: "local"
  Received: "byok"
 2 pass  1 fail
(pass) classifies the observable inference route without exposing credentials
(pass) a base URL without a scheme is still a local endpoint
(pass) a remote base URL is not local, with or without a scheme
 3 pass
 0 fail

The second new test is the guard against over-correcting: api.openai.com/v1 (also scheme-less) must stay byok, so the fix is not "treat everything schemeless as local". The local cases cover localhost:, a bare 127.0.0.1: with a port, and an uppercase LOCALHOST:. local-availability and local-runtime are unaffected: 9 pass / 0 fail across the three files.

The change

   function local(value: unknown) {
     if (typeof value !== "string" || !value) return false
     try {
-      const host = new URL(value).hostname.toLowerCase().replace(/^\[|\]$/g, "")
+      // "localhost:11434/v1" parses with the scheme "localhost:", so the
+      // hostname never came out as "localhost". LocalProvider.normalizeBaseURL
+      // accepts a missing scheme, so this has to agree or the same endpoint
+      // reads as local there and as a cloud key here.
+      const absolute = /^https?:\/\//i.test(value) ? value : `http://${value}`
+      const host = new URL(absolute).hostname.toLowerCase().replace(/^\[|\]$/g, "")
       return host === "localhost" || host === "127.0.0.1" || host === "0.0.0.0" || host === "::1"
     } catch {
       return false
     }
   }

The try/catch stays, so a genuinely unparseable value still returns false rather than throwing.

Touched files are Prettier-clean (verified on LF-normalized copies; this Windows checkout's core.autocrlf=true makes Prettier flag every file repo-wide).

Fixes #836

@vercel

vercel Bot commented Sep 29, 2026

Copy link
Copy Markdown

ANIRUDDHA ADAK (@aniruddhaadak80) is attempting to deploy a commit to the InkVell Team on Vercel.

A member of the Team first needs to authorize it.

@aniruddhaadak80
ANIRUDDHA ADAK (aniruddhaadak80) force-pushed the fix/inference-schemeless-local branch 2 times, most recently from d212b35 to e4d4f3f Compare September 30, 2026 09:46
localhost:11434/v1 parses with the scheme localhost:, so the hostname never read as localhost and the route fell through to byok. LocalProvider.normalizeBaseURL accepts that form, so the same endpoint was local there and a cloud key here.

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A scheme-less loopback base URL (localhost:11434/v1) is classified as a billable cloud route

1 participant