Skip to content

Latest commit

 

History

History
110 lines (88 loc) · 25 KB

File metadata and controls

110 lines (88 loc) · 25 KB

Agents

Fern SDK Regeneration

Overview

This SDK is generated by Fern. Most files under src/deepgram/ are auto-generated and should not be edited directly. Some files have manual patches and are listed in .fernignore to prevent the generator from overwriting them.

When a new Fern generator release is available, we prepare the repo so the generator can overwrite previously-frozen files, then re-apply manual patches after reviewing the diff.

Freeze classification rules

Every entry in .fernignore falls into one of two categories. The comment above each entry in .fernignore indicates which category it belongs to, but when in doubt, apply these rules:

Never unfreeze (permanently frozen)

These files are entirely hand-written — they have no Fern-generated counterpart. The generator would delete or replace them with something unrelated. They must stay in .fernignore at all times.

How to identify:

  • The file was created by us, not by Fern (e.g., src/deepgram/client.py, custom tests, helpers, transport layer)
  • The file is a doc, config, or folder we maintain independently (README, CHANGELOG, .github, examples, etc.)
  • The file lives outside src/deepgram/ in a hand-maintained location (e.g., .claude/, docs/)

Current permanently frozen files:

  • src/deepgram/client.py — entirely custom (Bearer auth, session ID, transport_factory, reconnect parity flag); no Fern equivalent
  • src/deepgram/_secure_logging.py — hand-written security utility that installs a logging.Filter on the websockets client/server loggers to mask the Authorization header in DEBUG handshake logs; called from client.py; no Fern equivalent
  • src/deepgram/listen/v2/types/_dict_compat.py — hand-written read-side compatibility base for Listen V2 response models. It provides read-only wire-key subscript access during the SDK 7.x transition from raw dict responses to typed models; no Fern counterpart. Remove in the next major release after restoring the temporarily frozen response models below to inherit UncheckedBaseModel directly.
  • src/deepgram/helpers/ — hand-written TextBuilder helpers
  • src/deepgram/agent/v1/types/agent_v1history_content.py, src/deepgram/agent/v1/types/agent_v1history_function_calls.py, src/deepgram/agent/v1/types/agent_v1settings_agent_context_messages_item.py, src/deepgram/agent/v1/types/agent_v1settings_agent_context_messages_item_content.py, src/deepgram/agent/v1/types/agent_v1settings_agent_context_messages_item_content_role.py, src/deepgram/agent/v1/types/agent_v1settings_agent_context_messages_item_function_calls.py, src/deepgram/agent/v1/types/agent_v1settings_agent_context_messages_item_function_calls_function_calls_item.py — hand-written compatibility aliases preserving old public Agent History type imports after regen renames
  • src/deepgram/agent/v1/requests/agent_v1history_content.py, src/deepgram/agent/v1/requests/agent_v1history_function_calls.py, src/deepgram/agent/v1/requests/agent_v1settings_agent_context_messages_item.py, src/deepgram/agent/v1/requests/agent_v1settings_agent_context_messages_item_content.py, src/deepgram/agent/v1/requests/agent_v1settings_agent_context_messages_item_function_calls.py, src/deepgram/agent/v1/requests/agent_v1settings_agent_context_messages_item_function_calls_function_calls_item.py — hand-written compatibility aliases preserving old public Agent History request-param imports after regen renames
  • src/deepgram/types/create_key_v1request_one.py, src/deepgram/requests/create_key_v1request_one.py — hand-written compatibility aliases preserving the old public create-key request imports after the regen rename to CreateKeyV1Request
  • src/deepgram/types/anthropic_think_provider_model.py, src/deepgram/types/cartesia_speak_provider_model_id.py, src/deepgram/types/cartesia_speak_provider_voice.py, src/deepgram/types/deepgram_speak_provider_model.py, src/deepgram/types/google_think_provider_model.py, src/deepgram/types/google_think_provider_version.py, src/deepgram/types/groq_think_provider_reasoning_mode.py, src/deepgram/requests/cartesia_speak_provider_voice.py, src/deepgram/agent/v1/types/agent_v1history_content_role.py, src/deepgram/agent/v1/types/agent_v1history_function_calls_function_calls_item.py, src/deepgram/agent/v1/types/agent_v1settings_agent_context_context_messages_item_content_role.py, src/deepgram/agent/v1/types/agent_v1settings_agent_context_context_messages_item_function_calls_function_calls_item.py, src/deepgram/agent/v1/requests/agent_v1history_function_calls_function_calls_item.py, src/deepgram/agent/v1/requests/agent_v1settings_agent_context_context_messages_item_function_calls_function_calls_item.py — hand-written compatibility aliases for the 2026-08-18 regen renames. The generator dropped the *ThinkProvider*/*SpeakProvider* prefixes from the public model/voice/version types (AnthropicThinkProviderModelAnthropicModel, CartesiaSpeakProvider*Cartesia*, DeepgramSpeakProviderModelDeepgramModel, GoogleThinkProvider*Google*, GroqThinkProviderReasoningModeGroqReasoningMode), renamed the agent conversation-history subtypes (AgentV1HistoryContentRoleConversationHistoryMessageRole, AgentV1HistoryFunctionCallsFunctionCallsItemFunctionCallHistoryMessageFunctionCallsItem), and deleted the doubled AgentV1SettingsAgentContextContextMessagesItem* leaf modules. These aliases preserve every old public import path. (The parallel single-Context agent_v1settings_agent_context_messages_item_content_role.py / ..._function_calls_function_calls_item.py shims, already listed in this permanently-frozen block above, were also re-pointed to the new targets in the same regen.) Regression coverage in tests/custom/test_compat_aliases.py.
  • src/deepgram/agent/v1/types/agent_v1settings_agent_context_listen_provider_v1.py, src/deepgram/agent/v1/types/agent_v1settings_agent_context_listen_provider_v2.py, src/deepgram/agent/v1/types/agent_v1settings_agent_context_listen_provider_v2language_hint.py, src/deepgram/agent/v1/types/agent_v1settings_agent_listen_provider_v1.py, src/deepgram/agent/v1/types/agent_v1settings_agent_listen_provider_v2.py, src/deepgram/agent/v1/requests/agent_v1settings_agent_context_listen_provider_v1.py, src/deepgram/agent/v1/requests/agent_v1settings_agent_context_listen_provider_v2.py, src/deepgram/agent/v1/requests/agent_v1settings_agent_context_listen_provider_v2language_hint.py, src/deepgram/agent/v1/requests/agent_v1settings_agent_listen_provider_v1.py, src/deepgram/agent/v1/requests/agent_v1settings_agent_listen_provider_v2.py — hand-written compatibility aliases for the 2026-05-14 spec dedup that consolidated AgentV1SettingsAgent[Context]ListenProviderV{1,2,V2LanguageHint} into top-level DeepgramListenProvider* types
  • src/deepgram/types/deepgram_listen_provider_v2language_hint.py, src/deepgram/requests/deepgram_listen_provider_v2language_hint.py — hand-written shims recreating the top-level DeepgramListenProviderV2LanguageHint/...Params type (Union[str, List[str]]) that Fern removed in the 2026-06-15 regen. The *V2LanguageHint listen-provider aliases above import from these and they remain part of the public import surface, so they are recreated by hand and frozen so Fern won't delete them again
  • src/deepgram/listen/v2/types/listen_v2close_stream_type.py — hand-written shim recreating ListenV2CloseStreamType, which Fern removed in the 2026-06-15 regen (docs #946). The original generated type wrongly allowed Union[Literal["Finalize","CloseStream","KeepAlive"], Any] (v2 copied v1's control-message enum); a CloseStream message's type can only ever be "CloseStream". Recreated as the corrected Literal["CloseStream"] to preserve the public import path without resurrecting the invalid values. Re-exported from the three listen __init__.py files (temporarily frozen, below).
  • src/deepgram/transport_interface.py, src/deepgram/transport.py, src/deepgram/transports/ — custom transport layer. _TARGET_MODULES in transport.py must include every generated client.py and raw_client.py that uses the patchable WebSocket connector symbols; update it whenever a regen adds a WebSocket surface. tests/custom/test_transport.py enforces this so transport_factory cannot silently fall back to Deepgram Cloud.
  • tests/custom/test_agent_history.py — hand-written regression test for Agent History websocket payload parsing
  • tests/custom/test_api_key_env_resolution.py — hand-written regression test that DEEPGRAM_API_KEY is resolved when the client is constructed rather than when the module is imported. The generated base client takes os.getenv(...) as a default argument, which Python evaluates once at import, so load_dotenv() placed below the imports left the default captured as None (issue #734). client.py re-reads the variable; this pins that, plus explicit-key and access_token precedence and the no-key-anywhere error
  • tests/custom/test_compat_aliases.py — hand-written regression test for backward-compatible alias imports after regen renames
  • tests/custom/test_model_dict_compat.py — hand-written regression coverage for the read-only subscript compatibility shim on typed Listen V2 response models, including sync/async parsing, every response variant, nested objects, unknown fields, omitted-key behavior, and scope isolation from unrelated generated models
  • tests/custom/test_query_encoder.py — hand-written regression test that core/query_encoder.py coerces Python bools to lowercase "true"/"false" before urlencode so websocket query strings stay wire-correct
  • tests/custom/test_regen_response_fields.py — hand-written regression test guarding the response fields added by the 2026-08-18 regen: ListenV1Response/ResultsMetadata.diarize_info (model_uuid, arch), per-word speaker / speaker_confidence, and ListenV2TurnInfo.trigger. Same rationale as test_latency_report_stt_compat.py: UncheckedBaseModel + skip_validation makes response parsing lenient, so a future spec removal would silently turn these into None rather than failing. Fixtures are real captured payloads; the trigger cases pin the parsing contract for the values the server sends (model / manual / timeout) plus the absent case, which is what a deployment without the server-gated ForceEndTurn feature returns. Live coverage of the same field lives in tests/manual/listen/v2/force_end_turn/
  • tests/custom/test_secure_logging.py — hand-written regression test that the websockets Authorization-header DEBUG logs are redacted (API key never logged in clear text)
  • tests/custom/test_speak_v2_interrupt_configure.py — hand-written coverage for the Speak V2 barge-in / mid-stream reconfigure surface (send_interrupt, send_configure, SpeechInterrupted, ConfigureSuccess/ConfigureFailure, and the speed/expressivity connect params)
  • tests/custom/test_text_builder.py, tests/custom/test_transport.py — hand-written tests
  • tests/typecheck/compat_aliases.py — hand-written mypy assert_type coverage for backward-compatible alias TypedDicts
  • tests/manual/ — manual standalone tests
  • .coveragerc — hand-written coverage configuration (branch coverage on, scoped to deepgram, generated data models and package plumbing excluded, fail_under gate). Deliberately kept out of pyproject.toml, which Fern regenerates: freezing pyproject.toml would also block generator dependency updates (see 5c1e845, where a pydantic-core bound fix for issue #701 shipped as a pyproject.toml-only regen change). No Fern counterpart, so it never needs unfreezing.
  • README.md, CHANGELOG.md, CONTRIBUTING.md, reference.md — docs
  • CLAUDE.md, AGENTS.md, .claude/ — agent files
  • .github/, docs/, examples/ — folders

Unfreeze for regen (temporarily frozen)

These files are Fern-generated but carry manual patches to fix issues in the generator output. We freeze them to protect our patches between regenerations, but unfreeze them before a regen so we can compare the new output against our patches.

How to identify:

  • The file exists in Fern's output — if you removed it from .fernignore and ran the generator, Fern would produce a version of it
  • Our version is a modified copy of what Fern generates (e.g., changed float to int, added optional defaults, broadened a Union type)

Current temporarily frozen files:

  • .gitignore — Fern generates a baseline version and has regenerated it before (fdcce88, ad93815, 4bee463). We hand-add the local coverage artifact ignores (.coverage, htmlcov/, coverage.xml). Before each regen, unfreeze and re-diff so Fern's own additions are picked up, then re-apply the three coverage lines.
  • src/deepgram/speak/v1/socket_client.py — optional message param defaults, broad exception catch
  • src/deepgram/speak/v2/socket_client.py — same (optional send_flush/send_close/send_interrupt defaults, broad exception catch); new websocket TTS client added in the 2026-07-08 regen. send_interrupt carries no required payload so it takes the same optional-default treatment as the other control sends; send_configure deliberately keeps its required argument (a Configure with no settings is meaningless)
  • src/deepgram/listen/v1/socket_client.py — same
  • src/deepgram/listen/v2/socket_client.py — same (broad except, optional send_close_stream default). As of the 2026-08-11 regen the generator properly types send_configure(ListenV2Configure) and puts ListenV2ConfigureSuccess in the response Union, so those are taken from the generator; the only send_configure patch retained is runtime tolerance for a raw dict (sent verbatim) for back-compat with pre-typed-model callers
  • src/deepgram/agent/v1/socket_client.py — same + _sanitize_numeric_types
  • src/deepgram/agent/v1/types/agent_v1settings_agent_context.py, src/deepgram/agent/v1/types/agent_v1settings_agent.py, src/deepgram/agent/v1/types/agent_v1settings.py, src/deepgram/agent/v1/requests/agent_v1settings_agent_context.py, src/deepgram/agent/v1/requests/agent_v1settings_agent.py, src/deepgram/agent/v1/requests/agent_v1settings.py — backward-compat patches for the 2026-05-05 Agent Settings schema restructure. These preserve callable AgentV1SettingsAgent(...), keep AgentV1Settings.agent accepting both that wrapper and agent_id strings, restore the legacy request TypedDict shapes, remap legacy messages=[...] / nested context=AgentV1SettingsAgentContext(messages=[...]) usage into the new context={"messages": [...]} wire shape, and keep read-side obj.messages access working.
  • src/deepgram/core/api_error.py, src/deepgram/core/parse_error.py — credential redaction. Every websocket connect() path raises ApiError(headers=dict(headers), ...) with the full request headers, and both error types stringify that dict, so an unredacted Authorization reached str(e), tracebacks, log aggregators and error trackers (which serialise attributes as well as the message). Both now mask credential values at construction via _secure_logging.redact_sensitive_headers, preserving non-sensitive headers (dg-request-id) for debugging. This is the same threat _secure_logging.py covers for the websockets DEBUG handshake logs, via the other path to it. Regression coverage in tests/custom/test_api_error_redaction.py. Unfreeze if the generator starts redacting credentials itself.
  • src/deepgram/core/query_encoder.py — coerces Python bools to lowercase "true"/"false" before they reach urllib.parse.urlencode (which would otherwise produce "True"/"False" via str() and break websocket query strings). Only the four */connect() paths call urlencode; HTTP raw clients hand params to httpx, which lowercases bools itself, so the patch is a no-op for the HTTP path. Once Fern's websocket codegen normalizes bools (or the spec types these as boolean end-to-end), this can be unfrozen.
  • src/deepgram/listen/v2/types/listen_v2connected.py, src/deepgram/listen/v2/types/listen_v2turn_info.py, src/deepgram/listen/v2/types/listen_v2turn_info_words_item.py, src/deepgram/listen/v2/types/listen_v2configure_success.py, src/deepgram/listen/v2/types/listen_v2configure_success_thresholds.py, src/deepgram/listen/v2/types/listen_v2configure_failure.py, src/deepgram/listen/v2/types/listen_v2fatal_error.py — read-side compatibility for the SDK 7.7 Listen V2 response retype. Through 7.6, V2SocketClientResponse contained typing.Any, so every response was returned as a raw dict; fixing the union made responses typed models and broke callers using response["field"]. These generated response classes inherit the hand-written base above, preserving read-only wire-key subscript access alongside canonical attribute access. Restore direct UncheckedBaseModel inheritance and unfreeze these files in the next major release.
  • src/deepgram/types/deepgram_listen_provider_v2.py, src/deepgram/agent/v1/types/agent_v1settings_agent_listen_provider.py, src/deepgram/agent/v1/types/agent_v1settings_agent_context_listen_provider.py — behavioural back-compat shim for the language_hint -> language_hints rename (2026-06-15 regen). The public field was historically (incorrectly) singular and accepted a str or a list; the API field is language_hints (a list, and the server uses deny_unknown_fields so the singular key is rejected on the wire). Each carries a hand-added model_validator(mode='before') / root_validator(pre=True) that remaps a legacy language_hint= kwarg and drops the dead singular key. Remove and unfreeze when the singular alias is retired in a future major.
  • src/deepgram/types/speak_v2speed.py — behavioural back-compat patch for the 2026-08-18 regen SpeakV2Speed retype. The generator changed it from float to Union[Literal["0.85"…"1.15"], Any] (a string-literal enum), which silently changed the documented domain of the speak.v2.connect(speed=...) parameter from numeric to string and contradicts the actual API contract (SpeakV2SpeedValue = float, used by the mid-stream SpeakV2Configure message). Only the Any fallback kept speed=1.05 working. Restored to float so the connect param stays exactly what it was on main, mypy-precise, and consistent with the Configure message. Regression coverage in tests/custom/test_speak_v2_coverage.py. Unfreeze when the spec types the connect speed as a number.
  • src/deepgram/agent/v1/types/agent_v1update_listen_listen.py — backward-compat patch for the 2026-07-31 AgentV1UpdateListen provider retype. The provider field changed from a bare DeepgramListenProviderV2 to the required discriminated union AgentV1UpdateListenListenProvider (_V1/_V2, discriminant version). Carries a hand-added model_validator(mode='before') / root_validator(pre=True) that coerces a legacy DeepgramListenProviderV1/V2 (or a dict lacking the version discriminant) into the new shape so existing callers keep working. Remove and unfreeze when the old provider payloads are retired in a future major. NOTE: this patch was silently lost once (it was absent from .fernignore, so a regen overwrote it) — keep it frozen.
  • tests/wire/test_manage_v1_projects_keys.py — restored wire coverage for the legacy CreateKeyV1RequestOneParams request alias so future regens do not silently drop that compatibility check
  • tests/wire/test_manage_v1_projects_requests.py — restored query-parameter coverage for manage.v1.projects.requests.list. The 2026-08-11 regen simplified the upstream spec example, and Fern derives the wire test from the example, so all ten optional query params (and the datetime → ISO-8601 Z encoding) lost their assertions while the client signature still forwarded them. Frozen for the same reason as the _keys.py entry above.
  • tests/wire/test_listen_v1_media.py — restored query-parameter coverage for listen.v1.media.transcribe_url. Third instance of the same mechanism as the two entries above, this time on the SDK's highest-traffic endpoint: the 2026-08-18 regen simplified the upstream /v1/listen spec example (the wiremock mapping was renamed "default""Remote File"), so the generated wire test dropped all 37 optional query params — including the bool → lowercase "true"/"false" assertions — while the client signature still forwards every one of them. The generated test is left untouched; a second hand-written test with its own test_id carries the assertions, which is why no change to wiremock/wiremock-mappings.json is needed (verify_request_count matches WireMock's recorded requests via the X-Test-Id header, not the stub matcher). Pattern to watch: any regen that simplifies an upstream example silently erodes that endpoint's wire coverage; check git diff on tests/wire/ every cycle.
  • src/deepgram/__init__.py, src/deepgram/agent/__init__.py, src/deepgram/agent/v1/__init__.py, src/deepgram/agent/v1/types/__init__.py, src/deepgram/agent/v1/requests/__init__.py, src/deepgram/types/__init__.py, src/deepgram/requests/__init__.py — package __init__.py files carrying hand-applied legacy alias re-exports for CreateKeyV1RequestOne, AgentV1HistoryContent, AgentV1HistoryFunctionCalls, AgentV1SettingsAgentContextMessagesItemContent, AgentV1SettingsAgentContextMessagesItemFunctionCalls (and their *Params variants). Fern would otherwise regenerate these and strip the legacy entries. After unfreezing for the next regen and reviewing the new generated content, re-apply the legacy re-exports plus any genuine new entries Fern added.
  • src/deepgram/listen/__init__.py, src/deepgram/listen/v2/__init__.py, src/deepgram/listen/v2/types/__init__.py__init__.py files re-exporting the hand-written ListenV2CloseStreamType shim. Frozen so Fern won't strip the re-export on regen. Same handling as the package __init__.py files above: after unfreezing, re-apply the ListenV2CloseStreamType re-export plus any genuine new entries Fern added.

