Skip to content

feat: runtime API base URL and an SSE parser - #18

Merged
RohanReddy44 merged 3 commits into
mainfrom
feat/experience-api-1-config-and-sse
Sep 23, 2026
Merged

RohanReddy44 merged 3 commits into
mainfrom
feat/experience-api-1-config-and-sse

Conversation

@RohanReddy44

Copy link
Copy Markdown
Collaborator

What changed

Ground work for talking to the Experience API. No visible change with stubs on, which is still the default.

  • api.baseUrl in config.json replaces the empty, build-time apiUrl constant. Default /api, used as written. A deployment behind a sub-path sets it in its mounted config, for example /experience/api. The base is read per call so tests can import the module before config loads.
  • An SSE parser, src/lib/sse.ts. The API streams answers as server-sent events over a POST, which EventSource cannot make, so the body is read from fetch and framed here. Handles comment lines, all three line endings, multi-line data, frames and multi-byte characters cut across chunks, and a final frame with no blank line after. Nothing uses it yet; PR feat: run the client with no backend via a stub API layer #2 does.

Why

The client's server coupling has to move from the old backend's shape to the Experience API contract (docs/implementation/experience-api-contract.md, local for now). Both pieces here are needed by that change and stand on their own.

Verified

  • npm run lint, typecheck, knip, test (33 tests) and build pass at each commit. Route tree unchanged.
  • npm run dev serves the new block in /config.json.
  • The parser was run against the Experience API on localhost and framed its stream into started, two deltas and completed.

Follow-ups noted from review

  • The README's sub-path section does not yet say that api.baseUrl must be set for a sub-path deployment.
  • The parser does not strip a leading BOM, which the SSE spec asks for.

🤖 Generated with Claude Code

RohanReddy44 and others added 2 commits September 24, 2026 00:27
Where the client sends API calls was `apiUrl` in environment.ts, a
build-time constant that has always been empty, so every call fell
through to `${BASE_URL}api/`. Changing it meant a rebuild, and the
deployment behind /exp needs the API at /exp/api, which the app's
own base path cannot express.

It is now `api.baseUrl` in config.json, used as written, so a
deployment sets it by mounting a config file. The default `/api` keeps
the same-origin behaviour of the root build. The base is read per call
and the axios instance built on first use, because configuration only
exists after loadRuntimeConfig() resolves and this module is imported
by tests.

Verified: lint, typecheck, knip, tests and build pass; the dev server
serves the new block in /config.json.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The Experience API streams each answer as server-sent events over a
POST, which the browser's EventSource cannot make. The client already
reads the body from fetch; what it lacked was framing. Today it treats
every network chunk as answer text.

readSseEvents() turns a body into event/data frames by the SSE rules:
blank line ends a frame, data lines join, comment lines and unknown
fields are ignored, all three line endings work. Lines and multi-byte
characters are reassembled across chunk boundaries, and a final frame
the stream closes on is still delivered. Stopping early cancels the
body so a live connection is closed.

Nothing uses it yet; the next change does. Verified against the
running API as well as the tests: its stream parses to started,
two deltas and completed.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The reference deployment runs the shipped config.json as it is, with
nothing mounted over it, so the bundled default has to be the real
value. The UI is served at /experience and the API at /experience/api
behind one front proxy; `/api` pointed at the domain root and would
have missed.

The value is still used as written and not derived from the build's
base path. The two can differ, and an API call that quietly went to
the wrong place would be hard to trace. A deployment at another path
changes this one key. The README's sub-path section now shows the
proxy block the API path needs: buffering off so the answer streams,
a read timeout that covers a whole turn, and a body limit above the
DSS's own so the DSS is the one that says a conversation is too long.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@RohanReddy44
RohanReddy44 merged commit e657428 into main Sep 23, 2026
3 of 4 checks passed
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.

1 participant