Skip to content

Add in-process FFI transport for Rust and TypeScript SDKs#1915

Draft
SteveSandersonMS wants to merge 41 commits into
mainfrom
stevesa/ffi-inproc-rust-ts
Draft

Add in-process FFI transport for Rust and TypeScript SDKs#1915
SteveSandersonMS wants to merge 41 commits into
mainfrom
stevesa/ffi-inproc-rust-ts

Conversation

@SteveSandersonMS

Copy link
Copy Markdown
Contributor

Summary

Ports the C# in-process FFI hosting transport (merged in #1901) to the Rust and TypeScript/Node SDKs, mirroring the .NET RuntimeConnection.ForInProcess() API. Both SDKs load the runtime cdylib and speak JSON-RPC over its C ABI (copilot_runtime_host_start / connection_open / connection_write / connection_close / host_shutdown) instead of spawning a stdio/TCP child process. Framing is unchanged — it's a transport swap over the existing LSP Content-Length JSON-RPC codec.

Rust

  • New Transport::InProcess variant; COPILOT_SDK_DEFAULT_CONNECTION (inprocess/stdio/unset) selects the default, mirroring .NET's ResolveDefaultConnection.
  • rust/src/ffi.rs: libloading C-ABI bindings, AsyncRead/AsyncWrite bridge between the outbound callback and connection_write, ordered teardown.
  • build.rs / embeddedcli.rs: best-effort extract + rename of prebuilds/<platform>/runtime.nodelibcopilot_runtime.{so,dylib} / copilot_runtime.dll next to the CLI (mirrors the .NET .targets).
  • Discovery: flat natural lib name, else prebuilds/<node-platform>-<arch>/runtime.node.
  • In-process E2E test + test-inprocess CI job.

TypeScript

  • RuntimeConnection.forInProcess() + InProcessRuntimeConnection.
  • nodejs/src/ffiRuntimeHost.ts: koffi loads runtime.node directly from the @github/copilot-<platform> package (no rename); async host_start, registered outbound callback bridged to vscode-jsonrpc streams.
  • COPILOT_SDK_DEFAULT_CONNECTION default + in-process E2E test + transport CI matrix cell.

CI

Both workflows gain an inprocess transport cell that sets COPILOT_SDK_DEFAULT_CONNECTION=inprocess, matching dotnet-sdk-tests.yml.

Status / known gaps (draft — letting CI surface these)

  • Dedicated in-process E2E tests pass locally for both SDKs; Node client.e2e (incl. auth + list-models) passes fully in-process.
  • Node MCP tests time out in-process (mcp_and_agents): the in-process host addon runs in the SDK's own process and reads process.env, so the harness-supplied proxy-redirect env (passed as options.env) doesn't reach MCP-related host-side paths the way it does for a spawned stdio child. Needs follow-up on env propagation for the in-process worker/host.
  • Rust default (bundled, no COPILOT_CLI_PATH) path is untested locally because the GitHub-release CLI tarball doesn't yet ship runtime.node; CI uses the npm platform package via COPILOT_CLI_PATH.

🤖 Generated with in-process FFI port; opening as draft to let CI run the full matrix.

id: setup-copilot

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
toolchain: "1.94.0"

- uses: Swatinem/rust-cache@v2
Comment thread rust/src/ffi.rs
fn bind<'lib, T>(
lib: &'lib Library,
symbol: &[u8],
library_path: &Path,
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@SteveSandersonMS SteveSandersonMS force-pushed the stevesa/ffi-inproc-rust-ts branch from 9587efd to 259911f Compare July 7, 2026 13:49
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generated by SDK Consistency Review Agent for issue #1915 · sonnet46 2M

