Skip to content

feat(regen): agent function cancellation and defer-until-EOT - #790

Merged
GregHolmes merged 7 commits into
mainfrom
gh/sdk-gen-2026-09-16
Sep 17, 2026
Merged

GregHolmes merged 7 commits into
mainfrom
gh/sdk-gen-2026-09-16

Conversation

@GregHolmes

@GregHolmes GregHolmes commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Regenerates the Python SDK with fernapi/fern-python-sdk 5.27.1.
  • Adds Agent function-cancellation events and the optional defer_until_eot function setting.
  • Retains the custom transport socket behavior, credential redaction, lowercase WebSocket query booleans, legacy Agent Settings and language-hint compatibility, typed Listen V2 dictionary compatibility, package aliases, and restored wire coverage.
  • Preserves the generated JSON decode handling and FunctionCallCancelled response-union support while layering the existing custom-transport behavior back in.

Validation

  • poetry run pytest: 1,036 passed, 4 skipped
  • poetry run mypy src tests/typecheck: passed
  • Ruff passed for all regenerated and reconciled Python files
  • Repository-wide poetry run ruff check . reports 59 pre-existing violations in unrelated manual examples and tests; none are introduced by this regen.

@github-actions

github-actions Bot commented Sep 16, 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 100% 95% 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% (6504 / 6819) 91% (1419 / 1552) 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%.

@GregHolmes GregHolmes changed the title chore: SDK regeneration 2026-09-16 feat(regen): agent function cancellation and defer-until-EOT Sep 16, 2026

@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.

Requesting changes — all small, no blockers in the code itself. The regen is sound: public surface is purely additive and the new setting works on production. The gaps are discoverability.

What I verified

  • defer_until_eot works on production. Sent it to wss://agent.deepgram.com/v1/agent/converseSettingsApplied. To confirm the server actually understands it rather than ignoring it, I sent a bogus sibling field in the same position → Error UNPARSABLE_CLIENT_MESSAGE ("Check the agent.think field against the API spec"). Unknown-field rejection is on for agent.think, so the acceptance is meaningful. Not server-gated, unlike last cycle's force-end-turn.
  • No breaking changes. Imported __all__ from 14 packages on main vs this branch: 0 removed, 12 added.
  • FunctionCallCancelled does not shadow FunctionCallRequest in V1SocketClientResponse — both resolve correctly, including Cancelled with an unknown nested field and with an empty functions list. Distinct type consts, so no discriminator collapse.
  • Gate: pytest 1054 passed / 1 skipped, mypy src tests/typecheck clean (908 files), ruff check src/deepgram clean, poetry check --lock clean.
  • websockets.WebSocketException resolves on the dependency floor (websockets==12.0), so the new narrow except is safe.
  • Version bump is fine — the generator patch-bumps and release-please recomputes from the squash title, per the "Version bump" note in bacd1b5. Just keep the squash title as feat(regen): … or the feature ships labeled 7.9.1.

Please fix

1. FunctionCallCancelled is missing from reference.md

Both agent-connect import blocks still list the fourteen pre-existing event types. A developer working from the reference never learns the event exists, or that they must stop sending a FunctionCallResponse for a cancelled id.

Add after AgentV1FunctionCallRequest, at reference.md:6171 and reference.md:6279:

    AgentV1FunctionCallCancelled,

reference.md is permanently frozen in .fernignore, so the generator can't do this — it's re-applied by hand each cycle, same as send_force_end_turn and SpeechInterrupted last time.

2. No example or live coverage for either feature

The only coverage is unit tests against fake sockets. tests/manual/agent/v1/connect/main.py still dispatches AgentV1FunctionCallRequest with no cancellation branch, and nothing demonstrates defer_until_eot. Last regen shipped examples/32-voice-agent-force-end-turn.py plus tests/manual/listen/v2/force_end_turn/ for a comparable feature.

Suggest extending tests/manual/agent/v1/connect/main.py: handle AgentV1FunctionCallCancelled, and add a defer_until_eot=True function to its settings.

3. Both except clauses are now present with identical bodies

Ten sites across all five socket clients now have:

except (websockets.WebSocketException, JSONDecodeError) as exc:
    self._emit(EventType.ERROR, exc)
except Exception as exc:
    self._emit(EventType.ERROR, exc)

The first clause can never change behavior. More importantly, .fernignore describes this patch as "except Exception broad catch (supports custom transports, generator narrows to WebSocketException)" — which no longer matches the file, so the next regen reviewer will be misled about which line is the patch.

