Skip to content

docs: mention WebSocket support in README intro and request journal#1

Open
NathanTarbert wants to merge 106 commits into
mainfrom
docs/update-readme-websocket
Open

docs: mention WebSocket support in README intro and request journal#1
NathanTarbert wants to merge 106 commits into
mainfrom
docs/update-readme-websocket

Conversation

@NathanTarbert

@NathanTarbert NathanTarbert commented Mar 12, 2026

Copy link
Copy Markdown
Owner

Summary

  • Updated the tagline and intro paragraph to mention WebSocket transport alongside SSE
  • Added OpenAI Realtime and Gemini Live to the list of supported APIs in the intro
  • Updated the Request Journal section to note that WebSocket endpoint requests are also recorded

Test plan

  • Verify README renders correctly on GitHub

jpr5 added 8 commits March 11, 2026 17:16
Comprehensive /write-fixtures skill covering match fields, response
types, agent loop patterns (tool call → tool result → final response),
predicate routing, catch-alls, error injection, stream interruption,
and debugging fixture mismatches.

Distributed as a Claude Code plugin via marketplace:
  /plugin marketplace add CopilotKit/llmock
  /plugin install llmock@copilotkit-tools

Also available via --plugin-dir, --add-dir, or manual copy.
Four install methods documented: plugin marketplace (recommended),
local plugin from node_modules, --add-dir, and manual copy.
Nav link and feature cards added to docs site.
Changelog updated for 1.3.1.
Adds .claude-plugin, .claude, and skills directories to the package
files array so downstream consumers get the fixture authoring skill
when they npm install.
## Summary

- Adds `.claude/commands/write-fixtures.md` — a comprehensive fixture
authoring guide available as `/write-fixtures` slash command in Claude
Code
- Covers match fields, response types, common patterns (tool call loops,
catch-alls, predicate routing), critical gotchas, debugging mismatches,
and E2E test setup
- Verified against current API surface (v1.3.0) including interruption
options (`truncateAfterChunks`/`disconnectAfterMs`)

## Test plan

