Skip to content

Commit fbeb45f

Browse files
Complete Rust in-process transport support
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: da0a9335-969e-4a77-838a-daac9206a454
1 parent cc686bb commit fbeb45f

22 files changed

Lines changed: 889 additions & 667 deletions

.github/workflows/rust-sdk-tests.yml

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ jobs:
9898
if: runner.os == 'Linux'
9999
env:
100100
BUNDLED_CLI_CACHE_DIR: ${{ github.workspace }}/rust/.bundled-cli-cache
101-
run: cargo clippy --all-targets --features test-support -- --no-deps -D warnings -D clippy::unwrap_used -D clippy::disallowed_macros -D clippy::await_holding_invalid_type
101+
run: cargo clippy --all-targets --features test-support,bundled-in-process -- --no-deps -D warnings -D clippy::unwrap_used -D clippy::disallowed_macros -D clippy::await_holding_invalid_type
102102

103103
- name: cargo doc
104104
if: runner.os == 'Linux'
@@ -205,7 +205,7 @@ jobs:
205205
# The harness forces serial execution in-process (both the async semaphore and
206206
# libtest via --test-threads=1) because it mirrors each test's environment onto
207207
# the shared process environment, so RUST_E2E_CONCURRENCY is not set here.
208-
run: cargo test --no-default-features --features test-support --test e2e -- --test-threads=1 --nocapture
208+
run: cargo test --no-default-features --features test-support,bundled-in-process --test e2e -- --test-threads=1 --nocapture
209209

210210
# Validates the bundled-CLI build path on all three supported
211211
# platforms. While the regular `cargo test` job above also exercises
@@ -264,7 +264,9 @@ jobs:
264264
path: ./rust/.bundled-cli-cache
265265
key: bundled-cli-${{ matrix.os }}-${{ steps.cli-version.outputs.version }}
266266

267-
- name: cargo build (bundled-cli is the default feature)
267+
- name: Test minimal bundled CLI archive
268268
env:
269269
BUNDLED_CLI_CACHE_DIR: ${{ github.workspace }}/rust/.bundled-cli-cache
270-
run: cargo build
270+
run: |
271+
cargo test --lib embedded_archive_contains_only_expected_files
272+
cargo test --features bundled-in-process --lib embedded_archive_contains_only_expected_files

rust/Cargo.lock

Lines changed: 3 additions & 79 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

rust/Cargo.toml

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ name = "github_copilot_sdk"
2727

2828
[features]
2929
default = ["bundled-cli"]
30-
bundled-cli = ["dep:tar", "dep:flate2", "dep:zip"]
30+
bundled-cli = ["dep:tar", "dep:flate2"]
31+
bundled-in-process = ["bundled-cli", "dep:libloading"]
3132
derive = ["dep:schemars"]
3233
test-support = []
3334

@@ -49,11 +50,13 @@ tokio-stream = { version = "0.1", features = ["sync"] }
4950
tokio-util = { version = "0.7", default-features = false }
5051
tracing = "0.1"
5152
dirs = "5"
52-
libloading = "0.8"
53+
libloading = { version = "0.8", optional = true }
5354
parking_lot = "0.12"
5455
regex = "1"
5556
getrandom = "0.2"
5657
uuid = { version = "1", default-features = false, features = ["v4"] }
58+
flate2 = { version = "1", optional = true }
59+
tar = { version = "0.4", optional = true }
5760
# LLM inference callback transport: idiomatic HTTP/WebSocket forwarding for the
5861
# `CopilotRequestHandler`, plus base64/byte/stream plumbing for the chunk protocol.
5962
base64 = "0.22"
@@ -63,13 +66,6 @@ futures-util = "0.3"
6366
reqwest = { version = "0.12", default-features = false, features = ["stream", "http2", "default-tls"] }
6467
tokio-tungstenite = { version = "0.24", default-features = false, features = ["connect", "native-tls"] }
6568

66-
[target.'cfg(windows)'.dependencies]
67-
zip = { version = "2", default-features = false, features = ["deflate"], optional = true }
68-
69-
[target.'cfg(not(windows))'.dependencies]
70-
flate2 = { version = "1", optional = true }
71-
tar = { version = "0.4", optional = true }
72-
7369
[dev-dependencies]
7470
rusqlite = { version = "0.35", features = ["bundled"] }
7571
schemars = "1"
@@ -90,9 +86,10 @@ name = "protocol_version_test"
9086
required-features = ["test-support"]
9187

