Skip to content

fix(settings): Providers screen fetches models through /api/models, not the gateway (follow-up to #17) - #28

Closed
lightcloud00 wants to merge 1 commit into
JPeetz:mainfrom
lightcloud00:fix/providers-screen-models-via-api-route
Closed

fix(settings): Providers screen fetches models through /api/models, not the gateway (follow-up to #17)#28
lightcloud00 wants to merge 1 commit into
JPeetz:mainfrom
lightcloud00:fix/providers-screen-models-via-api-route

Conversation

@lightcloud00

@lightcloud00 lightcloud00 commented Aug 16, 2026

Copy link
Copy Markdown

Relationship to #25 — please read first.

#25 (also mine) contains this exact providers-screen.tsx fix as part of the
Issue #23 dashboard work — the fetch('/api/models') repoint is the same
change. #25 is 25 files and also ports a gateway-key.ts credential-discovery
module.

This PR is the minimal standalone version: one file, one function, no new
modules.

Merge whichever fits — not both. If you take #25, close this one. If you'd
rather land the small screen fix on its own, take this and I'll drop the
overlapping hunk from #25.


Not a duplicate of #19 — it's the other half

#19 by @coolswood fixes all eight files named in #17 and should merge first. This PR touches none of them. Its files are hermes-jobs.ts, hermes-jobs.$jobId.ts, models.ts, hermes-runs.ts, hermes-runs.$runId.events.ts, approvals.$approvalId.approve.ts, approvals.$approvalId.deny.ts, hermes-proxy/$.ts. This PR changes one file: src/screens/settings/providers-screen.tsx.

#17's symptom list includes "Model dropdown empty". #19 fixes the outbound leg — it puts the bearer on /api/models's call to the gateway — which repairs the model pickers in settings-dialog, chat-screen, conductor-settings, hermes-onboarding and routes/settings/index.

But the Providers screen never calls /api/models. It calls the gateway itself, from the browser:

const HERMES_API_URL = process.env.HERMES_API_URL || 'http://127.0.0.1:8642'
const response = await fetch(`${HERMES_API_URL}/v1/models`)

So after #19 merges, that one screen is still empty. This PR closes that gap. The two changes are complementary — neither fixes this screen alone.

Three separate failures in that one call

  1. No Authorization header. The gateway returns 401 whenever API_SERVER_KEY is set.
  2. Cross-origin. The browser hits a host the gateway needn't CORS-allow.
  3. 127.0.0.1:8642 is the wrong machine. vite.config.ts inlines HERMES_API_URL at build time, so a container build bakes in a container-internal address that resolves on the viewer's laptop.

Verified live against a gateway with API_SERVER_KEY set. On the base commit, the browser produces:

OPTIONS http://127.0.0.1:8642/v1/models → 403 Forbidden
GET     http://127.0.0.1:8642/v1/models   [FAILED: net::ERR_FAILED]

Failure 2 firing before failure 1 even gets a chance.

The fix

Point it at /api/models, as every other model consumer already does — hermes-onboarding.tsx:180, settings-dialog.tsx:1386, routes/settings/index.tsx:278, chat-screen.tsx:859, conductor-settings.tsx:165. Five to one; this screen was the outlier.

That route already gates on isAuthenticated (models.ts:131) so the browser call is authorized by the hermes-auth session cookie, and its normalizeHermesModel (models.ts:83-110) is field-for-field equivalent to the normalization this screen was doing by hand — same id → name → model precedence, same name → display_name → label → id, same provider → owned_by → id.split('/')[0] → 'hermes-agent'. So the ~85 hand-rolled lines just go away; fetchModels() already declared the /api/models return shape.

After: exactly one same-origin GET /api/models, and no browser request to :8642 from this screen.

Two intentional behaviour deltas: the screen gains the auth-store models (Anthropic/OpenAI/xAI) the other five screens already show — consistency, not regression — and loses an unused bare-array /v1/models branch that no other consumer supports.

Verification

Against a live gateway that 401s unauthenticated and 200s with the key:

Step Result
Base commit, browser absolute GET :8642/v1/models → CORS 403 + ERR_FAILED, list empty
This PR, browser same-origin GET /api/models → served by Studio; zero browser requests to :8642 from this screen
This PR + #19's auth applied locally GET /api/models → 200 with the model list populated

That last row is the point: this PR and #19 together fix the screen. I applied #19's authHeaders() change to models.ts locally just to confirm it, and it is not part of this diff.

tsc --noEmit clean (the deletion removes three symbols — HERMES_API_URL, HermesCatalogEntry, isHermesCatalogEntry, none referenced elsewhere). 190 tests pass. Lint is unchanged from the base commit — the one finding in this file exists identically before and after, shifted by the deletion.

No test covers fetchModels upstream; adding one would need jsdom + MSW for a 13-line function, so I've left it — flagging that rather than implying coverage.

Adjacent, deliberately not touched

Conflicts

None. #24 edits this same file but in different regions (imports, the config path, the render guard) — nearest hunk ends ~25 lines above the first line this PR touches. #19 and #22 don't touch it at all.

Refs #17 rather than a closing keyword — #19's merge is what should close #17.

🤖 Generated with Claude Code

…rectly

The Providers screen was the only client-side model consumer still
calling the Hermes gateway from the browser:

  const HERMES_API_URL = process.env.HERMES_API_URL || 'http://127.0.0.1:8642'
  const response = await fetch(`${HERMES_API_URL}/v1/models`)

That request carries no Authorization header, so the gateway returns 401
whenever API_SERVER_KEY is set and the Providers model list stays empty.
It is also cross-origin, and in a container or remote deploy
127.0.0.1:8642 resolves on the viewer's machine rather than the gateway
(vite inlines HERMES_API_URL at build time).

Route it through Studio's own /api/models instead, as hermes-onboarding,
settings-dialog, chat-screen, conductor-settings and routes/settings
already do. That route authenticates the caller via the hermes-auth
session cookie, attaches the gateway bearer server-side, and performs the
same id/name/provider normalization this screen was doing by hand — so
the hand-rolled normalization is deleted.

Refs JPeetz#17
@vercel

vercel Bot commented Aug 16, 2026

Copy link
Copy Markdown

@lightcloud00 is attempting to deploy a commit to the Joerg Peetz's projects Team on Vercel.

A member of the Team first needs to authorize it.

@lightcloud00

Copy link
Copy Markdown
Author

Closing this — we're continuing this work on our own repo (lightcloud00/Hermes-Studio) rather than upstream. Nothing here is abandoned; the branch and all its commits are preserved there. Thanks for the project, and apologies for the churn in your PR queue.

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.

Proxy routes call gateway without Authorization → 401 on Jobs/Models/Runs/Approvals when API_SERVER_KEY is set

1 participant