Skip to content

Expose exp_assignments injection on session create/resume across all SDKs#1750

Draft
ellismg wants to merge 3 commits into
mainfrom
ellismg-expose-exp-assignments
Draft

Expose exp_assignments injection on session create/resume across all SDKs#1750
ellismg wants to merge 3 commits into
mainfrom
ellismg-expose-exp-assignments

Conversation

@ellismg

@ellismg ellismg commented Jun 22, 2026

Copy link
Copy Markdown

What

Adds an internal/trusted-integrator option to inject ExP ("flight") assignment data into session create and resume, forwarded to the wire key expAssignments and omitted entirely when unset. The payload is opaque JSON in the exact shape of CopilotExpAssignmentResponse (matching the runtime contract and each SDK's already-generated SessionOpenOptions.expAssignments field — no typed schema struct introduced).

Implemented consistently across all six SDKs. Per-language surface:

  • Rust#[doc(hidden)] pub fn with_exp_assignments(serde_json::Value) -> Self + #[doc(hidden)] pub exp_assignments: Option<serde_json::Value> on SessionConfig / ResumeSessionConfig; forwarded through the hand-written SessionCreateWire / SessionResumeWire (skip_serializing_if = "Option::is_none").
  • Node@internal expAssignments?: Record<string, unknown> on SessionConfigBase; forwarded in the inline session.create / session.resume payloads in client.ts.
  • Pythonexp_assignments: dict[str, Any] | None = None kwarg on create_session / resume_session; mapped to payload["expAssignments"] when set.
  • GoExpAssignments any (documented Internal:) on SessionConfig / ResumeSessionConfig; forwarded into the create/resume wire structs with json:"expAssignments,omitempty".
  • .NET[EditorBrowsable(Never)] JsonElement? ExpAssignments on SessionConfigBase; wired through the internal CreateSessionRequest / ResumeSessionRequest records ([JsonPropertyName("expAssignments")], null auto-omitted).
  • JavaJsonNode expAssignments field + fluent setExpAssignments / getExpAssignments on SessionConfig / ResumeSessionConfig; mapped through CreateSessionRequest / ResumeSessionRequest in SessionRequestBuilder (@JsonProperty("expAssignments"), @JsonInclude(NON_NULL)).

Why

Lets out-of-process integrators — the GitHub Copilot desktop app — inject ExP flight data over JSON-RPC, mirroring the runtime contract added in github/copilot-agent-runtime#9955. The runtime already feeds supplied expAssignments through the same FeatureFlagService.setExpAssignments() path as CLI-fetched flights, stamps exp_assignment_context on telemetry, sets the CAPI header X-Copilot-Exp-Assignment-Context, and is non-blocking / fail-open when absent. Injection is supported on both create and resume.

Each SDK's generated SessionOpenOptions already reflects the field, but the create/resume paths serialize hand-written config + wire structs, so an out-of-process consumer had no way to send it. The desktop app is the first out-of-process consumer (starting with the Rust SDK); in-process callers already had the capability. Adding it to every SDK keeps the surface consistent per review feedback. Part of the github/github-app ExP onboarding epic #7452.

Internal posture

The field/builder are marked internal in each language's idiom (Rust #[doc(hidden)], Node @internal, Go Internal: doc, .NET [EditorBrowsable(Never)], Java doc note; Python documents it as a trusted-integrator option) to mirror upstream's .asInternal() posture — a trusted-integrator API, not broadly advertised public surface.

Validation

All six SDKs add create + resume serialization tests asserting expAssignments is emitted on both paths and omitted when unset.

  • Rustcargo +nightly-2026-04-14 fmt --check clean; cargo clippy --all-features --all-targets -- -D warnings clean; cargo test --all-features --lib → 166 passed.
  • Nodenpm run typecheck clean; new unit tests pass.
  • Python — new tests pass; ruff check / ruff format --check clean.
  • Gogofmt clean; go build ./... + go test ./... pass.
  • .NET — 37 serialization tests pass (incl. 2 new).
  • Java — 82 SessionRequestBuilderTest tests pass (incl. 2 new); mvn spotless:apply applied.

Also verified the github-app consumer compiles against the synced Rust shape (cargo check -p copilot-tauri green); with_exp_assignments is reachable from the inject call site.

Notes

Opening as a draft pending the paired github-app consumer work. The vendored sync into github-app will follow via the auto-sync agent after merge.

cc @criemen — he asked to be kept in the loop on this.

Expose ExP assignment ("flight") data on the SDK's session-open and
session-resume paths so an out-of-process integrator can inject the same
CopilotExpAssignmentResponse payload the CLI fetches itself. The runtime
already accepts expAssignments on the wire, but the hand-written
SessionCreateWire / SessionResumeWire structs (and their public configs)
did not carry it.

- SessionConfig / ResumeSessionConfig: add doc-hidden exp_assignments
  field (serde_json::Value) plus a doc-hidden with_exp_assignments builder
- SessionCreateWire / SessionResumeWire: add exp_assignments, serialized
  as camelCase expAssignments and omitted when None
- Forward the field through both into_wire paths
- Unit tests asserting expAssignments is emitted on create and resume and
  omitted when unset

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@stephentoub

Copy link
Copy Markdown
Collaborator

Even if the main known consumer at present is the GH Copilot App, I'd like to see us add whatever we need here consistently across all 6 languages. I'd like to avoid having to keep track of differences in exposed functionality by language. And presumably other languages will end up needing this, too.

…SDKs

Mirror the Rust SDK change in the remaining five SDKs so out-of-process
integrators can inject ExP ("flight") assignment data into session create
and resume. Adds an internal/trusted-integrator config field that forwards
to the wire key `expAssignments` (omitted when unset), in the opaque JSON
shape of `CopilotExpAssignmentResponse`:

- Node: `expAssignments?: Record<string, unknown>` on `SessionConfigBase`
  (`@internal`), forwarded in the inline session.create/session.resume
  payloads in client.ts.
- Python: `exp_assignments: dict[str, Any] | None = None` kwarg on
  `create_session`/`resume_session`, mapped to `payload["expAssignments"]`.
- Go: `ExpAssignments any` on `SessionConfig`/`ResumeSessionConfig`
  (documented Internal:), forwarded into the create/resume wire structs
  with `json:"expAssignments,omitempty"`.
- .NET: `JsonElement? ExpAssignments` on `SessionConfigBase`
  (`[EditorBrowsable(Never)]`), wired through the internal
  CreateSessionRequest/ResumeSessionRequest records.
- Java: `JsonNode expAssignments` field + fluent setter/getter on
  SessionConfig/ResumeSessionConfig, mapped through
  CreateSessionRequest/ResumeSessionRequest in SessionRequestBuilder.

Each language gains create+resume serialization tests asserting the field
serializes to `expAssignments` when set and is omitted when unset.

Part of github/github-app epic #7452; mirrors the runtime contract added
in github/copilot-agent-runtime#9955.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@ellismg ellismg changed the title Expose exp_assignments injection on SessionConfig / ResumeSessionConfig Expose exp_assignments injection on session create/resume across all SDKs Jun 22, 2026
…signments

# Conflicts:
#	nodejs/test/client.test.ts
Comment thread java/src/test/java/com/github/copilot/SessionRequestBuilderTest.java Dismissed
Comment thread java/src/test/java/com/github/copilot/SessionRequestBuilderTest.java Dismissed
@github-actions

Copy link
Copy Markdown
Contributor

Cross-SDK Consistency Review ✅

Reviewed all 6 SDK implementations for expAssignments consistency.

Dimension Dotnet Go Java Node.js Python Rust
Wire key (expAssignments)
Both create + resume paths
Omit when null/unset
Language-idiomatic type JsonElement? any JsonNode Record<string,unknown> dict[str,Any] Option<Value>
Internal/hidden marking [EditorBrowsable(Never)] Internal: doc Javadoc note @internal trusted-integrator doc #[doc(hidden)]
Tests (set + unset)

No consistency issues found. The null-omission mechanisms are all idiomatic: Rust uses skip_serializing_if = "Option::is_none", Go uses omitempty, Python uses an explicit if ... is not None guard, Node.js relies on undefined being omitted from JSON, .NET uses null-default records, and Java relies on the class-level @JsonInclude(NON_NULL) already present on CreateSessionRequest and ResumeSessionRequest.

Generated by SDK Consistency Review Agent for issue #1750 · sonnet46 1M ·

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants