feat: support transcription replay and API key validation - #345
Open
jpr5 wants to merge 16 commits into
Open
Conversation
commit: |
jpr5
force-pushed
the
fix/live-transcribe-support-pr
branch
from
July 30, 2026 01:54
f2ffc7e to
23aca19
Compare
jpr5
added a commit
that referenced
this pull request
Aug 3, 2026
…ilies (wrong modality)
OpenAI shipped two transcription families on 2026-07-28. `gpt-transcribe`
serves /v1/audio/transcriptions plus realtime transcription sessions;
`gpt-live-transcribe` is realtime/WebSocket transcription only. Neither
responds on /v1/chat/completions, so neither is text-generation drift.
Two independent listing canaries were both red on them:
- models.drift.ts (via includeFamilies ∪ excludeFamilies) reported
`Unclassified model family "gpt-transcribe"` / `"gpt-live-transcribe"`.
- ws-realtime.drift.ts (via knownVoiceModelFamilies, whose isVoiceModelId
regex matches /transcribe/) reported
UNKNOWN_REALTIME_MODELS=gpt-transcribe,gpt-live-transcribe.
Add both families to excludeFamilies.openai's transcribe cluster (alongside
gpt-4o-transcribe / whisper-1 / gpt-realtime-*) and to
knownVoiceModelFamilies' transcription group. The two sets are deliberately
disjoint surfaces, so both need the entry. Re-pin the excludeFamilies.openai
DATA_FROZEN membership hash accordingly — the sorted-membership delta is
exactly these two additions and nothing else.
Record the decision on the two drift-proposals/ notes PR #343 carries. The
decision is EXCLUDE, written in prose rather than via drift-sync's automated
`Decision: include` marker: that path calls
addFamilyLiteralInSource(..., "includeFamilies", ...) exclusively, so using it
would have put these in includeFamilies and mismarked them as families aimock
mocks on the chat surface.
Does not touch src/ws-realtime.ts's runtime serve-gate — that is PR #345.
jpr5
force-pushed
the
fix/live-transcribe-support-pr
branch
from
August 3, 2026 16:35
23aca19 to
89d4e5c
Compare
jpr5
added a commit
that referenced
this pull request
Aug 3, 2026
…ilies (wrong modality)
OpenAI shipped two transcription families on 2026-07-28. `gpt-transcribe`
serves /v1/audio/transcriptions plus realtime transcription sessions;
`gpt-live-transcribe` is realtime/WebSocket transcription only. Neither
responds on /v1/chat/completions, so neither is text-generation drift.
Two independent listing canaries were both red on them:
- models.drift.ts (via includeFamilies ∪ excludeFamilies) reported
`Unclassified model family "gpt-transcribe"` / `"gpt-live-transcribe"`.
- ws-realtime.drift.ts (via knownVoiceModelFamilies, whose isVoiceModelId
regex matches /transcribe/) reported
UNKNOWN_REALTIME_MODELS=gpt-transcribe,gpt-live-transcribe.
Add both families to excludeFamilies.openai's transcribe cluster (alongside
gpt-4o-transcribe / whisper-1 / gpt-realtime-*) and to
knownVoiceModelFamilies' transcription group. The two sets are deliberately
disjoint surfaces, so both need the entry. Re-pin the excludeFamilies.openai
DATA_FROZEN membership hash accordingly — the sorted-membership delta is
exactly these two additions and nothing else.
Record the decision on the two drift-proposals/ notes PR #343 carries. The
decision is EXCLUDE, written in prose rather than via drift-sync's automated
`Decision: include` marker: that path calls
addFamilyLiteralInSource(..., "includeFamilies", ...) exclusively, so using it
would have put these in includeFamilies and mismarked them as families aimock
mocks on the chat surface.
Does not touch src/ws-realtime.ts's runtime serve-gate — that is PR #345.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Final Timing Contract Coverage
Commit
b5ce7dfis test coverage only; it does not change replay behavior. The fake-clock writer test now proves no frame arrives one millisecond before each scheduled 40/20 ms boundary, each expected frame arrives at its boundary in order, and the stream stays open until the final frame. The route test spies on the real writer boundary and asserts the fixture forwards its exactrecordedTimingsobject andreplaySpeed: 2.Focused timing tests passed 20/20 repeated runs. Full pre-push validation passed: format, lint, TypeScript, 4,794 tests (3 skipped), build, and export checks.
Timing Determinism Correction
Commit
4b6cedbfixes a completion-gate flake in the replay-speed integration test without changing replay behavior. The real HTTP test had a 200 ms ceiling even though its actual 8-frame schedule is 180 ms before HTTP, event-loop, and timer-resolution overhead: role + 6 content + finish frames receive[40, 20, 20, 20, 20, 20, 20, 20]ms at 2x speed. The upper bound is replaced by a fake-timer writer assertion of that exact schedule; the HTTP test remains to prove the route wiresreplaySpeedinto the writer.Local Red-Green Proof
Red, clean
c45bc66, actual target test repeated 30 times:Green,
4b6cedb, same actual target test repeated 30 times:The deterministic writer contract and real route both pass. Full suite: 166 files, 4,794 passed, 3 skipped; format, lint, TypeScript check, build, and export checks pass.
Nested GA Output Correction
Commit
c45bc66accepts and round-trips nested GAsession.audio.output.voiceand its complete PCMformatobject without changing Beta or legacy session translation. The recorder integration now explicitly proves fixture persistence and replay of terminallanguagesandusagemetadata after the client closes.Direct Local Red-Green Proof
A standalone real local recorder and WebSocket probe ran against clean
ead88d4before the change, then against this commit.Red (
ead88d4):Green (
c45bc66):Focused real integration coverage: 168 tests passed. Full pre-push suite: 4,793 passed, 3 skipped.
Post-Rebase Corrections
ead88d4closes the rebase-review gaps without changing the release version: typed transcription terminals are now treated as complete on client close; Beta session events retain rootturn_detection; GA retains the documented PCMrate.Direct Local Red-Green Proof
The same standalone probe used a real upstream SSE recorder proxy, client abort, persisted-fixture local replay, and live WebSocket connections.
Red (
89d4e5c):Green (
ead88d4):The red JSON omits Beta keys because JavaScript omits
undefinedobject properties. The green output verifies default null, nested GA update, and legacy flat update independently.Release / Rebase Status
Rebased the transcription implementation and its contract coverage onto current
main(9ce3107) without changing the intended runtime behavior. The PR now releases@copilotkit/aimock1.38.0and aligns its publish surfaces: root package, dated changelog entry, HelmappVersion, pytest default pin plus pytest package0.5.1, and Claude plugin metadata/range.Current branch commits:
3cf3956 feat: support OpenAI transcription replaycfbbe0a test: cover transcription replay contracts89d4e5c chore: release v1.38.0The retained proof below was captured against the original behavior branch; the rebased branch passed the focused 400-test transcription suite, full 4,746-test npm suite, format, lint, explicit TypeScript check, build, exports, frozen-lockfile validation, pytest 17/17, and pytest package build.
Summary
Aligns OpenAI transcription HTTP, Realtime, recorder, and replay behavior with the current nested GA wire contract.
intent=transcriptionsockets ignore regular realtime query models.audio.input.formatandaudio.input.transcription; Beta keeps its flat shim.transcript.text.donewithout a[DONE]sentinel.Baseline vs Candidate Local Proof
The exact probe ran against a clean detached
8d425a4worktree and the isolated candidate worktree. It used real local HTTP, WebSocket, recorder proxy, persisted fixture, and replay paths.Red: clean
8d425a4Green: candidate
6ae6ec4Validation
Fourth-Pass Red-Green
Red: terminal reported four stale GA expectations (legacy audio transcription field and missing ).
Green:
RUN v3.2.4 /private/tmp/aimock-live-transcribe-pr.0mGErr
✓ src/tests/ws-realtime.test.ts (83 tests) 7614ms
Test Files 1 passed (1)
Tests 83 passed (83)
Start at 14:39:20
Duration 8.13s (transform 235ms, setup 0ms, collect 300ms, tests 7.61s, environment 0ms, prepare 44ms) passed 83/83. Direct clean-baseline vs candidate VAD probe changed into nested , progressive live deltas, and journal interruption before completion. Full format, lint, typecheck, 145-file test suite, and build passed.
Fourth-pass proof: red terminal ws-realtime test had four stale GA audio expectation failures. Green exact file: 83 of 83 passed. Clean baseline VAD emitted an error; candidate returned nested audio.input.turn_detection server_vad, progressive deltas, and truncateAfterChunks journal interruption. Full format, lint, typecheck, 145-file tests, and build passed.
Fifth-pass red-green: baseline emitted GA session type conversation, top-level turn_detection, and accepted model mutation. Candidate emits realtime for normal GA sessions, keeps VAD under audio.input, rejects established model mutation, and preserves explicit transcription/translation session types. Red conformance test at ws-api-conformance line 618 is now green (66/66); full format, lint, tsc, tests, and build passed.
Sixth-Pass Red-Green
Red: clean baseline 2871866 accepted a real WebSocket mixed model/type update and persisted
gpt-4o-transcribe transcription must-not-mutate.Green: candidate returned
error invalid_session_config; its next valid update reportedgpt-realtime-mini realtime safe, proving rejection occurs before any session mutation. Live-transcription interruption now uses socket destruction and journals the interruption.Validation: focused ws-realtime suite 83/83 passed. Full format check, lint, typecheck, test suite, and build passed locally.
P2 Coverage Hardening
Commit f2ffc7e adds tests only; it does not change runtime behavior. The clean 71452a7 baseline already demonstrated the documented intent=transcription strict no-match and ErrorResponse paths, versioned model matching, abrupt interrupted socket termination with journal details, and scheduled inter-frame delivery.
The new direct tests cover those established paths, HTTP stream=true JSON errors, and JSON/SSE recorder-to-local-replay preservation of languages and usage. Applied as a test-only diff to detached 71452a7, the focused suite passed 132/132; the candidate focused suite also passed 132/132. Full format, lint, typecheck, test, and build passed on the candidate.
API-key validation and proxy isolation
This PR also adds opt-in, server-wide inbound API-key validation for the pending
1.38.0Node release, plus theaimock-pytest0.5.2helper support. No major-version change is included.Authorization: Bearer,X-API-Key,X-Goog-API-Key,Api-Key, andxi-api-keybefore fixture, proxy, control, and WebSocket handling./health,/ready, and/metricspublic; validates auth-enabled bareOPTIONSrequests and WebSocket upgrades before mount routing.502, without calling upstream.api_keyand--aimock-api-keysupport to the pytest helper, injected through the child environment rather than command-line arguments.Local API-key red-green proof
Observed against the real server before the implementation:
The same raw HTTP/WebSocket probe after the implementation:
Value checks
200to401; the configured key remains200with the recorded fixture.101 Switching Protocolsto literal401; the configured key still upgrades with101.502and the fake upstream receives zero requests.PYTHONPATH=src AIMOCK_CLI_PATH=../../dist/cli.js pytest -q -p aimock_pytest.pluginpassed all 18 tests.Validation
CR repair: request-scoped API-key egress
This follow-up removes the process-global record-config auth marker. The egress policy is now tied to the authenticated incoming request, so servers that reuse a
RecordConfigcannot affect one another. It also acceptsbearerandkeyschemes case-insensitively, removesAuthorizationcase-insensitively before OpenRouter off-origin unsigned-URL fetches, and applies the same no-implicit-provider-key policy to AG-UI, fal, and video egress.The release remains Node
1.38.0and pytest0.5.2; no major or additional root-version bump is included. PyPI publishing is now gated on the pinned npm release existing first, enforcing npm1.38.0before pytest0.5.2.Local red-green proof
Clean
f3a21fbbaseline, exercised against live local server sockets and a local proxy upstream:The identical raw HTTP/WebSocket/proxy probes after the repair:
Coverage added
bearer/keyWebSocket upgrade coverage.RecordConfigisolation regression.Authorizationis absent.502with zero upstream requests when no provider key exists; OpenRouter video path is covered by the foreign-host capture.AIMOCK_API_KEYSoverride integration.--aimock-api-keyand the plugin fixture.Final validation
Pytest CI repair
The release test subprocess now disables automatic plugin discovery before it
explicitly loads
aimock_pytest.plugin. This prevents installed pytest entrypoint discovery from registering the same plugin a second time.
Local installed-package red-green proof (Python 3.12):
The repeated final gate is green: format, lint, TypeScript, full Node suite
(167 files, 4,806 passed, 3 skipped), build, exports, and source-tree pytest
integration (19 passed).
Publish-workflow security repair
actions/setup-nodev7 enabled package-manager caching by default in the PyPIpublish workflow. Caching is unnecessary for this small
npm viewreleaseordering check, so the workflow now explicitly sets
package-manager-cache: false.