Repository navigation
feat: send chat turns to POST /v1/chat and stream the answer - #19
Merged
Merged
Conversation
The client spoke the old backend's shape: GET /chat/ with the question in the query string, the answer read as raw text chunks with a magic keep-alive word filtered out, a trace id in an X-QID header, and "connection closed" taken as success. None of that survives the Experience API contract (docs/implementation/experience-api-contract.md). sendUserQuery now posts the contract's body (sessionId, messageId, query, history, language, location when known) and reads the reply as server-sent events: started, delta, completed, error. `completed` is the answer and replaces the streamed text. Every failure is one ApiError with a code the rest of the client can branch on. Where the API sent no code the client makes one up: history_too_large for a bare 413, upstream_error for a stream that ends without completed or error or is not an event stream at all, unknown for anything else. The stub no longer bypasses this. It returns a Response whose body is the same event stream, so the parser and event handling run on every stubbed reply too. Its cycle now covers a refusal and a turn that failed after the DSS accepted it, so Retry can be seen with stubs on. In the store, sendText and sendImage shared two copies of the streaming and error code; they now share one turn runner. The bubble is drawn on the first delta, with the API's assistantMessageId as its id; traceId replaces qid. The "daily question limit" message for 429 goes: under the contract 429 means busy and is retryable. Errors still show the one generic message; copy per code follows in a later change, as does sending the real history, which is [] here. Verified: lint, typecheck, knip, 45 tests and the build pass. The real call was also run against the Experience API on localhost and streamed started, two deltas and completed through this code. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
With stubs off, the dev server answered POST /experience/api/v1/chat itself with index.html, which the client reported as upstream_error. Now that an Experience API runs locally there is something real to talk to. The dev server forwards `api.baseUrl` the way the front proxy does in deployment: same origin for the browser, the prefix stripped before the API sees the path. The path is read from the same config file the app uses, so the two cannot drift. The API's own port is the default and DEV_API_URL overrides it. Nothing changes with stubs on, which is still the default. Verified: a POST through localhost:3000/experience/api/v1/chat streams the API's started, delta and completed events back. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
RohanReddy44
force-pushed
the
feat/experience-api-2-chat-call
branch
from
September 23, 2026 19:44
1b07c5b to
b5d54fa
Compare
Production runs the shipped config.json as it is, so the bundled default decides what a deployment does. With stubs on it would answer farmers with canned advice. Now that the client speaks the Experience API contract, the default is off and chat goes to `api.baseUrl`. What changes for a developer: `npm run dev` needs an Experience API, which the dev server proxies to (DEV_API_URL, default localhost:8078). Without one, every question shows an error card. Flipping `stubs.enabled` to true locally brings the canned replies back; the flag is a runtime config value, so nothing else changes, and the stub still speaks the API's own event stream. The change must not be committed, and the docs now say so. ADR-0000 §6.1, README, CLAUDE.md and AGENTS.md are updated in this commit, as the ADR rule for a flag flip asks. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
RohanReddy44
force-pushed
the
feat/experience-api-2-chat-call
branch
from
September 23, 2026 19:47
cf8eca3 to
fb411f5
Compare
RohanReddy44
changed the base branch from
feat/experience-api-1-config-and-sse
to
main
September 23, 2026 20:04
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changed
The client now speaks the Experience API contract for chat.
sendUserQueryposts to{api.baseUrl}/v1/chatwith the contract's body (sessionId,messageId,query,history,language,locationwhen known) and reads the reply as events:started,delta,completed,error.completedis the answer and replaces the streamed text.ApiErrorwith acode. From the API where it sent one. Made up where it did not:history_too_largefor a bare 413,upstream_errorfor a stream that ends withoutcompletedorerroror is not an event stream,unknownotherwise.Responseof SSE frames, so the real parser and event handling run on every stubbed reply. Its cycle is three answers, a refusal, and a turn that failed after the DSS accepted it, so Retry can be seen with stubs on.sendTextandsendImagehad two copies of the streaming and error code. The bubble is drawn on the first delta with the API'sassistantMessageIdas its id.traceIdreplacesqid. The "daily question limit" message for 429 is gone: 429 isbusyand retryable.npm run devforwards the configuredapi.baseUrlpath (/experience/api, read from the same config file) to a local Experience API (http://localhost:8078,DEV_API_URLoverrides), prefix stripped as the deployment proxy does. Stubs on: nothing changes.Not in this PR, by plan
historyis sent as[]. Session id on clear chat, real history and the samemessageIdon retry are the next PR.history_too_large, follows after that.Verified
npm run lint,typecheck,knip,test(45 tests in 10 files) andbuildpass at each commit.started, twodeltas andcompleted./experience/api/v1/chatreaches the API and streams back.{"detail": ...}), not yet the contract's{"error": {...}}. The client's fallback covers it and the 422 case is in the tests.Follow-ups noted from review, pending decision
startedlikecompletedanderrorare, instead ofString()-coercing missing ids.api.baseUrl.completed.errorand reject emptycontentwithouterror.sendTextandsendImage.🤖 Generated with Claude Code