Comment thread nodejs/src/types.ts
* corresponding environment variables on the host process instead. See
* https://github.com/github/copilot-sdk/issues/1934.
*/
forInProcess(): InProcessRuntimeConnection {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cross-SDK consistency note — option validation approach differs from .NET

The .NET SDK (added in #1901) throws ArgumentException at construction time if Environment or Telemetry options are set alongside ForInProcess():

// dotnet/src/Client.cs
if (options.Environment is not null)
    throw new ArgumentException("...per-client values. Set the variables on the host process environment instead.");
if (options.Telemetry is not null)
    throw new ArgumentException("...Configure telemetry via the host process environment...");

TypeScript currently documents the limitation in the @experimental tag but silently ignores env, telemetry, gitHubToken, and baseDirectory at runtime without any error or warning. This means a caller who sets gitHubToken and then switches to in-process transport will see silent auth failures rather than a clear error.

Worth considering whether to match .NET's fail-fast approach (throw during CopilotClient construction when incompatible options are detected) so the SDK surface is consistent across languages. Rust takes a third path: it explicitly builds the env and passes it to host_start via the C ABI, so those options are actually honored there.

@github-actions

This comment has been minimized.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generated by SDK Consistency Review Agent for issue #1915 · sonnet46 2.4M

Comment thread nodejs/src/client.ts
* values. Configure the in-process runtime via the host process environment instead.
* See https://github.com/github/copilot-sdk/issues/1934.
*/
private async startInProcessFfi(): Promise<void> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cross-SDK consistency note: The Rust SDK's build_ffi_environment (rust/src/lib.rs:1439) explicitly applies per-client options — github_tokenCOPILOT_SDK_AUTH_TOKEN, telemetry, base_directory, custom env — to the in-process transport. This method silently ignores those same options, inheriting only the host process's ambient environment.\n\nThis creates a cross-SDK behavioral difference: a user who sets gitHubToken: 'ghp_...' will have auth work automatically in the Rust SDK but will need to manually set COPILOT_SDK_AUTH_TOKEN in the process environment for the TypeScript SDK.\n\nFor short-term alignment with .NET's approach, consider adding validation that throws when options that can't be honored are set in combination with forInProcess(), e.g.:\nts\nif (this.options.gitHubToken) {\n throw new Error(\n 'CopilotClientOptions.gitHubToken is not currently supported with the in-process transport. ' +\n 'Set COPILOT_SDK_AUTH_TOKEN on the host process environment instead. ' +\n 'See https://github.com/github/copilot-sdk/issues/1934.'\n );\n}\n\nThis avoids silent misconfiguration until #1934 is resolved.

Comment thread rust/src/lib.rs Outdated
});
}

fn build_ffi_environment(options: &ClientOptions) -> Vec<(String, String)> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cross-SDK consistency note: This Rust build_ffi_environment correctly applies per-client options (github_token, telemetry, base_directory, custom env) to the in-process transport, which is more user-friendly than .NET's approach (which rejects Environment/Telemetry with ArgumentException and silently ignores GitHubToken).\n\nHowever, the TypeScript SDK in this same PR takes a third approach: it silently ignores all per-client options for in-process transport (tracked in #1934). It would be worth adding a comment here or in the Rust client docs noting that the Rust in-process transport honors per-client options, since this is a divergence from the TypeScript behavior in this PR.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generated by SDK Consistency Review Agent for issue #1915 · sonnet46 2M

Comments that could not be inline-anchored

nodejs/src/client.ts:639

Cross-SDK consistency: missing eager validation for inprocess + incompatible options

