Skip to content

Add WebSocket docs, CHANGELOG catch-up, and CopilotKit usage reference#25

Merged
jpr5 merged 4 commits intomainfrom
docs/ws-docs
Mar 11, 2026
Merged

Add WebSocket docs, CHANGELOG catch-up, and CopilotKit usage reference#25
jpr5 merged 4 commits intomainfrom
docs/ws-docs

Conversation

@jpr5
Copy link
Copy Markdown
Contributor

@jpr5 jpr5 commented Mar 11, 2026

Summary

  • CHANGELOG.md catch-up for 1.0.1, 1.1.1, and 1.2.0 releases
  • WebSocket API documentation in README (endpoints, usage examples for Responses, Realtime, Gemini Live)
  • WebSocket feature card and code example in docs/index.html landing page
  • Real-world usage section referencing CopilotKit E2E tests as examples

Test plan

  • Docs-only changes, no code modified
  • Prettier clean

🤖 Generated with Claude Code

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Mar 11, 2026

Open in StackBlitz

npm i https://pkg.pr.new/CopilotKit/llmock/@copilotkit/llmock@25

commit: 5223d77

@jpr5 jpr5 merged commit 278cec3 into main Mar 11, 2026
9 checks passed
@jpr5 jpr5 deleted the docs/ws-docs branch March 11, 2026 20:04
Comment on lines +560 to +561
// {"serverContent": {"modelTurnComplete": false, "parts": [{"text": "Hello"}]}}
// {"serverContent": {"modelTurnComplete": true}}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 The Gemini Live serverContent response format in the docs does not match the actual server implementation. The field is turnComplete (not modelTurnComplete), and parts is nested inside a modelTurn object (not directly under serverContent). Correct format: {"serverContent": {"modelTurn": {"parts": [{"text": "Hello"}]}, "turnComplete": false}}.

Extended reasoning...

What the bug is

The README documentation for the Gemini Live WebSocket API (lines 560-561) shows an incorrect response format for serverContent messages. The documented format has two errors compared to what the server actually sends.

Documented vs actual format

The README shows:

{"serverContent": {"modelTurnComplete": false, "parts": [{"text": "Hello"}]}}
{"serverContent": {"modelTurnComplete": true}}

The server actually sends (ws-gemini-live.ts lines 354-356):

{"serverContent": {"modelTurn": {"parts": [{"text": "Hello"}]}, "turnComplete": false}}

Two specific errors

  1. Wrong field name: The field is turnComplete, not modelTurnComplete. The server code at line 356 explicitly uses turnComplete: isLast.
  2. Wrong nesting of parts: The parts array is nested inside a modelTurn object (serverContent.modelTurn.parts), not directly under serverContent (serverContent.parts). The server constructs modelTurn: { parts: [{ text: chunks[i] }] } at line 355.

Step-by-step proof

A user following the documented format would write code like:

const text = msg.serverContent.parts[0].text;        // undefined - parts is not at this level
const done = msg.serverContent.modelTurnComplete;      // undefined - field does not exist

The correct parsing (confirmed by tests at ws-gemini-live.test.ts lines 103-104, 129-135, 180-188) is:

const text = msg.serverContent.modelTurn.parts[0].text;  // correct
const done = msg.serverContent.turnComplete;              // correct

Impact

This is a documentation-only PR where accuracy is the core deliverable. Users integrating with the Gemini Live WebSocket API based on these docs would write parsers that look for non-existent fields (modelTurnComplete, serverContent.parts) and would fail to extract the actual text data nested under modelTurn. Both field name and structure are wrong, so there is no partial success path - the documented format is completely unusable for parsing real responses.

Fix

Replace the two example lines at README.md:560-561 with the correct format:

// {"serverContent": {"modelTurn": {"parts": [{"text": "Hello"}]}, "turnComplete": false}}
// {"serverContent": {"modelTurn": {"parts": [{"text": "..."}]}, "turnComplete": true}}

Comment on lines +509 to +514
// ← Server streams:
// {"type": "response.created", ...}
// {"type": "response.output_item.added", ...}
// {"type": "response.content_part.added", ...}
// {"type": "response.output_item.done", ...}
// {"type": "response.done", ...}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 The WS Responses API event sequence example (lines 509-514) has two issues: (1) the final event is listed as response.done but the server sends response.completed (confirmed in responses.ts:258 and tests), and (2) the sequence omits critical events including response.output_text.delta (the events carrying actual streamed text), response.in_progress, response.output_text.done, and response.content_part.done — the server sends 9 event types, not the 5 shown. A user implementing a client from this documentation would miss the text content and wait for a terminal event that never arrives.

Extended reasoning...

What the bug is

The documented event sequence for the OpenAI Responses API over WebSocket (README.md lines 509-514) contains two distinct issues in the same code block:

  1. Wrong terminal event name: Line 514 shows response.done as the final event, but the server actually sends response.completed. These are different event type strings — the Responses API uses response.completed while the separate Realtime API (/v1/realtime) uses response.done. The confusion is understandable since both are OpenAI protocols, but they use different terminal event names.

  2. Missing intermediate events: The example shows only 5 event types but the server actually sends 9. Critically missing are response.output_text.delta (the events that carry the actual streamed text content), along with response.in_progress, response.output_text.done, and response.content_part.done.

