Skip to content

feat(csharp): add opt-in User-Agent fallback flag#15802

Merged
fern-support merged 5 commits into
mainfrom
devin/1778215363-csharp-user-agent-fallback
May 8, 2026
Merged

feat(csharp): add opt-in User-Agent fallback flag#15802
fern-support merged 5 commits into
mainfrom
devin/1778215363-csharp-user-agent-fallback

Conversation

@fern-support
Copy link
Copy Markdown
Collaborator

@fern-support fern-support commented May 8, 2026

Description

Linear ticket: Refs

Bring the C# SDK generator to optional parity with the TypeScript generator's User-Agent fallback. When sdkConfig.platformHeaders.userAgent is unset in the IR (which happens for SDKs imported from OpenAPI when version/packageName aren't both passed to generateIntermediateRepresentation), the C# generator emits no User-Agent header at all. The TS generator already falls back to <npm-package-name>/<version> in the analogous branch — see <ref_snippet file="/home/ubuntu/repos/fern/generators/typescript/sdk/client-class-generator/src/BaseClientTypeGenerator.ts" lines="171-181" />.

This PR adds an opt-in user-agent-name-from-package boolean custom config (default false) that makes C# emit $"<NuGetPackageId>/{Version.Current}" in that fallback branch, where:

  • <NuGetPackageId> resolves via this.generation.names.project.packageId. The cascade is package-idnamespacePascalCase(<organization>_<api-name>), so the prefix always matches the name the SDK ships under on NuGet (e.g. DocuSign.eSign, or FernPlantstore if neither package-id nor namespace is set).
  • Version.Current is the same compile-time constant already used for the X-Fern-SDK-Version header.

Defaulting to false preserves the historical "no User-Agent header for OpenAPI-imported SDKs" behavior; turning it on is now a one-line generator config change.

The companion docs PR is fern-api/docs#5439, which adds the new flag to the C# configuration reference.

Changes Made

  • generators/csharp/codegen/src/custom-config/CsharpConfigSchema.ts: new optional boolean user-agent-name-from-package.
  • generators/csharp/codegen/src/context/generation-info.ts: new userAgentNameFromPackage setting wired to the new schema key, defaulting to false.
  • generators/csharp/sdk/src/root-client/buildUserAgentHeaderEntry.ts: new pure helper that returns the platform-headers Dictionary.MapEntry for User-Agent, branching on whether userAgent is set on the IR and whether the flag is on.
  • generators/csharp/sdk/src/root-client/RootClientGenerator.ts: the existing if (userAgent != null) block is replaced with a call to the new helper, gated on this.settings.userAgentNameFromPackage.
  • generators/csharp/sdk/src/root-client/__test__/buildUserAgentHeaderEntry.test.ts: 5 unit tests covering both branches (explicit IR header, fallback) plus regression guards for dotted NuGet ids, the version-expression interpolation, and — crucially — the names.project.packageId cascade when no package-id/namespace is configured.
  • generators/csharp/sdk/changes/unreleased/user-agent-fallback.yml: feat changelog entry.
  • Updated README.md generator (if applicable) — N/A

Testing

  • Unit tests added/updated — 5 cases in buildUserAgentHeaderEntry.test.ts; all pass via pnpm --filter=@fern-api/fern-csharp-sdk run test (41 passed, 41 total).

  • pnpm run check (biome) clean.

  • pnpm turbo run compile --filter=@fern-api/fern-csharp-sdk clean.

  • All required CI checks green (49 pass, 0 fail), including every csharp-sdk seed-fixture matrix job and seed-test-results (csharp-sdk).

  • Manual end-to-end against an IR with platformHeaders.userAgent unset (fern-unions test fixture, run through the locally-built cli.cjs).

    Flag off (default), package-id: "Plantstore.eSign" — generated FernPlantstoreClient.cs has no User-Agent entry (matches pre-PR behavior):

    new Dictionary<string, string>()
    {
        { "X-Fern-Language", "C#" },
        { "X-Fern-SDK-Name", "FernUnions" },
        { "X-Fern-SDK-Version", Version.Current },
    }

    Flag on, package-id: "Plantstore.eSign" — fallback entry is emitted:

    { "User-Agent", $"Plantstore.eSign/{Version.Current}" },

    Flag on, no package-id, no namespace — fallback degrades to the cascade, producing $"FernUnions/{Version.Current}" (the same string the SDK ships under as the NuGet project), confirming the User-Agent prefix is never empty.

    The Fern-Definition path is byte-identical: seed/csharp-sdk/simple-api/no-custom-config/.../SeedSimpleApiClient.cs continues to emit { "User-Agent", "Fernsimple-api/0.0.1" } regardless of the flag.

