Skip to content

feat(agent): add raw control sender - #789

Open
GregHolmes wants to merge 2 commits into
mainfrom
gh/agent-raw-control-sender
Open

GregHolmes wants to merge 2 commits into
mainfrom
gh/agent-raw-control-sender

Conversation

@GregHolmes

@GregHolmes GregHolmes commented Sep 14, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Add public sync and async send_raw() methods for protocol-transparent Agent control-frame bridges.
  • Serialize mapping inputs without model validation and preserve serialized JSON strings unchanged.
  • Document send_raw in the existing agent/v1/socket_client.py freeze entry (.fernignore, AGENTS.md) and cover both input forms on both clients.

Closes #785

Validation

  • 32 tests in tests/custom/test_socket_client_shims.py
  • mypy src tests/typecheck/compat_aliases.py
  • ruff check on changed files
  • All validation ran in a clean Python 3.12 container

@github-actions

github-actions Bot commented Sep 14, 2026 •

Copy link
Copy Markdown
Contributor

Code Coverage

Package Line Rate Branch Rate Complexity Health
src.deepgram 97% 94% 0 ✔
src.deepgram.agent 100% 100% 0 ✔
src.deepgram.agent.v1 98% 100% 0 ✔
src.deepgram.agent.v1.settings 100% 100% 0 ✔
src.deepgram.agent.v1.settings.think 100% 100% 0 ✔
src.deepgram.agent.v1.settings.think.models 97% 100% 0 ✔
src.deepgram.auth 100% 100% 0 ✔
src.deepgram.auth.v1 100% 100% 0 ✔
src.deepgram.auth.v1.tokens 97% 100% 0 ✔
src.deepgram.core 88% 81% 0 ➖
src.deepgram.errors 100% 100% 0 ✔
src.deepgram.helpers 98% 92% 0 ✔
src.deepgram.listen 100% 100% 0 ✔
src.deepgram.listen.v1 98% 93% 0 ✔
src.deepgram.listen.v1.media 97% 100% 0 ✔
src.deepgram.listen.v2 98% 93% 0 ✔
src.deepgram.manage 100% 100% 0 ✔
src.deepgram.manage.v1 100% 100% 0 ✔
src.deepgram.manage.v1.models 96% 100% 0 ✔
src.deepgram.manage.v1.projects 97% 100% 0 ✔
src.deepgram.manage.v1.projects.billing 100% 100% 0 ✔
src.deepgram.manage.v1.projects.billing.balances 96% 100% 0 ✔
src.deepgram.manage.v1.projects.billing.breakdown 97% 100% 0 ✔
src.deepgram.manage.v1.projects.billing.fields 97% 100% 0 ✔
src.deepgram.manage.v1.projects.billing.purchases 97% 100% 0 ✔
src.deepgram.manage.v1.projects.keys 96% 100% 0 ✔
src.deepgram.manage.v1.projects.members 97% 100% 0 ✔
src.deepgram.manage.v1.projects.members.invites 96% 100% 0 ✔
src.deepgram.manage.v1.projects.members.scopes 96% 100% 0 ✔
src.deepgram.manage.v1.projects.models 96% 100% 0 ✔
src.deepgram.manage.v1.projects.usage 98% 100% 0 ✔
src.deepgram.manage.v1.projects.usage.breakdown 97% 100% 0 ✔
src.deepgram.manage.v1.projects.usage.fields 97% 100% 0 ✔
src.deepgram.read 100% 100% 0 ✔
src.deepgram.read.v1 100% 100% 0 ✔
src.deepgram.read.v1.text 98% 100% 0 ✔
src.deepgram.self_hosted 100% 100% 0 ✔
src.deepgram.self_hosted.v1 100% 100% 0 ✔
src.deepgram.self_hosted.v1.distribution_credentials 96% 100% 0 ✔
src.deepgram.speak 100% 100% 0 ✔
src.deepgram.speak.v1 98% 97% 0 ✔
src.deepgram.speak.v1.audio 91% 80% 0 ✔
src.deepgram.speak.v2 98% 93% 0 ✔
src.deepgram.speak.v2.audio 100% 100% 0 ✔
src.deepgram.voice_agent 100% 100% 0 ✔
src.deepgram.voice_agent.configurations 95% 100% 0 ✔
src.deepgram.voice_agent.variables 95% 100% 0 ✔
Summary 95% (6554 / 6871) 91% (1438 / 1574) 0 ✔

