Add in-process FFI transport for Rust and TypeScript SDKs#1915
Add in-process FFI transport for Rust and TypeScript SDKs#1915SteveSandersonMS wants to merge 33 commits into
Conversation
| id: setup-copilot | ||
|
|
||
| - name: Install Rust toolchain | ||
| uses: dtolnay/rust-toolchain@stable |
| with: | ||
| toolchain: "1.94.0" | ||
|
|
||
| - uses: Swatinem/rust-cache@v2 |
| fn bind<'lib, T>( | ||
| lib: &'lib Library, | ||
| symbol: &[u8], | ||
| library_path: &Path, |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Ports the C# in-process FFI hosting transport to the Rust and Node SDKs, mirroring the .NET `RuntimeConnection.ForInProcess()` API. Both load the runtime cdylib and speak JSON-RPC over its C ABI (host_start/connection_*) instead of spawning a stdio/TCP child; framing is unchanged (a transport swap over the existing LSP Content-Length codec). Rust: - `Transport::InProcess` variant + `COPILOT_SDK_DEFAULT_CONNECTION` default - `src/ffi.rs`: libloading C-ABI bindings, AsyncRead/AsyncWrite bridge, teardown - build.rs/embeddedcli extract+rename runtime.node -> libcopilot_runtime.* - in-process E2E test + CI matrix cell TypeScript: - `RuntimeConnection.forInProcess()` + `InProcessRuntimeConnection` - `src/ffiRuntimeHost.ts`: koffi loads runtime.node from node_modules directly - `COPILOT_SDK_DEFAULT_CONNECTION` default + in-process E2E test + CI matrix cell Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
9587efd to
259911f
Compare
This comment has been minimized.
This comment has been minimized.
Per-client options that lower to environment variables are not honored by the in-process transport, because the native runtime loads into the shared host process and its worker inherits that process's ambient environment. Match .NET: the in-process path no longer wires `env`, `telemetry`, `gitHubToken`, or `baseDirectory` into the FFI worker (previously it passed a partially-honored env block); the worker now inherits the ambient host environment. This is tracked as a shared cross-SDK gap in #1934 (workaround: set the variables on the host process environment). - client.ts: startInProcessFfi passes no per-client env; buildRuntimeEnv is now child-process (stdio/tcp) only. - types.ts: document + mark @experimental that in-process ignores env-lowered options; point to #1934. - E2E harness: for in-process, mirror the per-test redirects/home/credentials onto the real process env (Node process.env writes reach native getenv), route auth via GH_TOKEN/GITHUB_TOKEN, disable HMAC so host-side auth picks the Bearer token the replay snapshots expect, and restore the mutated entries afterwards. - Narrow the dedicated in-process E2E test to the single explicit ForInProcess smoke test, matching .NET; env-var resolution is covered by the inprocess CI matrix cell running the full suite. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Generated by SDK Consistency Review Agent for issue #1915 · sonnet46 2M
| * corresponding environment variables on the host process instead. See | ||
| * https://github.com/github/copilot-sdk/issues/1934. | ||
| */ | ||
| forInProcess(): InProcessRuntimeConnection { |
There was a problem hiding this comment.
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.
My previous commit mirrored the per-test environment onto process.env once at context construction. Since a test file contains several createSdkTestContext calls (each with its own CapiProxy on a distinct port) and process.env is a single global, the last-constructed context won — so every in-process worker inherited the wrong proxy URL and requests hit an unconfigured/torn-down proxy (ECONNREFUSED / "ReplayingCapiProxy not yet initialized"). That regressed the in-process cell from 9 to 32 failures. Mirror per-test instead: apply this context's environment in beforeEach (the client auto-starts on first use inside the test body, so the worker spawns under the right values) and restore in afterEach. vitest runs tests within a file serially and separate files in separate forks, so the global process.env is coherent for the running test. Also neutralize the ambient COPILOT_HMAC_KEY/CAPI_HMAC_KEY at module load (the analogue of .NET's InProcessEnvIsolation ModuleInitializer): in-process auth resolves host-side in this process and ranks HMAC above the GitHub token, and the key can be captured as early as client construction — before any beforeEach runs — so clearing it only per-test is too late. Gated to the in-process transport; stdio/tcp resolve auth in their own process where the token outranks HMAC. Verified locally with an ambient COPILOT_HMAC_KEY set: the previously-failing multi-context files (rpc, session, rpc_session_state) now pass in-process, and their embedded stdio/tcp sub-tests are unaffected. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Generated by SDK Consistency Review Agent for issue #1915 · sonnet46 2.4M
| * 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> { |
There was a problem hiding this comment.
Cross-SDK consistency note: The Rust SDK's build_ffi_environment (rust/src/lib.rs:1439) explicitly applies per-client options — github_token → COPILOT_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.
| }); | ||
| } | ||
|
|
||
| fn build_ffi_environment(options: &ClientOptions) -> Vec<(String, String)> { |
There was a problem hiding this comment.
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.
Four tests exercise behavior the in-process (FFI) transport cannot support, because the runtime loads into the shared host process. Skip them when COPILOT_SDK_DEFAULT_CONNECTION=inprocess (via a shared `isInProcessTransport` guard / it.skipIf); they remain covered by the default (stdio) cell. This matches .NET, which pins the equivalent tests to stdio. - telemetry file export: telemetry lowers to env vars the in-process worker can't receive per-client (#1934). - copilot_request_cancel_error "fires ctx.signal": a CopilotRequestHandler registers a process-wide LLM inference provider, so a second in-process client can't register while another holds it. - rpc_workspace_checkpoints unknown-checkpoint: readCheckpoint is answered by the native runtime in-process, which decodes the id as u32 and rejects the MAX_SAFE_INTEGER sentinel (runtime gap, #1934). - client.test.ts child-process-kill: no child process exists in-process. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This comment has been minimized.
This comment has been minimized.
Bring the Rust in-process transport in line with the .NET and TypeScript SDKs. Product: - Transport::InProcess passes no per-client env to the FFI worker; the worker inherits this host process's ambient environment. Per-client options that lower to env vars (env/env_remove, telemetry, github_token, base_directory) are not honored in-process, matching .NET/TS; documented as experimental, pointing at #1934. Removes the now-unused build_ffi_environment. E2E harness: - InProcessEnvGuard mirrors each test's environment onto the real process environment (which the in-process worker inherits) and restores it on drop, plus disables ambient HMAC so host-side auth picks the token the snapshots expect. Forces serial execution in-process (concurrency 1) so the process-wide env mutation is coherent; stdio/tcp are unchanged. - skip_inprocess guard skips tests for features the in-process transport can't support; applied to the telemetry file-export test (telemetry lowers to env vars) and the unknown-checkpoint test (readCheckpoint decodes the id as u32 natively in-process and rejects the i64::MAX sentinel). CI: - The in-process job now runs the whole E2E suite over the in-process transport (serially) instead of only the smoke test, mirroring the .NET/TS inprocess cell. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This comment has been minimized.
This comment has been minimized.
The in-process FFI host has no libuv handle of its own, so koffi delivers inbound (server→client) frames only when the event loop turns. The keep-alive timer used a 60s interval, which kept the loop from exiting but let it park between ticks. When the SDK issues a bare request and only awaits the response — e.g. the sequential `session.rpc.permissions.*` calls — there is no other loop activity, so on macOS the inbound response frame sat undelivered until the next tick and the round-trip stalled past the test timeout. Streaming turns kept the loop busy and so were unaffected, which is why only a handful of RPC-only tests hung and only on macOS. Shorten the pump interval so inbound frames are serviced within a few milliseconds. The empty callback is cheap and only runs while a connection is open. This is the koffi analogue of the .NET host's thread-safe Channel callback, which wakes its reader immediately regardless of loop state. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The `test` and `bundle` jobs are 3-OS matrices but their names omitted the OS,
so all three cells showed identical names. The in-process job named itself
"(in-process transport)" without the OS. Align all with the Node/.NET convention
"(<os>, <transport>)":
- test: "Rust SDK Tests (${{ matrix.os }}, default)"
- test-inprocess: "Rust SDK Tests (ubuntu-latest, inprocess)"
- bundle: "Rust SDK Bundled CLI Build (${{ matrix.os }})"
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This comment has been minimized.
This comment has been minimized.
The native outbound callback previously wrote inbound frames straight to the receive stream, which synchronously drives frame parsing and JSON-RPC dispatch — and can re-enter the SDK's write path — all while still on the native call stack. For most flows this worked, but the `session.rpc.permissions.*` round-trips hung on macOS (silent 30s timeouts), consistent with a re-entrant delivery stalling against an in-flight connection_write. Copy the frame bytes eagerly (the native pointer is only valid during the call) but defer delivery to the receive stream to a setImmediate drain on a clean stack. This keeps the callback minimal and non-reentrant — the koffi analogue of the .NET host's thread-safe Channel callback, which enqueues and returns immediately. Combined with the short keep-alive pump, queued frames are delivered within a few milliseconds. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
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>The in-process job was ubuntu-only, so the transport was never exercised on macOS. Make it a matrix over ubuntu-latest and macos-latest (excluding windows, matching dotnet-sdk-tests.yml, pending in-process SQLite file-locking on shutdown), and pass --test-threads=1 so libtest also runs serially (the harness mirrors each test's environment onto the shared process environment). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Root-cause fix for hung session.rpc.permissions.* round-trips on macOS: the SDK called connection_write synchronously, blocking the single JS thread inside the native call. koffi delivers inbound frames (the response, or a server→client request) on a secondary thread and queues them to run when the event loop gets a turn — but a synchronous write never yields, so when the runtime produced the response during the write, main and worker deadlocked. koffi's own docs warn about exactly this. Issue the write via koffi's async FFI call so the event loop stays free to service inbound callbacks; Node's Writable still serializes writes, preserving frame order. Also guard both Node-API callbacks (the native outbound callback and the async write completion) with try/catch that logs, since a throw across the FFI boundary is otherwise swallowed and only surfaces as a DEP0168 "Uncaught Node-API callback exception" warning. This both hardens teardown (a late inbound frame after dispose can no longer escape) and reveals the underlying error when it occurs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This comment has been minimized.
This comment has been minimized.
The in-process cell is green, but teardown emitted repeated DEP0168 "Uncaught Node-API callback exception" warnings. They come from inside koffi, not the SDK's callbacks (the guards added alongside never logged): koffi delivers the native outbound callback from a secondary thread by queuing it onto the event loop, and at teardown one such delivery can still be queued when dispose() calls koffi.unregister synchronously. Unregistering while a queued call is pending makes koffi invoke a freed callback and raise inside its own native code, which no JS try/catch can intercept. Defer the unregister to a setImmediate: the native connection and host are already closed by then, so no new deliveries originate, and a pending delivery fires in libuv's poll phase with the callback still valid (it no-ops since we're disposed) before the check-phase setImmediate frees the slot. The immediate is unref'd so it never keeps the process alive. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This comment has been minimized.
This comment has been minimized.
Adds env-gated (COPILOT_FFI_TRACE=1) stderr tracing of FFI frame writes, write completions, inbound arrivals, deliveries, and an event-loop heartbeat, with JSON-RPC id/method correlation. Enabled on the Node inprocess CI cell to capture exactly where the macOS session.rpc.permissions.* round-trips stall (write vs inbound delivery vs loop parking). To be reverted once the root cause is fixed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This comment has been minimized.
This comment has been minimized.
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>
This comment has been minimized.
This comment has been minimized.
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>
This comment has been minimized.
This comment has been minimized.
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>
This comment has been minimized.
This comment has been minimized.
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>
This comment has been minimized.
This comment has been minimized.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This comment has been minimized.
This comment has been minimized.
…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>
This comment has been minimized.
This comment has been minimized.
…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>
This comment has been minimized.
This comment has been minimized.
…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>
This comment has been minimized.
This comment has been minimized.
…quest causation Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This comment has been minimized.
This comment has been minimized.
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>
This comment has been minimized.
This comment has been minimized.
…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>
Cross-SDK Consistency Review ✅ (Node + Rust) / ⏳ (Python, Go, Java)This PR correctly ports the in-process FFI transport from .NET (#1901) to TypeScript/Node and Rust. The API shapes are well-aligned across those three SDKs:
What Python would need# New dataclass
`@dataclass`
class InProcessRuntimeConnection(RuntimeConnection):
"""Hosts the runtime in-process via FFI. Construct via RuntimeConnection.for_in_process()."""
# New factory on RuntimeConnection
`@staticmethod`
def for_in_process() -> InProcessRuntimeConnection:
return InProcessRuntimeConnection()Plus exporting What Go would need// InProcessConnection hosts the runtime in-process via FFI.
type InProcessConnection struct{}
func (InProcessConnection) runtimeConnection() {}Plus a What Java would needA new This PR is clearly scoped to Node + Rust — the gaps above are not blockers for merging. Flagging them here so follow-up issues/PRs can be tracked. The naming and env-var conventions are consistent with .NET, which is the right call.
|
…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>
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 LSPContent-LengthJSON-RPC codec.Rust
Transport::InProcessvariant;COPILOT_SDK_DEFAULT_CONNECTION(inprocess/stdio/unset) selects the default, mirroring .NET'sResolveDefaultConnection.rust/src/ffi.rs:libloadingC-ABI bindings,AsyncRead/AsyncWritebridge between the outbound callback andconnection_write, ordered teardown.build.rs/embeddedcli.rs: best-effort extract + rename ofprebuilds/<platform>/runtime.node→libcopilot_runtime.{so,dylib}/copilot_runtime.dllnext to the CLI (mirrors the .NET.targets).prebuilds/<node-platform>-<arch>/runtime.node.test-inprocessCI job.TypeScript
RuntimeConnection.forInProcess()+InProcessRuntimeConnection.nodejs/src/ffiRuntimeHost.ts:koffiloadsruntime.nodedirectly from the@github/copilot-<platform>package (no rename); asynchost_start, registered outbound callback bridged tovscode-jsonrpcstreams.COPILOT_SDK_DEFAULT_CONNECTIONdefault + in-process E2E test +transportCI matrix cell.CI
Both workflows gain an
inprocesstransport cell that setsCOPILOT_SDK_DEFAULT_CONNECTION=inprocess, matchingdotnet-sdk-tests.yml.Status / known gaps (draft — letting CI surface these)
client.e2e(incl. auth + list-models) passes fully in-process.mcp_and_agents): the in-process host addon runs in the SDK's own process and readsprocess.env, so the harness-supplied proxy-redirect env (passed asoptions.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.COPILOT_CLI_PATH) path is untested locally because the GitHub-release CLI tarball doesn't yet shipruntime.node; CI uses the npm platform package viaCOPILOT_CLI_PATH.🤖 Generated with in-process FFI port; opening as draft to let CI run the full matrix.