Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
8726e39
Add LLM inference callback support to Node SDK
Jun 15, 2026
e905012
feat: register llm inference handler globally on the SDK client
SteveSandersonMS Jun 15, 2026
0651631
test: assert /models catalog request now intercepts in callback
SteveSandersonMS Jun 15, 2026
38b8d35
feat: streaming LLM inference callback (httpStreamStart + e2e)
SteveSandersonMS Jun 15, 2026
1999811
test: e2e for LLM inference callback error mapping
SteveSandersonMS Jun 15, 2026
3dc3a55
refactor(llm-callback): drop inferred request metadata field
SteveSandersonMS Jun 15, 2026
ba4f25a
feat(llm-callback): collapse to a single onLlmRequest with chunked body
SteveSandersonMS Jun 15, 2026
441c684
feat(llm-callback): surface req.signal and propagate cancellation
SteveSandersonMS Jun 16, 2026
90792fe
test(llm-callback): cover consumer-initiated cancellation
SteveSandersonMS Jun 16, 2026
8e7011d
Add WebSocket transport to the LLM inference provider
SteveSandersonMS Jun 16, 2026
53f00cc
Add LlmRequestHandler base class for SDK consumers
SteveSandersonMS Jun 16, 2026
b21e42b
Harden LLM inference SDK adapter + WS handler; add unit tests
SteveSandersonMS Jun 16, 2026
f812ac2
Add SDK e2e asserting sessionId reaches the LLM callback (CAPI + BYOK)
SteveSandersonMS Jun 16, 2026
65df1a1
Port LLM inference callbacks to the .NET SDK
SteveSandersonMS Jun 16, 2026
2eb2754
Collapse LLM inference callback public API to LlmRequestHandler
SteveSandersonMS Jun 16, 2026
815bbd0
Refine LLM inference callback handlers
SteveSandersonMS Jun 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions dotnet/src/Client.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,13 @@ public sealed partial class CopilotClient : IDisposable, IAsyncDisposable
private List<ModelInfo>? _modelsCache;
private ServerRpc? _serverRpc;

/// <summary>
/// Client-global RPC handlers (e.g. the LLM inference provider adapter),
/// built once at construction when the corresponding option is configured and
/// registered on every connection. Null when no client-global API is enabled.
/// </summary>
private readonly ClientGlobalApiHandlers? _clientGlobalApis;

private sealed record LifecycleSubscription(Type EventType, Action<SessionLifecycleEvent> Handler);

/// <summary>
Expand Down Expand Up @@ -165,6 +172,8 @@ public CopilotClient(CopilotClientOptions? options = null)
_logger = _options.Logger ?? NullLogger.Instance;
_onListModels = _options.OnListModels;

_clientGlobalApis = BuildClientGlobalApis();

// Empty mode: validate at construction time that the app supplied a
// per-session persistence location. The runtime is mode-agnostic, so
// without this check it would silently fall back to ~/.copilot, which
Expand Down Expand Up @@ -276,6 +285,8 @@ async Task<Connection> StartCoreAsync(CancellationToken ct)
sessionFsTimestamp);
}

await ConfigureLlmInferenceAsync(ct);

LoggingHelpers.LogTiming(_logger, LogLevel.Debug, null,
"CopilotClient.StartAsync complete. Elapsed={Elapsed}",
startTimestamp);
Expand Down Expand Up @@ -1674,6 +1685,39 @@ await Rpc.SessionFs.SetProviderAsync(
cancellationToken: cancellationToken);
}

/// <summary>
/// Builds the client-global RPC handler bag at construction time. Currently
/// only the LLM inference provider adapter is registered; returns null when no
/// client-global API is configured so the registration is skipped entirely.
/// </summary>
private ClientGlobalApiHandlers? BuildClientGlobalApis()
{
var handler = _options.LlmInference?.Handler;
if (handler is null)
{
return null;
}

return new ClientGlobalApiHandlers
{
LlmInference = new LlmInferenceAdapter(handler, () => _serverRpc),
};
}

/// <summary>
/// Tells the runtime to route its outbound model-layer requests through this
/// client's LLM inference provider. No-op when interception is not configured.
/// </summary>
private async Task ConfigureLlmInferenceAsync(CancellationToken cancellationToken)
{
if (_clientGlobalApis?.LlmInference is null)
{
return;
}

await Rpc.LlmInference.SetProviderAsync(cancellationToken);
}

private void ConfigureSessionFsHandlers(CopilotSession session, Func<CopilotSession, SessionFsProvider>? createSessionFsHandler)
{
if (_options.SessionFs is null)
Expand Down Expand Up @@ -2067,6 +2111,10 @@ private async Task<Connection> ConnectToServerAsync(Process? cliProcess, string?
var session = GetSession(sessionId) ?? throw new ArgumentException($"Unknown session {sessionId}");
return session.ClientSessionApis;
});
if (_clientGlobalApis is not null)
{
ClientGlobalApiRegistration.RegisterClientGlobalApiHandlers(rpc, _clientGlobalApis);
}
rpc.StartListening();
LoggingHelpers.LogTiming(_logger, LogLevel.Debug, null,
"CopilotClient.ConnectToServerAsync transport setup complete. Elapsed={Elapsed}",
Expand Down
335 changes: 335 additions & 0 deletions dotnet/src/Generated/Rpc.cs

Large diffs are not rendered by default.

15 changes: 15 additions & 0 deletions dotnet/src/Generated/SessionEvents.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions dotnet/src/GitHub.Copilot.SDK.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@
<NoWarn>$(NoWarn);GHCP001</NoWarn>
</PropertyGroup>

<ItemGroup>
<InternalsVisibleTo Include="GitHub.Copilot.SDK.Test" />
</ItemGroup>

<PropertyGroup Condition="'$(CI)' == 'true' or '$(TF_BUILD)' == 'true'">
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
</PropertyGroup>
Expand Down
Loading