- [ ] Invoke `/write-fixtures` in Claude Code while working in the
llmock repo — confirm skill loads and renders correctly
- [ ] Verify code examples are syntactically correct TypeScript
- [ ] Confirm all documented API methods exist on the `LLMock` class
Track gif, jpg, jpeg, png, pdf, mp4, webm, and svg files via
Git LFS. Matches CopilotKit/CopilotKit convention (PR #3418).
Needed before any binary assets (screenshots, demo videos) land.
## Summary

- Adds `.gitattributes` tracking gif, jpg, jpeg, png, pdf, mp4, webm,
and svg via Git LFS
- Matches CopilotKit/CopilotKit convention (PR #3418)
- Needed before any binary assets (screenshots, demo videos) are added
to the repo

## Test plan

- [ ] `git lfs env` confirms LFS is active
- [ ] Adding a binary file shows it tracked via `git lfs status`
skills/write-fixtures/SKILL.md is now a symlink to
.claude/commands/write-fixtures.md. Single source of truth,
both --plugin-dir and --add-dir discovery paths still work.
## Summary

- Replaces `skills/write-fixtures/SKILL.md` (full copy) with a symlink
to `.claude/commands/write-fixtures.md`
- Single source of truth, both `--plugin-dir` and `--add-dir` discovery
paths still work

## Test plan

- [ ] `claude --plugin-dir ./node_modules/@copilotkit/llmock` discovers
`/llmock:write-fixtures`
- [ ] `claude --add-dir ./node_modules/@copilotkit/llmock` discovers
`/write-fixtures`
- [ ] Symlink resolves correctly after `npm install`
@NathanTarbert
NathanTarbert force-pushed the docs/update-readme-websocket branch from 8116137 to c1593fd Compare March 12, 2026 01:49
jpr5 and others added 21 commits March 14, 2026 22:32
OpenAI now returns a `refusal` field (null for non-refusal responses)
on all Chat Completions messages. Both the SDK types and real API
include it, but llmock was omitting it — causing shape mismatches
for consumers that validate response structure.
Three-layer triangulation between SDK types, real API responses, and
llmock output to detect response shape drift across OpenAI (Chat +
Responses), Anthropic Claude, and Google Gemini.

- schema.ts: shape extraction, three-way comparison, severity classification
- sdk-shapes.ts: expected shapes from SDK types
- providers.ts: raw fetch clients, SSE parsing, model listing
- helpers.ts: shared test fixtures and server lifecycle
- 4 provider drift test files (16 tests) + model deprecation checks (3 tests)
- vitest.config.drift.ts: separate config with 30s timeout
- Weekly CI workflow (.github/workflows/test-drift.yml)
- DRIFT.md: full documentation
## Summary

- **Bug fix**: OpenAI Chat Completions responses now include `refusal:
null` — a field both the SDK and real API return that llmock was
omitting. Conformance and unit tests updated to assert the field.
- **New feature**: Three-layer drift detection test suite that
triangulates between SDK types, real API responses, and llmock output to
catch response shape drift across all 4 providers (OpenAI Chat, OpenAI
Responses, Anthropic Claude, Google Gemini)
- **CI**: Weekly GitHub Actions workflow for automated drift checks +
manual trigger

## Details

19 drift tests across 5 files:
- 16 shape comparison tests (4 per provider × 4 scenarios: non-streaming
text/tool, streaming text/tool)
- 3 model deprecation checks (one per provider)

Key robustness features:
- All provider functions fail fast on non-2xx responses with status code
+ body in the error message
- All streaming tests assert events were actually received (no silent
pass on zero events)
- SSE parsers handle `\r\n` line endings and continuation lines (Gemini
sends wrapped JSON)
- Retry with exponential backoff on 429/500/502/503
- `ping` and other transport-level SSE events classified as `info`, not
`critical`
- Known intentional differences (usage fields, system_fingerprint, etc.)
allowlisted

The refusal bug was discovered by running the drift tests against real
APIs — exactly the value prop.

See [DRIFT.md](DRIFT.md) for full documentation.

## Test plan

- [x] `pnpm test` — 540/540 existing tests pass (including new refusal
assertions)
- [x] `pnpm test:drift` with all 3 API keys — 19/19 pass
- [x] `pnpm test:drift` without keys — 19 tests skip gracefully
- [x] Prettier + ESLint clean
- [x] 4 rounds of code review (code-reviewer, silent-failure-hunter,
code-simplifier, comment-analyzer, pr-test-analyzer,
type-design-analyzer) — all clean

🤖 Generated with [Claude Code](https://claude.com/claude-code)
- Add Gemini base URL setup instructions to README (both SDK versions)
- Add missing ANTHROPIC_API_KEY=mock-key to README env block
- Center orphan WebSocket APIs card in features grid
- Move Real-World Usage section to bottom of docs site
- Tighten section padding (6rem → 3rem)
- Remove inconsistent border-top on comparison section
- Match Real-World Usage heading and description style to other sections
The real OpenAI Responses WS API expects a flat message format:
  { type: "response.create", model: "...", input: [...] }

The handler previously required fields nested under a "response"
object, which doesn't match the real API. Updated the handler and
all existing WS tests to use the flat format.
…emini Live

TLS WebSocket client (ws-providers.ts) connects to real provider WS
endpoints using node:tls with RFC 6455 framing, ping/pong, and
connection-scoped message cursors for multi-step protocols.

4 verified drift tests:
- OpenAI Responses WS: text + tool call
- OpenAI Realtime: text + tool call (gpt-4o-mini-realtime-preview)

Canaries:
- Realtime: checks gpt-4o-mini-realtime-preview still exists in model
  listing API, with hints for the GA replacement
- Gemini Live: checks model listing API for text-capable
  bidiGenerateContent models; full drift tests skipped until Google
  ships a non-audio Live model

Supporting changes:
- sdk-shapes.ts: Realtime + Gemini Live event shapes
- helpers.ts: collectMockWSMessages(), classifyGeminiMessage, GEMINI_WS_PATH
- models.drift.ts: filter markdown anchor fragments from model scraper
DRIFT.md: WS coverage table with verified/unverified status, Gemini
Live explanation, cost estimate (25 API calls), "Adding a New Provider"
WS step.

README.md: fix Gemini Live response shape example, update model name,
add unverified warning, fix Responses WS example to use flat format.

docs/index.html: add unverified note to Gemini Live in feature list
and comparison table.

CHANGELOG.md: 1.3.3 patch notes.
vitest.config.drift.ts: increase testTimeout to 60s for WS protocols.
## Summary

- **Fix Responses WS input format**: handler now accepts the flat
`response.create` format matching the real OpenAI API (previously
required a non-standard nested `response: { ... }` envelope)
- **4 verified WS drift tests**: OpenAI Responses WS (text + tool call)
and OpenAI Realtime (text + tool call), triangulated against real APIs
- **Model canaries**: Realtime preview model availability check (detects
deprecation, suggests GA replacement); Gemini Live text-capable model
availability check (enables drift tests when Google ships one)
- **Gemini Live**: protocol implemented per docs, documented as
unverified — no text-capable `bidiGenerateContent` model exists yet
- TLS WebSocket client (`ws-providers.ts`) with RFC 6455 framing,
ping/pong, connection-scoped cursors
- SDK shapes for Realtime and Gemini Live event sequences
- Fix README Gemini Live response shape example and Responses WS example

## Test plan

- [x] `pnpm test` — 540 unit tests pass
- [x] `pnpm test:drift` without keys — all 27 tests skip gracefully
- [x] `pnpm test:drift` with keys — 25 pass, 2 skip (Gemini Live
text/tool)
- [x] `pnpm run format:check` — clean
- [x] `pnpm run lint` — clean
- [x] 5 rounds of CR→fix loop with full pr-review-toolkit suite

🤖 Generated with [Claude Code](https://claude.com/claude-code)
New section between "Fixture-driven" and "llmock vs MSW" with:
- SVG triangle diagram showing three-way drift detection
  (SDK types × Real API × llmock)
- Three diagnosis cards (mock drift, provider ahead of SDK, all clear)
- Real drift report output showing mixed severity results
- Daily CI badge footer

Also: drift cron weekly → daily, nav link added, DRIFT.md cost
updated for daily cadence, README "weekly" → "daily".
…otKit#38)

## Summary

- New "Reliability" section on the docs site between code examples and
MSW comparison
- SVG triangle diagram showing three-way drift detection (SDK types ×
Real API × llmock)
- Three diagnosis cards: mock drift (red), provider ahead of SDK
(amber), all clear (green)
- Real drift report output showing mixed severity results
- Daily CI badge footer
- Drift cron changed from weekly to daily
- Nav link added for new section
- DRIFT.md cost updated for daily cadence
- README "weekly" → "daily"

## Test plan

- [x] `pnpm test` — 540/540 pass
- [x] All CSS uses site custom properties (no hardcoded hex)
- [x] Mobile breakpoints for diagnosis grid and CI footer
- [x] 2 rounds of CR — all 6 toolkit agents, clean on final round

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Adds the Drift Tests CI badge next to the existing Unit Tests badge.
New src/logger.ts with silent/info/debug levels. Warnings and
errors always print regardless of level. Logger threaded through
createServer via the widened defaults object. WebSocket upgrade
console.error calls migrated to logger.
validateFixtures() checks: response type recognition, empty content,
tool call name/JSON validity, empty toolCalls arrays, error message
presence, HTTP status range, numeric field bounds. Warns on duplicate
userMessage shadowing and catch-all positioning. Load functions accept
optional logger with backward-compatible console.warn fallback.
--watch (-w): 500ms debounced fs.watch with in-place fixture
  reload. Error handler on FSWatcher surfaces dead-watcher
  conditions. Actionable error messages on reload failure.
  Keeps previous fixtures when validation fails.

--log-level: silent/info/debug (default: info). Startup, reload,
  and shutdown messages at info. Warnings/errors always print.

--validate-on-load: exits 1 on errors at startup. In watch mode,
  errors prevent reload without killing the server.

CLI options table updated. Future Direction CLI section removed.
…tKit#40)

## Summary

Three CLI features from the Future Direction section:

- **--watch** (`-w`): 500ms debounced `fs.watch` with in-place fixture
reload. Keeps previous fixtures on validation failure. Error handler on
FSWatcher surfaces dead-watcher conditions.
- **--log-level**: `silent` / `info` (default) / `debug`. Startup
messages and per-request access logs at info. Match traces at debug.
Warnings/errors always print.
- **--validate-on-load**: Runs `validateFixtures()` at startup, exits 1
on errors. In watch mode, errors prevent reload but don't exit.

### Validation checks

**Errors** (exit 1): unrecognized response type, empty content, empty
tool call name, unparseable tool call arguments, empty error message,
invalid HTTP status, negative latency, chunkSize < 1,
truncateAfterChunks < 1, negative disconnectAfterMs.

**Warnings** (logged): duplicate userMessage shadowing, catch-all not in
last position.

### New files

- `src/logger.ts` — Logger class with silent/info/debug levels
- `src/watcher.ts` — File watcher for fixture hot-reload

### Documentation

- CLI options table updated with new flags
- Future Direction CLI section removed (all items implemented)

## Test plan

- [x] 564/564 tests pass (24 new: 15 validation + 9 CLI integration)
- [x] Prettier + ESLint clean
- [x] 2 rounds of CR (code-reviewer, silent-failure-hunter,
code-simplifier, pr-test-analyzer) — clean on final round

🤖 Generated with [Claude Code](https://claude.com/claude-code)
)

## Summary

Post-merge follow-up to PR CopilotKit#40. Addresses 3 findings from code review:

- Watcher error handler now clears debounce timer and closes FSWatcher
to avoid indeterminate state after fs.watch errors
- Reload guards against replacing fixtures with empty array on parse
failure, preserving previous working fixtures
- Update README Future Direction to reflect that `--validate-on-load`
and `validateFixtures()` now exist

## Test plan

- [x] All 565 existing tests pass
- [ ] Manually verify watcher error recovery (kill the watched file's
filesystem)
- [ ] Verify reload with invalid JSON preserves previous fixtures
jpr5 and others added 29 commits March 21, 2026 11:19
…d time

Add error-severity validation checks in validateFixtures for streamingProfile
(ttft >= 0, tps > 0, jitter in [0,1]) and chaos (all rates in [0,1]). Catches
nonsensical streaming physics and out-of-range chaos rates early with clear
error messages rather than silently producing broken behavior at request time.
…G chaos flags

- docker.html: fix health probes (TCP socket → httpGet on /health and /ready)
- docker.html: remove "CLI Configuration (v1.7.0)" section (references non-existent --config
  flag and aimock binary name)
- docker.html: fix --chaos-error-rate → --chaos-drop/--chaos-malformed/--chaos-disconnect
- docker.html: fix mountPath /fixtures → /app/fixtures (matches actual values.yaml)
- docs.html: add POST /v2/chat (Cohere) and POST /api/generate (Ollama) to endpoint table
- CHANGELOG.md: fix "via --chaos CLI flag" → list all three chaos flags
- README.md: fix chaos-testing link (chaos.html → chaos-testing.html)
… bedrock SSE; body timeout

- chaos.ts: add optional logger param to resolveChaosConfig/evaluateChaos/applyChaos;
  replace all console.warn calls with logger?.warn
- stream-collapse.ts: logger param on collapseStreamingResponse; replace console.warn;
  add explicit case "bedrock" routing to collapseAnthropicSSE; add bounds check in
  decodeEventStreamFrames — return {frames, truncated:true} when totalLength extends
  past buffer, preventing out-of-bounds reads on malformed/truncated EventStream frames
- recorder.ts: pass defaults.logger to collapseStreamingResponse; add res.setTimeout
  body accumulation timeout (30s) to prevent unbounded memory growth on slow responses
- bedrock.ts: update module docstring to describe all four endpoint families
- all handlers: pass defaults.logger as final arg to all applyChaos call sites
…edrock SSE, and body timeout

- chaos.test.ts: verify evaluateChaos without logger does not call console.warn;
  verify invalid chaos header with logLevel:silent is silently ignored end-to-end
- stream-collapse.test.ts: verify bounds check returns {truncated:true} for
  oversized totalLength; verify provider="bedrock" routes to collapseAnthropicSSE
- recorder.test.ts: verify proxyAndRecord calls res.setTimeout(30_000) on
  upstream IncomingMessage
…ation, type unions

- recorder.ts: fix misleading 'saving raw response' log → 'saving as error fixture'
- recorder.ts: warn when stream collapse produces empty content
- recorder.ts: preserve both empty-match and truncation warnings in fixture JSON
- cli.ts: exit(1) on zero fixtures in strict/validate mode
- server.ts: warn on out-of-range chaos config values at startup
- bedrock.ts/messages.ts: narrow content block type from string to union
- aws-event-stream.ts: fix writeEventStream docstring return semantics
…Kit#53)

## Summary

Major feature release adding 8 capabilities to llmock, plus 29 bugs
found and fixed in code review.

### Provider Endpoints
- **Bedrock Streaming** — invoke-with-response-stream (AWS Event Stream
binary) + Converse API
- **Vertex AI** — Routes to existing Gemini handler
- **Ollama** — /api/chat, /api/generate, /api/tags (NDJSON streaming)
- **Cohere** — /v2/chat (typed SSE events)

### Infrastructure
- **Chaos Testing** — Probabilistic drop/malformed/disconnect, three
precedence levels (header > fixture > server), rate clamping to [0,1]
- **Prometheus Metrics** — Opt-in /metrics, counters, cumulative
histograms, gauges

### Record-and-Replay
- **Proxy-on-miss** — Real API responses saved as fixtures with 30s
upstream timeout
- **Stream collapsing** — 6 functions (SSE, NDJSON, EventStream)
supporting both Converse and Messages formats
- **Strict mode (503)** — Catch missing fixtures in CI
- **Auth safety** — Forwarded but redacted in journal, never in fixtures

### Quality
- **1250 tests** across 37 files
- 7 rounds of 7-agent code review, 29 bugs found and fixed
- Build/format/lint clean, zero external dependencies, zero as-any in
source

## Review Fixes (29 total across 7 rounds)

### Round 1: Original review (20 findings)
- HandlerDefaults type extracted, fixing silent undefined access in 5
handlers
- Provider-specific error formats (Anthropic, Gemini, Bedrock)
- Recorder binary relay corruption (UTF-8 round-trip on EventStream)
- collapseOllamaNDJSON tool_calls + buildFixtureResponse priority
- ChaosAction dedup, RecordProviderKey union, OllamaMessage.role union
- collapseCohereSSE naming, chaos rate clamping, recorder auth comment
- SKILL.md 503 status, warn log level, README provider list, types.ts
header

### Round 2 (2 findings)
- applyChaos registry argument missing in 5 handlers (chaos metrics
incomplete)
- Bedrock Converse response format missing in buildFixtureResponse

### Round 5 — fresh context (2 findings)
- Global recordCounter → crypto.randomUUID() (concurrent test
determinism)
- rawBody pass-through in OpenAI completions proxy path

### Round 6 — fresh context (2 findings)
- 30s upstream timeout in makeUpstreamRequest (prevents indefinite
hangs)
- collapseBedrockEventStream: handle both Converse (camelCase) and
Messages (flat type) formats

### Round 7 — fresh context (3 findings)
- new URL() validation with specific 502 error for malformed provider
URLs
- writtenToDisk flag to prevent misleading "Response recorded" log on
write failure
- res.on("error") handler for upstream response stream mid-transfer
drops

All fixes have corresponding regression tests.
Automated weekly update based on competitor README analysis.
Writes a markdown file with a change table and mermaid flowchart
grouped by competitor. Mermaid node labels are quoted and subgraph
IDs sanitized to handle special characters in competitor/capability
names.
Covers markdown table generation, mermaid flowchart structure,
special character escaping (parentheses, quotes, slashes),
competitor grouping, node ID uniqueness, and file I/O.
Pass --summary to the script and use gh pr create --body-file to
inject the markdown directly, avoiding shell interpolation of
backticks from the mermaid code fences.
## Summary

- The `update-competitive-matrix.ts` script now accepts `--summary
<path>` to write a markdown summary with a change table and mermaid
flowchart grouped by competitor
- The workflow uses `--body-file` to inject the summary directly into
the PR body, avoiding shell interpolation of mermaid backtick fences
- Mermaid node labels are quoted and subgraph IDs sanitized to handle
special characters (parentheses, slashes, quotes)
- Unit tests cover formatting, mermaid structure, escaping, and edge
cases

## Test plan

- [x] `pnpm test` — 1318 tests pass (13 new)
- [x] `pnpm run format:check` — clean
- [x] `pnpm run lint` — clean
- [x] `pnpm run build` — clean
- [ ] Trigger workflow via `workflow_dispatch` and confirm PR body
renders correctly
The URL constructor drops the base path when the pathname is absolute:
new URL("/v1/chat/completions", "https://openrouter.ai/api") resolves to
https://openrouter.ai/v1/chat/completions — losing /api.

Extract resolveUpstreamUrl() that normalizes inputs for RFC 3986 relative
resolution (trailing slash on base, strip leading slash from pathname).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The record proxy only forwarded a hardcoded list of headers (authorization,
x-api-key, api-key, content-type, accept), silently dropping provider-specific
headers like anthropic-version. This made --record mode unusable with the
Anthropic API.

Forward all request headers by default, excluding only hop-by-hop headers
that should not be proxied (host, connection, content-length,
transfer-encoding, keep-alive, upgrade).
## Problem

The record proxy loses the base URL path prefix when constructing
upstream URLs.

`new URL("/v1/chat/completions", "https://openrouter.ai/api")` resolves
to
`https://openrouter.ai/v1/chat/completions` — the `/api` prefix is
dropped.
This is standard `URL` constructor behavior (absolute pathname replaces
the
base path), but breaks providers like OpenRouter whose API lives at a
non-root path (`/api/v1/...`).

Providers with root-path APIs (OpenAI, Anthropic) are unaffected.

## Fix

Extract URL joining into a `resolveUpstreamUrl()` helper that normalizes
inputs for RFC 3986 relative resolution:
- Ensure base URL has a trailing slash (marks it as a "directory")
- Strip leading slash from pathname (makes it relative, not absolute)

This preserves any path prefix while keeping existing behavior for
root-path providers unchanged. Unit tests cover all combinations.

## Examples

| Base URL | Pathname | Before | After |
|---|---|---|---|
| `https://openrouter.ai/api` | `/v1/chat/completions` |
`https://openrouter.ai/v1/chat/completions` |
`https://openrouter.ai/api/v1/chat/completions` |
| `https://api.openai.com` | `/v1/chat/completions` |
`https://api.openai.com/v1/chat/completions` |
`https://api.openai.com/v1/chat/completions` |
| `https://api.anthropic.com` | `/v1/messages` |
`https://api.anthropic.com/v1/messages` |
`https://api.anthropic.com/v1/messages` |
## Problem

The record proxy only forwards 5 hardcoded request headers to the
upstream provider:
`authorization`, `x-api-key`, `api-key`, `content-type`, `accept`.

Provider-specific headers are silently dropped. For example, the
Anthropic API
requires the `anthropic-version` header on every request — without it,
the API
returns 400:

```
{'type': 'error', 'error': {'type': 'invalid_request_error',
 'message': 'anthropic-version: header is required'}}
```

This makes `--record` mode unusable with the Anthropic provider.

## Fix

Forward all request headers by default, stripping only headers that
should
not be proxied. The strip list is a module-level constant:

**Hop-by-hop headers** ([RFC 2616
§13.5.1](https://datatracker.ietf.org/doc/html/rfc2616#section-13.5.1)):
`connection`, `keep-alive`, `transfer-encoding`, `te`, `trailer`,
`upgrade`,
`proxy-authorization`, `proxy-authenticate`

**Set by the HTTP client** (from target URL / body):
`host`, `content-length`

**LLM proxy specific** (avoid leaking or encoding mismatch):
`cookie`, `accept-encoding`

Auth headers (`authorization`, `x-api-key`) are still forwarded —
they're
no longer special-cased since all non-stripped headers pass through.
They
continue to be excluded from saved fixture files (that logic is separate
and unchanged).

`Via` / `X-Forwarded-*` are **not** set or stripped — consistent with
how
other LLM proxy tools (LiteLLM, Portkey, Helicone) handle upstream
forwarding, and LLM APIs (OpenAI, Anthropic) do not use them.
Guard the Float32Array decode with try/catch so corrupted base64 data
falls through to the error path instead of crashing.

Add 4 tests covering the encoding_format × embedding-type matrix:
- base64 string + encoding_format:base64 → decodes to float array
- base64 string + no encoding_format → proxy_error (original bug path)
- array embedding + encoding_format:base64 → array passthrough
- truncated base64 (odd byte count) → empty embedding, no crash
## Problem

When recording fixtures, the recorder fails to save OpenAI embedding
responses that use base64 encoding. The OpenAI API returns
base64-encoded embeddings when `encoding_format: "base64"` is set (the
default in many SDKs, including Python's `openai` package).

`buildFixtureResponse` checks `Array.isArray(first.embedding)` but the
embedding is a base64 string, not an array. The fixture is saved with a
`proxy_error`:

```json
{
  "response": {
    "error": { "message": "Could not detect response format from upstream", "type": "proxy_error" },
    "status": 200
  }
}
```

## Fix

Extract `encoding_format` from the raw request body and pass it to
`buildFixtureResponse`. When `encoding_format === "base64"` and the
embedding is a string, decode it via `Buffer.from(embedding, "base64")`
→ `Float32Array` → `Array.from(floats)`.

This is forward-safe — only decodes when `encoding_format` explicitly
says `"base64"`, so future encoding formats (e.g. `"int8"`) won't be
misinterpreted.

## Changes

- `recorder.ts`: Extract `encoding_format` from raw request, pass to
`buildFixtureResponse`, decode base64 when detected
Patch release with recorder fixes:
- Preserve upstream URL path prefixes in record proxy (CopilotKit#57)
- Forward all request headers in record proxy (CopilotKit#58)
- Decode base64-encoded embeddings in recorder (CopilotKit#64)
- Guard base64 decode against corrupted data
- Update CHANGELOG, skill docs
## Summary

Patch release capturing recorder fixes that landed since v1.6.0.

### Patch Changes

- Fix record proxy to preserve upstream URL path prefixes (CopilotKit#57)
- Fix record proxy to forward all request headers to upstream (CopilotKit#58)
- Fix recorder to decode base64-encoded embeddings with
`encoding_format: "base64"` (CopilotKit#64)
- Guard base64 decode against corrupted data
- Update CHANGELOG, skill docs, competitive matrix script

### Files changed

- `package.json` — version 1.6.0 → 1.6.1
- `CHANGELOG.md` — new 1.6.1 section
- `skills/write-fixtures/SKILL.md` — recorder docs updated

All 1,327 tests pass. Build clean.
Warns when source commits accumulate on main without a version bump,
preventing fixes from sitting unreleased like the v1.6.0 → v1.6.1 gap.
Adds explicit git tag creation step before gh release create, so tags
exist even if the release step fails partway. Also passes --verify-tag
to gh release create for safety.
…tKit#66)

## Summary

Two CI improvements to prevent the v1.6.0 → v1.6.1 gap from happening
again.

### 1. Unreleased changes check (`unreleased-check.yml`)
Runs on every push to main. Compares HEAD against the latest version tag
and emits a `::warning` annotation when source commits exist without a
version bump. Skips gracefully when the tag doesn't exist yet (i.e., the
push IS the release).

### 2. Tag safety in release workflow (`publish-release.yml`)
- Explicit `git tag` + `git push origin <tag>` step before `gh release
create`
- `--verify-tag` flag on `gh release create` for belt-and-suspenders
safety
- Outputs version for reuse across steps

### Also going forward
Release commits must use `chore: release vX.Y.Z` format to pass
commitlint. Always create a branch first, never commit directly to main.
The detailed MSW comparison table is better suited for the docs site
competitive matrix than the package landing page. Keeps the README
focused on what llmock does, not what competitors don't.
Removes the MSW vs llmock comparison section and table from the
README/npm landing page. This content lives on the [docs site
competitive matrix](https://llmock.copilotkit.dev/) instead.
Add video demo, document WebSocket endpoints (OpenAI Responses, Realtime,
Gemini Live), expand fixture matching/response reference, and add error
injection and request journal sections.
@NathanTarbert
NathanTarbert force-pushed the docs/update-readme-websocket branch from be2c131 to 5f04ca5 Compare April 1, 2026 01:12
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.

3 participants