Skip to content

Add getBearerToken callback for BYOK providers (Managed Identity v1)#1746

Draft
SteveSandersonMS wants to merge 1 commit into
mainfrom
stevesandersonms/byok-bearer-token-provider
Draft

Add getBearerToken callback for BYOK providers (Managed Identity v1)#1746
SteveSandersonMS wants to merge 1 commit into
mainfrom
stevesandersonms/byok-bearer-token-provider

Conversation

@SteveSandersonMS

Copy link
Copy Markdown
Contributor

Summary

Adds SDK-side bearer-token resolution for BYOK providers — the v1 design for Azure Managed Identity support. Consumers supply a per-provider getBearerToken callback (e.g. wrapping @azure/identity's DefaultAzureCredential / ManagedIdentityCredential). The Copilot SDK takes zero Azure dependency, and the Rust runtime stays token-agnostic: it asks for a token before each request, uses it as the Authorization header, and caches/refreshes automatically.

Pairs with the runtime half: github/copilot-agent-runtime#10933.

Public surface

getBearerToken?: (args: { providerName, scope }) => Promise<{ token, expiresOnTimestamp } | string> plus bearerTokenScope? on ProviderConfig and NamedProviderConfig. Return the rich object so the runtime refreshes only near expiry; a bare string is also accepted (re-fetched every request).

How it works

The callback isn't serializable over JSON-RPC, so the SDK strips it and sends a bearerTokenProvider: true wire flag (same pattern as hooks / userInput). The runtime synthesizes a getBearerToken that dispatches back over the session-scoped providerToken.acquire RPC; the SDK routes each acquisition to the matching per-provider callback by provider name, so multi-provider sessions stay correctly partitioned.

The bearer-token surface is provider-agnosticbearerTokenScope is forwarded verbatim (empty when unset), with no baked-in default scope.

Changes

  • types.ts — public ProviderTokenArgs / ProviderBearerToken / GetBearerToken types; getBearerToken / bearerTokenScope on the provider configs.
  • generated/rpc.ts — wire fields, providerToken.acquire DTOs + handler, registration.
  • session.ts — per-provider bearerTokenProviders registry; routes providerToken.acquire by provider name.
  • client.ts — extracts callbacks, registers them, and emits the wire flag in create/resume payloads.

Tests

New e2e suite (byok_bearer_token_provider.e2e.test.ts) over the real RPC transport + replay proxy:

  1. callback token reaches the model as Authorization: Bearer <token>;
  2. token is re-acquired when the previous one has expired;
  3. per-provider dispatch routes each turn to its own callback.

All three pass in replay mode.

Supersedes the earlier declarative-config approach (#1745).

Adds a per-provider getBearerToken callback on ProviderConfig / NamedProviderConfig so SDK consumers can resolve bearer tokens (e.g. via @azure/identity) on the client side. The SDK strips the non-serializable function, sends a bearerTokenProvider wire flag, and answers the runtime's session-scoped providerToken.acquire RPC by dispatching to the matching per-provider callback. The token surface is provider-agnostic: bearerTokenScope is forwarded verbatim with no default.

Includes e2e coverage (callback token reaches the model as the Authorization header, token refresh on expiry, and per-provider dispatch) with hand-authored replay snapshots.

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

Copy link
Copy Markdown
Contributor

Cross-SDK Consistency Review

This PR adds the getBearerToken callback mechanism and bearerTokenScope field to Node.js only. Here's a summary of the current state across SDKs:

SDK Static bearerToken string getBearerToken callback bearerTokenScope providerToken.acquire RPC
Node.js (this PR) ✅ (new) ✅ (new) ✅ (new)
Python ✅ (bearer_token)
Go ✅ (BearerToken)
.NET ✅ (BearerToken)
Java ✅ (bearerToken)
Rust ✅ (bearer_token)

Observation

This PR deliberately introduces the feature in Node.js first as a "v1" proof-of-concept (the description calls it "Managed Identity v1" and notes it's @experimental). That's a perfectly reasonable approach for an experimental surface — land and validate the design in one SDK before propagating.

Suggestion

Since Managed Identity is a cross-platform need (Azure workloads run on all stacks), the equivalent should eventually reach Python, Go, .NET, Java, and Rust. For each other SDK the work involves:

  • Adding get_bearer_token / GetBearerToken / Func<ProviderTokenArgs, Task<ProviderBearerToken>> callback + bearer_token_scope field on ProviderConfig / NamedProviderConfig
  • Registering the providerToken.acquire session-level RPC handler
  • Stripping the callback before serialization and sending the bearerTokenProvider: true wire flag instead

Consider filing follow-up tracking issues for each remaining SDK so this doesn't get lost — or add a note in the PR description pointing to a tracking item.

Generated by SDK Consistency Review Agent for issue #1746 · sonnet46 1.2M ·

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.

1 participant