Skip to content

Latest commit

 

History

History
103 lines (74 loc) · 18.2 KB

AGENTS.md

File metadata and controls

103 lines (74 loc) · 18.2 KB

Agents

Fern SDK Regeneration

Overview

This SDK is generated by Fern. Most files under src/main/java/com/deepgram/ are auto-generated and should not be edited directly. Some files are hand-written or carry 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 or are maintained independently from Fern. 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, such as custom client wrappers or transport abstractions
  • The file is a doc, config, build, or CI artifact we maintain independently
  • The file lives outside the generated Java package tree or is a manually maintained test/example

Current permanently frozen files:

  • src/main/java/com/deepgram/DeepgramClient.java, src/main/java/com/deepgram/AsyncDeepgramClient.java, src/main/java/com/deepgram/DeepgramClientBuilder.java, src/main/java/com/deepgram/AsyncDeepgramClientBuilder.java - custom wrapper entrypoints that add Bearer auth, session ID support, and custom transport behavior on top of Fern's generated API client
  • src/main/java/com/deepgram/core/transport/ - hand-written transport abstraction
  • src/main/java/com/deepgram/resources/listen/v1/websocket/ListenV1ErrorException.java and src/main/java/com/deepgram/resources/listen/v1/websocket/ListenV1ConfigureFailureException.java - hand-written exceptions carrying Listen V1 server Error and ConfigureFailure frames to the generic onError handler when no typed handler is registered; thrown by the frozen listen v1 V1WebSocketClient. No Fern equivalent
  • build.gradle, settings.gradle, gradle/, gradlew, gradlew.bat, mvnw, mvnw.cmd, .mvn/, pom.xml, Makefile - build and project configuration
  • README.md, CHANGELOG.md, CONTRIBUTING.md, LICENSE, docs/ - docs
  • src/test/ - manually maintained tests
  • examples/ - manually maintained examples
  • .editorconfig, .githooks/, .github/, .gitignore - repo and CI configuration
  • context7.json - generated-documentation context configuration maintained outside Fern
  • target/ - build output
  • CLAUDE.md, AGENTS.md, .claude/, .agents/ - agent files and agent-agnostic skills

Note: .fernignore also includes flat-path variants such as src/main/java/DeepgramClient.java as defensive entries for alternate local generation layouts. Those files do not currently exist in this repo checkout and should not be treated as current maintained source files.

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

Current temporarily frozen files:

  • src/main/java/com/deepgram/resources/agent/v1/websocket/V1WebSocketClient.java - Agent V1 already delivers every raw control frame through onMessage(String). Suppress generic errors for unknown frames only when a raw handler is registered; otherwise retain legacy onError delivery. Preserve the generated Think Live, dynamic-injection, and frustration handlers during reconciliation.
  • src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ConversationText.java restores the legacy content(...): _FinalStage builder stage and defaults omitted or empty conversational_behavior to default. The schema now requires the wire field, while this shim preserves source and binary compatibility for callers compiled before Think Live existed. ConversationalBehaviorStage remains as an intentionally retained generated type, even though the restored legacy stage no longer returns it.
  • src/main/java/com/deepgram/resources/agent/v1/types/AgentV1ThinkLiveUpdated.java - fields-less event type with a manual hashCode() patch. Fern generates equals() but no matching hashCode(), violating the Object contract; retain the patch, freeze entry, and RegenTypesTest.FieldsLessMessageContract assertion until Fern fixes the pattern.
  • src/main/java/com/deepgram/core/ClientOptions.java - preserves release-please version markers, correct SDK header constants, and the accurate close() lifecycle Javadoc that Fern currently overwrites; use the standard .bak swap/restore workflow during regen review. Since generator 4.18.0 Fern emits a getSdkVersion() helper reading Package.getImplementationVersion() instead of a literal. That does resolve in the published artifact (CI publishes via ./mvnw deploy -P release, and pom.xml's maven-jar-plugin sets addDefaultImplementationEntries=true, so the JAR manifest carries Implementation-Version), but it resolves to null under Gradle and in tests, where it silently falls back to a hardcoded literal the generator does not keep current. We keep the explicit literals because they are correct in every context and because .github/release-please-config.json already lists this file in extra-files, so release-please bumps it alongside pom.xml, build.gradle, and .fern/metadata.json. Fern also emits User-Agent with a com.deepgram. prefix while leaving X-Fern-SDK-Name on the com.deepgram: Maven-coordinate form; we keep both on the colon form. Fern 4.22.1 tracks and closes child WebSockets before shutting down SDK-owned OkHttp resources; the permanent custom client wrappers inherit it, so do not add wrapper-local lifecycle code and retain Fern's lifecycle implementation during reconciliation.
  • src/main/java/com/deepgram/core/ReconnectingWebSocketListener.java - carries bug fixes for maxRetries(0) semantics ("connect once, don't retry") and a configurable connectionTimeoutMs field (was hardcoded 4000ms), plus an applyOptionsOverride(...) hook used by TransportWebSocketFactory to apply per-transport reconnect policy; pull this back out once the fixes are upstreamed into the Fern generator. Use the standard .bak swap/restore workflow during regen review.
  • src/main/java/com/deepgram/resources/speak/v2/websocket/V2WebSocketClient.java and src/main/java/com/deepgram/resources/listen/v2/websocket/V2WebSocketClient.java - forward-compat patch (both clients). Fern's generated handleIncomingMessage dispatcher routes any unrecognized message type to onError with "Update your SDK version...", which makes a benign new server control frame look fatal to a deployed client. Patched so the unrecognized-type branch is a no-op — the raw frame is already delivered via onMessage(String) earlier in the method, so consumers still see it. Mirrors the JS/Python SDKs' forward-compat behavior and is regression-guarded by src/test/java/com/deepgram/SpeakV2ForwardCompatTest.java and src/test/java/com/deepgram/ListenV2ForwardCompatTest.java. These two clients also carry the streaming query-param patches described in the next entry. Listen V2 additionally tracks a successfully sent CloseStream and treats a following no-status (1005) close as terminal; queued or rejected controls still reconnect, and src/test/java/com/deepgram/ListenV2ServerCloseTest.java guards all three paths. Use the standard .bak swap/restore workflow during regen review; re-apply the no-op and terminal-close safeguards after regen.
  • src/main/java/com/deepgram/resources/listen/v1/websocket/V1WebSocketClient.java and src/main/java/com/deepgram/resources/speak/v1/websocket/V1WebSocketClient.java (and the v2 clients above) - streaming query-param patches on the generated connect() builders. Two fixes: (1) multi-value serialization — array-valued params (listen: keyterm, keywords, replace, search, tag, extra, language_hint; speak: tag) were serialized with String.valueOf(union.get()), collapsing a List into one param (keyterm=[a, b]) instead of repeats (keyterm=a&keyterm=b); (2) an additionalProperties escape hatch — the builder exposes additionalProperty(key, value) for unmodeled params (e.g. no_delay) but connect() never emitted them to the URL. Both patched to route through QueryStringMapper(arraysAsRepeats=true), matching the REST path. Listen V1 also forwards a parsed server Error to onError as a ListenV1ErrorException (carrying the typed ListenV1Error) when no onErrorMessage handler is registered, preserving the prior generic error-handler behavior. Use the standard .bak swap/restore workflow during regen review; re-apply after regen and unfreeze once the generator emits array params as repeats and serializes additionalProperties on the WS connect() path (tracked as an upstream Fern request).
  • src/main/java/com/deepgram/resources/listen/v1/websocket/V1ConnectOptions.java, src/main/java/com/deepgram/resources/listen/v1/media/requests/ListenV1RequestUrl.java, and src/main/java/com/deepgram/resources/listen/v1/media/requests/MediaTranscribeRequestOctetStream.java - keep newly added languageHint(...) methods on public _FinalStage builder interfaces as default no-ops. Generated concrete builders override them normally; the defaults preserve source and binary compatibility for third-party implementations compiled before the optional setting existed. Re-apply after regen until Fern emits compatible defaults.
  • Fields-less message types carrying a manual hashCode() patch (Fern generates equals() but no hashCode() for these, violating the Object contract): src/main/java/com/deepgram/resources/listen/v2/types/ListenV2CloseStream.java, src/main/java/com/deepgram/resources/listen/v2/types/ListenV2ForceEndTurn.java, src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Close.java, src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2Flush.java, and the AgentV1* event types src/main/java/com/deepgram/resources/agent/v1/types/{AgentV1ListenUpdated,AgentV1SpeakUpdated,AgentV1AgentAudioDone,AgentV1SettingsApplied,AgentV1UserStartedSpeaking,AgentV1KeepAlive,AgentV1ThinkUpdated,AgentV1PromptUpdated,AgentV1ForceEndTurn}.java. Use the standard .bak swap/restore workflow during regen review; drop the patches and unfreeze all of them once the generator emits a matching equals/hashCode pair for fields-less types (tracked as an upstream Fern request).
  • src/main/java/com/deepgram/types/DeepgramModel.java - restores the FLUX_RENEE_EN constant that generator 4.18.0 dropped. Renee is an unlisted voice: the spec owner removed it from the catalog on purpose (#project-flux-tts, 2026-08-19), but it still synthesizes (POST /v2/speak?model=flux-renee-en returns 200 with valid audio), so dropping the constant would be a source break for 0.8.0+ callers over a catalog delisting alone. The constant and its Visitor method are marked @Deprecated with a note pointing callers to a listed voice. Five touchpoints: the constant, the Value enum entry, the visit() case, the valueOf() case, and the Visitor method (the constant and Visitor method carry the @Deprecated tag and Javadoc). This file is unlike the other temporarily frozen ones — it receives frequent additive spec changes (4.18.0 alone added 25 constants), so on the next regen do NOT restore the .bak wholesale. Diff the .bak against the newly generated file, carry forward every new voice, and re-apply only the FLUX_RENEE_EN touchpoints. The spec will not list the voice again, so drop the Renee touchpoints at the next major release; keep the file frozen until then for the reasons above.
  • Union default-variant fix on the agent listen-provider unions: src/main/java/com/deepgram/resources/agent/v1/types/AgentV1UpdateListenListenProvider.java, src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentListenProvider.java, src/main/java/com/deepgram/resources/agent/v1/types/AgentV1SettingsAgentContextListenProvider.java. version is an optional discriminator, so a provider payload without it is valid (and is what 0.7.x emits), but Fern points @JsonTypeInfo defaultImpl at the empty-bodied _UnknownValue, so such a payload deserializes to an unknown variant carrying null — getProvider() returns null and re-serialization emits {"provider":null}, silently dropping the provider on the wire. Patched to defaultImpl = V2Value on each; guarded by src/test/java/com/deepgram/AgentSettingsProviderDefaultTest.java. Use the standard .bak swap/restore workflow during regen review; drop the patches and unfreeze once the generator stops defaulting unions to the empty _UnknownValue (tracked as an upstream Fern request).
  • src/main/java/com/deepgram/resources/speak/v2/types/SpeakV2ConfigureFailureCode.java makes Visitor.visitControlCombinationInvalid() a default method that delegates to visitUnknown("CONTROL_COMBINATION_INVALID"). This restores source compatibility for Visitor implementations written against 0.8.0–0.10.x, before the value existed (0.11.0 shipped the method as abstract and listed it as breaking), while retaining the new generated enum value. Guarded by src/test/java/com/deepgram/RegenTypesTest.java. Use the standard .bak swap/restore workflow during regen review so new failure codes from the spec are carried forward, and re-apply only the default method. Keep frozen until the next major release.

Prepare repo for regeneration

  1. Create a new branch off main named <YOUR_INITIALS>/sdk-gen-<YYYY-MM-DD> (e.g. gh/sdk-gen-2026-07-09). Use your own initials as the prefix.
  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 versus 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. For src/main/java/com/deepgram/core/ClientOptions.java, use the standard .bak restore flow if the generated output still overwrites the Deepgram SDK header constants and // x-release-please-version markers.

  3. In .fernignore, replace each .bak path back to the original path for files that still need patches.

  4. Remove .fernignore entries entirely for any files where the generator now produces correct output.

  5. Delete all .bak files once review is complete.

  6. Sweep for newly generated files that need an existing patch class. Diffing .bak files only catches regressions in files we already froze — a brand-new generated type has no .bak and will slip through. At minimum, re-run the fields-less hashCode() sweep:

    for f in $(grep -rl 'instanceof' src/main/java/com/deepgram --include='*.java'); do
      grep -q 'public boolean equals' "$f" && ! grep -q 'public int hashCode' "$f" && echo "$f"
    done

    Ignore core/Nullable.java and core/NullableNonemptyFilter.java (Jackson utility classes, not message types). Anything else in the list needs the hashCode() patch, a .fernignore entry, an entry in the list above, and a RegenTypesTest.FieldsLessMessageContract assertion. ListenV2ForceEndTurn reached main unpatched this way.

  7. Run checks (./gradlew spotlessCheck test compileExamples) to verify. test covers unit/wire tests including the README snippet compilation; compileExamples separately compiles the hand-maintained examples/ directory and catches stale API call sites that test alone would miss. spotlessCheck is required — CI runs it as a separate job, so test compileExamples alone can pass locally while CI fails. Re-applying patches by hand almost always trips it: inserted comments and renamed types change line lengths, which changes how the formatter wraps chained calls. Run ./gradlew spotlessApply to fix, then re-check.

  8. Commit as chore: re-apply manual patches after regen and push.

Releasing to Maven Central

release-please tags the release, then the publish job in .github/workflows/release-please.yml runs ./mvnw deploy -DskipTests -P release. The Maven Wrapper (mvnw, .mvn/wrapper/maven-wrapper.properties) pins Maven 3.9.16 so the build does not depend on the runner's Maven.

  • Do not bump the wrapper to Maven 3.10+ until the Central publishing flow handles it. Maven 3.10 adds maven-metadata-local.xml to the Central bundle, and Central rejects the deployment (this blocked 0.11.1 on 2026-10-12). Upgrading central-publishing-maven-plugin past 0.7.0 may fix this; verify the bundle contents with ./mvnw deploy -DskipTests -P release -Dgpg.skip=true -DskipPublishing=true (then inspect target/central-publishing/central-bundle.zip) before changing the pin.
  • If the Central publish fails after release-please has tagged, fix the cause on main, then run the Retry Maven Central Publish workflow (.github/workflows/retry-maven-central-publish.yml) with the release tag (for example, v0.11.1). It builds the tagged commit with the wrapper from main, and refuses tags that don't match the version in .github/.release-please-manifest.json. Maven Central versions are immutable, so a version that already published cannot be re-uploaded.