github-copilot-sdk 0.1.0

Rust SDK for programmatic control of the GitHub Copilot CLI via JSON-RPC. Technical preview, pre-1.0.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
# Changelog

All notable changes to the `github-copilot-sdk` crate will be documented in this file.

The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

After 0.1.0 ships, [release-plz](https://release-plz.dev/) will prepend new
entries from conventional-commit history. The Unreleased entry below is
hand-curated so that crates.io readers get a usable summary of the public
surface on first publish, not a flat list of merge commits — release-plz
will rename `[Unreleased]` to `[0.1.0] - <date>` and add a fresh empty
`[Unreleased]` above it when it cuts the first release PR.

## [Unreleased]

Initial public release. Programmatic Rust access to the GitHub Copilot CLI
over JSON-RPC 2.0 (stdio or TCP), with handler-based event dispatch, typed
tool/permission/elicitation helpers, and runtime session management.

This is a **technical preview**. The crate is pre-1.0 and the public API may
change in breaking ways before 1.0. The rendered docs on
[docs.rs](https://docs.rs/github-copilot-sdk) are the canonical reference for the
public surface.

### Added

#### Client lifecycle
- `Client::start` — spawn and manage a GitHub Copilot CLI child process.
- `Client::from_streams` — connect to a CLI server over caller-supplied
  `AsyncRead`/`AsyncWrite` (testing, custom transports).
- `Client::stop` / `Client::force_stop` — graceful and immediate shutdown.
- `Client::state` returning `ConnectionState` (`Connecting`, `Connected`,
  `Disconnecting`, `Disconnected`).
- `Client::subscribe_lifecycle` returning a `LifecycleSubscription` for
  runtime observation of created / destroyed / foreground / background
  events. Implements `tokio_stream::Stream` and offers an inherent
  `recv()`; drop the value to unsubscribe.
- `Client::ping(message)` returning typed `PingResponse` and
  `Client::verify_protocol_version` for handshake validation.
- `Client::list_sessions`, `get_session_metadata`, `delete_session`,
  `get_last_session_id`, `get_foreground_session_id`,
  `set_foreground_session_id`.
- `Client::list_models`, `get_status` (typed `GetStatusResponse`),
  `get_auth_status` (typed `GetAuthStatusResponse`).

#### Sessions
- `Client::create_session` and `Client::resume_session` accepting
  `SessionConfig` with handler, capabilities, system message, mode, model,
  permission policy, working directory, and resume parameters.
- `Session::send` returning the assigned message ID for
  correlation with later events.
- `Session::send_and_wait` for synchronous prompt → final-event flows.
- `Session::subscribe` returning an `EventSubscription` for observe-only
  access to the session's event stream. Implements `tokio_stream::Stream`
  and offers an inherent `recv()`; drop the value to unsubscribe.
- `Session::set_model(model, SetModelOptions)` with `reasoning_effort`
  and `model_capabilities` overrides (matches Node/Python/.NET).
- UI primitives: `session.ui().elicitation()`, `confirm()`, `select()`,
  `input()` — grouped under a `SessionUi` sub-API to mirror .NET / Python /
  Go.
- `Session::log(message, LogOptions)` with optional severity and
  ephemeral flag.
- `Session::abort` (matches all other SDKs).
- `Session::disconnect` (canonical) and `Session::destroy` (alias)
  preserve on-disk session state for later resume.
- `Session::stop_event_loop` for shutting down the per-session loop.
- `Session::cancellation_token()` returns a [`tokio_util::sync::CancellationToken`]
  child token that fires when the session shuts down (via
  `stop_event_loop`, `destroy`, or `Drop`). Lets external tasks bind their
  lifetime to a session via `tokio::select!` without taking a strong
  reference to the session. Cancelling the returned child token does not
  shut the session down — only `stop_event_loop` (or dropping the session)
  does.

#### Handlers + helpers
- `SessionHandler` trait with default fallback impls for each event
  (permissions, external tools, elicitation, plan-mode prompts).
- `ApproveAllHandler` / `DenyAllHandler` reference handlers.
- Permission policy helpers: `permission::approve_all`,
  `permission::deny_all`, `permission::approve_if`, plus chainable
  builders on `SessionConfig` (`approve_all_permissions`,
  `deny_all_permissions`, `approve_if`).
- `PermissionResult` is `#[non_exhaustive]` and supports `Approved`,
  `Denied`, `Deferred` (handler will resolve via
  `handlePendingPermissionRequest` itself — notification path only;
  direct RPC falls back to `Approved`), and
  `Custom(serde_json::Value)` for response shapes beyond
  `{ "kind": "approve-once" | "reject" }` (e.g. allowlist payloads).
- All extension-point and protocol-evolving public enums are
  `#[non_exhaustive]` so future variants are additive (non-breaking):
  `Error`, `ProtocolError`, `SessionError`, `Transport`, `Attachment`,
  `ToolResult`, `ElicitationMode`, `InputFormat`, `GitHubReferenceType`,
  `SessionLifecycleEventType`, plus the handler/hook event/response enums.
  Closed taxonomies (`LogLevel`, `ConnectionState`, `CliProgram`) remain
  exhaustive so callers benefit from compile-time exhaustiveness checks.
- Tool helpers: `tool::DefineTool`, `tool::tool_schema_for<T>`,
  `tool::ToolHandlerRouter`, derive support via `derive` feature.
  `ToolHandlerRouter` overrides each `SessionHandler` per-event method
  directly, so callers can use the narrow-typed entry points (e.g.
  `router.on_external_tool(invocation).await -> ToolResult`) instead of
  unwrapping a `HandlerResponse` from `on_event`. The default `on_event`
  still routes correctly through the per-event methods, so legacy
  callers are unaffected.
- Hooks API for instrumenting send/receive flows (`github_copilot_sdk::hooks`).
- `SessionHandler::on_auto_mode_switch` — typed handler for the CLI's
  rate-limit-recovery prompt (`autoModeSwitch.request` JSON-RPC
  callback, added in copilot-agent-runtime PR #7024). Returns a typed
  [`AutoModeSwitchResponse`] enum with `Yes`, `YesAlways`, `No`
  variants (`#[serde(rename_all = "snake_case")]`, wire values byte-
  identical to the runtime's `"yes" | "yes_always" | "no"` schema).
  Default impl declines (`No`); override only if your application
  surfaces a UX for the prompt. `SessionConfig::request_auto_mode_switch`
  and `ResumeSessionConfig::request_auto_mode_switch` default to
  `Some(true)` so the CLI advertises the callback to the SDK out of the
  box. **Cross-SDK divergence:** typed handler is Rust-only as of 0.1.0.
  Node, Python, Go, and .NET observe the request as a raw JSON-RPC
  callback today; parity ports for those SDKs are post-release follow-up
  work.
- New session-event fields surfaced by the `@github/copilot ^1.0.39`
  schema bump:
  - `SessionErrorData.eligible_for_auto_switch: Option<bool>` — set on
    `errorType: "rate_limit"` to signal the runtime will follow with an
    `auto_mode_switch.requested` event. UI clients can suppress
    duplicate rendering of the rate-limit error when they show their
    own auto-mode-switch prompt.
  - `SessionErrorData.error_code: Option<String>` — fine-grained
    upstream provider error code (e.g.
    `"user_weekly_rate_limited"`, `"integration_rate_limited"`).
  - `SessionModelChangeData.cause: Option<String>`    `"rate_limit_auto_switch"` for changes triggered by the
    auto-mode-switch recovery path. Lets UI render contextual copy.
  - `AutoModeSwitchRequestedData.retry_after_seconds: Option<f64>`    seconds until the rate limit resets, when known. Clients can
    render a humanized reset time alongside the prompt. (The request-
    callback path's `retry_after_seconds` parameter on
    [`SessionHandler::on_auto_mode_switch`]crate::handler::SessionHandler::on_auto_mode_switch
    uses `Option<u64>` for HTTP `Retry-After` `delta-seconds`
    semantics.)

#### Types
- Newtype `SessionId`, plus generated RPC types under `github_copilot_sdk::generated`.
- `LogLevel`, `LogOptions`, `SetModelOptions`, `PingResponse`,
  `SessionLifecycleEvent`, `SessionLifecycleEventType`, `ConnectionState`,
  `SystemMessageConfig`, `MessageOptions`, `SectionOverride`, `Attachment`,
  `InputFormat`, `InputOptions`.
- Strongly-typed `Error` and `ProtocolError` with `is_transport_failure`
  classifier and `error_codes` constants.

#### Typed RPC namespace
- `Client::rpc()` and `Session::rpc()` accessors exposing a generated, typed
  view over the full GitHub Copilot CLI JSON-RPC API. Sub-namespaces mirror the
  schema (e.g. `client.rpc().models().list()`, `session.rpc().workspaces()
  .list_files()`, `session.rpc().agent().list()`,
  `session.rpc().tasks().list()`).
- All hand-authored helpers (`list_workspace_files`, `read_plan`, `set_mode`,
  `list_models`, `get_quota`, etc.) are now thin one-line delegations over
  this namespace. Wire-method strings exist in exactly one place
  (`generated/rpc.rs`), making typo bugs like the `session.workspace.*`
  `session.workspaces.*` regression structurally impossible. Public
  helper signatures are unchanged.

#### Configuration parity
- All remaining public configuration types are now `#[non_exhaustive]`
  for forward-compatibility — adding fields post-1.0 is non-breaking on
  consumers that construct via `Default::default()` plus field
  assignment or the `with_*` builders. Affected: `SessionConfig`,
  `ResumeSessionConfig`, `ClientOptions`, `ProviderConfig`,
  `McpServerConfig`, `Tool`, `CustomAgentConfig`,
  `InfiniteSessionConfig`, `SystemMessageConfig`, `ConnectionState`.
  (`HookEvent`, `HookOutput`, `MessageOptions`, `TelemetryConfig`,
  `SessionFsConfig`, `FsError`, `FileInfo`, `DirEntry`, `ToolInvocation`,
  `Error`, `Transport`, `DeliveryMode` were already marked.) Callers
  using exhaustive struct literals must switch to
  `let mut x = Type::default(); x.field = ...;` or the available `with_*`
  builders; `..Default::default()` no longer compiles for these types
  outside the defining crate.
- `MessageOptions::mode` is now typed `Option<DeliveryMode>` (was
  `Option<String>`). `DeliveryMode` is `#[non_exhaustive]` and serializes
  to the wire strings `"enqueue"` (default) and `"immediate"`. The prior
  rustdoc incorrectly described this field as a permission mode; the
  field controls how the prompt is delivered relative to in-flight work.
  `MessageOptions::with_mode` now takes `DeliveryMode` directly. Callers
  that previously passed `"agent"` or `"autopilot"` were already silently
  no-ops at the CLI level — switch to a `DeliveryMode` variant or omit
  the field entirely.
- `SessionConfig::default()` and `ResumeSessionConfig::new()` now set the
  four permission-flow flags (`request_user_input`, `request_permission`,
  `request_exit_plan_mode`, `request_elicitation`) to `Some(true)` instead
  of `None`. Mirrors Node's `client.ts` behavior of always advertising the
  permission surface and deriving handler presence from the
  `SessionHandler` impl. The default `DenyAllHandler` refuses all
  permission requests so the wire surface is safe out-of-the-box; callers
  that want the wire surface fully disabled set the flags explicitly to
  `Some(false)`.
- `SessionListFilter` — typed filter for `Client::list_sessions` covering
  `cwd`, `git_root`, `repository`, and `branch`. Replaces the prior
  `Option<serde_json::Value>` parameter.
- `McpServerConfig` tagged enum (`Stdio` / `Http` / `Sse`) with
  `McpStdioServerConfig` and `McpHttpServerConfig` payload structs.
  `SessionConfig::mcp_servers`, `ResumeSessionConfig::mcp_servers`, and
  `CustomAgentConfig::mcp_servers` are now `Option<HashMap<String,
  McpServerConfig>>` instead of typeless `Value` maps. Stdio configurations
  serialized by older callers (no explicit `type`, or `type: "local"`) are
  accepted on the deserialize path.
- `PermissionRequestData` gains typed `kind: Option<PermissionRequestKind>`
  and `tool_call_id: Option<String>` fields covering the eight CLI
  permission categories (`shell`, `write`, `read`, `url`, `mcp`,
  `custom-tool`, `memory`, `hook`); unknown values fall through to
  `PermissionRequestKind::Unknown` for forward compatibility. The original
  params object is still available via the existing `extra: Value` flatten.
- `PermissionResult` gains `UserNotAvailable` (sent as
  `{ "kind": "user-not-available" }`) and `NoResult` (sent as
  `{ "kind": "no-result" }`) variants for headless agents and explicit
  fall-through-to-CLI-default responses.
- `Client::stop` cooperatively shuts down active sessions before killing
  the CLI child: walks every session still registered with the client,
  sends `session.destroy` for each, then kills the child. Errors from
  per-session destroys and the terminal child-kill are collected into a
  new `StopErrors` aggregate (`Result<(), StopErrors>`) instead of
  short-circuiting on the first failure, mirroring the Node SDK's
  `Error[]` return shape. `StopErrors` implements `std::error::Error`
  and exposes `errors()` / `into_errors()` for inspection. Callers that
  previously used `client.stop().await?` should switch to
  `client.stop().await.ok();` (best-effort) or match on the aggregate.
- `ResumeSessionConfig::disable_resume: Option<bool>` — force-fail resume
  if the session does not exist on disk, instead of silently starting a
  new session.
- `SessionConfig` and `ResumeSessionConfig` gain six configuration knobs
  matching the Node SDK shape (Bucket B.1):
  - `session_id: Option<SessionId>` (SessionConfig only — required on
    resume, where it remains `SessionId`) — supply a custom session ID
    instead of letting the CLI generate one.
  - `working_directory: Option<PathBuf>` — per-session cwd override,
    independent of [`ClientOptions::cwd`]crate::ClientOptions::cwd.
  - `config_dir: Option<PathBuf>` — override the default configuration
    directory location for this session.
  - `model_capabilities: Option<ModelCapabilitiesOverride>` — per-property
    overrides for model capabilities, deep-merged over runtime defaults.
    The same type was previously available only on
    `SetModelOptions::model_capabilities`.
  - `github_token: Option<String>` — per-session GitHub token. Distinct
    from [`ClientOptions::github_token`], which authenticates the CLI
    process; this token determines the GitHub identity used for content
    exclusion, model routing, and quota checks for this session. The
    field is redacted from the `Debug` output.
  - `include_sub_agent_streaming_events: Option<bool>` — forward streaming
    delta events from sub-agents to this connection (Node default: true).
- `ClientOptions` gains the simple subset of Node's
  `CopilotClientOptions` knobs (Bucket B.2):
  - `log_level: Option<LogLevel>` — typed enum (`None`, `Error`, `Warning`,
    `Info`, `Debug`, `All`) replacing the previously hard-coded
    `--log-level info` argument. When unset, the SDK still passes
    `--log-level info` for parity with prior behavior.
  - `session_idle_timeout_seconds: Option<u64>` — server-wide idle
    timeout for sessions in seconds. When `Some(n)` with `n > 0`, the
    SDK passes `--session-idle-timeout <n>`. `None` or `Some(0)` leaves
    sessions running indefinitely (the CLI default).
  - The Node knob `isChildProcess` (sub-CLI parent-stdio mode) and
    `autoStart` (lazy-init pattern) are intentionally **not** ported —
    `isChildProcess` requires a transport variant the Rust SDK does not
    yet support; `autoStart` does not apply because [`Client::start`] is
    a single explicit constructor rather than a deferred-init pattern.
  - `on_list_models: Option<Arc<dyn ListModelsHandler>>` — BYOK escape
    hatch matching Node's `onListModels`. When set, [`Client::list_models`]
    returns the handler's result without making a `models.list` RPC.
    `ListModelsHandler` is a new public `async_trait` (mirrors the shape
    of `SessionHandler` / `SessionHooks`) with a single
    `async fn list_models(&self) -> Result<Vec<Model>, Error>` method.
    `ClientOptions` switched from `#[derive(Debug)]` to a manual `Debug`
    impl that prints the handler as `<set>` / `None` (same precedent as
    `SessionConfig::handler` and `github_token`).
- `MessageOptions` gains `request_headers: Option<HashMap<String, String>>`
  with a corresponding [`MessageOptions::with_request_headers`] builder
  method, matching Node's `MessageOptions.requestHeaders` and Go's
  `MessageOptions.RequestHeaders`. Custom HTTP headers are forwarded to
  the CLI via the `requestHeaders` field on `session.send`. The field is
  omitted from the wire when `None` or empty (matches Node's
  `omitempty` semantics).
- Slash command registration: new [`CommandHandler`] async trait,
  [`CommandDefinition`] with `new`/`with_description` builders, and
  [`CommandContext`] `session_id`, `command`, `command_name`, `args`
  hand-authored in `crate::types`. `SessionConfig::commands` and
  `ResumeSessionConfig::commands` accept a `Vec<CommandDefinition>` via
  the new `with_commands` builder, matching Node's
  `SessionConfig.commands`, Python's `SessionConfig.commands`, and Go's
  `SessionConfig.Commands`. The SDK serializes only `{name, description?}`
  on the wire (handlers stay client-side), and dispatches incoming
  `command.execute` events to the registered handler — acking with no
  error on success, `error: <message>` on `Err`, and
  `error: "Unknown command: <name>"` when the name is unregistered.
  `CommandContext` and `CommandDefinition` are `#[non_exhaustive]` so
  forward-compatible fields (e.g. aliases, completion providers) can land
  without breaking callers.
- Custom session filesystem: new [`SessionFsProvider`] async trait,
  [`SessionFsConfig`], [`FsError`], [`FileInfo`], [`DirEntry`],
  [`DirEntryKind`], and [`SessionFsConventions`] in `crate::session_fs`
  (also re-exported from `crate::types`). When [`ClientOptions::session_fs`]
  is set, [`Client::start`] calls `sessionFs.setProvider` on the CLI to
  delegate per-session filesystem operations to a provider supplied via
  [`SessionConfig::with_session_fs_provider`] /
  [`ResumeSessionConfig::with_session_fs_provider`]. Inbound `sessionFs.*`
  requests dispatch to the provider; `FsError::NotFound` maps to the wire
  `ENOENT` code and other `FsError` values map to `UNKNOWN`.
  `From<std::io::Error>` is provided so handlers backed by `std::fs` /
  `tokio::fs` can propagate errors with `?`. All trait methods have
  default implementations returning `Err(FsError::Other("not supported"))`,
  so providers only override the methods they need and forward-compatible
  schema additions land without breaking existing implementations.
  Diverges from Node/Python/Go's factory-closure pattern in favor of
  direct `Arc<dyn SessionFsProvider>` registration.
- W3C Trace Context propagation: new [`TraceContext`] struct and
  [`TraceContextProvider`] async trait in `crate::trace_context` (also
  re-exported from `crate::types`). Hybrid shape combines Node's
  callback-based `onGetTraceContext` and Go's per-turn
  `MessageOptions.Traceparent` / `Tracestate`:
  [`ClientOptions::on_get_trace_context`] supplies an ambient provider that
  injects `traceparent` / `tracestate` on `session.create`,
  `session.resume`, and `session.send`, while
  [`MessageOptions::with_traceparent`], [`MessageOptions::with_tracestate`],
  and [`MessageOptions::with_trace_context`] override per-turn (override
  wins; provider is not invoked when MessageOptions carries trace headers).
  [`ToolInvocation`] is now `#[non_exhaustive]` and exposes inbound
  `traceparent` / `tracestate` populated from `external_tool.requested`
  events, plus a [`ToolInvocation::trace_context`] helper. Wire fields are
  omitted when unset (matches Node/Go `omitempty` semantics).
- `ToolInvocation` and `SessionId` now derive `Default`. Production code
  never constructs `ToolInvocation` literals (it's a CLI-emitted read-only
  type), but downstream test scaffolding can now use
  `ToolInvocation { tool_name: "...".into(), ..Default::default() }` and
  absorb future `#[non_exhaustive]` field additions automatically.
- OpenTelemetry env-var passthrough: new [`TelemetryConfig`] struct and
  [`OtelExporterType`] enum (both `#[non_exhaustive]`), wired on
  [`ClientOptions::telemetry`]. When `Some(...)`, the SDK injects
  `COPILOT_OTEL_ENABLED=true` plus `OTEL_EXPORTER_OTLP_ENDPOINT`,
  `COPILOT_OTEL_FILE_EXPORTER_PATH`, `COPILOT_OTEL_EXPORTER_TYPE`,
  `COPILOT_OTEL_SOURCE_NAME`, and
  `OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT` into the spawned CLI
  process — verbatim env-var names matching Node/Python/Go. Pure
  passthrough: no `opentelemetry-rust` dependency; the CLI itself owns the
  exporter. `exporter_type` is a typed enum (`OtlpHttp` / `File`) following
  the [`LogLevel`]LogLevel precedent for finite, enumerated CLI knobs;
  serialized verbatim as `"otlp-http"` / `"file"`. User-supplied
  `ClientOptions::env` continues to win over telemetry-injected values.
- `ClientOptions::copilot_home: Option<PathBuf>` (and
  `with_copilot_home`) — overrides the directory where the CLI persists
  its state. Exported as `COPILOT_HOME` to the spawned CLI process.
  Useful for sandboxing test runs or running multiple isolated SDK
  instances side-by-side. Mirrors Node `copilotHome` /
  Python `copilot_home`.
- `ClientOptions::tcp_connection_token: Option<String>` (and
  `with_tcp_connection_token`) — optional auth token for TCP transport.
  Sent in the new `connect` JSON-RPC handshake (with backward-compat
  fall-back to `ping` for legacy CLI servers) and exported as
  `COPILOT_CONNECTION_TOKEN` to spawned CLI processes. When the SDK
  spawns its own CLI in TCP mode and this is left unset, a UUID is
  generated automatically so the loopback listener is safe by default.
  Combining with `Transport::Stdio` returns
  `Error::InvalidConfig` from `Client::start`.
- `SessionConfig::instruction_directories: Option<Vec<PathBuf>>` and
  `ResumeSessionConfig::instruction_directories` (plus
  `with_instruction_directories` builders on both) — additional
  directories searched for custom instruction files. Distinct from
  `skill_directories`. Forwarded to the CLI on session create / resume.
- `Error::InvalidConfig(String)` variant for client-construction errors
  that surface from `Client::start` (e.g. `tcp_connection_token` paired
  with `Transport::Stdio`, empty token, etc).

### Documentation
- `README.md` with quickstart, architecture diagram, and feature matrix.
- Examples under `examples/`: `chat`, `hooks`, `tool_server`,
  `lifecycle_observer`.
- `RELEASING.md` operational runbook for maintainers.

#### Builder ergonomics
- `ClientOptions::new()` plus a chainable `with_*` builder per public
  field (`with_program`, `with_prefix_args`, `with_cwd`, `with_env`,
  `with_env_remove`, `with_extra_args`, `with_transport`,
  `with_github_token`, `with_use_logged_in_user`, `with_log_level`,
  `with_session_idle_timeout_seconds`, `with_list_models_handler`,
  `with_session_fs`, `with_trace_context_provider`, `with_telemetry`).
  Mirrors the existing [`MessageOptions::new`] / `with_*` shape and
  closes the cross-crate ergonomics gap on `#[non_exhaustive]`  external callers no longer need to write
  `let mut opts = ClientOptions::default(); opts.field = ...;` for
  every field they touch. Existing `ClientOptions::default()` and
  mut-let-and-assign continue to work unchanged.
- `Tool::new(name)` plus `with_namespaced_name`, `with_description`,
  `with_instructions`, `with_parameters`, `with_overrides_built_in_tool`,
  `with_skip_permission` for tool definitions. Same rationale —
  `Tool` is the most-instantiated `#[non_exhaustive]` type at consumer
  call sites in real-world consumer code, where the
  builder shape replaces the per-consumer `make_tool(name, desc,
  params)` helper that consumers were writing to smooth over the
  mut-let pattern.
- Per-field `with_*` builder methods on `SessionConfig` and
  `ResumeSessionConfig` covering every public scalar, vector, and
  optional-struct field (~30 new methods on each). Mirrors the
  `ClientOptions` / `Tool` shape; existing closure-installing
  chains (`with_handler`, `with_hooks`, `with_transform`,
  `with_commands`, `with_session_fs_provider`,
  `approve_all_permissions`, etc.) continue to work unchanged. The
  primary win: external session-construction sites collapse from
  `let mut cfg = ResumeSessionConfig::new(id); cfg.client_name =
  Some("...".into()); cfg.streaming = Some(true); ...` (10-15
  lines per site) to a single fluent chain.
- Round out builder coverage on the remaining consumer-facing
  config structs: `CustomAgentConfig::new(name, prompt)` plus
  `with_display_name`, `with_description`, `with_tools`,
  `with_mcp_servers`, `with_infer`, `with_skills`;
  `InfiniteSessionConfig::new()` plus `with_enabled`,
  `with_background_compaction_threshold`,
  `with_buffer_exhaustion_threshold`;
  `ProviderConfig::new(base_url)` plus `with_provider_type`,
  `with_wire_api`, `with_api_key`, `with_bearer_token`,
  `with_azure`, `with_headers`, `with_model_id`, `with_wire_model`,
  `with_max_prompt_tokens`, `with_max_output_tokens`; `SystemMessageConfig::new()` plus
  `with_mode`, `with_content`, `with_sections`;
  `TelemetryConfig::new()` plus `with_otlp_endpoint`,
  `with_file_path`, `with_exporter_type`, `with_source_name`,
  `with_capture_content`. `TraceContext` also gains a symmetric
  `new()` + `with_traceparent` pair alongside the existing
  `from_traceparent` shorthand.
- Documented the direct-field-assignment escape hatch on
  `SessionConfig` and `ResumeSessionConfig` for callers forwarding
  `Option<T>` values from upstream code (matches the
  `http::request::Parts` / `hyper::Body::Builder` convention; per-
  field `with_*_opt` setters intentionally omitted to keep the
  primary API surface small).

#### Build infrastructure
- `build.rs` no longer shells out to `curl` for the bundled-CLI
  download. The `embedded-cli` feature now downloads the
  `SHA256SUMS.txt` and platform tarball through `ureq` (rustls TLS,
  pure-Rust, no system dependencies). Removes the implicit `curl`-
  on-PATH requirement that previously broke the build on minimal
  Windows / container environments. Includes bounded retries with
  exponential backoff (1s/2s/4s) on transient failures (5xx,
  connect/read timeouts, transport errors) — 4xx responses still
  fail fast as before.

### Fixed
- `SessionEvent` and `TypedSessionEvent` now expose the `agentId`
  envelope field added to `session-events.schema.json` upstream
  (`f8cf846`, "Derive session event envelopes from schema"). Sub-agent
  events were silently dropping the attribution at the deserialization
  boundary; consumers had no way to distinguish events emitted by the
  root agent from events emitted by a sub-agent. Other SDKs (Node,
  Python, Go, .NET) all carry this field. Round-trip parity test added
  in `types::tests::session_event_round_trips_agent_id_on_envelope`.
- `Session::user_input` no longer double-dispatches when the CLI sends
  both a `user_input.requested` notification (for observers) and a
  `userInput.request` JSON-RPC call (the actual prompt) for the same
  prompt. The notification path is now a no-op; the JSON-RPC path
  remains authoritative. Matches Python / Go / .NET / Node SDK
  behavior, all of which only register the JSON-RPC handler. Fixes
  github/github-app#4249, where consumers saw duplicate `ask_user`
  and `exit_plan` widgets on every prompt.
- `SessionUi::elicitation` (and the `confirm` / `select` / `input`
  convenience helpers that delegate through it) now sends the user-supplied
  JSON Schema as `requestedSchema` on the wire, matching the
  `session.ui.elicitation` request shape that all other SDKs ship and that
  this crate's own generated `UIElicitationRequest` type expects. The
  hand-authored convenience layer was sending it as `schema`, so every UI
  helper call was effectively dead — the CLI saw a missing required
  `requestedSchema` field. The mock-server test for elicitation
  round-tripped through the same misnamed field, so the bug slipped past
  unit tests; the test now asserts on `requestedSchema` and explicitly
  rejects a stray `schema` key.
- `Client::list_sessions` now wraps the optional filter under `params.filter`
  on the wire, matching the `session.list` request shape that Node, Python,
  Go, and .NET ship. The hand-authored implementation was flattening the
  filter fields directly onto `params`, which the runtime silently ignored
  — so `list_sessions(Some(filter))` was functionally equivalent to
  `list_sessions(None)` in 0.0.x. Same class of bug as the elicitation
  wire fix above: the existing mock-server test asserted on the flat shape
  it observed rather than the schema's wrapped shape, so the bug
  round-tripped through both ends. The test now asserts the wrapped path
  (`params.filter.repository`) and explicitly rejects the flattened
  fallback (`params.repository`).
- `Client::get_status` and `Client::get_auth_status` now use the
  correct wire method names (`status.get` and `auth.getStatus`)
  matching Node, Go, Python, and .NET. The hand-authored
  implementation was sending `getStatus` and `getAuthStatus` — names
  that aren't registered on the CLI runtime — so both calls would
  have returned a "method not found" error (or a misleading no-such-
  method log) instead of the expected status payload. Same class of
  bug as the elicitation `requestedSchema` and `list_sessions`
  filter-wrapping fixes above: the mock-server test for these
  methods asserted on the wrong-name strings the implementation
  used, so the bugs round-tripped through both ends. The test now
  asserts on the canonical wire names AND explicitly rejects the
  hand-authored aliases (`assert_ne!(request["method"], "getStatus")`
  / `"getAuthStatus"`).

### Notes
- Minimum supported Rust version (MSRV): 1.94.0 (pinned via
  `rust-toolchain.toml`).
- No `Client::actual_port` accessor — this SDK is strictly stream-based,
  so the concept doesn't apply. See `Client::from_streams` rustdoc.
- `cargo semver-checks` runs in `continue-on-error` mode for 0.1.0; will
  flip to blocking once 0.1.0 is published and serves as the baseline.
- `infinite_sessions: Option<InfiniteSessionConfig>` is wired on both
  `SessionConfig` and `ResumeSessionConfig` and follows the same
  default-omit-on-the-wire semantics as Node/Go: when `None`, the field
  is skipped and the CLI applies its own default. No behavioral
  divergence from the other SDKs.
- `Client::stop` returns `Result<(), StopErrors>` and now cooperatively
  shuts down each active session via `session.destroy` before killing
  the CLI child, aggregating all per-session and child-kill errors into
  the returned `StopErrors`. See the entry under "Configuration parity"
  above for the migration note.