WebSocket wrappers discard every query argument, and ReasoningWs dials the literal template path /ws/ml/reasoning/{{.id}}
Context: API Makeathon participant. Found reviewing the official clients; independently confirmed by a second review pass. Root cause is the generator template cmd/tmpl/websocket.tmpl.
Every generated WebSocket wrapper accepts configuration parameters in its signature but never serializes them into the dialed URL. Unlike the REST wrappers, the WS wrappers never call expandURL.
Evidence
paths.go (CommandsWs ~10605):
func (s *ModelingService) CommandsWs(videoResWidth int, videoResHeight int, fps int,
unlockedFramerate bool, postEffect PostEffectType, webrtc bool, pool string,
showGrid bool, replay string, apicallId string, orderIndependentTransparency bool,
pr int, body any) (*websocket.Conn, error) {
path := "/ws/modeling/commands"
targetURL := resolveRelative(s.client.server, path) // none of the 13 params used
...
conn, _, err := websocket.DefaultDialer.Dial(strings.ReplaceAll(targetURL, "https://", "wss://"), headers)
ReasoningWs (~10571) is worse: path := "/ws/ml/reasoning/{{.id}}" is dialed verbatim. REST paths call expandURL to substitute {{.id}}; the WS wrapper skips that step, so the dialed URL is literally wss://api.zoo.dev/ws/ml/reasoning/{{.id}} and id is silently discarded. Affected: CopilotWs, CommandsWs, CreateTerm, ReasoningWs. The body any parameter is dead in all of them.
Concrete failure
ReasoningWs(id, ...) can never reach a valid per-id endpoint (404 on a garbage path) for any input.
CommandsWs(1024, 768, ...webrtc=true...) connects but silently gets the server defaults; the video resolution, fps, webrtc, pool, replay, and copilot conversation_id never leave the process. Nothing errors. The Rust client sends all of these correctly (kittycad.rs/kittycad/src/modeling.rs).
Verify
Point the client at an httptest WebSocket server and assert r.URL.RawQuery on the server side for CommandsWs(...) with non-default values: it is empty. For ReasoningWs, the dial target still contains {{.id}}.
Suggested fix
Have the WS template build the query string from its parameters and call expandURL for path templating, mirroring the REST template.
Environment
Reviewed against the current main of KittyCAD/kittycad.go.
WebSocket wrappers discard every query argument, and
ReasoningWsdials the literal template path/ws/ml/reasoning/{{.id}}Context: API Makeathon participant. Found reviewing the official clients; independently confirmed by a second review pass. Root cause is the generator template
cmd/tmpl/websocket.tmpl.Every generated WebSocket wrapper accepts configuration parameters in its signature but never serializes them into the dialed URL. Unlike the REST wrappers, the WS wrappers never call
expandURL.Evidence
paths.go(CommandsWs~10605):ReasoningWs(~10571) is worse:path := "/ws/ml/reasoning/{{.id}}"is dialed verbatim. REST paths callexpandURLto substitute{{.id}}; the WS wrapper skips that step, so the dialed URL is literallywss://api.zoo.dev/ws/ml/reasoning/{{.id}}andidis silently discarded. Affected:CopilotWs,CommandsWs,CreateTerm,ReasoningWs. Thebody anyparameter is dead in all of them.Concrete failure
ReasoningWs(id, ...)can never reach a valid per-id endpoint (404 on a garbage path) for any input.CommandsWs(1024, 768, ...webrtc=true...)connects but silently gets the server defaults; the video resolution, fps,webrtc,pool,replay, and copilotconversation_idnever leave the process. Nothing errors. The Rust client sends all of these correctly (kittycad.rs/kittycad/src/modeling.rs).Verify
Point the client at an
httptestWebSocket server and assertr.URL.RawQueryon the server side forCommandsWs(...)with non-default values: it is empty. ForReasoningWs, the dial target still contains{{.id}}.Suggested fix
Have the WS template build the query string from its parameters and call
expandURLfor path templating, mirroring the REST template.Environment
Reviewed against the current
mainof KittyCAD/kittycad.go.