Either drop the narrow clause and the now-unused import websockets at agent/v1/socket_client.py:127,283, listen/v1/socket_client.py:72,179, listen/v2/socket_client.py:75,187, speak/v1/socket_client.py:73,180, speak/v2/socket_client.py:92,206 — or keep it and update the .fernignore comment to say both are deliberate.

Nits

  • .fernignore:219 adds tests/custom/test_defer_until_eot.py but the AGENTS.md freeze ledger isn't updated; bacd1b5 updated both in step.
  • agent/v1/socket_client.py:49 drops See: internal-api-specs/issues/205 from the _sanitize_numeric_types docstring — that was the only recorded reason the frozen patch exists.
  • .fern/metadata.json lost its trailing newline. tests/custom/test_defer_until_eot.py:9 uses the Pydantic-v1 .dict() API and duplicates an assertion already added at test_socket_client_shims.py:247.

One question

Does production emit FunctionCallCancelled yet? The client parses it and the paired setting is accepted on prod, but the event only fires when a speculative call is retracted mid-turn, which I couldn't force from a probe. Last cycle's force-end-turn turned out staging-only. If emission isn't live on prod yet, the release notes should carry that caveat.

@GregHolmes

Copy link
Copy Markdown
Contributor Author

Addressed the requested discoverability and maintenance follow-ups.

  • Added AgentV1FunctionCallCancelled to both synchronous and asynchronous agent-connect imports in reference.md.
  • Extended the live Agent connect script with defer_until_eot=True and FunctionCallCancelled handling that does not send a stale response.
  • Removed the redundant narrow websocket exception branches and now-unused imports; the broad catch remains the documented compatibility patch.
  • Removed the duplicate defer_until_eot test and its .fernignore entry because equivalent sync and async settings serialization coverage already exists in test_socket_client_shims.py.
  • Restored the internal-api-specs/issues/205 rationale to _sanitize_numeric_types.

Validation: poetry run pytest (1,050 passed, 4 skipped), poetry run mypy src tests/typecheck, poetry run ruff check src/deepgram, and poetry check --lock all pass.

@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 — all three requested changes are in, verified against the code rather than the summary.

Re-verified on 1f2ce25

  • reference.mdAgentV1FunctionCallCancelled is now in both agent-connect import blocks, matching the existing style (every event type there is listed to show what the connection can deliver).
  • Live coverage — I serialized the exact ThinkSettingsV1FunctionsItem from the updated manual script through the SDK, got {"name","description","parameters","defer_until_eot":true}, and sent it to wss://agent.deepgram.com/v1/agent/converseSettingsApplied. The script will work when run.
  • Exception cleanup — all ten narrow clauses removed along with both now-unused imports; zero WebSocketException references remain in src/deepgram/; the broad except Exception is still present twice per file; listen/v1, speak/v1 and speak/v2 socket clients are byte-identical to main again, and .fernignore's description of the patch matches the files once more.
  • Deleting the duplicate test was the better call. .fernignore is now identical to main, so no ledger row is owed. Coverage isn't lost — test_socket_client_shims.py:247,253 assert the same field through the full send_settings path on both sync and async clients, which is stronger than the model-level assertion that went away.
  • Gate: pytest 1053 passed / 1 skipped (exactly one fewer than before, matching the single deleted test), mypy src tests/typecheck clean across 908 files, ruff check src/deepgram clean.
  • Public surface: re-ran the 14-package __all__ comparison against main — 0 removed, 12 added, unchanged from the first pass.

Two things to carry into the merge

1. Keep the squash title as feat(regen): … — the patch version in the diff is only correct because release-please recomputes the bump from the commit type. If it lands as chore or fix, this ships as 7.9.1 and the new feature goes out labeled a patch.

2. Does production emit FunctionCallCancelled yet? Still open. The setting half is confirmed live on prod and the client parses the event on both sync and async paths, but nothing confirms the server actually sends it outside staging. If it is not live yet, the release notes should say so — the way last cycle's force-end-turn gate was documented.

Non-blocking leftovers

  • .fern/metadata.json still has no trailing newline.
  • Only the sync manual script covers the new event; async.py doesn't, and the two agent socket clients are separate implementations with separate listen loops. The parse path is unit-tested in both directions, so this is completeness rather than a hole — and it was not in the original ask, which named only main.py.
  • examples/ has nothing for either feature, where the previous regen added examples/32-voice-agent-force-end-turn.py for a comparable addition. Worth a follow-up if we want parity.

@GregHolmes
GregHolmes merged commit 7f5b642 into main Sep 17, 2026
10 checks passed
@GregHolmes
GregHolmes deleted the gh/sdk-gen-2026-09-16 branch September 17, 2026 13:04
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.

2 participants