Add experimental-API enforcement tooling for TS, Python, Go, and Rust#1719
Add experimental-API enforcement tooling for TS, Python, Go, and Rust#1719stephentoub wants to merge 3 commits into
Conversation
Bring TypeScript, Python, Go, and Rust in line with the C# ([Experimental]) and Java (@CopilotExperimental) SDKs, where consuming an experimental API produces a real diagnostic the user must explicitly suppress. Previously these four languages only had doc comments. - TypeScript: new @github/eslint-plugin-copilot-sdk package with a type-aware no-experimental-api rule keyed off the @experimental JSDoc tag. - Python: @experimental decorator emitting ExperimentalWarning with a configurable warn/error/ignore policy (COPILOT_EXPERIMENTAL); emitted by codegen onto generated experimental methods. - Go: go/analysis analyzer (go/copilotexperimental) that flags references to Experimental:-documented symbols; suppress with //nolint:copilotexperimental. - Rust: experimental Cargo feature; codegen gates experimental RPC methods via conditional visibility (pub with the feature, pub(crate) fallback without) so external callers get a hard compile error while internal SDK code keeps working. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR adds enforceable “experimental API” opt-in mechanisms for the TypeScript, Python, Go, and Rust SDKs (bringing them closer to the C# / Java [Experimental] / @CopilotExperimental behavior) by introducing language-idiomatic enforcement and updating codegen so experimental markers stay in sync with the schema.
Changes:
- Rust: Gate experimental generated RPC methods behind a new
experimentalCargo feature via conditional visibility, and enable that feature in Rust CI for lint/tests. - Python: Add a runtime
@experimentaldecorator + policy controls and have codegen decorate experimental generated RPC methods; add unit tests and public re-exports. - TypeScript & Go: Introduce a new ESLint plugin rule for TS consumers and a
go/analysisvettool analyzer for Go consumers; document usage.
Show a summary per file
| File | Description |
|---|---|
| scripts/codegen/rust.ts | Emits feature-gated conditional visibility for experimental generated Rust RPC methods. |
| scripts/codegen/python.ts | Imports and applies @experimental to generated Python RPC methods when marked experimental. |
| rust/README.md | Documents the experimental feature and how to opt in to experimental Rust APIs. |
| rust/Cargo.toml | Adds experimental feature and marks example(s) requiring it. |
| python/test_experimental.py | Adds unit tests validating runtime experimental gating behavior. |
| python/copilot/generated/rpc.py | Regenerated RPC client with @experimental decorators on experimental methods. |
| python/copilot/experimental.py | Implements Python runtime experimental decorator, warning type, and policy controls. |
| python/copilot/init.py | Re-exports experimental gating utilities in the public copilot package API. |
| nodejs/README.md | Documents experimental API enforcement via the companion ESLint plugin rule. |
| nodejs/eslint-plugin/test/no-experimental-api.test.js | Adds tests for the ESLint rule behavior against fixture consumers. |
| nodejs/eslint-plugin/test/fixtures/tsconfig.json | TS project config used by the rule’s type-aware tests. |
| nodejs/eslint-plugin/test/fixtures/sdk.ts | Fixture SDK surface with @experimental JSDoc tags. |
| nodejs/eslint-plugin/test/fixtures/re-export.ts | Fixture re-export used by consumer aliasing test scenarios. |
| nodejs/eslint-plugin/test/fixtures/consumer-suppressed.ts | Fixture demonstrating per-use suppression. |
| nodejs/eslint-plugin/test/fixtures/consumer-stable.ts | Fixture verifying stable API usage is not flagged. |
| nodejs/eslint-plugin/test/fixtures/consumer-experimental.ts | Fixture verifying experimental usages are flagged. |
| nodejs/eslint-plugin/test/fixtures/consumer-aliased.ts | Fixture verifying aliased experimental references are flagged. |
| nodejs/eslint-plugin/rules/no-experimental-api.js | Implements the type-aware ESLint rule that flags @experimental symbol references. |
| nodejs/eslint-plugin/README.md | Documents installation/configuration and suppression for the ESLint plugin. |
| nodejs/eslint-plugin/package.json | Defines the new standalone ESLint plugin package metadata/deps. |
| nodejs/eslint-plugin/package-lock.json | Locks dependencies for the standalone ESLint plugin package. |
| nodejs/eslint-plugin/index.js | Exposes plugin entry point, rule registration, and recommended configs. |
| nodejs/eslint-plugin/eslint.config.js | Flat-config used for plugin’s own tests/fixtures. |
| nodejs/eslint-plugin/.gitignore | Ignores local node_modules for the plugin package. |
| go/README.md | Documents running the new copilotexperimental analyzer via go vet -vettool. |
| go/copilotexperimental/testdata/src/sdk/sdk.go | Analyzer testdata: “SDK” declarations marked experimental via doc markers. |
| go/copilotexperimental/testdata/src/consumer/consumer.go | Analyzer testdata: consumer references + suppression directive. |
| go/copilotexperimental/README.md | Documents analyzer install/run and suppression behavior. |
| go/copilotexperimental/go.sum | Dependency checksums for the analyzer’s nested module. |
| go/copilotexperimental/go.mod | Nested module definition for the analyzer. |
| go/copilotexperimental/experimental.go | Implements the analyzer using exported analysis Facts + suppression scanning. |
| go/copilotexperimental/experimental_test.go | Runs analysistest suite for the analyzer. |
| go/copilotexperimental/cmd/copilotexperimental/main.go | Provides a go vet-compatible singlechecker entry point. |
| .github/workflows/rust-sdk-tests.yml | Enables experimental feature during Rust clippy and test runs. |
Copilot's findings
Files not reviewed (1)
- nodejs/eslint-plugin/package-lock.json: Generated file
- Files reviewed: 31/35 changed files
- Comments generated: 2
This comment has been minimized.
This comment has been minimized.
The standalone @github/eslint-plugin-copilot-sdk package ships its own node:test suite (run via its package's npm test). The SDK root 'vitest run' was globbing eslint-plugin/test/no-experimental-api.test.js and failing to collect it (No test suite found), breaking Node.js SDK Tests on all platforms. Exclude eslint-plugin/** from the root vitest config so the plugin's tests run only under their own runner. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This comment has been minimized.
This comment has been minimized.
…anchor - go/test.sh now explicitly runs the copilotexperimental nested module's tests, which the repo-root `go test ./...` does not descend into, so analyzer regressions are caught in CI. - nodejs/eslint-plugin/README.md adds a Rules section with a `no-experimental-api` heading so the rule's generated docs URL fragment (#no-experimental-api) resolves to a real anchor. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Cross-SDK Consistency ReviewThis PR does exactly what it says: brings TypeScript, Python, Go, and Rust up to parity with the existing .NET and Java experimental-API enforcement. The overall design is consistent — each language gets an idiomatic enforcement mechanism, and codegen is updated to emit the markers in the two languages (Python, Rust) that were missing them. ✅ What's consistent across all 6 SDKs
All per-call-site suppression directives are inline annotations/comments — consistent across TypeScript and Go. The Java and .NET declaration-level opt-ins are analogous and idiomatic for those languages.
|
Why
The C# and Java SDKs mark unstable APIs with
[Experimental]/@CopilotExperimental, and the toolchain produces a real diagnostic that a consumer must explicitly suppress to opt in. The TypeScript, Python, Go, and Rust SDKs only had doc comments, so nothing actually stopped a user from depending on an experimental API by accident. This brings all four languages up to parity with a mechanism that the compiler or linter legitimately flags.Approach
Each language gets the idiomatic equivalent of
[Experimental], and codegen emits the marker/gate onto generated experimental methods so the surface stays in sync with the schema:@github/eslint-plugin-copilot-sdkpackage with a type-awareno-experimental-apirule that flags references to any symbol whose JSDoc carries@experimental. Opt in per call site with// eslint-disable-next-line @github/copilot-sdk/no-experimental-api.@experimentaldecorator that raisesExperimentalWarningunder a configurable warn / error / ignore policy (also driven by theCOPILOT_EXPERIMENTALenv var), plusallow_experimental()andset_experimental_policy(). Codegen applies it to generated experimental methods.go/analysisanalyzer (go/copilotexperimental/, nested module) that flags references toExperimental:-documented symbols via exported analysis Facts. Suppress with a same-line//nolint:copilotexperimental.experimentalCargo feature. Codegen gates experimental RPC methods by conditional visibility:pubwhen the feature is enabled, and apub(crate)fallback (identical body) when it is not. External consumers hit a hardE0624compile error unless they opt in withfeatures = ["experimental"], while the SDK's own internal callers keep compiling.Notes for reviewers
rust/src/generated/rpc.rs,python/copilot/generated/rpc.py) are produced by the codegen changes inscripts/codegen/*.ts, not hand-edited..github/workflows/rust-sdk-tests.yml) now passestest-support,experimentalto the clippy and test steps. The SDK's own tests and themanual_tool_resumeexample exercise experimental RPCs directly, so they need the feature enabled, just as the C#/Java test projects suppress the experimental diagnostic.cargo doc --all-featuresalready covers it, and themanual_tool_resumeexample is declared withrequired-features = ["experimental"].E0624for an external consumer, and the regenerated file passesrustfmt. Go, Python, and the TS plugin test suites all pass.