9288
[build-dependencies]
89+
base64 = "0.22"
9390
dirs = "5"
9491
flate2 = "1"
92+
serde_json = "1"
9593
sha2 = "0.10"
9694
tar = "0.4"
9795
ureq = { version = "2", default-features = false, features = ["tls"] }
98-
zip = { version = "2", default-features = false, features = ["deflate"] }

rust/README.md

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,10 @@ client.stop().await?;
7777
| `program` | `CliProgram` | `Resolve` (default: auto-detect) or `Path(PathBuf)` (explicit) |
7878
| `prefix_args` | `Vec<OsString>` | Args before `--server` (e.g. script path for node) |
7979
| `cwd` | `PathBuf` | Working directory for CLI process |
80-
| `env` | `Vec<(OsString, OsString)>` | Environment variables for CLI process |
81-
| `env_remove` | `Vec<OsString>` | Environment variables to remove |
80+
| `env` | `Vec<(OsString, OsString)>` | Deprecated; use the child-process transport's `env` option |
81+
| `env_remove` | `Vec<OsString>` | Deprecated; omit variables from the transport replacement env |
8282
| `extra_args` | `Vec<String>` | Extra CLI flags |
83-
| `transport` | `Transport` | `Stdio` (default), `Tcp { port }`, or `External { host, port }` |
83+
| `transport` | `Transport` | `Stdio`, `InProcess`, `Tcp`, or `External` |
8484

8585
With the default `CliProgram::Resolve`, `Client::start()` resolves the CLI in this order: an explicit `CliProgram::Path(path)`, the `COPILOT_CLI_PATH` env var, then the bundled CLI that was embedded at build time. There is no PATH scanning — if you've opted out of bundling (`default-features = false`) you must supply either `CliProgram::Path` or `COPILOT_CLI_PATH`.
8686