Unfrozen files with residual manual concerns

Files Fern now owns outright, but that carry a caveat worth knowing before the next regen:

  • src/deepgram/core/client_wrapper.py — unfrozen in the 2026-08-11 regen. The generator's runtime_version: true mode now derives the wire version from importlib.metadata.version("deepgram-sdk"), which supersedes the old hand-applied # x-release-please-version annotations (its generic entry was removed from .github/release-please-config.json at the same time, since with the annotations gone it was a no-op). Caveat: the except PackageNotFoundError fallback is a hardcoded version literal that nothing bumps — release-please's extra-files now covers only pyproject.toml and .fern/metadata.json. It only applies where package metadata is undiscoverable (source checkout on sys.path, vendored copy, zipapp, some frozen bundles), and it is refreshed only by the next regen. Accepted deliberately rather than re-freezing the file; if that fallback ever needs to be accurate on those paths, re-add a release-please generic entry with an annotation on the fallback line.
  • src/deepgram/agent/v1/types/agent_v1latency_report.py and its requests/ twin — unfrozen in the 2026-08-11 regen because the spec restored stt_latency natively, making the hand-added read-side field redundant. tests/custom/test_latency_report_stt_compat.py stays frozen and still guards the field's presence, so a future spec removal is caught rather than silently re-breaking readers.

