feat(csharp): add opt-in User-Agent fallback flag#15802
Conversation
Co-Authored-By: will.kendall@buildwithfern.com <wpk235@gmail.com>
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
There was a problem hiding this comment.
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.
SDK Generation Benchmark ResultsComparing PR branch against median of 5 nightly run(s) on Full benchmark table (click to expand)
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 |
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>
Description
Linear ticket: Refs
Bring the C# SDK generator to optional parity with the TypeScript generator's User-Agent fallback. When
sdkConfig.platformHeaders.userAgentis unset in the IR (which happens for SDKs imported from OpenAPI whenversion/packageNamearen't both passed togenerateIntermediateRepresentation), the C# generator emits noUser-Agentheader 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-packageboolean custom config (defaultfalse) that makes C# emit$"<NuGetPackageId>/{Version.Current}"in that fallback branch, where:<NuGetPackageId>resolves viathis.generation.names.project.packageId. The cascade ispackage-id→namespace→PascalCase(<organization>_<api-name>), so the prefix always matches the name the SDK ships under on NuGet (e.g.DocuSign.eSign, orFernPlantstoreif neitherpackage-idnornamespaceis set).Version.Currentis the same compile-time constant already used for theX-Fern-SDK-Versionheader.Defaulting to
falsepreserves the historical "noUser-Agentheader 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 booleanuser-agent-name-from-package.generators/csharp/codegen/src/context/generation-info.ts: newuserAgentNameFromPackagesetting wired to the new schema key, defaulting tofalse.generators/csharp/sdk/src/root-client/buildUserAgentHeaderEntry.ts: new pure helper that returns the platform-headersDictionary.MapEntryforUser-Agent, branching on whetheruserAgentis set on the IR and whether the flag is on.generators/csharp/sdk/src/root-client/RootClientGenerator.ts: the existingif (userAgent != null)block is replaced with a call to the new helper, gated onthis.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 — thenames.project.packageIdcascade when nopackage-id/namespaceis configured.generators/csharp/sdk/changes/unreleased/user-agent-fallback.yml:featchangelog entry.Testing
Unit tests added/updated — 5 cases in
buildUserAgentHeaderEntry.test.ts; all pass viapnpm --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-sdkclean.All required CI checks green (49 pass, 0 fail), including every
csharp-sdkseed-fixture matrix job andseed-test-results (csharp-sdk).Manual end-to-end against an IR with
platformHeaders.userAgentunset (fern-unionstest fixture, run through the locally-builtcli.cjs).Flag off (default),
package-id: "Plantstore.eSign"— generatedFernPlantstoreClient.cshas noUser-Agententry (matches pre-PR behavior):Flag on,
package-id: "Plantstore.eSign"— fallback entry is emitted:Flag on, no
package-id, nonamespace— 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.cscontinues to emit{ "User-Agent", "Fernsimple-api/0.0.1" }regardless of the flag.Things worth a careful look
writer.writeNode(this.context.getCurrentVersionValueAccess()). Confirmed via the unit tests and manual e2e that the rendered output is exactly$"<PackageId>/{Version.Current}"and that theVersionusing directive is wired up the same way as the existingX-Fern-SDK-Versionentry.packageName,userAgent.header, anduserAgent.valueare interpolated unescaped into$"..."/"...". NuGet package ids in practice only use letters/digits/./-/_, so this matches the existing convention used by theX-Fern-SDK-Nameentry; the IR-supplied header/value path is identical to the pre-PR behavior. Worth a sanity check that we're comfortable with that assumption.Generationrather than the{} asmock used elsewhere in this file, so it actually exercisesnames.project.packageIdend-to-end. If anyone changes that cascade ingeneration-info.ts, this test should fail.user-agent-from-packagetouser-agent-name-from-packagehappened 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 nameduser-agent-fallback.ymlto avoid bake-in of either flag spelling.Link to Devin session: https://app.devin.ai/sessions/02d3233caaac4616b6f38488ff08ef1a