@@ -622,7 +622,7 @@ opts.telemetry = Some(telem);
622622
let client = Client::start(opts).await?;
623623
```
624624

625-
The SDK injects the appropriate environment variables (`COPILOT_OTEL_EXPORTER_TYPE`, `OTEL_EXPORTER_OTLP_ENDPOINT`, `OTEL_EXPORTER_OTLP_PROTOCOL`, ...) into the spawned CLI process. The SDK takes no OpenTelemetry dependency; the CLI itself owns the exporter pipeline. Caller-supplied `ClientOptions::env` entries override telemetry-injected values.
625+
The SDK injects the appropriate environment variables (`COPILOT_OTEL_EXPORTER_TYPE`, `OTEL_EXPORTER_OTLP_ENDPOINT`, `OTEL_EXPORTER_OTLP_PROTOCOL`, ...) into the spawned CLI process. The SDK takes no OpenTelemetry dependency; the CLI itself owns the exporter pipeline. A transport-level replacement environment is applied first, followed by SDK-managed authentication and telemetry variables. Deprecated `ClientOptions::env` entries retain their previous override behavior.
626626

627627
### Progress Reporting (`send_and_wait`)
628628

@@ -749,7 +749,7 @@ none of them are scheduled for removal.
749749
caller-supplied `AsyncRead` / `AsyncWrite`. Useful for testing,
750750
in-process embedding, or custom transports. Other SDKs are spawn-only
751751
or fixed-stdio.
752-
- **`enum Transport { Stdio, Tcp, External }`** — explicit, exhaustive
752+
- **`enum Transport { Default, Stdio, InProcess, Tcp, External }`** — explicit
753753
transport selector on `ClientOptions::transport`. Node/Python/Go rely
754754
on conditional config field combinations instead.
755755
- **Split `prefix_args` / `extra_args`** on `ClientOptions` — separate
@@ -776,7 +776,14 @@ none of them are scheduled for removal.
776776

777777
## Embedded CLI
778778

779-
The SDK provisions the Copilot CLI binary at build time. By default the `bundled-cli` feature embeds the verified binary directly in your compiled crate, so end-user binaries are self-contained — no env var setup, no separate install, just `cargo build`.
779+
The SDK provisions the Copilot CLI binary at build time. By default the
780+
`bundled-cli` feature embeds only the verified CLI executable in your compiled
781+
crate. Enable `bundled-in-process` to additionally embed the native
782+
runtime library and use `Transport::InProcess`:
783+
784+
```toml
785+
github-copilot-sdk = { version = "0.1", features = ["bundled-in-process"] }
786+
```
780787

781788
For builds that prefer a smaller artifact, disable the `bundled-cli` feature:
782789

@@ -795,7 +802,7 @@ github-copilot-sdk = { version = "0.1", default-features = false }
795802
> together.
796803
>
797804
> **Convenience on the build machine only.** As a special case,
798-
> `build.rs` downloads and SHA-verifies the compatible CLI version and
805+
> `build.rs` downloads and integrity-verifies the compatible CLI version and
799806
> drops it into the build machine's per-user cache; the runtime
800807
> resolver on that same machine will pick it up automatically. This
801808
> makes local development and CI ergonomic, but it does **not** carry
@@ -812,8 +819,11 @@ github-copilot-sdk = { version = "0.1", default-features = false }
812819

813820
The resolved version is baked into the crate via `cargo:rustc-env=COPILOT_SDK_CLI_VERSION` regardless of mode. The runtime resolver consumes it to recompute the on-disk path by convention, so no absolute paths leak into the rlib.
814821

815-
2. **Build time:** `build.rs` downloads the platform-appropriate archive from the [`github/copilot-cli` GitHub Releases](https://github.com/github/copilot-cli/releases) (`copilot-{platform}.tar.gz` on macOS/Linux, `.zip` on Windows), live-fetches the matching `SHA256SUMS.txt`, and verifies the archive hash. Then:
816-
- **`bundled-cli` on (default, release):** embeds the raw archive bytes via `include_bytes!()`. Runtime extracts on first `Client::start()`.
822+
2. **Build time:** `build.rs` downloads the platform-specific npm package and
823+
verifies its `sha512` integrity against the lockfile or publish snapshot.
824+
Then:
825+
- **`bundled-cli` on (default):** creates and embeds a minimal archive containing only the CLI executable.
826+
- **`bundled-in-process` on:** the minimal archive additionally contains the platform-native runtime library (`.dll`, `.so`, or `.dylib`); no other npm package files are embedded.
817827
- **`bundled-cli` off:** extracts the binary directly into the platform cache (staging file + atomic rename), idempotent across rebuilds. If the extracted binary is already present at the expected path, the download is skipped entirely — the extracted binary *is* the cache.
818828

819829
3. **Runtime:** in both modes the binary lives at:
@@ -899,10 +909,11 @@ Supported: `darwin-arm64`, `darwin-x64`, `linux-x64`, `linux-arm64`, `win32-x64`
899909

900910
## Features
901911

902-
| Feature | Default | Description |
903-
| -------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
904-
| `bundled-cli` || Build-time CLI embedding. Pulls in `tar`+`flate2` (Linux/macOS) or `zip` (Windows). Disable via `default-features = false` to opt out (e.g. when shipping a smaller binary or when always supplying the CLI via `CliProgram::Path` / `COPILOT_CLI_PATH`). |
905-
| `derive` || `schema_for::<T>()` for generating JSON Schema from Rust types (adds `schemars`). Enable when defining [tool parameters](#tool-registration). |
912+
| Feature | Default | Description |
913+
| ------- | ------- | ----------- |
914+
| `bundled-cli` || Embeds only the CLI executable. Disable via `default-features = false` when supplying the CLI via `CliProgram::Path` or `COPILOT_CLI_PATH`. |
915+
| `bundled-in-process` || Enables `Transport::InProcess`, implies `bundled-cli`, and additionally embeds only the platform-native runtime library. |
916+
| `derive` || `schema_for::<T>()` for generating JSON Schema from Rust types (adds `schemars`). |
906917

907918
```toml
908919
# These examples use registry syntax for illustration; until the crate is
@@ -911,7 +922,10 @@ Supported: `darwin-arm64`, `darwin-x64`, `linux-x64`, `linux-arm64`, `win32-x64`
911922
# Default — bundles the Copilot CLI in your binary.
912923
github-copilot-sdk = "0.1"
913924

914-
# Opt out of bundling — resolve CLI from COPILOT_CLI_PATH or system PATH instead.
925+
# Enable the in-process transport and bundle its native runtime library.
926+
github-copilot-sdk = { version = "0.1", features = ["bundled-in-process"] }
927+
928+
# Opt out of bundling — supply the CLI explicitly at runtime.
915929
github-copilot-sdk = { version = "0.1", default-features = false }
916930

917931
# Derive JSON Schema for tool parameters (adds to default bundled-cli).

0 commit comments

Comments
 (0)