The .NET SDK (reference implementation since #1901) throws an ArgumentException in the constructor when Environment or Telemetry options are set alongside InProcessRuntimeConnection — see Client.cs ValidateEnvironmentOptions(). The new TypeScript implementation only documents the limitation in JSDoc but silently ignores those options, which can be confusing.

Suggest adding a "fail fast" chec…

rust/src/lib.rs:1012

Cross-SDK consistency: missing eager validation for Transport::InProcess + incompatible options

The .NET SDK (reference since #1901) rejects incompatible options early with ArgumentException. The TypeScript implementation (also added in this PR) has the same gap. For Rust, suggest adding a validation block after the External checks, mirroring the pattern here:

if matches!(options.transport, Transport::InProcess) {
    if options.github_token.is_some() {
        return Err(Er…

</details>

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

SteveSandersonMS and others added 27 commits July 9, 2026 14:54
Isolates the in-process FFI hang hypothesis with no SDK, runtime cdylib, or
JSON-RPC: a tiny C shared library (compiled on the fly with cc/clang) spawns a
background thread that invokes a koffi-registered callback after the main event
loop goes idle (only a keep-alive timer), mirroring how the runtime's
worker-reader thread invokes our outbound callback while the SDK awaits a
response. Runs on ubuntu + macOS via a dedicated fast CI job to get a clean
cross-platform signal. To be removed once the hang is fixed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Trace analysis showed inbound frame delivery stops mid-stream on macOS (a
streaming turn's events flow then halt), not just on idle bare RPCs. The async
(libuv-threadpool) write variant competes with koffi's blocking inbound callback
relay, so revert connection_write to a synchronous call matching the .NET host
and drop the async broker pump. Also add a repro scenario that blasts a rapid
burst of background-thread callbacks including a 23638-byte payload (the size
that preceded the stall) to test whether koffi drops frames under burst on macOS.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds the one untested koffi interaction matching real in-process usage: the
inbound callback (blocking the reader thread via napi_tsfn_blocking) synchronously
re-enters native (like connection_write from inside feedInbound). If this passes on
macOS, koffi is fully exonerated and the stall is runtime/worker-side.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
koffi 3.0 rewrote call preparation/execution ("vastly improved performance") and
distributes prebuilt binaries as @koromix/koffi-<platform> optionalDependency
subpackages (so `npm ci --ignore-scripts` still resolves the native binary).
Testing whether the newer callback/threading machinery fixes the macOS/Windows
in-process RPC stalls. Our koffi API surface (load/func/pointer/proto/register/
unregister/decode/array) is unchanged across the major bump.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Remove the COPILOT_FFI_TRACE frame-level instrumentation from FfiRuntimeHost
(writes/inbound/heartbeat and the log_dropped_count binding) now that the FFI
transport is proven to deliver every frame. Restore the plain 1s keep-alive and
synchronous writes.

Add COPILOT_EVENT_TRACE session-event tracing at the client's
session.event / session.lifecycle notification handlers, logging each dispatched
event's type. This pinpoints whether the in-process macOS stall is a missing
session.idle (runtime never emits it) versus an emitted-but-not-dispatched event,
for the approve-all-short-circuit + shell-tool turn that wedges sendAndWait.
Enabled on the Node inprocess CI cell.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Log the full sessionId (not truncated to 8 chars, which was ambiguous across
sessions sharing a prefix) and add a trace line inside sendAndWait's event
listener, to conclusively determine whether the in-process macOS hang is caused
by session.idle events arriving for an unregistered session ("no-session" drop)
vs sendAndWait's own listener not receiving them.

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

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

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…st (match .NET)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…expose bug)

Make in-process connection_write async so the JS main thread is never blocked in
a synchronous write while koffi holds the cdylib reader thread in a blocking
inbound callback (bidirectional deadlock). Reverts the noresult test's abort so
the failing disconnect-while-pending pattern is exercised against the transport fix.

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

Instrument feedInbound entry/exit and a keepalive heartbeat, plus log the parked
requestId when a no-result permission handler skips the reply, to determine at the
macOS/Windows in-process wedge whether koffi stops invoking the inbound callback
(worker/cdylib side) or invokes it but stalls inside (JS side).

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

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Root cause of the macOS/Windows-only in-process stall: koffi services its
threadsafe-function broker (which marshals foreign-thread outbound callbacks to
the JS main thread) ONLY while a koffi async FFI call is in flight; a plain JS
timer does not pump it. During an idle stretch mid-turn (worker awaiting a model
HTTP response, no client->server writes), the next outbound frame — the model
response and everything after — sits undelivered until the next koffi call,
wedging the session for 30s. Linux's libuv services the broker differently, so it
only reproduced off-Linux.

Keep exactly one cheap async FFI call (log_dropped_count) continuously in flight
so the broker always pumps; the in-flight libuv request also keeps the event loop
alive, replacing the keep-alive timer. Restores the no-result permission test to
its real disconnect-while-pending pattern (no longer needs to reply to pass).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…3-core)

Test whether the in-process wedge is CPU-count dependent (tokio in the runtime
cdylib uses Builder::new_multi_thread() = one worker per core; macos-latest is
3-core M1). macos-latest-xlarge is the same Apple-Silicon arch with 6 cores, so a
pass would isolate core count from OS/arch as the trigger. Only the runner changes
vs the last failing run.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…rker-count theory

Reverts mac runner to macos-latest. If Linux under taskset -c 0 (=> tokio default
worker_threads=1 via available_parallelism, which honors CPU affinity on Linux)
reproduces the in-process wedge, the trigger is tokio worker-thread count, not
OS/arch. Linux runs are fast and reliable, making this a cleaner test than swapping
mac runner sizes.

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

Linux on 1 CPU reproduced the in-process wedge (tokio worker_threads=1), confirming
the trigger is tokio worker-thread count. Test 2 CPUs to locate the threshold at
which it stops wedging.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…tore full in-proc CI

1.0.70-0 is expected to fix the in-process runtime worker-thread starvation that
wedged low-core (macOS/Windows) hosts. Locally the full permissions E2E file now
passes pinned to a single CPU (taskset -c 0) with the real no-result+disconnect
pattern (previously 4 failures). Restore the inprocess CI cell to run the full
suite on all OSes (removed the single-file/taskset/tracing diagnostic scaffolding)
so macOS/Windows in-proc get a real validation against 1.0.70-0.

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

forceStop() skips the runtime.shutdown() RPC, so the in-process runtime's
SQLite session-store.db handle is never closed. On Windows the locked file
fails temp-dir cleanup (EBUSY), exhausting rmDir's 30x1s retry budget and
tripping the 30s afterAll hook timeout. Graceful stop() closes the DB.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Fixes a Windows STATUS_ACCESS_VIOLATION (0xc0000005) crash in the Rust e2e
binary: FfiShared::Drop unloaded the cdylib (FreeLibrary/dlclose) when a
connection closed, racing the runtime's still-live worker threads — a late
worker-thread callback into the unmapped module faults on Windows. The crash hit
both Rust transport cells because the in-process smoke test runs regardless of
transport.

Load each cdylib once into a process-global cache and leak it (Box::leak) so its
code stays mapped for the process lifetime, matching the Node host (module-global
load, never unloaded) and the runtime's never-shutdown process-global tokio
runtime. close() still shuts the host down; only the library unload is removed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The in-process FFI host never set the runtime worker's current directory, so it
inherited the SDK process's cwd instead of the client's working_directory. Unlike
the stdio/tcp transports (build_command sets .current_dir(working_directory)),
workspace-relative file operations therefore resolved against the wrong directory:
the model's tool calls used a relative path (e.g. 'order.txt') that the runtime
rejected as 'not absolute', diverging from the recorded replay snapshot and
surfacing as a proxy 500 / send_and_wait failure — but only in-process.

Mirror the Node in-process host: switch cwd to working_directory for the duration
of the blocking host_start (which spawns the worker), then restore it. Confirmed
locally: the affected event_fidelity + hooks tests pass in-process, and the test
already passed over stdio.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Merge std::sync imports onto one line per .rustfmt.nightly.toml
(imports_granularity=Module); fixes the ubuntu 'cargo fmt --check (nightly)' step.

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

In-process, the SDK github_token (lowered to --auth-token-env COPILOT_SDK_AUTH_TOKEN
for the spawned child) is not passed to the worker, so host-side auth resolves from
GH_TOKEN/GITHUB_TOKEN. The InProcessEnvGuard set these to a placeholder the replay
mock never registered, so auth resolved as unauthenticated (client::should_get_
authenticated_status / should_list_models_when_authenticated) or hit real GitHub.
Use DEFAULT_TEST_TOKEN — the token set_default_copilot_user registers as the
authenticated Copilot user — so in-process auth matches the mock. Fixes the two
client auth tests and callback_token_is_applied_as_authorization_header in-process.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The runtime registers the LLM inference provider per connection and, by design,
never releases the slot on disconnect (runtime shared_api/llm_inference.rs). Over
the in-process transport every client shares this process's runtime, so a second
provider-registering client is refused ('Another client is already the LLM
inference provider') — deterministically failing reacquires_a_fresh_token_for_each_
request and dispatches_token_acquisition_per_provider when they run after the first
BYOK test. stdio spawns a separate child per test, so all three run there.

The BYOK bearer-token path over the in-process transport is validated by
callback_token_is_applied_as_authorization_header (still runs in-process); the
per-request / per-provider dispatch these two exercise is transport-agnostic and
fully covered over stdio. Guard them with skip_inprocess, matching the branch's
pattern for genuine in-process runtime limitations.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… gate in-process

Two in-process-only E2E fixes:

1. BYOK provider poisoning (the big cascade): the runtime's LLM inference provider
   slot is process-global and is never released when the registering connection
   disconnects (runtime shared_api/llm_inference.rs). Over the in-process transport
   all clients share one runtime, so once callback_token_is_applied registers a BYOK
   provider and its client stops, the dangling registration routes every later
   model-inference request to the dead connection and hangs it (180s timeouts). This
   is what wedged should_list_models_when_authenticated and cascaded into
   event_fidelity/hooks/mode_*/etc. Skip all three BYOK provider tests in-process
   (they run over stdio, one child process per test); locally this clears the cascade.

2. provider_endpoint: the test opts into session.provider.getEndpoint via per-client
   options.env (COPILOT_ALLOW_GET_PROVIDER_ENDPOINT), which the in-process transport
   does not pass to the shared worker. Apply that process-global runtime gate via the
   InProcessEnvGuard (inert for tests that don't call the API).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@SteveSandersonMS SteveSandersonMS force-pushed the stevesa/ffi-inproc-rust-ts branch from 6b989d4 to b1576e0 Compare July 9, 2026 14:55
@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Cross-SDK Consistency Review

This PR ports the in-process FFI transport from .NET (#1901) to TypeScript and Rust. Here's how the implementations compare.

✅ API naming — consistent across the three SDKs

SDK Transport selection Factory / variant
.NET RuntimeConnection.ForInProcess() InProcessRuntimeConnection class
TypeScript RuntimeConnection.forInProcess() InProcessRuntimeConnection interface
Rust Transport::InProcess enum variant

COPILOT_SDK_DEFAULT_CONNECTION env-var override is implemented consistently in all three SDKs with the same accepted values (inprocess / stdio / unset) and the same error on any other value. ✅

⚠️ One consistency gap: missing fail-fast validation for per-client options

The .NET SDK throws ArgumentException at construction time if Environment or Telemetry are set alongside RuntimeConnection.ForInProcess() (see ValidateEnvironmentOptions in Client.cs). This gives callers an early, actionable error rather than silently ignoring the config.

Neither the TypeScript nor the Rust SDK currently validate this. A caller who passes gitHubToken / telemetry / env with the in-process transport will see those values silently dropped, which can be hard to debug (especially gitHubToken — auth appearing not to work with no error message).

I've left inline suggestions on the specific lines in nodejs/src/client.ts and rust/src/lib.rs where this validation could be added to match .NET behaviour.

i️ Python, Go, Java — expected gap

None of the three remaining SDKs implement in-process FFI transport yet, and none handle COPILOT_SDK_DEFAULT_CONNECTION. This is an expected gap for a multi-PR rollout (no action needed in this PR), but worth tracking so it doesn't stay inconsistent long-term once the feature graduates from experimental.

Generated by SDK Consistency Review Agent for issue #1915 · sonnet46 2M ·

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generated by SDK Consistency Review Agent for issue #1915 · sonnet46 2M

Comment thread nodejs/src/client.ts
CopilotClient.resolveDefaultConnection(options);

if (
conn.kind === "uri" &&

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cross-SDK consistency gap: missing validation for per-client options with inprocess transport

The .NET SDK (added in #1901) throws ArgumentException at construction time when Environment or Telemetry options are set alongside RuntimeConnection.ForInProcess() (Client.cs ValidateEnvironmentOptions). This gives callers an early, actionable error rather than silently ignoring the configuration.

The TypeScript SDK documents the limitation well in the JSDoc, but doesn't fail fast. A caller who passes gitHubToken, telemetry, baseDirectory, or env with { kind: "inprocess" } will see those values silently dropped.

To mirror .NET, consider adding a guard in the constructor alongside the existing uri validation above:

if (conn.kind === "inprocess") {
    if (options.gitHubToken !== undefined) {
        throw new Error(
            "gitHubToken is not supported with RuntimeConnection.forInProcess(): the in-process " +
            "transport inherits the host process environment. Set COPILOT_SDK_AUTH_TOKEN on the " +
            "host process instead."
        );
    }
    if (options.telemetry !== undefined) {
        throw new Error(
            "telemetry is not supported with RuntimeConnection.forInProcess(): telemetry config " +
            "is lowered to environment variables that cannot vary per-client in-process."
        );
    }
    // optionally: env, baseDirectory
}

This is especially important for gitHubToken — a caller who sets it expecting authentication to work will get a confusing silent failure without this guard.

Comment thread rust/src/lib.rs
}
// Auth options only make sense when the SDK spawns the CLI; with an
// external server, the server manages its own auth.
if matches!(options.transport, Transport::External { .. }) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cross-SDK consistency gap: missing validation for per-client options with Transport::InProcess

The .NET SDK throws ArgumentException when Environment or Telemetry are passed with RuntimeConnection.ForInProcess() (see ValidateEnvironmentOptions in Client.cs). Rust documents the limitation in InProcess's docstring but doesn't return an early Err.

Consider adding a validation block here analogous to the Transport::External check just above:

if matches!(options.transport, Transport::InProcess) {
    if options.github_token.is_some() {
        return Err(Error::with_message(
            ErrorKind::InvalidConfig,
            "github_token cannot be used with Transport::InProcess: the in-process \
             runtime inherits the host process environment. Set COPILOT_SDK_AUTH_TOKEN \
             on the host process instead.",
        ));
    }
    if options.telemetry.is_some() {
        return Err(Error::with_message(
            ErrorKind::InvalidConfig,
            "telemetry cannot be used with Transport::InProcess: telemetry config is \
             lowered to environment variables that cannot vary per-client in-process.",
        ));
    }
    // optionally: env, env_remove, base_directory
}

Without this, callers who set github_token expecting authentication will get a silent failure rather than an actionable error message.

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.

2 participants