fix: land grandcamel's four wire-shape fixes from an in-repo branch - #89
Merged
Conversation
…_sidecar bypass
calling.md showed live_transcribe(call_id, action="start", lang="en"),
but the generated signature takes action as a keyword-only TypedDict
union ({"start": {...}} or the literal "stop") and has no lang/from_lang
/to_lang parameters — copying the documented call raises TypeError. The
examples now show the real shape, with the start parameters inside the
action object where the schema defines them.
The sidecar pattern test appended ai_sidecar to the raw document with a
comment that the verb was not in the live SWML schema yet. It landed in
4645d48, so the test now uses add_verb_to_section directly — the path
the comment promised, and stronger, since the verb goes through schema
validation instead of around it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MgA3KeCEPMKMJVvroZY1wV
(cherry picked from commit 77fc34c)
prepare() puts conversation_timeout into the start params, but the HTTP dispatch rebuilt the create_conversation call with only id and config_url, silently dropping it. A gateway configured with conversation_timeout=900 told the browser 900 (via effective_timeout) while the service kept its own 3600 default — the page schedules its idle warning around a number the service never enforces. The chat path was unaffected (raw_post streams params verbatim), which kept the drift invisible for conversations opened by a first message. The regression test drives the real HTTP dispatch through the ASGI harness — prepare() was already correct, so a prepare()-level assertion would have passed with the bug in place. Verified red without the fix, green with it; it also pins the auto-create chat path so the two paths cannot drift apart again. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MgA3KeCEPMKMJVvroZY1wV (cherry picked from commit 5bf93c7)
execute_swml(transfer=True) wrote the flag INSIDE the SWML document —
{"SWML": {..., "transfer": "true"}} — where it is not a SWML key, so
the document executed but the call never exited the agent. The platform
documents transfer as a sibling of the SWML key in the action object,
which is exactly the shape the live-proven connect() and
swml_transfer() helpers already emit. The action is now
{"SWML": <doc>, "transfer": "true"}; transfer=False still omits the
key, and the caller's dict is still never mutated.
The three tests that pinned the inside placement now pin the sibling
placement, one of them asserting shape-parity with connect()'s action
so the two paths cannot drift apart again.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MgA3KeCEPMKMJVvroZY1wV
(cherry picked from commit 295c17e)
…mitted The tap() helper disagreed with the SWML schema (and the platform docs) twice over. It accepted and emitted "hear", which is not a tap direction — the verb's enum is speak/listen/both, exactly the set record_call() already uses — so direction="hear" produced SWML the platform rejects; it now raises the existing ValueError instead of silently emitting a dead tap. And its omit-if-default logic assumed the verb's default matches the helper's "both" when the verb actually defaults to "speak", so tap(uri) — documented as tapping both directions — produced a speak-only tap. direction is now always emitted; codec/rtp_ptime omissions stay (their helper defaults match the verb defaults). Note for the port audit: tap.direction's Literal feeds python_signatures.json in porting-sdk as enum<...>, so the oracle needs a regen alongside this change. docs/swaig_reference.md and docs/api_reference.md updated to the real enum (api_reference had a third variant — inbound/outbound plus a G722 codec and sip: URIs that the helper never accepted). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MgA3KeCEPMKMJVvroZY1wV (cherry picked from commit 0851a94)
Two small follow-ups to the four fixes cherry-picked above.
The `conversation_timeout` test arrived unannotated. mypy has `tests` in scope
deliberately ("a new untyped test fails the gate"), so it reds TYPECHECK on
main. Annotated; mypy is back to main's exact baseline.
`from __future__ import annotations` in search_service.py is the one line worth
keeping from #83, which was otherwise superseded by #81. Measured on its own: no
change to the mypy count, so it is hygiene rather than a fix.
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.
Lands #84, #85, #86 and #87 from an in-repo branch so they can actually run CI, plus the one line worth keeping from #83.
Why re-homed rather than merged directly: those four are fork PRs, and fork
PRs do not receive repository secrets. Every job died in ~6s on
Input required and not supplied: tokenat theCheckout porting-sdkstep,which needs
PORTING_SDK_TOKEN. They can never go green from a fork, whateveris in them. Commits are cherry-picked with
-x, so grandcamel stays the authorof all four.
The four, each verified against the authoritative source
#87 —
execute_swmltransfer rides beside the SWML document. Confirmed inthe engine:
mod_openai/actions.c:129-140readstransferoffitem(theaction object), not off
thing(the document). Inside the document it is neverread, so
do_transferstays 0 andexecute_swml(..., transfer=True)doesnothing today.
connect()andswml_transfer()already emit the sibling shape;this was the outlier. Note
actions.calso gates transfer onfrom_relay, sothe behaviour change only shows on relay-originated calls.
#86 — tap direction is speak/listen/both. The engine-generated SWML types say
direction: Literal["speak"] | Literal["listen"] | Literal["both"]andcodec: Literal["PCMU"] | Literal["PCMA"]. The SDK's runtime validator accepted"hear"(which the engine rejects) and refused"listen"(the correct value).It also omitted
directionwhen it equalled the SDK default"both"— and theSWML verb defaults to
"speak"when absent, sotap(uri)asked for both and gotspeak-only. Now always emitted.
#84 —
conversation_timeoutforwarded on the start path.prepare()puts itin the params (gateway.py:451) and
create_conversation()acceptstimeout=,but the HTTP route rebuilt the call and dropped it, so the browser was told 900
while the service kept its 3600 default.
#85 — live_transcribe docs + ai_sidecar test. The generated REST client takes
action: LiveTranscribeStartAction | …(object keyed by phase); the docs showedaction="start", lang="en". Andai_sidecaris in the bundled schema now, sothe test's raw-document bypass is obsolete — it goes through
add_verb_to_sectionand gets schema validation.My two follow-up commits
The
conversation_timeouttest arrived unannotated, which reds TYPECHECK (mypyhas
testsin scope on purpose). Annotated. Andfrom __future__ import annotationsinsearch_service.pyis the one line salvaged from #83, which wasotherwise superseded by #81 — measured on its own it changes no mypy count, so
it is hygiene.
Verification
Full run-ci: everything passes except TYPECHECK and TEST, both at main's exact
baseline — 19 mypy findings in mcp_gateway/search that depend on which optional
extras are installed and that CI does not report, and the 6 pre-existing
mcp_gateway test failures that fail identically on unmodified main. 5942 tests
pass (up 2, the new ones).
Heads-up: #86 and #87 change emitted wire shapes
Signatures barely move, so DRIFT will not notice, but EMISSION byte-compares
to_dict()and the 9 ports still emit the old shapes. This adds port work on topof the ChatGateway drift. Both are fixing silently-broken behaviour, so the risk
is code that has been relying on the breakage — chiefly
tap()now sending bothdirections instead of speak-only.
Closes #84. Closes #85. Closes #86. Closes #87.
🤖 Generated with Claude Code
https://claude.ai/code/session_015dYktt85Ltj3oK9gG5VBww