Skip to content

Record gpt-oss harmony tool calls + stream/recorder hardening#245

Merged
jpr5 merged 5 commits into
mainfrom
fix/aimock-gpt5-recording
Jun 2, 2026
Merged

Record gpt-oss harmony tool calls + stream/recorder hardening#245
jpr5 merged 5 commits into
mainfrom
fix/aimock-gpt5-recording

Conversation

@jpr5
Copy link
Copy Markdown
Contributor

@jpr5 jpr5 commented Jun 2, 2026

Summary

aimock could not record fixtures from local gpt-oss models (e.g. via Ollama/vLLM/OpenRouter) that emit OpenAI harmony channel tokens — the raw control tokens (<|channel|>commentary to=functions.NAME <|constrain|>json<|message|>{...}<|call|>) leaked into recorded content instead of being parsed into tool calls. This PR adds harmony parsing plus a round of stream/recorder hardening surfaced while fixing it.

Reported in Slack: local gpt-oss (gpt-5.x class) recordings were unusable because hosted OpenAI pre-parses harmony, but local runtimes pass it through raw.

What changed (by concern)

  1. Harmony channel parser (src/harmony.ts) — a two-phase lexer + state-machine that routes harmony analysis/commentary/final channels into reasoning/tool-calls/content. Uniform all-or-nothing fail-safe: on any structural deviation it returns the original content verbatim and signals harmonyUnparsed/harmonyNote — it never mangles, leaks a control token, or glues message bodies together. Wired as fallback-only (used only when no structured tool calls were already parsed), so it can never produce phantom/merged calls.

  2. Stream collapser hardening (src/stream-collapse.ts) — multi-data:-line and CRLF SSE handling; missing/uncorrelated tool_call index guards with symmetric dropped-chunk accounting across OpenAI/Anthropic/Bedrock/Cohere (uncorrelated arg deltas now increment droppedChunks + capture a firstDroppedSample instead of vanishing silently); Bedrock EventStream header-bounds validation (malformed frames degrade to truncated instead of throwing).

  3. Recorder (src/recorder.ts, src/types.ts) — incremental multibyte UTF-8 decoding (StringDecoder) so characters split across stream chunks are no longer corrupted on the frame-timing path; CRLF-tolerant frame-timing splitter; webSearches propagation into fixtures; audio-branch companion fields (toolCalls/content/reasoning) persisted; firstDroppedSample logged alongside the dropped-chunk warning.

  4. Gemini audio companion replay (src/gemini.ts) — the audio replay builders now re-emit companion tool-call/text/thought parts (mirroring the non-audio builder) instead of dropping them, completing the record→replay round-trip for audio turns that interleave tool calls.

Tests

+~3.4k lines of tests: harmony structural acceptance matrix + boundary/fail-safe regressions; collapser robustness (index guards, dropped-chunk accounting, CRLF); multibyte decode (both decode paths, byte-by-byte split); Gemini audio companion record→replay. Full suite: 3308 passed / 0 failed, tsc clean, tsdown build green.

Documented limitations

  • Harmony quoted-structure: deeply ambiguous quoted control-token-like sequences inside message bodies fall to the verbatim fail-safe rather than being heuristically parsed — by design (no corruption over best-effort parsing).
  • Audio companions are Gemini-replay-only: audioB64 collapse is currently Gemini-only, so cross-provider audio fixtures would not replay companion modalities. Documented at the type and builder.

Test plan

  • npx vitest run — 3308 passed, 37 skipped, 0 failed
  • npx tsc --noEmit — clean
  • pnpm build (tsdown) — clean
  • CI green

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Jun 2, 2026

Open in StackBlitz

npm i https://pkg.pr.new/@copilotkit/aimock@245

commit: 75c1066

@jpr5 jpr5 merged commit b43fafe into main Jun 2, 2026
23 checks passed
@jpr5 jpr5 deleted the fix/aimock-gpt5-recording branch June 2, 2026 21:57
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