Proof via code walkthrough

The test at ws-responses.test.ts:77-86 explicitly asserts the full event sequence:

expect(types[0]).toBe("response.created");
expect(types[1]).toBe("response.in_progress");
expect(types).toContain("response.output_item.added");
expect(types).toContain("response.content_part.added");
expect(types).toContain("response.output_text.delta");
expect(types).toContain("response.output_text.done");
expect(types).toContain("response.content_part.done");
expect(types).toContain("response.output_item.done");
expect(types[types.length - 1]).toBe("response.completed");

The implementation in responses.ts:258 sends type: "response.completed", and the conformance test at api-conformance.test.ts:334 also requires response.completed.

Compare to the README documentation (lines 509-514) which shows:

// {"type": "response.created", ...}
// {"type": "response.output_item.added", ...}
// {"type": "response.content_part.added", ...}
// {"type": "response.output_item.done", ...}
// {"type": "response.done", ...}

Impact

The response.done vs response.completed issue is the more severe of the two: a client parsing the WS Responses stream would wait for response.done which never arrives, effectively hanging or timing out on every request. The missing response.output_text.delta events mean a user would not know to listen for the events that carry the actual streamed text — the primary purpose of the streaming API.

Note: The Realtime API section just below correctly uses response.done (lines 546-547), which is correct for that different protocol. This makes the Responses API section inconsistent within the same document.

Regarding the duplicate refutation

One verifier flagged bug_002 as a duplicate of bug_004. This merged bug combines the original bug_002 (wrong terminal event name) with bug_003 (missing intermediate events) into a single cohesive report covering both issues in the same code block, so no separate bug_004 report is needed.

Fix

Update lines 509-514 to show the actual event sequence:

// {"type": "response.created", ...}
// {"type": "response.in_progress", ...}
// {"type": "response.output_item.added", ...}
// {"type": "response.content_part.added", ...}
// {"type": "response.output_text.delta", "delta": "Hi"}
// ...
// {"type": "response.output_text.done", ...}
// {"type": "response.content_part.done", ...}
// {"type": "response.output_item.done", ...}
// {"type": "response.completed", ...}

Comment on lines +1309 to +1312
<tr>
<td>WebSocket APIs (Realtime, Gemini Live)</td>
<td class="yes">Built-in ✓</td>
<td class="no">No</td>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 The comparison table claims MSW has "No" WebSocket support, but MSW v2 added first-class WebSocket mocking via the ws API (ws.link()). Every other streaming row in this table uses the "Manual — build X yourself" pattern to acknowledge MSW can do it with effort; this row should follow suit (e.g., <td class="manual">Manual — build protocol events yourself</td>) for consistency and factual accuracy.

Extended reasoning...

What the bug is

The newly added WebSocket row in the comparison table (lines 1309-1312 of docs/index.html) uses <td class="no">No</td> for the MSW column. This states that MSW has zero WebSocket capability, which is factually incorrect.

MSW v2 WebSocket support

MSW v2 (released in 2024) introduced first-class WebSocket mocking through the ws namespace. Users can intercept WebSocket connections with ws.link("wss://example.com") and programmatically send/receive messages. While MSW does not provide built-in LLM-specific protocol handling (e.g., OpenAI Realtime event sequences or Gemini Live setup/content flows), it absolutely can handle WebSocket connections — you just need to manually construct the protocol-level events yourself.

Internal inconsistency

The table already has an established pattern for describing capabilities that MSW supports but requires manual effort:

  • Chat Completions SSE → Manual — build data/[DONE] yourself
  • Responses API SSE → Manual — MSW sse() uses wrong format
  • Claude Messages API SSE → Manual — build event/data SSE yourself
  • Gemini streaming → Manual — build data SSE yourself

All of these use the manual CSS class (amber/warning color). The WebSocket row breaks this pattern by using no (dim color), implying a complete absence of capability rather than a "possible but requires effort" situation.

Concrete walkthrough

A user evaluating llmock vs MSW reads the comparison table. They see every streaming protocol listed as "Manual" for MSW, correctly understanding that MSW can handle these but without built-in format support. Then they reach the WebSocket row and see "No" — concluding MSW cannot do WebSocket mocking at all. They might choose llmock solely for WebSocket support, when in fact they could have used MSW with manual protocol event construction, just like all the SSE rows describe.

Impact

This is a documentation accuracy issue. It could mislead users evaluating the two tools and misrepresents MSW's capabilities. It's not a functional bug — no code behavior is affected.

Fix

Change line 1312 from <td class="no">No</td> to something like <td class="manual">Manual — build protocol events yourself</td>, consistent with the other streaming rows in the table.

jpr5 added a commit that referenced this pull request Apr 3, 2026
#25)

## Summary

- CHANGELOG.md catch-up for 1.0.1, 1.1.1, and 1.2.0 releases
- WebSocket API documentation in README (endpoints, usage examples for
Responses, Realtime, Gemini Live)
- WebSocket feature card and code example in docs/index.html landing
page
- Real-world usage section referencing CopilotKit E2E tests as examples

## Test plan

- [x] Docs-only changes, no code modified
- [x] Prettier clean

🤖 Generated with [Claude Code](https://claude.com/claude-code)
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