Scope: hand-maintained SDK logic. Fern-generated data models (types/, requests/), package __init__.py files, version.py, and the unused core/http_sse/ scaffolding are excluded — see .coveragerc. Unscoped whole-package coverage is ~70%.

@dg-coreylweathers dg-coreylweathers left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Verdict: approve with nits.

What this PR does
Adds public send_raw(message: Dict | str) on the sync and async Voice Agent socket clients so protocol-transparent bridges can forward any control frame without calling the private _send() (issue #785). Dicts are JSON-serialized; strings are sent byte-for-byte.

What I checked

  • Does it send unchanged? Yes: _send does json.dumps for dicts and passes strings straight to websocket.send; the four new tests capture exactly that, including that 44100.0 is not converted to an int.
  • Is the patch protected from regen? Yes: agent/v1/socket_client.py was already frozen; the PR adds the ledger bullet.
  • Gates in Docker: mypy clean, ruff clean on changed files, pytest 1038 passed.

Should-fix

  • send_raw appears only in the socket client, its tests, .fernignore, and AGENTS.md. Add a send_raw(message) line to the agent.v1 section of reference.md and a one-line mention in a voice-agent example or the skill file, otherwise bridge authors keep using _send().
  • State the number contract in the docstring: "Values are sent as given; whole-number floats are not converted to integers, so pass ints where the API expects them." That is what the tests pin and how it differs from the typed senders.

Nits: the Union[Dict, str] annotation is narrower than _send accepts (bytes also work), fine as the public contract. Needs a rebase: AGENTS.md/.fernignore conflict with main now, and after #797 lands agent/v1/socket_client.py is regenerated, so re-apply the two methods there.

@GregHolmes
GregHolmes force-pushed the gh/agent-raw-control-sender branch from 589b545 to fb06762 Compare October 6, 2026 12:20
@GregHolmes

Copy link
Copy Markdown
Contributor Author

@dg-coreylweathers Rebased onto current main and resolved the test conflict. Addressed both doc requests: send_raw(message) is now in reference.md and the Voice Agent skill, and both sync/async docstrings state the unchanged numeric-value contract. Validation passed in Python 3.12 Docker: 46 socket-shim tests, mypy, Ruff, and git diff --check. Please re-review when you have a moment.

@GregHolmes
GregHolmes force-pushed the gh/agent-raw-control-sender branch from fb06762 to 2b738dd Compare October 16, 2026 09:37
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@dg-coreylweathers dg-coreylweathers left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Approving.

What this PR does
Adds a public send_raw() to the sync and async Voice Agent socket clients so relay apps can forward any JSON control message, including types the SDK does not model yet, without calling the private _send() (issue #785). A dict is converted to JSON; a string is sent exactly as given. No existing send_* method changes.

What I checked

  • Were my earlier asks done? Yes: send_raw is in reference.md and the Voice Agent skill, both docstrings say whole-number floats are not converted to ints, and the branch is rebased on current main.
  • Does it send what it says? Yes: unit tests compare the exact text that reaches the socket, and a probe confirmed a string like not json{ goes out byte-for-byte while typed senders still convert 1.0 to 1.
  • Does the live API accept it? Yes (request_id 01a1461b-33ab-7471-98ea-0325c23d0d34): Settings sent as a string got SettingsApplied, a KeepAlive dict was accepted, an InjectAgentMessage dict made the agent speak; an unknown type got Error UNPARSABLE_CLIENT_MESSAGE.
  • Will the next Fern regen drop it? No: the file stays in .fernignore, and a test merge with #803 (the open regen) keeps both methods with 46 socket tests passing and mypy clean.

Nits fixed in be28f5c: the reference.md entry now has an example line (agent.send_raw({"type": "KeepAlive"})) and says to prefer the typed method when one exists, and that the server answers an unknown type with Error UNPARSABLE_CLIENT_MESSAGE. The PR body now says this documents send_raw in the existing freeze entry.

Merge note: this conflicts with #788 in AGENTS.md (both edit the agent/v1/socket_client.py line). Keep this PR's line, then #788's StrictInt line below it.

This branch has not been deployed

No deployments
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.

feat(agent): expose a public sender for raw Voice Agent control frames

2 participants