Prepare repo for regeneration

  1. Create a new branch off main named lo/sdk-gen-<YYYY-MM-DD>.
  2. Push the branch and create a PR titled chore: SDK regeneration <YYYY-MM-DD> (empty commit if needed).
  3. Read .fernignore and classify each entry using the rules above.
  4. For each temporarily frozen file only:
    • Copy the file to <filename>.bak alongside the original.
    • In .fernignore, replace the original path with the .bak path. This protects our patched version from the generator while allowing Fern to overwrite the original.
  5. Never touch permanently frozen entries. Leave them in .fernignore as-is.
  6. Commit as chore: unfreeze files pending regen and push.
  7. The branch is now ready for the Fern generator to push changes.

After regeneration

The .bak files are our manually-patched versions (protected by .fernignore). The original paths now contain the freshly generated versions. By comparing the two, we can see what the generator now produces vs what we had patched.

  1. Diff each .bak file against the new generated version to understand what changed and whether our patches are still needed.
  2. Re-apply any patches that are still necessary to the newly generated files.
  3. In .fernignore, replace each .bak path back to the original path for any files that still need manual patches.
  4. Remove .fernignore entries entirely for any files where the generator now produces correct output (patches no longer needed).
  5. Delete all .bak files once review is complete.
  6. Run tests (pytest) and linting (ruff check, mypy) to verify.
  7. Commit as chore: re-apply manual patches after regen and push.