refactor(agent-service): extract WebSocket message types into types/ws#5751
refactor(agent-service): extract WebSocket message types into types/ws#5751bobbai00 wants to merge 10 commits into
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #5751 +/- ##
============================================
+ Coverage 54.60% 55.62% +1.02%
Complexity 2927 2927
============================================
Files 1109 1109
Lines 42828 42769 -59
Branches 4608 4603 -5
============================================
+ Hits 23385 23792 +407
+ Misses 18081 17596 -485
- Partials 1362 1381 +19
*This pull request uses carry forward flags. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
This PR is a foundational refactor in agent-service/ that centralizes shared DTO/type definitions, introduces a dedicated backend-endpoints config module, and relocates JWT/auth helpers into a focused auth/ module (with new unit tests), while keeping api/backend-api.ts as a transitional entry point.
Changes:
- Added centralized type modules under
src/types/(api.ts,metadata.ts) and re-exported them from the types barrel. - Added
src/config/endpoints.tswithgetServiceEndpoints()to centralize service base URLs. - Moved JWT/auth helpers into
src/auth/jwt.ts, updated import paths, and addedsrc/auth/jwt.test.ts.
Reviewed changes
Copilot reviewed 9 out of 10 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| agent-service/src/types/metadata.ts | Introduces extracted operator-metadata type definitions. |
| agent-service/src/types/api.ts | Adds wire DTOs for backend/service payloads and agent WS protocol. |
| agent-service/src/types/index.ts | Re-exports the new metadata and api type modules. |
| agent-service/src/config/endpoints.ts | Adds a centralized service-endpoint accessor. |
| agent-service/src/auth/jwt.ts | New JWT/auth helper module (moved from api/). |
| agent-service/src/auth/jwt.test.ts | Adds unit tests for JWT helpers. |
| agent-service/src/server.ts | Updates auth import path to the new auth/jwt module. |
| agent-service/src/api/workflow-api.ts | Updates auth header helper import path. |
| agent-service/src/api/index.ts | Updates barrel export to re-export JWT helpers from auth/. |
| agent-service/src/api/backend-api.ts | Re-exports metadata types from src/types/metadata (transitional). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
676cc26 to
c565c4a
Compare
Automated Reviewer SuggestionsBased on the
|
| import type { WorkflowContent } from "./workflow"; | ||
|
|
||
| export interface WsMessage { | ||
| type: "message" | "stop"; |
There was a problem hiding this comment.
rename to prompt and kill-command
There was a problem hiding this comment.
inherit on interface, define kill-command seperately
| export interface WsOutgoingMessage { | ||
| type: "step" | "state" | "error" | "complete" | "init" | "headChange"; | ||
| step?: ReActStep; | ||
| state?: string; | ||
| error?: string; | ||
| steps?: ReActStep[]; | ||
| headId?: string; | ||
| operatorResults?: Record<string, OperatorResultSummaryWs>; | ||
| workflowContent?: WorkflowContent; | ||
| } |
There was a problem hiding this comment.
you need to seperate them into their own definition. don't mix them together.
| state: string; | ||
| inputTuples: number; | ||
| outputTuples: number; | ||
| inputPortShapes?: { portIndex: number; rows: number; columns: number }[]; |
There was a problem hiding this comment.
should this be output?
There was a problem hiding this comment.
this will be refactored in later's PR #5927
6ff0a71 to
9a00461
Compare
|
| config | throughput | MB/s | latency | max Δ latest / 7d | |
|---|---|---|---|---|---|
| 🔴 | bs=10 sw=10 sl=64 | 385 | 0.235 | 25,139/31,427/31,427 us | 🟢 -8.0% / 🟢 -10.2% |
| 🔴 | bs=100 sw=10 sl=64 | 799 | 0.488 | 122,151/157,858/157,858 us | 🔴 +5.6% / 🔴 +12.9% |
| ⚪ | bs=1000 sw=10 sl=64 | 928 | 0.566 | 1,077,487/1,128,224/1,128,224 us | ⚪ within ±5% / 🔴 -10.9% |
Baseline details
Latest main 1c580e5 from same runner
| config | metric | PR | latest main | 7d avg | Δ latest | Δ 7d |
|---|---|---|---|---|---|---|
| bs=10 sw=10 sl=64 | throughput | 385 tuples/sec | 402 tuples/sec | 410.82 tuples/sec | -4.2% | -6.3% |
| bs=10 sw=10 sl=64 | MB/s | 0.235 MB/s | 0.246 MB/s | 0.251 MB/s | -4.5% | -6.3% |
| bs=10 sw=10 sl=64 | p50 | 25,139 us | 23,528 us | 23,785 us | +6.8% | +5.7% |
| bs=10 sw=10 sl=64 | p95 | 31,427 us | 34,160 us | 34,980 us | -8.0% | -10.2% |
| bs=10 sw=10 sl=64 | p99 | 31,427 us | 34,160 us | 34,980 us | -8.0% | -10.2% |
| bs=100 sw=10 sl=64 | throughput | 799 tuples/sec | 823 tuples/sec | 891.94 tuples/sec | -2.9% | -10.4% |
| bs=100 sw=10 sl=64 | MB/s | 0.488 MB/s | 0.502 MB/s | 0.544 MB/s | -2.8% | -10.4% |
| bs=100 sw=10 sl=64 | p50 | 122,151 us | 119,101 us | 112,277 us | +2.6% | +8.8% |
| bs=100 sw=10 sl=64 | p95 | 157,858 us | 149,492 us | 139,802 us | +5.6% | +12.9% |
| bs=100 sw=10 sl=64 | p99 | 157,858 us | 149,492 us | 139,802 us | +5.6% | +12.9% |
| bs=1000 sw=10 sl=64 | throughput | 928 tuples/sec | 937 tuples/sec | 1,041 tuples/sec | -1.0% | -10.9% |
| bs=1000 sw=10 sl=64 | MB/s | 0.566 MB/s | 0.572 MB/s | 0.635 MB/s | -1.0% | -10.9% |
| bs=1000 sw=10 sl=64 | p50 | 1,077,487 us | 1,065,684 us | 972,714 us | +1.1% | +10.8% |
| bs=1000 sw=10 sl=64 | p95 | 1,128,224 us | 1,107,788 us | 1,023,057 us | +1.8% | +10.3% |
| bs=1000 sw=10 sl=64 | p99 | 1,128,224 us | 1,107,788 us | 1,023,057 us | +1.8% | +10.3% |
Raw CSV
config_idx,batch_size,schema_width,string_len,num_batches,total_ms,total_tuples,total_bytes,tuples_per_sec,mb_per_sec,lat_p50_us,lat_p95_us,lat_p99_us
0,10,10,64,20,519.03,200,128000,385,0.235,25138.52,31426.93,31426.93
1,100,10,64,20,2502.14,2000,1280000,799,0.488,122150.78,157858.47,157858.47
2,1000,10,64,20,21552.44,20000,12800000,928,0.566,1077487.28,1128224.11,1128224.119a00461 to
2bae592
Compare
2bae592 to
508e72c
Compare
508e72c to
bc67f57
Compare
Move the inline WS message definitions out of server.ts into a dedicated types/ws module, modeled as discriminated unions rather than one all-optional interface: - client.ts: WsClientRequest = WsClientRequestPrompt | WsClientRequestStopCommand - server.ts: WsServerMessage union (snapshot/step/status/completion/error/headChange) + OperatorResultSummaryWs - index.ts: barrel, re-exported from the types barrel The client->server wire discriminator changes from "message"/"stop" to "prompt"/"command" (stop becomes commandType: "stop"). The server->client discriminators are renamed to uniform nouns that name each frame's payload: init->snapshot, state->status, complete->completion (step/error unchanged), with interfaces renamed to match (WsServerSnapshotMessage / WsServerStatusMessage / WsServerCompletionMessage). server.ts parses WsClientRequest and switches on the new shapes; the frontend WS sends and the receive switch are updated in lockstep. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012qFkyrpTd5PrkNBPcBeo4Q
bc67f57 to
43f0b96
Compare
The `completion` frame carried both the agent's final `state` and the
operator-results snapshot. That duplicated the state path: the agent's
GENERATING->AVAILABLE transition (in TexeraAgent.sendMessage's finally) was
announced *only* inside `completion`, so `completion.state` and `status`
frames were two routes driving the same client state.
Route the end-of-run state through a `status` frame instead, emitted in a
finally covering both the success and error paths, and slim `completion` to
`{ type, operatorResults }` (a pure final-results snapshot). The frontend
`completion` handler drops its state branch; the `status` handler already
applies the resting state.
Side effect: this also fixes the client staying stuck on GENERATING after an
error — the error path previously emitted no resting-state update.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012qFkyrpTd5PrkNBPcBeo4Q
Convert the per-message comments in types/ws to uniform JSDoc blocks (so they surface in IDE hovers) and give every client and server frame a brief purpose line. Mark WsServerHeadChangeMessage @deprecated: it is redundant and unused (the checkout flow that emits it is unreachable) and is slated for removal in apache#5930. Doc-only; no type or runtime change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012qFkyrpTd5PrkNBPcBeo4Q
…lts on demand Operator result summaries were pushed over the socket (on snapshot, step, and completion frames), but the only frontend consumer — the per-operator chat popover — reads them from a subject that was fed solely by those pushes, and the step-frame results were never applied at all (dead payload). Most summary fields are server-side agent context that is never displayed. Move results to an on-demand pull and slim the socket to conversation + lifecycle: - Drop the `completion` frame entirely (type, server broadcast, frontend handler). Run-end is already signaled by the end-of-run `status` frame. - Drop `operatorResults` from the `snapshot` and `step` frames (and stop computing/sending them there). - The chat popover now calls fetchOperatorResults() (GET /operator-results) when it opens, pushing fresh summaries to operatorResultSummaries$. - Remove the dead result-annotations machinery (toggleResultAnnotations, resultAnnotationsVisible(Subject/$), getResultAnnotationsVisible) — no callers. Server frames are now: snapshot, step, status, error (plus the deprecated headChange, removed in apache#5930). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012qFkyrpTd5PrkNBPcBeo4Q
Add coverage for the parts of the WS framework this PR introduces, which the existing app.handle()-based suite never exercised (it only hits HTTP routes): - server.ws.test.ts: drives the real socket via app.listen + a WebSocket client. Covers the results-free snapshot on connect, the unknown-agent error, the stop command -> STOPPING status, prompt validation and malformed -frame errors, a stubbed prompt run streaming GENERATING -> step -> resting status, and a failed run still returning to a resting status (the stuck-on-GENERATING fix). No live LLM: the agent's sendMessage is stubbed. - server.ts: add a small `_getAgentForTests` hook (mirrors `_resetAgentStoreForTests`) so tests can stub agent behavior. - agent.service.spec.ts (frontend): cover fetchOperatorResults — the on-demand REST pull that replaced the WS results push — including the failure fallback. server.ts line coverage rises from ~53% to ~70%; the remaining gaps are pre-existing (agent construction, the deprecated checkout endpoint, the startup banner) and not introduced by this PR. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012qFkyrpTd5PrkNBPcBeo4Q
Extend the suite to exercise every reachable path in server.ts: - HTTP routes not previously covered: react-steps, system-info, operator-types, steps-by-operators, the operator-results mapping body (with a stubbed visible result), checkout (success + not-found), the empty-modelType guard, initial-settings-on-create, the non-fatal workflow-load failure, delegate-token masking, and the catch-all error handler for unknown routes. - WebSocket branches: a message for an agent that was removed mid-connection, the final-step re-broadcast, the broadcast path tolerating a websocket whose send throws, and the close handler on disconnect. - start(): boots the listening app + prints the banner, and tolerates a metadata-initialization failure (stubbed to reject). Also collapse the `if (import.meta.main) start()` entry guard to one line so the entry point is a covered statement rather than an untestable block. server.ts is now at 100% line and function coverage (116 tests, all green). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012qFkyrpTd5PrkNBPcBeo4Q
Rename the agent-service test files from *.test.ts to *.spec.ts to match the frontend convention and codecov.yml's `**/*.spec.ts` ignore rule, so test files stay out of the coverage denominator. Bun discovers .spec.ts the same as .test.ts; no test changes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012qFkyrpTd5PrkNBPcBeo4Q
When an operator's chat popover opens (the `element:chat` paper event), the editor now pulls the active agent's operator results on demand. Add a test that adds an operator, fires `element:chat` for it, and asserts fetchOperatorResults is called with the active agent id — the one line of the PR's frontend change that wasn't yet exercised. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012qFkyrpTd5PrkNBPcBeo4Q
The frontend CI job runs `prettier-eslint --list-different` before the tests; a multi-line `httpMock.expectOne(...)` in the added fetchOperatorResults spec fit on one line under the frontend's wider printWidth, so the check failed and the job exited before running tests — which is also why no frontend coverage report was produced. Reformat to satisfy the check. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012qFkyrpTd5PrkNBPcBeo4Q
Codecov flagged two uncovered changes in this PR's frontend:
- agent.service.ts: the stop-command websocket send in stopGeneration() was
never exercised. Add a spec that injects an open mock socket and asserts the
`{ type: "command", commandType: "stop" }` frame is sent (plus the REST
fallback when no socket is open).
- workflow-editor.component.ts: the `if (activeAgentId)` branch in the chat
popover's results pull was only half-covered. Add a no-active-agent case so
both branches are exercised.
Verified against the full frontend coverage run: both lines/branches now hit.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012qFkyrpTd5PrkNBPcBeo4Q
What changes were proposed in this PR?
This PR refactors the code of agent-service ↔ frontend WebSocket messaging definition.
Specifically, a new folder
types/wsis added and is dedicated for websocket related message definition.types/ws/client.tsholds the messages the frontend sends to agent-service. They share a base interfaceWsClientRequestBase(which only carries thetypefield), and form the unionWsClientRequestwith two cases:WsClientRequestPrompt— a user prompt to run (type: "prompt").WsClientRequestStopCommand— a request to stop the current run (type: "command",commandType: "stop").types/ws/server.tsholds the messages agent-service sends back to the frontend. They share a base interfaceWsServerMessageBase, and form the unionWsServerMessagewith these cases:WsServerSnapshotMessage("snapshot") — the full state, sent once when a client connects.WsServerStepMessage("step") — one step, sent live as the agent runs.WsServerStatusMessage("status") — a state change, e.g. GENERATING / AVAILABLE / STOPPING.WsServerErrorMessage("error") — an error to show the user.WsServerHeadChangeMessage("headChange") — deprecated and unused; removed later in refactor(agent-service): remove dead checkout/headChange path #5930.This PR also adds several test cases to ensure the coverage, and changes the filenames of test files in
agent-servicefrom*.test.tsto*.spec.tsto align with frontend's naming convention.Any related issues, documentation, discussions?
Closes #5749
How was this PR tested?
Several unit tests are added and passed. Also local end2end tests are passed.
Was this PR authored or co-authored using generative AI tooling?
Generated-by: Claude Opus 4.8 (1M context)