Things worth a careful look

  • The fallback emits a single interpolated string by combining a literal prefix with writer.writeNode(this.context.getCurrentVersionValueAccess()). Confirmed via the unit tests and manual e2e that the rendered output is exactly $"<PackageId>/{Version.Current}" and that the Version using directive is wired up the same way as the existing X-Fern-SDK-Version entry.
  • packageName, userAgent.header, and userAgent.value are interpolated unescaped into $"..." / "...". NuGet package ids in practice only use letters/digits/./-/_, so this matches the existing convention used by the X-Fern-SDK-Name entry; the IR-supplied header/value path is identical to the pre-PR behavior. Worth a sanity check that we're comfortable with that assumption.
  • The cascade test uses real Generation rather than the {} as mock used elsewhere in this file, so it actually exercises names.project.packageId end-to-end. If anyone changes that cascade in generation-info.ts, this test should fail.
  • Renaming the flag from my initial user-agent-from-package to user-agent-name-from-package happened mid-review per @fern-support's request — sanity-check that no stragglers from the old name remain (e.g. in changelog filenames). The changelog file is still named user-agent-fallback.yml to avoid bake-in of either flag spelling.

Link to Devin session: https://app.devin.ai/sessions/02d3233caaac4616b6f38488ff08ef1a

Co-Authored-By: will.kendall@buildwithfern.com <wpk235@gmail.com>
@devin-ai-integration
Copy link
Copy Markdown
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

Copy link
Copy Markdown

@claude claude Bot left a comment

Choose a reason for hiding this comment

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

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review to trigger a review and subscribe this PR to future pushes, or @claude review once for a one-time review.

Tip: disable this comment in your organization's Code Review settings.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 8, 2026

SDK Generation Benchmark Results

Comparing PR branch against median of 5 nightly run(s) on main (latest: 2026-05-08T04:59:46Z).

Full benchmark table (click to expand)
Generator Spec main (generator) main (E2E) PR (generator) Delta
csharp-sdk square 58s (n=5) 89s (n=5) 68s +10s (+17.2%)

main (generator): generator-only time via --skip-scripts (includes Docker image build, container startup, IR parsing, and code generation — this is the same Docker-based flow customers use via fern generate). main (E2E): full customer-observable time including build/test scripts (nightly baseline, informational). Delta is computed against generator-only baseline.
⚠️ = generation exited with a non-zero exit code (timing may not reflect a successful run).
Baseline from nightly runs on main (latest: 2026-05-08T04:59:46Z). Trigger benchmark-baseline to refresh.
Last updated: 2026-05-08 05:47 UTC

devin-ai-integration Bot and others added 4 commits May 8, 2026 05:04
Co-Authored-By: will.kendall@buildwithfern.com <wpk235@gmail.com>
Co-Authored-By: will.kendall@buildwithfern.com <wpk235@gmail.com>
Co-Authored-By: will.kendall@buildwithfern.com <wpk235@gmail.com>
…gured

Co-Authored-By: will.kendall@buildwithfern.com <wpk235@gmail.com>
@devin-ai-integration devin-ai-integration Bot changed the title fix(csharp): fall back to NuGet package name in User-Agent header feat(csharp): add opt-in User-Agent fallback flag May 8, 2026
@fern-support fern-support merged commit 9da5175 into main May 8, 2026
74 checks passed
@fern-support fern-support deleted the devin/1778215363-csharp-user-agent-fallback branch May 8, 2026 14:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants