diff --git a/docs/features/mcp.md b/docs/features/mcp.md
index 6f715bd2e..e974532b0 100644
--- a/docs/features/mcp.md
+++ b/docs/features/mcp.md
@@ -120,7 +120,7 @@ func main() {
"my-local-server": copilot.MCPStdioServerConfig{
Command: "node",
Args: []string{"./mcp-server.js"},
- Tools: []string{"*"},
+ Tools: &[]string{"*"},
},
},
})
diff --git a/docs/features/streaming-events.md b/docs/features/streaming-events.md
index 6bc560a48..ebf7d5e30 100644
--- a/docs/features/streaming-events.md
+++ b/docs/features/streaming-events.md
@@ -130,7 +130,7 @@ func main() {
session, _ := client.CreateSession(ctx, &copilot.SessionConfig{
Model: "gpt-4.1",
- Streaming: true,
+ Streaming: copilot.Bool(true),
OnPermissionRequest: func(req copilot.PermissionRequest, inv copilot.PermissionInvocation) (copilot.PermissionRequestResult, error) {
return copilot.PermissionRequestResult{Kind: copilot.PermissionRequestResultKindApproved}, nil
},
diff --git a/docs/getting-started.md b/docs/getting-started.md
index 3a43fad7c..0d5e5887e 100644
--- a/docs/getting-started.md
+++ b/docs/getting-started.md
@@ -467,7 +467,7 @@ func main() {
session, err := client.CreateSession(ctx, &copilot.SessionConfig{
Model: "gpt-4.1",
- Streaming: true,
+ Streaming: copilot.Bool(true),
})
if err != nil {
log.Fatal(err)
@@ -1046,7 +1046,7 @@ func main() {
session, err := client.CreateSession(ctx, &copilot.SessionConfig{
Model: "gpt-4.1",
- Streaming: true,
+ Streaming: copilot.Bool(true),
Tools: []copilot.Tool{getWeather},
})
if err != nil {
@@ -1482,7 +1482,7 @@ func main() {
session, err := client.CreateSession(ctx, &copilot.SessionConfig{
Model: "gpt-4.1",
- Streaming: true,
+ Streaming: copilot.Bool(true),
Tools: []copilot.Tool{getWeather},
})
if err != nil {
@@ -2001,7 +2001,7 @@ func main() {
ctx := context.Background()
client := copilot.NewClient(&copilot.ClientOptions{
- CLIUrl: "localhost:4321",
+ Connection: copilot.UriConnection{URL: "localhost:4321"},
})
if err := client.Start(ctx); err != nil {
@@ -2021,7 +2021,7 @@ func main() {
import copilot "github.com/github/copilot-sdk/go"
client := copilot.NewClient(&copilot.ClientOptions{
- CLIUrl: "localhost:4321",
+ Connection: copilot.UriConnection{URL: "localhost:4321"},
})
if err := client.Start(ctx); err != nil {
@@ -2105,7 +2105,7 @@ var session = client.createSession(
-**Note:** When `cli_url` / `cliUrl` / `CLIUrl` is provided, or Rust uses `Transport::External`, the SDK will not spawn or manage a CLI process - it will only connect to the existing server at the specified URL.
+**Note:** When `cli_url` / `cliUrl` / Go's `UriConnection` is provided, or Rust uses `Transport::External`, the SDK will not spawn or manage a CLI process - it will only connect to the existing server at the specified URL.
## Telemetry and observability
diff --git a/docs/setup/backend-services.md b/docs/setup/backend-services.md
index d9dd508e5..0552ee36e 100644
--- a/docs/setup/backend-services.md
+++ b/docs/setup/backend-services.md
@@ -6,7 +6,7 @@ Run the Copilot SDK in server-side applications—APIs, web backends, microservi
## How it works
-Instead of the SDK spawning a CLI child process, you run the CLI independently in **headless server mode**. Your backend connects to it over TCP using the `cliUrl` option.
+Instead of the SDK spawning a CLI child process, you run the CLI independently in **headless server mode**. Your backend connects to it over TCP using the `Connection` option (`UriConnection`).
```mermaid
flowchart TB
@@ -177,7 +177,7 @@ func main() {
message := "Hello"
client := copilot.NewClient(&copilot.ClientOptions{
- CLIUrl: "localhost:4321",
+ Connection: copilot.UriConnection{URL: "localhost:4321"},
})
client.Start(ctx)
defer client.Stop()
@@ -195,7 +195,7 @@ func main() {
```go
client := copilot.NewClient(&copilot.ClientOptions{
- CLIUrl:"localhost:4321",
+ Connection: copilot.UriConnection{URL: "localhost:4321"},
})
client.Start(ctx)
defer client.Stop()
diff --git a/docs/setup/bundled-cli.md b/docs/setup/bundled-cli.md
index c19bc85b6..8f036ee8b 100644
--- a/docs/setup/bundled-cli.md
+++ b/docs/setup/bundled-cli.md
@@ -71,7 +71,7 @@ await client.stop()
Go
> [!NOTE]
-> The Go SDK does not bundle the CLI. You must install the CLI separately or set `CLIPath` to point to an existing binary. See [Local CLI Setup](./local-cli.md) for details.
+> The Go SDK does not bundle the CLI. You must install the CLI separately or set `Connection` to point to an existing binary. See [Local CLI Setup](./local-cli.md) for details.
```go
@@ -137,7 +137,7 @@ Console.WriteLine(response?.Data.Content);
Java
> [!NOTE]
-> The Java SDK does not bundle or embed the Copilot CLI. You must install the CLI separately and configure its path via `cliPath` or the `COPILOT_CLI_PATH` environment variable.
+> The Java SDK does not bundle or embed the Copilot CLI. You must install the CLI separately and configure its path via `Connection` or the `COPILOT_CLI_PATH` environment variable.
```java
import com.github.copilot.sdk.CopilotClient;
diff --git a/docs/setup/local-cli.md b/docs/setup/local-cli.md
index e7da4d937..4c7b5dced 100644
--- a/docs/setup/local-cli.md
+++ b/docs/setup/local-cli.md
@@ -6,7 +6,7 @@ Use a specific CLI binary instead of the SDK's bundled CLI. This is an advanced
## How it works
-By default, the Node.js, Python, and .NET SDKs include their own CLI dependency (see [Default Setup](./bundled-cli.md)). If you need to override this—for example, to use a system-installed CLI—you can use the `cliPath` option.
+By default, the Node.js, Python, and .NET SDKs include their own CLI dependency (see [Default Setup](./bundled-cli.md)). If you need to override this—for example, to use a system-installed CLI—you can use the `Connection` option.
```mermaid
flowchart LR
@@ -78,7 +78,7 @@ await client.stop()
Go
> [!NOTE]
-> The Go SDK does not bundle a CLI, so you must always provide `CLIPath`.
+> The Go SDK does not bundle a CLI, so you must always provide `Connection`.
```go
@@ -95,7 +95,7 @@ func main() {
ctx := context.Background()
client := copilot.NewClient(&copilot.ClientOptions{
- CLIPath: "/usr/local/bin/copilot",
+ Connection: copilot.StdioConnection{Path: "/usr/local/bin/copilot"},
})
if err := client.Start(ctx); err != nil {
log.Fatal(err)
@@ -115,7 +115,7 @@ func main() {
```go
client := copilot.NewClient(&copilot.ClientOptions{
- CLIPath: "/usr/local/bin/copilot",
+ Connection: copilot.StdioConnection{Path: "/usr/local/bin/copilot"},
})
if err := client.Start(ctx); err != nil {
log.Fatal(err)
diff --git a/docs/troubleshooting/debugging.md b/docs/troubleshooting/debugging.md
index f01beafc7..3beadb99f 100644
--- a/docs/troubleshooting/debugging.md
+++ b/docs/troubleshooting/debugging.md
@@ -142,18 +142,25 @@ const client = new CopilotClient({
```go
package main
+import copilot "github.com/github/copilot-sdk/go"
+
func main() {
- // The Go SDK does not currently support passing extra CLI arguments.
- // For custom log directories, run the CLI manually with --log-dir
- // and connect via CLIUrl option.
+ client := copilot.NewClient(&copilot.ClientOptions{
+ Connection: copilot.StdioConnection{
+ Args: []string{"--log-dir", "/path/to/logs"},
+ },
+ })
+ _ = client
}
```
```go
-// The Go SDK does not currently support passing extra CLI arguments.
-// For custom log directories, run the CLI manually with --log-dir
-// and connect via CLIUrl option.
+client := copilot.NewClient(&copilot.ClientOptions{
+ Connection: copilot.StdioConnection{
+ Args: []string{"--log-dir", "/path/to/logs"},
+ },
+})
```
@@ -221,7 +228,7 @@ var client = new CopilotClient(new CopilotClientOptions
```go
client := copilot.NewClient(&copilot.ClientOptions{
- CLIPath: "/usr/local/bin/copilot",
+ Connection: copilot.StdioConnection{Path: "/usr/local/bin/copilot"},
})
```
diff --git a/go/README.md b/go/README.md
index b84bbab91..8dcffb1a8 100644
--- a/go/README.md
+++ b/go/README.md
@@ -94,7 +94,7 @@ Follow these steps to embed the CLI:
1. Run `go get -tool github.com/github/copilot-sdk/go/cmd/bundler`. This is a one-time setup step per project.
2. Run `go tool bundler` in your build environment just before building your application.
-That's it! When your application calls `copilot.NewClient` without a `CLIPath` nor the `COPILOT_CLI_PATH` environment variable, the SDK will automatically install the embedded CLI to a cache directory and use it for all operations.
+That's it! When your application calls `copilot.NewClient` without a `Connection` field (or with an empty `StdioConnection{}`) and no `COPILOT_CLI_PATH` environment variable, the SDK will automatically install the embedded CLI to a cache directory and use it for all operations.
## API Reference
@@ -110,8 +110,8 @@ That's it! When your application calls `copilot.NewClient` without a `CLIPath` n
- `ListSessions(filter *SessionListFilter) ([]SessionMetadata, error)` - List sessions (with optional filter)
- `DeleteSession(sessionID string) error` - Delete a session permanently
- `GetLastSessionID(ctx context.Context) (*string, error)` - Get the ID of the most recently updated session
-- `GetState() ConnectionState` - Get connection state
- `Ping(message string) (*PingResponse, error)` - Ping the server
+- `RuntimePort() int` - TCP port the runtime is listening on (0 if stdio)
- `GetForegroundSessionID(ctx context.Context) (*string, error)` - Get the session ID currently displayed in TUI (TUI+server mode only)
- `SetForegroundSessionID(ctx context.Context, sessionID string) error` - Request TUI to display a specific session (TUI+server mode only)
- `On(handler SessionLifecycleHandler) func()` - Subscribe to all lifecycle events; returns unsubscribe function
@@ -136,18 +136,20 @@ Event types: `SessionLifecycleCreated`, `SessionLifecycleDeleted`, `SessionLifec
**ClientOptions:**
-- `CLIPath` (string): Path to CLI executable (default: "copilot" or `COPILOT_CLI_PATH` env var)
-- `CLIUrl` (string): URL of existing CLI server (e.g., `"localhost:8080"`, `"http://127.0.0.1:9000"`, or just `"8080"`). When provided, the client will not spawn a CLI process.
-- `Cwd` (string): Working directory for CLI process
-- `CopilotHome` (string): Base directory for Copilot data (session state, config, etc.). Sets `COPILOT_HOME` on the spawned CLI process. When empty, the CLI defaults to `~/.copilot`. Useful in restricted environments where only specific directories are writable. Ignored when using `CLIUrl`. This does **not** affect where the Go SDK extracts the embedded CLI binary; use `embeddedcli.Config.Dir` for the extraction/cache location. You can vary `CopilotHome` per client independently of the shared extracted binary location.
-- `Port` (int): Server port for TCP mode (default: 0 for random)
-- `UseStdio` (bool): Use stdio transport instead of TCP (default: true)
-- `LogLevel` (string): Log level (default: "info")
-- `AutoStart` (\*bool): Auto-start server on first use (default: true). Use `Bool(false)` to disable.
-- `Env` ([]string): Environment variables for CLI process (default: inherits from current process)
+- `Connection` (RuntimeConnection): How the SDK connects to the runtime. Construct via one of:
+ - `StdioConnection{Path, Args}` — spawn a runtime over stdio (the default if `Connection` is nil)
+ - `TcpConnection{Port, ConnectionToken, Path, Args}` — spawn a runtime that listens on TCP
+ - `UriConnection{URL, ConnectionToken}` — connect to an already-running runtime (no process spawned)
+
+ When `Path` is empty for stdio/tcp, the SDK uses the bundled CLI (or `COPILOT_CLI_PATH` env var).
+- `Cwd` (string): Working directory for the runtime process
+- `BaseDirectory` (string): Base directory for Copilot data (session state, config, etc.). Sets `COPILOT_HOME` on the spawned runtime. When empty, the runtime defaults to `~/.copilot`. Ignored with `UriConnection`. This does **not** affect where the Go SDK extracts the embedded CLI binary; use `embeddedcli.Config.Dir` for the extraction/cache location.
+- `LogLevel` (string): Log level. When empty (default), the runtime uses its own default level (the SDK does not pass `--log-level`).
+- `Env` ([]string): Environment variables for the runtime process (default: inherits from current process)
- `GitHubToken` (string): GitHub token for authentication. When provided, takes priority over other auth methods.
-- `UseLoggedInUser` (\*bool): Whether to use logged-in user for authentication (default: true, but false when `GitHubToken` is provided). Cannot be used with `CLIUrl`.
-- `Telemetry` (\*TelemetryConfig): OpenTelemetry configuration for the CLI process. Providing this enables telemetry — no separate flag needed. See [Telemetry](#telemetry) below.
+- `UseLoggedInUser` (\*bool): Whether to use logged-in user for authentication (default: true, but false when `GitHubToken` is provided). Cannot be used with `UriConnection`.
+- `EnableRemoteSessions` (bool): Enable remote session support (Mission Control integration). Ignored with `UriConnection`.
+- `Telemetry` (\*TelemetryConfig): OpenTelemetry configuration for the runtime. Providing this enables telemetry — no separate flag needed. See [Telemetry](#telemetry) below.
**SessionConfig:**
@@ -160,7 +162,7 @@ Event types: `SessionLifecycleCreated`, `SessionLifecycleDeleted`, `SessionLifec
- **replace**: Replaces the entire prompt with `Content`
- **customize**: Selectively override individual sections via `Sections` map (keys: `SectionIdentity`, `SectionTone`, `SectionToolEfficiency`, `SectionEnvironmentContext`, `SectionCodeChangeRules`, `SectionGuidelines`, `SectionSafety`, `SectionToolInstructions`, `SectionCustomInstructions`, `SectionLastInstructions`; values: `SectionOverride` with `Action` and optional `Content`)
- `Provider` (\*ProviderConfig): Custom API provider configuration (BYOK). See [Custom Providers](#custom-providers) section.
-- `Streaming` (bool): Enable streaming delta events
+- `Streaming` (*bool): Enable streaming delta events (nil = runtime default)
- `InfiniteSessions` (\*InfiniteSessionConfig): Automatic context compaction configuration
- `OnPermissionRequest` (PermissionHandlerFunc): Optional handler called before each tool execution to approve or deny it. When nil, permission requests are emitted as events and left pending for manual resolution. Use `copilot.PermissionHandler.ApproveAll` to allow everything, or provide a custom function for fine-grained control. See [Permission Handling](#permission-handling) section.
- `OnUserInputRequest` (UserInputHandler): Handler for user input requests from the agent (enables ask_user tool). See [User Input Requests](#user-input-requests) section.
@@ -174,7 +176,7 @@ Event types: `SessionLifecycleCreated`, `SessionLifecycleDeleted`, `SessionLifec
- `Tools` ([]Tool): Tools to expose when resuming
- `ReasoningEffort` (string): Reasoning effort level for models that support it
- `Provider` (\*ProviderConfig): Custom API provider configuration (BYOK). See [Custom Providers](#custom-providers) section.
-- `Streaming` (bool): Enable streaming delta events
+- `Streaming` (*bool): Enable streaming delta events (nil = runtime default)
- `Commands` ([]CommandDefinition): Slash-commands. See [Commands](#commands) section.
- `OnElicitationRequest` (ElicitationHandler): Elicitation handler. See [Elicitation Requests](#elicitation-requests-serverclient) section.
@@ -183,15 +185,14 @@ Event types: `SessionLifecycleCreated`, `SessionLifecycleDeleted`, `SessionLifec
- `Send(ctx context.Context, options MessageOptions) (string, error)` - Send a message
- `On(handler SessionEventHandler) func()` - Subscribe to events (returns unsubscribe function)
- `Abort(ctx context.Context) error` - Abort the currently processing message
-- `GetMessages(ctx context.Context) ([]SessionEvent, error)` - Get message history
+- `GetEvents(ctx context.Context) ([]SessionEvent, error)` - Get event history
- `Disconnect() error` - Disconnect the session (releases in-memory resources, preserves disk state)
-- `Destroy() error` - _(Deprecated)_ Use `Disconnect()` instead
- `UI() *SessionUI` - Interactive UI API for elicitation dialogs
- `Capabilities() SessionCapabilities` - Host capabilities (e.g. elicitation support)
### Helper Functions
-- `Bool(v bool) *bool` - Helper to create bool pointers for `AutoStart` option
+- `Bool(v bool) *bool` - Helper to create bool pointers (e.g. for `Streaming`)
- `Int(v int) *int` - Helper to create int pointers for `MinLength`, `MaxLength`
- `String(v string) *string` - Helper to create string pointers
- `Float64(v float64) *float64` - Helper to create float64 pointers
@@ -398,7 +399,7 @@ func main() {
session, err := client.CreateSession(context.Background(), &copilot.SessionConfig{
Model: "gpt-5",
- Streaming: true,
+ Streaming: copilot.Bool(true),
})
if err != nil {
log.Fatal(err)
@@ -439,7 +440,7 @@ func main() {
}
```
-When `Streaming: true`:
+When `Streaming: copilot.Bool(true)`:
- `assistant.message_delta` events are sent with `DeltaContent` containing incremental text
- `assistant.reasoning_delta` events are sent with `DeltaContent` for reasoning/chain-of-thought (model-dependent)
@@ -797,7 +798,7 @@ confirmed, err := ui.Confirm(ctx, "Deploy to production?")
choice, ok, err := ui.Select(ctx, "Pick an environment", []string{"staging", "production"})
// Text input — returns (text, ok bool, error)
-name, ok, err := ui.Input(ctx, "Enter the release name", &copilot.InputOptions{
+name, ok, err := ui.Input(ctx, "Enter the release name", &copilot.UiInputOptions{
Title: "Release Name",
Description: "A short name for the release",
MinLength: copilot.Int(1),
diff --git a/go/client.go b/go/client.go
index 9fa772129..dab09a4dd 100644
--- a/go/client.go
+++ b/go/client.go
@@ -95,13 +95,17 @@ type Client struct {
client *jsonrpc2.Client
actualPort int
actualHost string
- state ConnectionState
+ state connectionState
sessions map[string]*Session
sessionsMux sync.Mutex
isExternalServer bool
conn net.Conn // stores net.Conn for external TCP connections
useStdio bool // resolved value from options
- autoStart bool // resolved value from options
+ // resolved process options for the spawned runtime (zero values for UriConnection)
+ cliPath string
+ cliArgs []string
+ port int
+ tcpConnectionToken string
modelsCache []ModelInfo
modelsCacheMux sync.Mutex
@@ -128,115 +132,81 @@ type Client struct {
internalRPC *rpc.InternalServerRpc
}
-// NewClient creates a new Copilot CLI client with the given options.
+// NewClient creates a new Copilot runtime client with the given options.
//
-// If options is nil, default options are used (spawns CLI server using stdio).
-// The client is not connected after creation; call [Client.Start] to connect.
+// If options is nil, default options are used (spawns the bundled runtime over
+// stdio). The client is not connected after creation; call [Client.Start] to
+// connect, or simply call [Client.CreateSession]/[Client.ResumeSession], which
+// auto-start the runtime on first use.
//
// Example:
//
-// // Default options
+// // Default options: bundled runtime over stdio
// client := copilot.NewClient(nil)
//
-// // Custom options
+// // Custom CLI path over stdio
// client := copilot.NewClient(&copilot.ClientOptions{
-// CLIPath: "/usr/local/bin/copilot",
-// LogLevel: "debug",
+// Connection: copilot.StdioConnection{Path: "/usr/local/bin/copilot"},
+// LogLevel: "debug",
+// })
+//
+// // Connect to an already-running runtime
+// client := copilot.NewClient(&copilot.ClientOptions{
+// Connection: copilot.UriConnection{URL: "localhost:8080"},
// })
func NewClient(options *ClientOptions) *Client {
- opts := ClientOptions{
- CLIPath: "",
- Cwd: "",
- Port: 0,
- LogLevel: "info",
- }
+ opts := ClientOptions{}
client := &Client{
options: opts,
- state: StateDisconnected,
+ state: stateDisconnected,
sessions: make(map[string]*Session),
actualHost: "localhost",
isExternalServer: false,
useStdio: true,
- autoStart: true, // default
}
if options != nil {
- // Validate mutually exclusive options
- if options.CLIUrl != "" && ((options.UseStdio != nil) || options.CLIPath != "") {
- panic("CLIUrl is mutually exclusive with UseStdio and CLIPath")
- }
+ opts = *options
+ }
- // Validate auth options with external server
- if options.CLIUrl != "" && (options.GitHubToken != "" || options.UseLoggedInUser != nil) {
- panic("GitHubToken and UseLoggedInUser cannot be used with CLIUrl (external server manages its own auth)")
+ // Resolve the connection. nil defaults to an empty StdioConnection.
+ connection := opts.Connection
+ if connection == nil {
+ connection = StdioConnection{}
+ }
+ switch conn := connection.(type) {
+ case StdioConnection:
+ client.useStdio = true
+ client.cliPath = conn.Path
+ if len(conn.Args) > 0 {
+ client.cliArgs = append([]string{}, conn.Args...)
}
-
- // Validate token vs stdio
- if options.TCPConnectionToken != "" && options.UseStdio != nil && *options.UseStdio {
- panic("TCPConnectionToken cannot be used with UseStdio: true")
+ case TcpConnection:
+ client.useStdio = false
+ client.cliPath = conn.Path
+ if len(conn.Args) > 0 {
+ client.cliArgs = append([]string{}, conn.Args...)
}
-
- // Parse CLIUrl if provided
- if options.CLIUrl != "" {
- host, port := parseCliUrl(options.CLIUrl)
- client.actualHost = host
- client.actualPort = port
- client.isExternalServer = true
- client.useStdio = false
- opts.CLIUrl = options.CLIUrl
+ client.port = conn.Port
+ client.tcpConnectionToken = conn.ConnectionToken
+ case UriConnection:
+ if conn.URL == "" {
+ panic("UriConnection requires a non-empty URL")
}
+ host, port := parseCliUrl(conn.URL)
+ client.actualHost = host
+ client.actualPort = port
+ client.isExternalServer = true
+ client.useStdio = false
+ client.tcpConnectionToken = conn.ConnectionToken
+ default:
+ panic(fmt.Sprintf("unknown RuntimeConnection type: %T", connection))
+ }
- if options.CLIPath != "" {
- opts.CLIPath = options.CLIPath
- }
- if len(options.CLIArgs) > 0 {
- opts.CLIArgs = append([]string{}, options.CLIArgs...)
- }
- if options.Cwd != "" {
- opts.Cwd = options.Cwd
- }
- if options.Port > 0 {
- opts.Port = options.Port
- // If port is specified, switch to TCP mode
- client.useStdio = false
- }
- if options.LogLevel != "" {
- opts.LogLevel = options.LogLevel
- }
- if options.Env != nil {
- opts.Env = options.Env
- }
- if options.UseStdio != nil {
- client.useStdio = *options.UseStdio
- }
- if options.AutoStart != nil {
- client.autoStart = *options.AutoStart
- }
- if options.GitHubToken != "" {
- opts.GitHubToken = options.GitHubToken
- }
- if options.UseLoggedInUser != nil {
- opts.UseLoggedInUser = options.UseLoggedInUser
- }
- if options.OnListModels != nil {
- client.onListModels = options.OnListModels
- }
- if options.SessionFs != nil {
- if err := validateSessionFsConfig(options.SessionFs); err != nil {
- panic(err.Error())
- }
- sessionFs := *options.SessionFs
- opts.SessionFs = &sessionFs
- }
- if options.Telemetry != nil {
- opts.Telemetry = options.Telemetry
- }
- if options.CopilotHome != "" {
- opts.CopilotHome = options.CopilotHome
- }
- opts.SessionIdleTimeoutSeconds = options.SessionIdleTimeoutSeconds
- opts.Remote = options.Remote
+ // Validate auth options when connecting to an external runtime.
+ if client.isExternalServer && (opts.GitHubToken != "" || opts.UseLoggedInUser != nil) {
+ panic("GitHubToken and UseLoggedInUser cannot be used with UriConnection (external runtime manages its own auth)")
}
// Default Env to current environment if not set
@@ -245,20 +215,29 @@ func NewClient(options *ClientOptions) *Client {
}
// Check effective environment for CLI path (only if not explicitly set via options)
- if opts.CLIPath == "" {
+ if client.cliPath == "" {
if cliPath := getEnvValue(opts.Env, "COPILOT_CLI_PATH"); cliPath != "" {
- opts.CLIPath = cliPath
+ client.cliPath = cliPath
}
}
// Resolve the effective connection token: explicit value if set; else if the SDK
- // spawns its own CLI in TCP mode, generate a UUID; otherwise empty.
- if options != nil && options.TCPConnectionToken != "" {
- client.effectiveConnectionToken = options.TCPConnectionToken
+ // spawns its own runtime in TCP mode, generate a UUID; otherwise empty.
+ if client.tcpConnectionToken != "" {
+ client.effectiveConnectionToken = client.tcpConnectionToken
} else if !client.useStdio && !client.isExternalServer {
client.effectiveConnectionToken = uuid.NewString()
}
+ if opts.OnListModels != nil {
+ client.onListModels = opts.OnListModels
+ }
+ if opts.SessionFs != nil {
+ if err := validateSessionFsConfig(opts.SessionFs); err != nil {
+ panic(err.Error())
+ }
+ }
+
client.options = opts
return client
}
@@ -342,17 +321,17 @@ func (c *Client) Start(ctx context.Context) error {
c.startStopMux.Lock()
defer c.startStopMux.Unlock()
- if c.state == StateConnected {
+ if c.state == stateConnected {
return nil
}
- c.state = StateConnecting
+ c.state = stateConnecting
// Only start CLI server process if not connecting to external server
if !c.isExternalServer {
if err := c.startCLIServer(ctx); err != nil {
c.process = nil
- c.state = StateError
+ c.state = stateError
return err
}
}
@@ -360,14 +339,14 @@ func (c *Client) Start(ctx context.Context) error {
// Connect to the server
if err := c.connectToServer(ctx); err != nil {
killErr := c.killProcess()
- c.state = StateError
+ c.state = stateError
return errors.Join(err, killErr)
}
// Verify protocol version compatibility
if err := c.verifyProtocolVersion(ctx); err != nil {
killErr := c.killProcess()
- c.state = StateError
+ c.state = stateError
return errors.Join(err, killErr)
}
@@ -387,12 +366,12 @@ func (c *Client) Start(ctx context.Context) error {
_, err := c.RPC.SessionFs.SetProvider(ctx, req)
if err != nil {
killErr := c.killProcess()
- c.state = StateError
+ c.state = stateError
return errors.Join(err, killErr)
}
}
- c.state = StateConnected
+ c.state = stateConnected
return nil
}
@@ -465,7 +444,7 @@ func (c *Client) Stop() error {
c.modelsCache = nil
c.modelsCacheMux.Unlock()
- c.state = StateDisconnected
+ c.state = stateDisconnected
if !c.isExternalServer {
c.actualPort = 0
}
@@ -537,7 +516,7 @@ func (c *Client) ForceStop() {
c.modelsCache = nil
c.modelsCacheMux.Unlock()
- c.state = StateDisconnected
+ c.state = stateDisconnected
if !c.isExternalServer {
c.actualPort = 0
}
@@ -550,17 +529,13 @@ func (c *Client) ensureConnected(ctx context.Context) error {
if c.client != nil {
return nil
}
- if c.autoStart {
- return c.Start(ctx)
- }
- return fmt.Errorf("client not connected. Call Start() first")
+ return c.Start(ctx)
}
// CreateSession creates a new conversation session with the Copilot CLI.
//
// Sessions maintain conversation state, handle events, and manage tool execution.
-// If the client is not connected and AutoStart is enabled, this will automatically
-// start the connection.
+// If the client is not connected, this will automatically start the runtime.
//
// The config parameter is optional. If no OnPermissionRequest handler is provided,
// permission requests are surfaced as events for the caller to resolve manually.
@@ -664,15 +639,15 @@ func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Ses
if config.OnElicitationRequest != nil {
req.RequestElicitation = Bool(true)
}
- if config.OnExitPlanMode != nil {
+ if config.OnExitPlanModeRequest != nil {
req.RequestExitPlanMode = Bool(true)
}
- if config.OnAutoModeSwitch != nil {
+ if config.OnAutoModeSwitchRequest != nil {
req.RequestAutoModeSwitch = Bool(true)
}
- if config.Streaming {
- req.Streaming = Bool(true)
+ if config.Streaming != nil {
+ req.Streaming = config.Streaming
}
if config.IncludeSubAgentStreamingEvents != nil {
req.IncludeSubAgentStreamingEvents = config.IncludeSubAgentStreamingEvents
@@ -726,11 +701,11 @@ func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Ses
if config.OnElicitationRequest != nil {
session.registerElicitationHandler(config.OnElicitationRequest)
}
- if config.OnExitPlanMode != nil {
- session.registerExitPlanModeHandler(config.OnExitPlanMode)
+ if config.OnExitPlanModeRequest != nil {
+ session.registerExitPlanModeHandler(config.OnExitPlanModeRequest)
}
- if config.OnAutoModeSwitch != nil {
- session.registerAutoModeSwitchHandler(config.OnAutoModeSwitch)
+ if config.OnAutoModeSwitchRequest != nil {
+ session.registerAutoModeSwitchHandler(config.OnAutoModeSwitchRequest)
}
c.sessionsMux.Lock()
@@ -738,13 +713,13 @@ func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Ses
c.sessionsMux.Unlock()
if c.options.SessionFs != nil {
- if config.CreateSessionFsHandler == nil {
+ if config.CreateSessionFsProvider == nil {
c.sessionsMux.Lock()
delete(c.sessions, sessionID)
c.sessionsMux.Unlock()
- return nil, fmt.Errorf("CreateSessionFsHandler is required in session config when SessionFs is enabled in client options")
+ return nil, fmt.Errorf("CreateSessionFsProvider is required in session config when SessionFs is enabled in client options")
}
- provider := config.CreateSessionFsHandler(session)
+ provider := config.CreateSessionFsProvider(session)
if c.options.SessionFs.Capabilities != nil && c.options.SessionFs.Capabilities.Sqlite {
if _, ok := provider.(SessionFsSqliteProvider); !ok {
c.sessionsMux.Lock()
@@ -823,8 +798,8 @@ func (c *Client) ResumeSessionWithOptions(ctx context.Context, sessionID string,
req.ModelCapabilities = config.ModelCapabilities
req.AvailableTools = config.AvailableTools
req.ExcludedTools = config.ExcludedTools
- if config.Streaming {
- req.Streaming = Bool(true)
+ if config.Streaming != nil {
+ req.Streaming = config.Streaming
}
if config.IncludeSubAgentStreamingEvents != nil {
req.IncludeSubAgentStreamingEvents = config.IncludeSubAgentStreamingEvents
@@ -847,7 +822,7 @@ func (c *Client) ResumeSessionWithOptions(ctx context.Context, sessionID string,
if config.EnableConfigDiscovery {
req.EnableConfigDiscovery = Bool(true)
}
- if config.DisableResume {
+ if config.SuppressResumeEvent {
req.DisableResume = Bool(true)
}
if config.ContinuePendingWork {
@@ -876,10 +851,10 @@ func (c *Client) ResumeSessionWithOptions(ctx context.Context, sessionID string,
if config.OnElicitationRequest != nil {
req.RequestElicitation = Bool(true)
}
- if config.OnExitPlanMode != nil {
+ if config.OnExitPlanModeRequest != nil {
req.RequestExitPlanMode = Bool(true)
}
- if config.OnAutoModeSwitch != nil {
+ if config.OnAutoModeSwitchRequest != nil {
req.RequestAutoModeSwitch = Bool(true)
}
@@ -911,11 +886,11 @@ func (c *Client) ResumeSessionWithOptions(ctx context.Context, sessionID string,
if config.OnElicitationRequest != nil {
session.registerElicitationHandler(config.OnElicitationRequest)
}
- if config.OnExitPlanMode != nil {
- session.registerExitPlanModeHandler(config.OnExitPlanMode)
+ if config.OnExitPlanModeRequest != nil {
+ session.registerExitPlanModeHandler(config.OnExitPlanModeRequest)
}
- if config.OnAutoModeSwitch != nil {
- session.registerAutoModeSwitchHandler(config.OnAutoModeSwitch)
+ if config.OnAutoModeSwitchRequest != nil {
+ session.registerAutoModeSwitchHandler(config.OnAutoModeSwitchRequest)
}
c.sessionsMux.Lock()
@@ -923,13 +898,13 @@ func (c *Client) ResumeSessionWithOptions(ctx context.Context, sessionID string,
c.sessionsMux.Unlock()
if c.options.SessionFs != nil {
- if config.CreateSessionFsHandler == nil {
+ if config.CreateSessionFsProvider == nil {
c.sessionsMux.Lock()
delete(c.sessions, sessionID)
c.sessionsMux.Unlock()
- return nil, fmt.Errorf("CreateSessionFsHandler is required in session config when SessionFs is enabled in client options")
+ return nil, fmt.Errorf("CreateSessionFsProvider is required in session config when SessionFs is enabled in client options")
}
- provider := config.CreateSessionFsHandler(session)
+ provider := config.CreateSessionFsProvider(session)
if c.options.SessionFs.Capabilities != nil && c.options.SessionFs.Capabilities.Sqlite {
if _, ok := provider.(SessionFsSqliteProvider); !ok {
c.sessionsMux.Lock()
@@ -1278,26 +1253,9 @@ func (c *Client) handleLifecycleEvent(event SessionLifecycleEvent) {
}
}
-// State returns the current connection state of the client.
-//
-// Possible states: StateDisconnected, StateConnecting, StateConnected, StateError.
-//
-// Example:
-//
-// if client.State() == copilot.StateConnected {
-// session, err := client.CreateSession(context.Background(), &copilot.SessionConfig{
-// OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
-// })
-// }
-func (c *Client) State() ConnectionState {
- c.startStopMux.RLock()
- defer c.startStopMux.RUnlock()
- return c.state
-}
-
-// ActualPort returns the TCP port the CLI server is listening on.
+// RuntimePort returns the TCP port the runtime is listening on.
// Returns 0 if the client is not connected or using stdio transport.
-func (c *Client) ActualPort() int {
+func (c *Client) RuntimePort() int {
return c.actualPort
}
@@ -1478,7 +1436,7 @@ const stderrBufferSize = 64 * 1024
// This spawns the CLI server as a subprocess using the configured transport
// mode (stdio or TCP).
func (c *Client) startCLIServer(ctx context.Context) error {
- cliPath := c.options.CLIPath
+ cliPath := c.cliPath
if cliPath == "" {
// If no CLI path is provided, attempt to use the embedded CLI if available
cliPath = embeddedcli.Path()
@@ -1489,14 +1447,19 @@ func (c *Client) startCLIServer(ctx context.Context) error {
}
// Start with user-provided CLIArgs, then add SDK-managed args
- args := append([]string{}, c.options.CLIArgs...)
- args = append(args, "--headless", "--no-auto-update", "--log-level", c.options.LogLevel)
+ args := append([]string{}, c.cliArgs...)
+ args = append(args, "--headless", "--no-auto-update")
+ // Only pass --log-level when explicitly configured; otherwise let the
+ // runtime use its own default.
+ if c.options.LogLevel != "" {
+ args = append(args, "--log-level", c.options.LogLevel)
+ }
// Choose transport mode
if c.useStdio {
args = append(args, "--stdio")
- } else if c.options.Port > 0 {
- args = append(args, "--port", strconv.Itoa(c.options.Port))
+ } else if c.port > 0 {
+ args = append(args, "--port", strconv.Itoa(c.port))
}
// Add auth-related flags
@@ -1518,7 +1481,7 @@ func (c *Client) startCLIServer(ctx context.Context) error {
args = append(args, "--session-idle-timeout", strconv.Itoa(c.options.SessionIdleTimeoutSeconds))
}
- if c.options.Remote {
+ if c.options.EnableRemoteSessions {
args = append(args, "--remote")
}
@@ -1549,8 +1512,8 @@ func (c *Client) startCLIServer(ctx context.Context) error {
c.process.Env = setEnvValue(c.process.Env, "COPILOT_CONNECTION_TOKEN", c.effectiveConnectionToken)
}
- if c.options.CopilotHome != "" {
- c.process.Env = setEnvValue(c.process.Env, "COPILOT_HOME", c.options.CopilotHome)
+ if c.options.BaseDirectory != "" {
+ c.process.Env = setEnvValue(c.process.Env, "COPILOT_HOME", c.options.BaseDirectory)
}
if c.options.Telemetry != nil {
@@ -1606,7 +1569,7 @@ func (c *Client) startCLIServer(ctx context.Context) error {
go func() {
c.startStopMux.Lock()
defer c.startStopMux.Unlock()
- c.state = StateDisconnected
+ c.state = stateDisconnected
}()
})
c.RPC = rpc.NewServerRpc(c.client)
@@ -1759,7 +1722,7 @@ func (c *Client) connectViaTcp(ctx context.Context) error {
go func() {
c.startStopMux.Lock()
defer c.startStopMux.Unlock()
- c.state = StateDisconnected
+ c.state = stateDisconnected
}()
})
c.RPC = rpc.NewServerRpc(c.client)
diff --git a/go/client_test.go b/go/client_test.go
index 9dd0080cc..f249a8fa6 100644
--- a/go/client_test.go
+++ b/go/client_test.go
@@ -22,9 +22,8 @@ import (
func TestClient_URLParsing(t *testing.T) {
t.Run("should parse port-only URL format", func(t *testing.T) {
client := NewClient(&ClientOptions{
- CLIUrl: "8080",
+ Connection: UriConnection{URL: "8080"},
})
-
if client.actualPort != 8080 {
t.Errorf("Expected port 8080, got %d", client.actualPort)
}
@@ -38,193 +37,99 @@ func TestClient_URLParsing(t *testing.T) {
t.Run("should parse host:port URL format", func(t *testing.T) {
client := NewClient(&ClientOptions{
- CLIUrl: "127.0.0.1:9000",
+ Connection: UriConnection{URL: "127.0.0.1:9000"},
})
-
- if client.actualPort != 9000 {
- t.Errorf("Expected port 9000, got %d", client.actualPort)
- }
- if client.actualHost != "127.0.0.1" {
- t.Errorf("Expected host 127.0.0.1, got %s", client.actualHost)
- }
- if !client.isExternalServer {
- t.Error("Expected isExternalServer to be true")
+ if client.actualPort != 9000 || client.actualHost != "127.0.0.1" {
+ t.Errorf("Expected 127.0.0.1:9000, got %s:%d", client.actualHost, client.actualPort)
}
})
t.Run("should parse http://host:port URL format", func(t *testing.T) {
client := NewClient(&ClientOptions{
- CLIUrl: "http://localhost:7000",
+ Connection: UriConnection{URL: "http://localhost:7000"},
})
-
- if client.actualPort != 7000 {
- t.Errorf("Expected port 7000, got %d", client.actualPort)
- }
- if client.actualHost != "localhost" {
- t.Errorf("Expected host localhost, got %s", client.actualHost)
- }
- if !client.isExternalServer {
- t.Error("Expected isExternalServer to be true")
+ if client.actualPort != 7000 || client.actualHost != "localhost" {
+ t.Errorf("Expected localhost:7000, got %s:%d", client.actualHost, client.actualPort)
}
})
t.Run("should parse https://host:port URL format", func(t *testing.T) {
client := NewClient(&ClientOptions{
- CLIUrl: "https://example.com:443",
+ Connection: UriConnection{URL: "https://example.com:443"},
})
-
- if client.actualPort != 443 {
- t.Errorf("Expected port 443, got %d", client.actualPort)
- }
- if client.actualHost != "example.com" {
- t.Errorf("Expected host example.com, got %s", client.actualHost)
- }
- if !client.isExternalServer {
- t.Error("Expected isExternalServer to be true")
+ if client.actualPort != 443 || client.actualHost != "example.com" {
+ t.Errorf("Expected example.com:443, got %s:%d", client.actualHost, client.actualPort)
}
})
- t.Run("should throw error for invalid URL format", func(t *testing.T) {
+ t.Run("should panic for invalid URL format", func(t *testing.T) {
defer func() {
if r := recover(); r == nil {
t.Error("Expected panic for invalid URL format")
- } else {
- matched, _ := regexp.MatchString("Invalid port in CLIUrl", r.(string))
- if !matched {
- t.Errorf("Expected panic message to contain 'Invalid port in CLIUrl', got: %v", r)
- }
}
}()
-
- NewClient(&ClientOptions{
- CLIUrl: "invalid-url",
- })
+ NewClient(&ClientOptions{Connection: UriConnection{URL: "invalid-url"}})
})
- t.Run("should throw error for invalid port - too high", func(t *testing.T) {
+ t.Run("should panic for invalid port - too high", func(t *testing.T) {
defer func() {
if r := recover(); r == nil {
- t.Error("Expected panic for invalid port")
- } else {
- matched, _ := regexp.MatchString("Invalid port in CLIUrl", r.(string))
- if !matched {
- t.Errorf("Expected panic message to contain 'Invalid port in CLIUrl', got: %v", r)
- }
+ t.Error("Expected panic")
}
}()
-
- NewClient(&ClientOptions{
- CLIUrl: "localhost:99999",
- })
+ NewClient(&ClientOptions{Connection: UriConnection{URL: "localhost:99999"}})
})
- t.Run("should throw error for invalid port - zero", func(t *testing.T) {
+ t.Run("should panic for invalid port - zero", func(t *testing.T) {
defer func() {
if r := recover(); r == nil {
- t.Error("Expected panic for invalid port")
- } else {
- matched, _ := regexp.MatchString("Invalid port in CLIUrl", r.(string))
- if !matched {
- t.Errorf("Expected panic message to contain 'Invalid port in CLIUrl', got: %v", r)
- }
+ t.Error("Expected panic")
}
}()
-
- NewClient(&ClientOptions{
- CLIUrl: "localhost:0",
- })
+ NewClient(&ClientOptions{Connection: UriConnection{URL: "localhost:0"}})
})
- t.Run("should throw error for invalid port - negative", func(t *testing.T) {
+ t.Run("should panic for invalid port - negative", func(t *testing.T) {
defer func() {
if r := recover(); r == nil {
- t.Error("Expected panic for invalid port")
- } else {
- matched, _ := regexp.MatchString("Invalid port in CLIUrl", r.(string))
- if !matched {
- t.Errorf("Expected panic message to contain 'Invalid port in CLIUrl', got: %v", r)
- }
+ t.Error("Expected panic")
}
}()
-
- NewClient(&ClientOptions{
- CLIUrl: "localhost:-1",
- })
- })
-
- t.Run("should throw error when CLIUrl is used with UseStdio", func(t *testing.T) {
- defer func() {
- if r := recover(); r == nil {
- t.Error("Expected panic for mutually exclusive options")
- } else {
- matched, _ := regexp.MatchString("CLIUrl is mutually exclusive", r.(string))
- if !matched {
- t.Errorf("Expected panic message to contain 'CLIUrl is mutually exclusive', got: %v", r)
- }
- }
- }()
-
- NewClient(&ClientOptions{
- CLIUrl: "localhost:8080",
- UseStdio: Bool(true),
- })
+ NewClient(&ClientOptions{Connection: UriConnection{URL: "localhost:-1"}})
})
- t.Run("should throw error when CLIUrl is used with CLIPath", func(t *testing.T) {
+ t.Run("should panic when UriConnection has empty URL", func(t *testing.T) {
defer func() {
if r := recover(); r == nil {
- t.Error("Expected panic for mutually exclusive options")
- } else {
- matched, _ := regexp.MatchString("CLIUrl is mutually exclusive", r.(string))
- if !matched {
- t.Errorf("Expected panic message to contain 'CLIUrl is mutually exclusive', got: %v", r)
- }
+ t.Error("Expected panic for empty URL")
}
}()
-
- NewClient(&ClientOptions{
- CLIUrl: "localhost:8080",
- CLIPath: "/path/to/cli",
- })
+ NewClient(&ClientOptions{Connection: UriConnection{}})
})
- t.Run("should set UseStdio to false when CLIUrl is provided", func(t *testing.T) {
- client := NewClient(&ClientOptions{
- CLIUrl: "8080",
- })
-
- if client.useStdio {
- t.Error("Expected UseStdio to be false when CLIUrl is provided")
- }
- })
-
- t.Run("should set UseStdio to true when UseStdio is set to true", func(t *testing.T) {
- client := NewClient(&ClientOptions{
- UseStdio: Bool(true),
- })
-
+ t.Run("stdio connection uses stdio transport", func(t *testing.T) {
+ client := NewClient(&ClientOptions{Connection: StdioConnection{}})
if !client.useStdio {
- t.Error("Expected UseStdio to be true when UseStdio is set to true")
+ t.Error("Expected useStdio=true for StdioConnection")
}
})
- t.Run("should set UseStdio to false when UseStdio is set to false", func(t *testing.T) {
- client := NewClient(&ClientOptions{
- UseStdio: Bool(false),
- })
-
+ t.Run("tcp connection uses tcp transport", func(t *testing.T) {
+ client := NewClient(&ClientOptions{Connection: TcpConnection{Port: 8080}})
if client.useStdio {
- t.Error("Expected UseStdio to be false when UseStdio is set to false")
+ t.Error("Expected useStdio=false for TcpConnection")
+ }
+ if client.port != 8080 {
+ t.Errorf("Expected port=8080, got %d", client.port)
}
})
- t.Run("should mark client as using external server", func(t *testing.T) {
+ t.Run("uri connection is treated as external server", func(t *testing.T) {
client := NewClient(&ClientOptions{
- CLIUrl: "localhost:8080",
+ Connection: UriConnection{URL: "localhost:8080"},
})
-
if !client.isExternalServer {
- t.Error("Expected isExternalServer to be true when CLIUrl is provided")
+ t.Error("Expected isExternalServer=true for UriConnection")
}
})
}
@@ -311,12 +216,12 @@ func TestClient_AuthOptions(t *testing.T) {
}
})
- t.Run("should throw error when GitHubToken is used with CLIUrl", func(t *testing.T) {
+ t.Run("should panic when GitHubToken is used with UriConnection", func(t *testing.T) {
defer func() {
if r := recover(); r == nil {
- t.Error("Expected panic for auth options with CLIUrl")
+ t.Error("Expected panic for auth options with UriConnection")
} else {
- matched, _ := regexp.MatchString("GitHubToken and UseLoggedInUser cannot be used with CLIUrl", r.(string))
+ matched, _ := regexp.MatchString("GitHubToken and UseLoggedInUser cannot be used with UriConnection", r.(string))
if !matched {
t.Errorf("Expected panic message about auth options, got: %v", r)
}
@@ -324,46 +229,41 @@ func TestClient_AuthOptions(t *testing.T) {
}()
NewClient(&ClientOptions{
- CLIUrl: "localhost:8080",
+ Connection: UriConnection{URL: "localhost:8080"},
GitHubToken: "gho_test_token",
})
})
- t.Run("should throw error when UseLoggedInUser is used with CLIUrl", func(t *testing.T) {
+ t.Run("should panic when UseLoggedInUser is used with UriConnection", func(t *testing.T) {
defer func() {
if r := recover(); r == nil {
- t.Error("Expected panic for auth options with CLIUrl")
- } else {
- matched, _ := regexp.MatchString("GitHubToken and UseLoggedInUser cannot be used with CLIUrl", r.(string))
- if !matched {
- t.Errorf("Expected panic message about auth options, got: %v", r)
- }
+ t.Error("Expected panic for auth options with UriConnection")
}
}()
NewClient(&ClientOptions{
- CLIUrl: "localhost:8080",
+ Connection: UriConnection{URL: "localhost:8080"},
UseLoggedInUser: Bool(false),
})
})
}
-func TestClient_CopilotHome(t *testing.T) {
- t.Run("should accept CopilotHome option", func(t *testing.T) {
+func TestClient_BaseDirectory(t *testing.T) {
+ t.Run("should accept BaseDirectory option", func(t *testing.T) {
client := NewClient(&ClientOptions{
- CopilotHome: "/custom/copilot/home",
+ BaseDirectory: "/custom/copilot/home",
})
- if client.options.CopilotHome != "/custom/copilot/home" {
- t.Errorf("Expected CopilotHome to be '/custom/copilot/home', got %q", client.options.CopilotHome)
+ if client.options.BaseDirectory != "/custom/copilot/home" {
+ t.Errorf("Expected BaseDirectory to be '/custom/copilot/home', got %q", client.options.BaseDirectory)
}
})
- t.Run("should default CopilotHome to empty string", func(t *testing.T) {
+ t.Run("should default BaseDirectory to empty string", func(t *testing.T) {
client := NewClient(&ClientOptions{})
- if client.options.CopilotHome != "" {
- t.Errorf("Expected CopilotHome to be empty, got %q", client.options.CopilotHome)
+ if client.options.BaseDirectory != "" {
+ t.Errorf("Expected BaseDirectory to be empty, got %q", client.options.BaseDirectory)
}
})
}
@@ -658,7 +558,7 @@ func TestOverridesBuiltInTool(t *testing.T) {
func TestClient_CreateSession_AllowsMissingPermissionHandler(t *testing.T) {
t.Run("accepts nil config before connection validation", func(t *testing.T) {
- client := NewClient(&ClientOptions{AutoStart: Bool(false)})
+ client := NewClient(&ClientOptions{Connection: StdioConnection{Path: "/__nonexistent_copilot_binary__"}})
_, err := client.CreateSession(t.Context(), nil)
if err == nil {
t.Fatal("Expected error when client is not connected")
@@ -669,7 +569,7 @@ func TestClient_CreateSession_AllowsMissingPermissionHandler(t *testing.T) {
})
t.Run("accepts missing OnPermissionRequest before connection validation", func(t *testing.T) {
- client := NewClient(&ClientOptions{AutoStart: Bool(false)})
+ client := NewClient(&ClientOptions{Connection: StdioConnection{Path: "/__nonexistent_copilot_binary__"}})
_, err := client.CreateSession(t.Context(), &SessionConfig{})
if err == nil {
t.Fatal("Expected error when client is not connected")
@@ -682,7 +582,7 @@ func TestClient_CreateSession_AllowsMissingPermissionHandler(t *testing.T) {
func TestClient_ResumeSession_AllowsMissingPermissionHandler(t *testing.T) {
t.Run("accepts nil config before connection validation", func(t *testing.T) {
- client := NewClient(&ClientOptions{AutoStart: Bool(false)})
+ client := NewClient(&ClientOptions{Connection: StdioConnection{Path: "/__nonexistent_copilot_binary__"}})
_, err := client.ResumeSessionWithOptions(t.Context(), "some-id", nil)
if err == nil {
t.Fatal("Expected error when client is not connected")
@@ -758,7 +658,7 @@ func TestClient_StartContextCancellationDoesNotKillProcess(t *testing.T) {
t.Skip("CLI not found")
}
- client := NewClient(&ClientOptions{CLIPath: cliPath})
+ client := NewClient(&ClientOptions{Connection: StdioConnection{Path: cliPath}})
t.Cleanup(func() { client.ForceStop() })
// Start with a context, then cancel it after the client is connected.
@@ -783,7 +683,7 @@ func TestClient_StartStopRace(t *testing.T) {
if cliPath == "" {
t.Skip("CLI not found")
}
- client := NewClient(&ClientOptions{CLIPath: cliPath})
+ client := NewClient(&ClientOptions{Connection: StdioConnection{Path: cliPath}})
defer client.ForceStop()
errChan := make(chan error)
wg := sync.WaitGroup{}
diff --git a/go/internal/e2e/abort_e2e_test.go b/go/internal/e2e/abort_e2e_test.go
index d71af962e..095345688 100644
--- a/go/internal/e2e/abort_e2e_test.go
+++ b/go/internal/e2e/abort_e2e_test.go
@@ -22,7 +22,7 @@ func TestAbortE2E(t *testing.T) {
session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{
OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
- Streaming: true,
+ Streaming: copilot.Bool(true),
})
if err != nil {
t.Fatalf("Failed to create session: %v", err)
diff --git a/go/internal/e2e/agent_and_compact_rpc_e2e_test.go b/go/internal/e2e/agent_and_compact_rpc_e2e_test.go
index a0d563c8b..cfb879917 100644
--- a/go/internal/e2e/agent_and_compact_rpc_e2e_test.go
+++ b/go/internal/e2e/agent_and_compact_rpc_e2e_test.go
@@ -19,8 +19,7 @@ func TestAgentSelectionRpcE2E(t *testing.T) {
t.Run("should list available custom agents", func(t *testing.T) {
client := copilot.NewClient(&copilot.ClientOptions{
- CLIPath: cliPath,
- UseStdio: copilot.Bool(true),
+ Connection: copilot.StdioConnection{Path: cliPath},
})
t.Cleanup(func() { client.ForceStop() })
@@ -74,8 +73,7 @@ func TestAgentSelectionRpcE2E(t *testing.T) {
t.Run("should return null when no agent is selected", func(t *testing.T) {
client := copilot.NewClient(&copilot.ClientOptions{
- CLIPath: cliPath,
- UseStdio: copilot.Bool(true),
+ Connection: copilot.StdioConnection{Path: cliPath},
})
t.Cleanup(func() { client.ForceStop() })
@@ -114,8 +112,7 @@ func TestAgentSelectionRpcE2E(t *testing.T) {
t.Run("should select and get current agent", func(t *testing.T) {
client := copilot.NewClient(&copilot.ClientOptions{
- CLIPath: cliPath,
- UseStdio: copilot.Bool(true),
+ Connection: copilot.StdioConnection{Path: cliPath},
})
t.Cleanup(func() { client.ForceStop() })
@@ -169,8 +166,7 @@ func TestAgentSelectionRpcE2E(t *testing.T) {
t.Run("should deselect current agent", func(t *testing.T) {
client := copilot.NewClient(&copilot.ClientOptions{
- CLIPath: cliPath,
- UseStdio: copilot.Bool(true),
+ Connection: copilot.StdioConnection{Path: cliPath},
})
t.Cleanup(func() { client.ForceStop() })
@@ -220,8 +216,7 @@ func TestAgentSelectionRpcE2E(t *testing.T) {
t.Run("should return no custom agents when none configured", func(t *testing.T) {
client := copilot.NewClient(&copilot.ClientOptions{
- CLIPath: cliPath,
- UseStdio: copilot.Bool(true),
+ Connection: copilot.StdioConnection{Path: cliPath},
})
t.Cleanup(func() { client.ForceStop() })
@@ -264,8 +259,7 @@ func TestAgentSelectionRpcE2E(t *testing.T) {
}
client := copilot.NewClient(&copilot.ClientOptions{
- CLIPath: cliPath,
- UseStdio: copilot.Bool(true),
+ Connection: copilot.StdioConnection{Path: cliPath},
})
t.Cleanup(func() { client.ForceStop() })
diff --git a/go/internal/e2e/client_e2e_test.go b/go/internal/e2e/client_e2e_test.go
index 9fda3cd83..e4dfed2d4 100644
--- a/go/internal/e2e/client_e2e_test.go
+++ b/go/internal/e2e/client_e2e_test.go
@@ -16,8 +16,7 @@ func TestClientE2E(t *testing.T) {
t.Run("should start and connect to server using stdio", func(t *testing.T) {
client := copilot.NewClient(&copilot.ClientOptions{
- CLIPath: cliPath,
- UseStdio: copilot.Bool(true),
+ Connection: copilot.StdioConnection{Path: cliPath},
})
t.Cleanup(func() { client.ForceStop() })
@@ -25,10 +24,6 @@ func TestClientE2E(t *testing.T) {
t.Fatalf("Failed to start client: %v", err)
}
- if client.State() != copilot.StateConnected {
- t.Errorf("Expected state to be 'connected', got %q", client.State())
- }
-
pong, err := client.Ping(t.Context(), "test message")
if err != nil {
t.Fatalf("Failed to ping: %v", err)
@@ -45,16 +40,11 @@ func TestClientE2E(t *testing.T) {
if err := client.Stop(); err != nil {
t.Errorf("Expected no errors on stop, got %v", err)
}
-
- if client.State() != copilot.StateDisconnected {
- t.Errorf("Expected state to be 'disconnected', got %q", client.State())
- }
})
t.Run("should start and connect to server using tcp", func(t *testing.T) {
client := copilot.NewClient(&copilot.ClientOptions{
- CLIPath: cliPath,
- UseStdio: copilot.Bool(false),
+ Connection: copilot.TcpConnection{Path: cliPath},
})
t.Cleanup(func() { client.ForceStop() })
@@ -62,10 +52,6 @@ func TestClientE2E(t *testing.T) {
t.Fatalf("Failed to start client: %v", err)
}
- if client.State() != copilot.StateConnected {
- t.Errorf("Expected state to be 'connected', got %q", client.State())
- }
-
pong, err := client.Ping(t.Context(), "test message")
if err != nil {
t.Fatalf("Failed to ping: %v", err)
@@ -82,15 +68,11 @@ func TestClientE2E(t *testing.T) {
if err := client.Stop(); err != nil {
t.Errorf("Expected no errors on stop, got %v", err)
}
-
- if client.State() != copilot.StateDisconnected {
- t.Errorf("Expected state to be 'disconnected', got %q", client.State())
- }
})
t.Run("should return errors on failed cleanup", func(t *testing.T) {
client := copilot.NewClient(&copilot.ClientOptions{
- CLIPath: cliPath,
+ Connection: copilot.StdioConnection{Path: cliPath},
})
t.Cleanup(func() { client.ForceStop() })
@@ -108,15 +90,11 @@ func TestClientE2E(t *testing.T) {
if err := client.Stop(); err != nil {
t.Logf("Got expected errors: %v", err)
}
-
- if client.State() != copilot.StateDisconnected {
- t.Errorf("Expected state to be 'disconnected', got %q", client.State())
- }
})
t.Run("should forceStop without cleanup", func(t *testing.T) {
client := copilot.NewClient(&copilot.ClientOptions{
- CLIPath: cliPath,
+ Connection: copilot.StdioConnection{Path: cliPath},
})
t.Cleanup(func() { client.ForceStop() })
@@ -128,16 +106,11 @@ func TestClientE2E(t *testing.T) {
}
client.ForceStop()
-
- if client.State() != copilot.StateDisconnected {
- t.Errorf("Expected state to be 'disconnected', got %q", client.State())
- }
})
t.Run("should get status with version and protocol info", func(t *testing.T) {
client := copilot.NewClient(&copilot.ClientOptions{
- CLIPath: cliPath,
- UseStdio: copilot.Bool(true),
+ Connection: copilot.StdioConnection{Path: cliPath},
})
t.Cleanup(func() { client.ForceStop() })
@@ -163,8 +136,7 @@ func TestClientE2E(t *testing.T) {
t.Run("should get auth status", func(t *testing.T) {
client := copilot.NewClient(&copilot.ClientOptions{
- CLIPath: cliPath,
- UseStdio: copilot.Bool(true),
+ Connection: copilot.StdioConnection{Path: cliPath},
})
t.Cleanup(func() { client.ForceStop() })
@@ -192,8 +164,7 @@ func TestClientE2E(t *testing.T) {
t.Run("should list models when authenticated", func(t *testing.T) {
client := copilot.NewClient(&copilot.ClientOptions{
- CLIPath: cliPath,
- UseStdio: copilot.Bool(true),
+ Connection: copilot.StdioConnection{Path: cliPath},
})
t.Cleanup(func() { client.ForceStop() })
@@ -232,9 +203,10 @@ func TestClientE2E(t *testing.T) {
t.Run("should report error when CLI fails to start", func(t *testing.T) {
client := copilot.NewClient(&copilot.ClientOptions{
- CLIPath: cliPath,
- CLIArgs: []string{"--nonexistent-flag-for-testing"},
- UseStdio: copilot.Bool(true),
+ Connection: copilot.StdioConnection{
+ Path: cliPath,
+ Args: []string{"--nonexistent-flag-for-testing"},
+ },
})
t.Cleanup(func() { client.ForceStop() })
diff --git a/go/internal/e2e/client_lifecycle_e2e_test.go b/go/internal/e2e/client_lifecycle_e2e_test.go
index 4fde70081..dca15c615 100644
--- a/go/internal/e2e/client_lifecycle_e2e_test.go
+++ b/go/internal/e2e/client_lifecycle_e2e_test.go
@@ -129,16 +129,10 @@ func TestClientLifecycleE2E(t *testing.T) {
if err := client.Start(t.Context()); err != nil {
t.Fatalf("Failed to start client: %v", err)
}
- if client.State() != copilot.StateConnected {
- t.Errorf("Expected state to be connected after Start, got %q", client.State())
- }
if err := client.Stop(); err != nil {
t.Fatalf("Failed to stop client: %v", err)
}
- if client.State() != copilot.StateDisconnected {
- t.Errorf("Expected state to be disconnected after Stop, got %q", client.State())
- }
})
t.Run("force stop disconnects client", func(t *testing.T) {
@@ -148,13 +142,7 @@ func TestClientLifecycleE2E(t *testing.T) {
if err := client.Start(t.Context()); err != nil {
t.Fatalf("Failed to start client: %v", err)
}
- if client.State() != copilot.StateConnected {
- t.Errorf("Expected state to be connected after Start, got %q", client.State())
- }
client.ForceStop()
- if client.State() != copilot.StateDisconnected {
- t.Errorf("Expected state to be disconnected after ForceStop, got %q", client.State())
- }
})
}
diff --git a/go/internal/e2e/client_options_e2e_test.go b/go/internal/e2e/client_options_e2e_test.go
index 3ffdf7693..d8d6399ee 100644
--- a/go/internal/e2e/client_options_e2e_test.go
+++ b/go/internal/e2e/client_options_e2e_test.go
@@ -17,60 +17,20 @@ import (
// Go's ClientOptions is a plain struct with no setter validation; equivalent behavior is covered
// in package-level unit tests.
func TestClientOptionsE2E(t *testing.T) {
- t.Run("autostart false requires explicit start", func(t *testing.T) {
- ctx := testharness.NewTestContext(t)
- client := ctx.NewClient(func(opts *copilot.ClientOptions) {
- opts.AutoStart = copilot.Bool(false)
- })
- t.Cleanup(func() { client.ForceStop() })
-
- if got := client.State(); got != copilot.StateDisconnected {
- t.Errorf("Expected initial state Disconnected, got %v", got)
- }
-
- if _, err := client.CreateSession(t.Context(), &copilot.SessionConfig{
- OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
- }); err == nil {
- t.Fatal("Expected CreateSession to fail when AutoStart=false and Start was not called")
- }
-
- if err := client.Start(t.Context()); err != nil {
- t.Fatalf("Start failed: %v", err)
- }
- if got := client.State(); got != copilot.StateConnected {
- t.Errorf("Expected state Connected after Start, got %v", got)
- }
-
- session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{
- OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
- })
- if err != nil {
- t.Fatalf("CreateSession failed after Start: %v", err)
- }
- if session.SessionID == "" {
- t.Error("Expected non-empty session id")
- }
- session.Disconnect()
- })
-
t.Run("should listen on configured tcp port", func(t *testing.T) {
ctx := testharness.NewTestContext(t)
port := getAvailableTcpPort(t)
client := ctx.NewClient(func(opts *copilot.ClientOptions) {
- opts.UseStdio = copilot.Bool(false)
- opts.Port = port
+ opts.Connection = copilot.TcpConnection{Path: ctx.CLIPath, Port: port}
})
t.Cleanup(func() { client.ForceStop() })
if err := client.Start(t.Context()); err != nil {
t.Fatalf("Start failed: %v", err)
}
- if got := client.State(); got != copilot.StateConnected {
- t.Errorf("Expected state Connected, got %v", got)
- }
- if got := client.ActualPort(); got != port {
- t.Errorf("Expected ActualPort=%d, got %d", port, got)
+ if got := client.RuntimePort(); got != port {
+ t.Errorf("Expected RuntimePort=%d, got %d", port, got)
}
// Ping over the connection to confirm it is usable.
@@ -138,10 +98,11 @@ func TestClientOptionsE2E(t *testing.T) {
}
client := ctx.NewClient(func(opts *copilot.ClientOptions) {
- opts.AutoStart = copilot.Bool(false)
- opts.CLIPath = cliPath
- opts.CLIArgs = []string{"--capture-file", capturePath}
- opts.CopilotHome = filepath.Join(ctx.WorkDir, "copilot-home-from-option")
+ opts.Connection = copilot.StdioConnection{
+ Path: cliPath,
+ Args: []string{"--capture-file", capturePath},
+ }
+ opts.BaseDirectory = filepath.Join(ctx.WorkDir, "copilot-home-from-option")
opts.Env = append([]string{}, opts.Env...)
opts.Env = append(opts.Env, "COPILOT_HOME="+filepath.Join(ctx.WorkDir, "copilot-home-from-env"))
opts.GitHubToken = "process-option-token"
@@ -276,23 +237,19 @@ func TestClientOptionsUnit(t *testing.T) {
}
})
- t.Run("should panic when GitHubToken used with CliUrl", func(t *testing.T) {
- // Mirrors: Should_Throw_When_GitHubToken_Used_With_CliUrl
- // Go's NewClient validates mutually exclusive auth + CLIUrl combinations
- // with panic() instead of an exception.
+ t.Run("should panic when GitHubToken used with UriConnection", func(t *testing.T) {
assertPanics(t, func() {
_ = copilot.NewClient(&copilot.ClientOptions{
- CLIUrl: "localhost:8080",
+ Connection: copilot.UriConnection{URL: "localhost:8080"},
GitHubToken: "gho_test_token",
})
})
})
- t.Run("should panic when UseLoggedInUser used with CliUrl", func(t *testing.T) {
- // Mirrors: Should_Throw_When_UseLoggedInUser_Used_With_CliUrl
+ t.Run("should panic when UseLoggedInUser used with UriConnection", func(t *testing.T) {
assertPanics(t, func() {
_ = copilot.NewClient(&copilot.ClientOptions{
- CLIUrl: "localhost:8080",
+ Connection: copilot.UriConnection{URL: "localhost:8080"},
UseLoggedInUser: copilot.Bool(false),
})
})
diff --git a/go/internal/e2e/commands_and_elicitation_e2e_test.go b/go/internal/e2e/commands_and_elicitation_e2e_test.go
index 501e13813..c38201204 100644
--- a/go/internal/e2e/commands_and_elicitation_e2e_test.go
+++ b/go/internal/e2e/commands_and_elicitation_e2e_test.go
@@ -14,8 +14,7 @@ import (
func TestCommandsE2E(t *testing.T) {
ctx := testharness.NewTestContext(t)
client1 := ctx.NewClient(func(opts *copilot.ClientOptions) {
- opts.UseStdio = copilot.Bool(false)
- opts.TCPConnectionToken = sharedTcpToken
+ opts.Connection = copilot.TcpConnection{Path: opts.Connection.(copilot.StdioConnection).Path, ConnectionToken: sharedTcpToken}
})
t.Cleanup(func() { client1.ForceStop() })
@@ -28,14 +27,13 @@ func TestCommandsE2E(t *testing.T) {
}
initSession.Disconnect()
- actualPort := client1.ActualPort()
- if actualPort == 0 {
+ runtimePort := client1.RuntimePort()
+ if runtimePort == 0 {
t.Fatalf("Expected non-zero port from TCP mode client")
}
client2 := copilot.NewClient(&copilot.ClientOptions{
- CLIUrl: fmt.Sprintf("localhost:%d", actualPort),
- TCPConnectionToken: sharedTcpToken,
+ Connection: copilot.UriConnection{URL: fmt.Sprintf("localhost:%d", runtimePort), ConnectionToken: sharedTcpToken},
})
t.Cleanup(func() { client2.ForceStop() })
@@ -65,7 +63,7 @@ func TestCommandsE2E(t *testing.T) {
// Client2 joins with commands
session2, err := client2.ResumeSession(t.Context(), session1.SessionID, &copilot.ResumeSessionConfig{
OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
- DisableResume: true,
+ SuppressResumeEvent: true,
Commands: []copilot.CommandDefinition{
{
Name: "deploy",
@@ -367,7 +365,7 @@ func TestUIElicitationCallbackE2E(t *testing.T) {
minLen := 1
maxLen := 20
- value, ok, err := session.UI().Input(t.Context(), "Enter value", &copilot.InputOptions{
+ value, ok, err := session.UI().Input(t.Context(), "Enter value", &copilot.UiInputOptions{
Title: "Value",
Description: "A value to test",
MinLength: &minLen,
@@ -510,8 +508,7 @@ func schemaHasProperty(schema map[string]any, name string) bool {
func TestUIElicitationMultiClientE2E(t *testing.T) {
ctx := testharness.NewTestContext(t)
client1 := ctx.NewClient(func(opts *copilot.ClientOptions) {
- opts.UseStdio = copilot.Bool(false)
- opts.TCPConnectionToken = sharedTcpToken
+ opts.Connection = copilot.TcpConnection{Path: opts.Connection.(copilot.StdioConnection).Path, ConnectionToken: sharedTcpToken}
})
t.Cleanup(func() { client1.ForceStop() })
@@ -524,8 +521,8 @@ func TestUIElicitationMultiClientE2E(t *testing.T) {
}
initSession.Disconnect()
- actualPort := client1.ActualPort()
- if actualPort == 0 {
+ runtimePort := client1.RuntimePort()
+ if runtimePort == 0 {
t.Fatalf("Expected non-zero port from TCP mode client")
}
@@ -559,12 +556,11 @@ func TestUIElicitationMultiClientE2E(t *testing.T) {
// Client2 joins with elicitation handler — should trigger capabilities.changed
client2 := copilot.NewClient(&copilot.ClientOptions{
- CLIUrl: fmt.Sprintf("localhost:%d", actualPort),
- TCPConnectionToken: sharedTcpToken,
+ Connection: copilot.UriConnection{URL: fmt.Sprintf("localhost:%d", runtimePort), ConnectionToken: sharedTcpToken},
})
session2, err := client2.ResumeSession(t.Context(), session1.SessionID, &copilot.ResumeSessionConfig{
OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
- DisableResume: true,
+ SuppressResumeEvent: true,
OnElicitationRequest: func(ctx copilot.ElicitationContext) (copilot.ElicitationResult, error) {
return copilot.ElicitationResult{Action: "accept", Content: map[string]any{}}, nil
},
@@ -620,12 +616,11 @@ func TestUIElicitationMultiClientE2E(t *testing.T) {
// Client3 (dedicated for this test) joins with elicitation handler
client3 := copilot.NewClient(&copilot.ClientOptions{
- CLIUrl: fmt.Sprintf("localhost:%d", actualPort),
- TCPConnectionToken: sharedTcpToken,
+ Connection: copilot.UriConnection{URL: fmt.Sprintf("localhost:%d", runtimePort), ConnectionToken: sharedTcpToken},
})
_, err = client3.ResumeSession(t.Context(), session1.SessionID, &copilot.ResumeSessionConfig{
OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
- DisableResume: true,
+ SuppressResumeEvent: true,
OnElicitationRequest: func(ctx copilot.ElicitationContext) (copilot.ElicitationResult, error) {
return copilot.ElicitationResult{Action: "accept", Content: map[string]any{}}, nil
},
diff --git a/go/internal/e2e/connection_token_test.go b/go/internal/e2e/connection_token_test.go
index 269c5ae5a..f68bb0bf8 100644
--- a/go/internal/e2e/connection_token_test.go
+++ b/go/internal/e2e/connection_token_test.go
@@ -13,8 +13,10 @@ func TestConnectionToken(t *testing.T) {
t.Run("explicit token round-trips successfully", func(t *testing.T) {
ctx := testharness.NewTestContext(t)
client := ctx.NewClient(func(opts *copilot.ClientOptions) {
- opts.UseStdio = copilot.Bool(false)
- opts.TCPConnectionToken = "right-token"
+ opts.Connection = copilot.TcpConnection{
+ Path: ctx.CLIPath,
+ ConnectionToken: "right-token",
+ }
})
t.Cleanup(func() { client.ForceStop() })
@@ -34,7 +36,7 @@ func TestConnectionToken(t *testing.T) {
t.Run("auto-generated token round-trips successfully", func(t *testing.T) {
ctx := testharness.NewTestContext(t)
client := ctx.NewClient(func(opts *copilot.ClientOptions) {
- opts.UseStdio = copilot.Bool(false)
+ opts.Connection = copilot.TcpConnection{Path: ctx.CLIPath}
})
t.Cleanup(func() { client.ForceStop() })
@@ -54,22 +56,26 @@ func TestConnectionToken(t *testing.T) {
t.Run("sibling client with wrong token is rejected", func(t *testing.T) {
ctx := testharness.NewTestContext(t)
good := ctx.NewClient(func(opts *copilot.ClientOptions) {
- opts.UseStdio = copilot.Bool(false)
- opts.TCPConnectionToken = "right-token"
+ opts.Connection = copilot.TcpConnection{
+ Path: ctx.CLIPath,
+ ConnectionToken: "right-token",
+ }
})
t.Cleanup(func() { good.ForceStop() })
if err := good.Start(t.Context()); err != nil {
t.Fatalf("good client Start failed: %v", err)
}
- port := good.ActualPort()
+ port := good.RuntimePort()
if port == 0 {
t.Fatalf("expected non-zero port from TCP mode client")
}
bad := copilot.NewClient(&copilot.ClientOptions{
- CLIUrl: fmt.Sprintf("localhost:%d", port),
- TCPConnectionToken: "wrong",
+ Connection: copilot.UriConnection{
+ URL: fmt.Sprintf("localhost:%d", port),
+ ConnectionToken: "wrong",
+ },
})
t.Cleanup(func() { bad.ForceStop() })
@@ -85,21 +91,23 @@ func TestConnectionToken(t *testing.T) {
t.Run("sibling client with no token is rejected", func(t *testing.T) {
ctx := testharness.NewTestContext(t)
good := ctx.NewClient(func(opts *copilot.ClientOptions) {
- opts.UseStdio = copilot.Bool(false)
- opts.TCPConnectionToken = "right-token"
+ opts.Connection = copilot.TcpConnection{
+ Path: ctx.CLIPath,
+ ConnectionToken: "right-token",
+ }
})
t.Cleanup(func() { good.ForceStop() })
if err := good.Start(t.Context()); err != nil {
t.Fatalf("good client Start failed: %v", err)
}
- port := good.ActualPort()
+ port := good.RuntimePort()
if port == 0 {
t.Fatalf("expected non-zero port from TCP mode client")
}
none := copilot.NewClient(&copilot.ClientOptions{
- CLIUrl: fmt.Sprintf("localhost:%d", port),
+ Connection: copilot.UriConnection{URL: fmt.Sprintf("localhost:%d", port)},
})
t.Cleanup(func() { none.ForceStop() })
diff --git a/go/internal/e2e/error_resilience_e2e_test.go b/go/internal/e2e/error_resilience_e2e_test.go
index 2a0162f2c..056fc79ff 100644
--- a/go/internal/e2e/error_resilience_e2e_test.go
+++ b/go/internal/e2e/error_resilience_e2e_test.go
@@ -49,8 +49,8 @@ func TestErrorResilienceE2E(t *testing.T) {
timeoutCtx, cancel := context.WithTimeout(t.Context(), 10*time.Second)
defer cancel()
- if _, err := session.GetMessages(timeoutCtx); err == nil {
- t.Fatal("Expected GetMessages on disconnected session to fail")
+ if _, err := session.GetEvents(timeoutCtx); err == nil {
+ t.Fatal("Expected GetEvents on disconnected session to fail")
}
})
diff --git a/go/internal/e2e/event_fidelity_e2e_test.go b/go/internal/e2e/event_fidelity_e2e_test.go
index f75fcffad..de1145b0a 100644
--- a/go/internal/e2e/event_fidelity_e2e_test.go
+++ b/go/internal/e2e/event_fidelity_e2e_test.go
@@ -190,9 +190,9 @@ func TestEventFidelityE2E(t *testing.T) {
t.Fatalf("SendAndWait failed: %v", err)
}
- messages, err := session.GetMessages(t.Context())
+ messages, err := session.GetEvents(t.Context())
if err != nil {
- t.Fatalf("GetMessages failed: %v", err)
+ t.Fatalf("GetEvents failed: %v", err)
}
types := make([]copilot.SessionEventType, 0, len(messages))
@@ -225,19 +225,19 @@ func TestEventFidelityE2E(t *testing.T) {
}
if sessionStartIdx < 0 {
- t.Fatalf("Expected session.start event in GetMessages; types=%v", types)
+ t.Fatalf("Expected session.start event in GetEvents; types=%v", types)
}
if userMsgIdx < 0 {
- t.Fatalf("Expected user.message event in GetMessages; types=%v", types)
+ t.Fatalf("Expected user.message event in GetEvents; types=%v", types)
}
if toolStartIdx < 0 {
- t.Fatalf("Expected tool.execution_start event in GetMessages; types=%v", types)
+ t.Fatalf("Expected tool.execution_start event in GetEvents; types=%v", types)
}
if toolCompleteIdx < 0 {
- t.Fatalf("Expected tool.execution_complete event in GetMessages; types=%v", types)
+ t.Fatalf("Expected tool.execution_complete event in GetEvents; types=%v", types)
}
if assistantMsgIdx < 0 {
- t.Fatalf("Expected assistant.message event in GetMessages; types=%v", types)
+ t.Fatalf("Expected assistant.message event in GetEvents; types=%v", types)
}
if sessionStartIdx >= userMsgIdx {
diff --git a/go/internal/e2e/mcp_and_agents_e2e_test.go b/go/internal/e2e/mcp_and_agents_e2e_test.go
index e7273edf2..b7e4c2400 100644
--- a/go/internal/e2e/mcp_and_agents_e2e_test.go
+++ b/go/internal/e2e/mcp_and_agents_e2e_test.go
@@ -21,7 +21,7 @@ func TestMCPServersE2E(t *testing.T) {
"test-server": copilot.MCPStdioServerConfig{
Command: "echo",
Args: []string{"hello"},
- Tools: []string{"*"},
+ Tools: &[]string{"*"},
},
}
@@ -63,7 +63,7 @@ func TestMCPServersE2E(t *testing.T) {
mcpServers := map[string]copilot.MCPServerConfig{
"test-server": copilot.MCPStdioServerConfig{
Command: "echo",
- Tools: []string{"*"},
+ Tools: &[]string{"*"},
},
}
@@ -118,7 +118,7 @@ func TestMCPServersE2E(t *testing.T) {
"test-server": copilot.MCPStdioServerConfig{
Command: "echo",
Args: []string{"hello"},
- Tools: []string{"*"},
+ Tools: &[]string{"*"},
},
}
@@ -159,7 +159,7 @@ func TestMCPServersE2E(t *testing.T) {
"env-echo": copilot.MCPStdioServerConfig{
Command: "node",
Args: []string{mcpServerPath},
- Tools: []string{"*"},
+ Tools: &[]string{"*"},
Env: map[string]string{"TEST_SECRET": "hunter2"},
Cwd: mcpServerDir,
},
@@ -198,12 +198,12 @@ func TestMCPServersE2E(t *testing.T) {
"server1": copilot.MCPStdioServerConfig{
Command: "echo",
Args: []string{"server1"},
- Tools: []string{"*"},
+ Tools: &[]string{"*"},
},
"server2": copilot.MCPStdioServerConfig{
Command: "echo",
Args: []string{"server2"},
- Tools: []string{"*"},
+ Tools: &[]string{"*"},
},
}
@@ -366,7 +366,7 @@ func TestCustomAgentsE2E(t *testing.T) {
"agent-server": copilot.MCPStdioServerConfig{
Command: "echo",
Args: []string{"agent-mcp"},
- Tools: []string{"*"},
+ Tools: &[]string{"*"},
},
},
},
@@ -437,7 +437,7 @@ func TestCombinedConfigurationE2E(t *testing.T) {
"shared-server": copilot.MCPStdioServerConfig{
Command: "echo",
Args: []string{"shared"},
- Tools: []string{"*"},
+ Tools: &[]string{"*"},
},
}
diff --git a/go/internal/e2e/mode_handlers_e2e_test.go b/go/internal/e2e/mode_handlers_e2e_test.go
index cdf6800a1..0fa9a3012 100644
--- a/go/internal/e2e/mode_handlers_e2e_test.go
+++ b/go/internal/e2e/mode_handlers_e2e_test.go
@@ -43,7 +43,7 @@ func TestModeHandlersE2E(t *testing.T) {
session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{
GitHubToken: modeHandlerToken,
OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
- OnExitPlanMode: func(request copilot.ExitPlanModeRequest, invocation copilot.ExitPlanModeInvocation) (copilot.ExitPlanModeResult, error) {
+ OnExitPlanModeRequest: func(request copilot.ExitPlanModeRequest, invocation copilot.ExitPlanModeInvocation) (copilot.ExitPlanModeResult, error) {
mu.Lock()
exitPlanModeRequests = append(exitPlanModeRequests, request)
mu.Unlock()
@@ -132,7 +132,7 @@ func TestModeHandlersE2E(t *testing.T) {
session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{
GitHubToken: modeHandlerToken,
OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
- OnAutoModeSwitch: func(request copilot.AutoModeSwitchRequest, invocation copilot.AutoModeSwitchInvocation) (copilot.AutoModeSwitchResponse, error) {
+ OnAutoModeSwitchRequest: func(request copilot.AutoModeSwitchRequest, invocation copilot.AutoModeSwitchInvocation) (copilot.AutoModeSwitchResponse, error) {
mu.Lock()
autoModeSwitchRequests = append(autoModeSwitchRequests, request)
mu.Unlock()
diff --git a/go/internal/e2e/multi_client_e2e_test.go b/go/internal/e2e/multi_client_e2e_test.go
index 84ad8909e..9dd8a15bf 100644
--- a/go/internal/e2e/multi_client_e2e_test.go
+++ b/go/internal/e2e/multi_client_e2e_test.go
@@ -17,8 +17,7 @@ func TestMultiClientE2E(t *testing.T) {
// Use TCP mode so a second client can connect to the same CLI process
ctx := testharness.NewTestContext(t)
client1 := ctx.NewClient(func(opts *copilot.ClientOptions) {
- opts.UseStdio = copilot.Bool(false)
- opts.TCPConnectionToken = sharedTcpToken
+ opts.Connection = copilot.TcpConnection{Path: opts.Connection.(copilot.StdioConnection).Path, ConnectionToken: sharedTcpToken}
})
t.Cleanup(func() { client1.ForceStop() })
@@ -31,14 +30,13 @@ func TestMultiClientE2E(t *testing.T) {
}
initSession.Disconnect()
- actualPort := client1.ActualPort()
- if actualPort == 0 {
+ runtimePort := client1.RuntimePort()
+ if runtimePort == 0 {
t.Fatalf("Expected non-zero port from TCP mode client")
}
client2 := copilot.NewClient(&copilot.ClientOptions{
- CLIUrl: fmt.Sprintf("localhost:%d", actualPort),
- TCPConnectionToken: sharedTcpToken,
+ Connection: copilot.UriConnection{URL: fmt.Sprintf("localhost:%d", runtimePort), ConnectionToken: sharedTcpToken},
})
t.Cleanup(func() { client2.ForceStop() })
@@ -488,8 +486,7 @@ func TestMultiClientE2E(t *testing.T) {
// Recreate client2 for cleanup (but don't rejoin the session)
client2 = copilot.NewClient(&copilot.ClientOptions{
- CLIUrl: fmt.Sprintf("localhost:%d", actualPort),
- TCPConnectionToken: sharedTcpToken,
+ Connection: copilot.UriConnection{URL: fmt.Sprintf("localhost:%d", runtimePort), ConnectionToken: sharedTcpToken},
})
// Now only stable_tool should be available
diff --git a/go/internal/e2e/pending_work_resume_e2e_test.go b/go/internal/e2e/pending_work_resume_e2e_test.go
index 170568ff2..41ca83021 100644
--- a/go/internal/e2e/pending_work_resume_e2e_test.go
+++ b/go/internal/e2e/pending_work_resume_e2e_test.go
@@ -43,9 +43,7 @@ func TestPendingWorkResumeE2E(t *testing.T) {
releasePermission := make(chan copilot.PermissionRequestResult, 1)
suspendedClient := ctx.NewClient(func(opts *copilot.ClientOptions) {
- opts.CLIUrl = cliURL
- opts.CLIPath = ""
- opts.TCPConnectionToken = sharedTcpToken
+ opts.Connection = copilot.UriConnection{URL: cliURL, ConnectionToken: sharedTcpToken}
})
session1, err := suspendedClient.CreateSession(t.Context(), &copilot.SessionConfig{
Tools: []copilot.Tool{originalTool},
@@ -110,9 +108,7 @@ func TestPendingWorkResumeE2E(t *testing.T) {
})
resumedClient := ctx.NewClient(func(opts *copilot.ClientOptions) {
- opts.CLIUrl = cliURL
- opts.CLIPath = ""
- opts.TCPConnectionToken = sharedTcpToken
+ opts.Connection = copilot.UriConnection{URL: cliURL, ConnectionToken: sharedTcpToken}
})
t.Cleanup(func() { resumedClient.ForceStop() })
@@ -187,9 +183,7 @@ func TestPendingWorkResumeE2E(t *testing.T) {
})
suspendedClient := ctx.NewClient(func(opts *copilot.ClientOptions) {
- opts.CLIUrl = cliURL
- opts.CLIPath = ""
- opts.TCPConnectionToken = sharedTcpToken
+ opts.Connection = copilot.UriConnection{URL: cliURL, ConnectionToken: sharedTcpToken}
})
session1, err := suspendedClient.CreateSession(t.Context(), &copilot.SessionConfig{
Tools: []copilot.Tool{originalTool},
@@ -225,9 +219,7 @@ func TestPendingWorkResumeE2E(t *testing.T) {
suspendedClient.ForceStop()
resumedClient := ctx.NewClient(func(opts *copilot.ClientOptions) {
- opts.CLIUrl = cliURL
- opts.CLIPath = ""
- opts.TCPConnectionToken = sharedTcpToken
+ opts.Connection = copilot.UriConnection{URL: cliURL, ConnectionToken: sharedTcpToken}
})
t.Cleanup(func() { resumedClient.ForceStop() })
@@ -299,9 +291,7 @@ func TestPendingWorkResumeE2E(t *testing.T) {
})
suspendedClient := ctx.NewClient(func(opts *copilot.ClientOptions) {
- opts.CLIUrl = cliURL
- opts.CLIPath = ""
- opts.TCPConnectionToken = sharedTcpToken
+ opts.Connection = copilot.UriConnection{URL: cliURL, ConnectionToken: sharedTcpToken}
})
session1, err := suspendedClient.CreateSession(t.Context(), &copilot.SessionConfig{
Tools: []copilot.Tool{originalA, originalB},
@@ -344,9 +334,7 @@ func TestPendingWorkResumeE2E(t *testing.T) {
suspendedClient.ForceStop()
resumedClient := ctx.NewClient(func(opts *copilot.ClientOptions) {
- opts.CLIUrl = cliURL
- opts.CLIPath = ""
- opts.TCPConnectionToken = sharedTcpToken
+ opts.Connection = copilot.UriConnection{URL: cliURL, ConnectionToken: sharedTcpToken}
})
t.Cleanup(func() { resumedClient.ForceStop() })
@@ -394,9 +382,7 @@ func TestPendingWorkResumeE2E(t *testing.T) {
var sessionID string
func() {
firstClient := ctx.NewClient(func(opts *copilot.ClientOptions) {
- opts.CLIUrl = cliURL
- opts.CLIPath = ""
- opts.TCPConnectionToken = sharedTcpToken
+ opts.Connection = copilot.UriConnection{URL: cliURL, ConnectionToken: sharedTcpToken}
})
defer firstClient.ForceStop()
@@ -422,9 +408,7 @@ func TestPendingWorkResumeE2E(t *testing.T) {
}()
resumedClient := ctx.NewClient(func(opts *copilot.ClientOptions) {
- opts.CLIUrl = cliURL
- opts.CLIPath = ""
- opts.TCPConnectionToken = sharedTcpToken
+ opts.Connection = copilot.UriConnection{URL: cliURL, ConnectionToken: sharedTcpToken}
})
t.Cleanup(func() { resumedClient.ForceStop() })
@@ -470,9 +454,7 @@ func TestPendingWorkResumeE2E(t *testing.T) {
})
suspendedClient := ctx.NewClient(func(opts *copilot.ClientOptions) {
- opts.CLIUrl = cliURL
- opts.CLIPath = ""
- opts.TCPConnectionToken = sharedTcpToken
+ opts.Connection = copilot.UriConnection{URL: cliURL, ConnectionToken: sharedTcpToken}
})
session1, err := suspendedClient.CreateSession(t.Context(), &copilot.SessionConfig{
Tools: []copilot.Tool{originalTool},
@@ -509,9 +491,7 @@ func TestPendingWorkResumeE2E(t *testing.T) {
suspendedClient.ForceStop()
resumedClient := ctx.NewClient(func(opts *copilot.ClientOptions) {
- opts.CLIUrl = cliURL
- opts.CLIPath = ""
- opts.TCPConnectionToken = sharedTcpToken
+ opts.Connection = copilot.UriConnection{URL: cliURL, ConnectionToken: sharedTcpToken}
})
t.Cleanup(func() { resumedClient.ForceStop() })
@@ -524,9 +504,9 @@ func TestPendingWorkResumeE2E(t *testing.T) {
}
// Verify resume event reflects ContinuePendingWork=false and SessionWasActive=true
- messages, err := session2.GetMessages(t.Context())
+ messages, err := session2.GetEvents(t.Context())
if err != nil {
- t.Fatalf("GetMessages failed: %v", err)
+ t.Fatalf("GetEvents failed: %v", err)
}
var resumeEvent *copilot.SessionResumeData
for _, msg := range messages {
@@ -577,9 +557,7 @@ func TestPendingWorkResumeE2E(t *testing.T) {
var sessionID string
func() {
firstClient := ctx.NewClient(func(opts *copilot.ClientOptions) {
- opts.CLIUrl = cliURL
- opts.CLIPath = ""
- opts.TCPConnectionToken = sharedTcpToken
+ opts.Connection = copilot.UriConnection{URL: cliURL, ConnectionToken: sharedTcpToken}
})
defer firstClient.ForceStop()
@@ -605,9 +583,7 @@ func TestPendingWorkResumeE2E(t *testing.T) {
}()
resumedClient := ctx.NewClient(func(opts *copilot.ClientOptions) {
- opts.CLIUrl = cliURL
- opts.CLIPath = ""
- opts.TCPConnectionToken = sharedTcpToken
+ opts.Connection = copilot.UriConnection{URL: cliURL, ConnectionToken: sharedTcpToken}
})
t.Cleanup(func() { resumedClient.ForceStop() })
@@ -620,9 +596,9 @@ func TestPendingWorkResumeE2E(t *testing.T) {
}
// Verify resume event reflects ContinuePendingWork=true and SessionWasActive=false (cold resume)
- messages, err := resumedSession.GetMessages(t.Context())
+ messages, err := resumedSession.GetEvents(t.Context())
if err != nil {
- t.Fatalf("GetMessages failed: %v", err)
+ t.Fatalf("GetEvents failed: %v", err)
}
var resumeEvent *copilot.SessionResumeData
for _, msg := range messages {
@@ -663,9 +639,9 @@ func TestPendingWorkResumeE2E(t *testing.T) {
// test failure if the port is not yet available.
func serverCliURL(t *testing.T, server *copilot.Client) string {
t.Helper()
- port := server.ActualPort()
+ port := server.RuntimePort()
if port == 0 {
- t.Fatal("Expected non-zero ActualPort from TCP server client; ensure the server is started before calling serverCliURL")
+ t.Fatal("Expected non-zero RuntimePort from TCP server client; ensure the server is started before calling serverCliURL")
}
return fmt.Sprintf("localhost:%d", port)
}
@@ -677,12 +653,11 @@ func serverCliURL(t *testing.T, server *copilot.Client) string {
const sharedTcpToken = "tcp-shared-test-token"
// startTcpServer starts a TCP-mode server client and returns its CLI URL.
-// It triggers an initial connection so ActualPort is populated.
+// It triggers an initial connection so RuntimePort is populated.
func startTcpServer(t *testing.T, ctx *testharness.TestContext) (*copilot.Client, string) {
t.Helper()
server := ctx.NewClient(func(opts *copilot.ClientOptions) {
- opts.UseStdio = copilot.Bool(false)
- opts.TCPConnectionToken = sharedTcpToken
+ opts.Connection = copilot.TcpConnection{Path: opts.Connection.(copilot.StdioConnection).Path, ConnectionToken: sharedTcpToken}
})
t.Cleanup(func() { server.ForceStop() })
// Trigger connection so we can read the port. CreateSession+Disconnect is the
diff --git a/go/internal/e2e/per_session_auth_e2e_test.go b/go/internal/e2e/per_session_auth_e2e_test.go
index d40546028..66a2768bb 100644
--- a/go/internal/e2e/per_session_auth_e2e_test.go
+++ b/go/internal/e2e/per_session_auth_e2e_test.go
@@ -101,7 +101,7 @@ func TestPerSessionAuthE2E(t *testing.T) {
ctx.ConfigureForTest(t)
noTokenClient := copilot.NewClient(&copilot.ClientOptions{
- CLIPath: ctx.CLIPath,
+ Connection: copilot.StdioConnection{Path: ctx.CLIPath},
Cwd: ctx.WorkDir,
Env: withoutAuthEnv(append(ctx.Env(), "COPILOT_DEBUG_GITHUB_API_URL="+ctx.ProxyURL)),
UseLoggedInUser: copilot.Bool(false),
diff --git a/go/internal/e2e/rpc_e2e_test.go b/go/internal/e2e/rpc_e2e_test.go
index 8f73afa9e..ccbf26d1d 100644
--- a/go/internal/e2e/rpc_e2e_test.go
+++ b/go/internal/e2e/rpc_e2e_test.go
@@ -17,8 +17,7 @@ func TestRpcE2E(t *testing.T) {
t.Run("should call RPC.Ping with typed params and result", func(t *testing.T) {
client := copilot.NewClient(&copilot.ClientOptions{
- CLIPath: cliPath,
- UseStdio: copilot.Bool(true),
+ Connection: copilot.StdioConnection{Path: cliPath},
})
t.Cleanup(func() { client.ForceStop() })
@@ -46,8 +45,7 @@ func TestRpcE2E(t *testing.T) {
t.Run("should call RPC.Models.List with typed result", func(t *testing.T) {
client := copilot.NewClient(&copilot.ClientOptions{
- CLIPath: cliPath,
- UseStdio: copilot.Bool(true),
+ Connection: copilot.StdioConnection{Path: cliPath},
})
t.Cleanup(func() { client.ForceStop() })
@@ -83,8 +81,7 @@ func TestRpcE2E(t *testing.T) {
t.Skip("account.getQuota not yet implemented in CLI")
client := copilot.NewClient(&copilot.ClientOptions{
- CLIPath: cliPath,
- UseStdio: copilot.Bool(true),
+ Connection: copilot.StdioConnection{Path: cliPath},
})
t.Cleanup(func() { client.ForceStop() })
diff --git a/go/internal/e2e/rpc_event_side_effects_e2e_test.go b/go/internal/e2e/rpc_event_side_effects_e2e_test.go
index 55f9835f3..765a570a2 100644
--- a/go/internal/e2e/rpc_event_side_effects_e2e_test.go
+++ b/go/internal/e2e/rpc_event_side_effects_e2e_test.go
@@ -168,7 +168,7 @@ func TestRpcEventSideEffectsE2E(t *testing.T) {
t.Fatalf("Failed to create persisted message: %v", err)
}
- messages, err := session.GetMessages(t.Context())
+ messages, err := session.GetEvents(t.Context())
if err != nil {
t.Fatalf("Failed to read messages: %v", err)
}
@@ -203,7 +203,7 @@ func TestRpcEventSideEffectsE2E(t *testing.T) {
t.Fatalf("Expected rewind count %d, got %+v", truncateResult.EventsRemoved, rewindData)
}
- messagesAfter, err := session.GetMessages(t.Context())
+ messagesAfter, err := session.GetEvents(t.Context())
if err != nil {
t.Fatalf("Failed to read messages after truncate: %v", err)
}
@@ -224,7 +224,7 @@ func TestRpcEventSideEffectsE2E(t *testing.T) {
t.Fatalf("Failed to create persisted message: %v", err)
}
- messages, err := session.GetMessages(t.Context())
+ messages, err := session.GetEvents(t.Context())
if err != nil {
t.Fatalf("Failed to read messages: %v", err)
}
diff --git a/go/internal/e2e/rpc_mcp_and_skills_e2e_test.go b/go/internal/e2e/rpc_mcp_and_skills_e2e_test.go
index 9a8ef8ebd..c636201da 100644
--- a/go/internal/e2e/rpc_mcp_and_skills_e2e_test.go
+++ b/go/internal/e2e/rpc_mcp_and_skills_e2e_test.go
@@ -19,7 +19,9 @@ func TestRpcMcpAndSkillsE2E(t *testing.T) {
// --yolo auto-approves extension permission gates at the CLI level,
// preventing breakage from new gates (e.g., extension-permission-access).
client := ctx.NewClient(func(o *copilot.ClientOptions) {
- o.CLIArgs = []string{"--yolo"}
+ stdio := o.Connection.(copilot.StdioConnection)
+ stdio.Args = []string{"--yolo"}
+ o.Connection = stdio
})
t.Cleanup(func() { client.ForceStop() })
@@ -110,7 +112,7 @@ func TestRpcMcpAndSkillsE2E(t *testing.T) {
serverName: copilot.MCPStdioServerConfig{
Command: "echo",
Args: []string{"rpc-list-mcp-server"},
- Tools: []string{"*"},
+ Tools: &[]string{"*"},
},
},
})
diff --git a/go/internal/e2e/rpc_session_state_e2e_test.go b/go/internal/e2e/rpc_session_state_e2e_test.go
index 8ac041255..cb68651ae 100644
--- a/go/internal/e2e/rpc_session_state_e2e_test.go
+++ b/go/internal/e2e/rpc_session_state_e2e_test.go
@@ -220,7 +220,7 @@ func TestRpcSessionStateE2E(t *testing.T) {
t.Errorf("Expected initial answer to contain FORK_SOURCE_ALPHA, got %v", initialAnswer.Data)
}
- sourceMessages, err := session.GetMessages(t.Context())
+ sourceMessages, err := session.GetEvents(t.Context())
if err != nil {
t.Fatalf("Failed to read source messages: %v", err)
}
@@ -250,7 +250,7 @@ func TestRpcSessionStateE2E(t *testing.T) {
t.Fatalf("Failed to resume forked session: %v", err)
}
- forkedMessages, err := forkedSession.GetMessages(t.Context())
+ forkedMessages, err := forkedSession.GetEvents(t.Context())
if err != nil {
t.Fatalf("Failed to read forked messages: %v", err)
}
@@ -272,7 +272,7 @@ func TestRpcSessionStateE2E(t *testing.T) {
t.Errorf("Expected forked answer to contain FORK_CHILD_BETA, got %v", forkAnswer.Data)
}
- sourceAfterFork, err := session.GetMessages(t.Context())
+ sourceAfterFork, err := session.GetEvents(t.Context())
if err != nil {
t.Fatalf("Failed to read source messages after fork: %v", err)
}
@@ -282,7 +282,7 @@ func TestRpcSessionStateE2E(t *testing.T) {
}
}
- forkAfterPrompt, err := forkedSession.GetMessages(t.Context())
+ forkAfterPrompt, err := forkedSession.GetEvents(t.Context())
if err != nil {
t.Fatalf("Failed to read forked messages after prompt: %v", err)
}
@@ -336,7 +336,7 @@ func TestRpcSessionStateE2E(t *testing.T) {
}
defer forkedSession.Disconnect()
- forkedMessages, err := forkedSession.GetMessages(t.Context())
+ forkedMessages, err := forkedSession.GetEvents(t.Context())
if err != nil {
t.Fatalf("Failed to read forked messages: %v", err)
}
@@ -366,7 +366,7 @@ func TestRpcSessionStateE2E(t *testing.T) {
t.Fatalf("Failed to send second prompt: %v", err)
}
- sourceEvents, err := session.GetMessages(t.Context())
+ sourceEvents, err := session.GetEvents(t.Context())
if err != nil {
t.Fatalf("Failed to read source messages: %v", err)
}
@@ -406,7 +406,7 @@ func TestRpcSessionStateE2E(t *testing.T) {
}
defer forkedSession.Disconnect()
- forkedEvents, err := forkedSession.GetMessages(t.Context())
+ forkedEvents, err := forkedSession.GetEvents(t.Context())
if err != nil {
t.Fatalf("Failed to read forked messages: %v", err)
}
diff --git a/go/internal/e2e/rpc_shell_and_fleet_e2e_test.go b/go/internal/e2e/rpc_shell_and_fleet_e2e_test.go
index ff7e545dd..7655d179e 100644
--- a/go/internal/e2e/rpc_shell_and_fleet_e2e_test.go
+++ b/go/internal/e2e/rpc_shell_and_fleet_e2e_test.go
@@ -196,7 +196,7 @@ func waitForFleetCompletion(t *testing.T, session *copilot.Session, contentNeedl
t.Helper()
deadline := time.Now().Add(120 * time.Second)
for time.Now().Before(deadline) {
- messages, err := session.GetMessages(t.Context())
+ messages, err := session.GetEvents(t.Context())
if err == nil {
for _, evt := range messages {
if d, ok := evt.Data.(*copilot.AssistantMessageData); ok && strings.Contains(strings.ToLower(d.Content), contentNeedle) {
diff --git a/go/internal/e2e/session_config_e2e_test.go b/go/internal/e2e/session_config_e2e_test.go
index de9dad9e2..d932ae31b 100644
--- a/go/internal/e2e/session_config_e2e_test.go
+++ b/go/internal/e2e/session_config_e2e_test.go
@@ -202,9 +202,9 @@ func TestSessionConfigExtrasE2E(t *testing.T) {
t.Errorf("Expected SessionID=%q, got %q", requestedSessionID, session.SessionID)
}
- messages, err := session.GetMessages(t.Context())
+ messages, err := session.GetEvents(t.Context())
if err != nil {
- t.Fatalf("GetMessages failed: %v", err)
+ t.Fatalf("GetEvents failed: %v", err)
}
if len(messages) == 0 || messages[0].Type() != copilot.SessionEventTypeSessionStart {
t.Fatalf("Expected first event to be session.start, got %+v", messages)
diff --git a/go/internal/e2e/session_e2e_test.go b/go/internal/e2e/session_e2e_test.go
index f0d249422..bddd7e8e1 100644
--- a/go/internal/e2e/session_e2e_test.go
+++ b/go/internal/e2e/session_e2e_test.go
@@ -33,7 +33,7 @@ func TestSessionE2E(t *testing.T) {
t.Errorf("Expected session ID to match UUID pattern, got %q", session.SessionID)
}
- messages, err := session.GetMessages(t.Context())
+ messages, err := session.GetEvents(t.Context())
if err != nil {
t.Fatalf("Failed to get messages: %v", err)
}
@@ -55,9 +55,9 @@ func TestSessionE2E(t *testing.T) {
t.Fatalf("Failed to disconnect session: %v", err)
}
- _, err = session.GetMessages(t.Context())
+ _, err = session.GetEvents(t.Context())
if err == nil || !strings.Contains(err.Error(), "not found") {
- t.Errorf("Expected GetMessages to fail with 'not found' after disconnect, got %v", err)
+ t.Errorf("Expected GetEvents to fail with 'not found' after disconnect, got %v", err)
}
})
@@ -525,7 +525,7 @@ func TestSessionE2E(t *testing.T) {
}
// When resuming with a new client, we check messages contain expected types
- messages, err := session2.GetMessages(t.Context())
+ messages, err := session2.GetEvents(t.Context())
if err != nil {
t.Fatalf("Failed to get messages: %v", err)
}
@@ -660,7 +660,7 @@ func TestSessionE2E(t *testing.T) {
}
// The session should still be alive and usable after abort
- messages, err := session.GetMessages(t.Context())
+ messages, err := session.GetEvents(t.Context())
if err != nil {
t.Fatalf("Failed to get messages after abort: %v", err)
}
@@ -781,7 +781,7 @@ func TestSessionE2E(t *testing.T) {
}
// Verify the assistant response contains the expected answer.
- // session.idle is ephemeral and not in GetMessages(), but we already
+ // session.idle is ephemeral and not in GetEvents(), but we already
// confirmed idle via the live event handler above.
assistantMessage, err := testharness.GetFinalAssistantMessage(t.Context(), session, true)
if err != nil {
@@ -882,10 +882,10 @@ func TestSessionE2E(t *testing.T) {
if sessionData.SessionID == "" {
t.Error("Expected sessionId to be non-empty")
}
- if sessionData.StartTime == "" {
+ if sessionData.StartTime.IsZero() {
t.Error("Expected startTime to be non-empty")
}
- if sessionData.ModifiedTime == "" {
+ if sessionData.ModifiedTime.IsZero() {
t.Error("Expected modifiedTime to be non-empty")
}
// isRemote is a boolean, so it's always set
@@ -996,11 +996,11 @@ func TestSessionE2E(t *testing.T) {
t.Errorf("Expected sessionId %s, got %s", session.SessionID, metadata.SessionID)
}
- if metadata.StartTime == "" {
+ if metadata.StartTime.IsZero() {
t.Error("Expected startTime to be non-empty")
}
- if metadata.ModifiedTime == "" {
+ if metadata.ModifiedTime.IsZero() {
t.Error("Expected modifiedTime to be non-empty")
}
@@ -1295,7 +1295,7 @@ func getEventMessage(evt copilot.SessionEvent) string {
// TestSessionAttachments mirrors the C# Should_Send_With_*_Attachment tests in SessionTests.cs.
// Each subtest exercises a different UserMessageAttachment shape end-to-end through SendAndWait
-// and verifies the resulting user.message event captured by GetMessages.
+// and verifies the resulting user.message event captured by GetEvents.
func TestSessionAttachmentsE2E(t *testing.T) {
ctx := testharness.NewTestContext(t)
client := ctx.NewClient()
@@ -1501,9 +1501,9 @@ func TestSessionAttachmentsE2E(t *testing.T) {
// lastUserAttachment returns the single attachment from the most recent user.message event.
func lastUserAttachment(t *testing.T, session *copilot.Session) copilot.Attachment {
t.Helper()
- messages, err := session.GetMessages(t.Context())
+ messages, err := session.GetEvents(t.Context())
if err != nil {
- t.Fatalf("GetMessages failed: %v", err)
+ t.Fatalf("GetEvents failed: %v", err)
}
for i := len(messages) - 1; i >= 0; i-- {
if messages[i].Type() != copilot.SessionEventTypeUserMessage {
@@ -1550,9 +1550,9 @@ func TestSessionMessageOptionsE2E(t *testing.T) {
t.Fatalf("SendAndWait failed: %v", err)
}
- messages, err := session.GetMessages(t.Context())
+ messages, err := session.GetEvents(t.Context())
if err != nil {
- t.Fatalf("GetMessages failed: %v", err)
+ t.Fatalf("GetEvents failed: %v", err)
}
var userMsg *copilot.UserMessageData
for i := len(messages) - 1; i >= 0; i-- {
diff --git a/go/internal/e2e/session_fs_e2e_test.go b/go/internal/e2e/session_fs_e2e_test.go
index d56dc14a3..2c014f9e0 100644
--- a/go/internal/e2e/session_fs_e2e_test.go
+++ b/go/internal/e2e/session_fs_e2e_test.go
@@ -43,8 +43,8 @@ func TestSessionFsE2E(t *testing.T) {
ctx.ConfigureForTest(t)
session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{
- OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
- CreateSessionFsHandler: createSessionFsHandler,
+ OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
+ CreateSessionFsProvider: createSessionFsHandler,
})
if err != nil {
t.Fatalf("Failed to create session: %v", err)
@@ -80,8 +80,8 @@ func TestSessionFsE2E(t *testing.T) {
ctx.ConfigureForTest(t)
session1, err := client.CreateSession(t.Context(), &copilot.SessionConfig{
- OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
- CreateSessionFsHandler: createSessionFsHandler,
+ OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
+ CreateSessionFsProvider: createSessionFsHandler,
})
if err != nil {
t.Fatalf("Failed to create session: %v", err)
@@ -110,8 +110,8 @@ func TestSessionFsE2E(t *testing.T) {
}
session2, err := client.ResumeSession(t.Context(), sessionID, &copilot.ResumeSessionConfig{
- OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
- CreateSessionFsHandler: createSessionFsHandler,
+ OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
+ CreateSessionFsProvider: createSessionFsHandler,
})
if err != nil {
t.Fatalf("Failed to resume session: %v", err)
@@ -139,7 +139,7 @@ func TestSessionFsE2E(t *testing.T) {
ctx.ConfigureForTest(t)
client1 := ctx.NewClient(func(opts *copilot.ClientOptions) {
- opts.UseStdio = copilot.Bool(false)
+ opts.Connection = copilot.TcpConnection{Path: ctx.CLIPath}
})
t.Cleanup(func() { client1.ForceStop() })
@@ -149,16 +149,16 @@ func TestSessionFsE2E(t *testing.T) {
t.Fatalf("Failed to create initial session: %v", err)
}
- actualPort := client1.ActualPort()
- if actualPort == 0 {
+ runtimePort := client1.RuntimePort()
+ if runtimePort == 0 {
t.Fatalf("Expected non-zero port from TCP mode client")
}
client2 := copilot.NewClient(&copilot.ClientOptions{
- CLIUrl: fmt.Sprintf("localhost:%d", actualPort),
- LogLevel: "error",
- Env: ctx.Env(),
- SessionFs: sessionFsConfig,
+ Connection: copilot.UriConnection{URL: fmt.Sprintf("localhost:%d", runtimePort)},
+ LogLevel: "error",
+ Env: ctx.Env(),
+ SessionFs: sessionFsConfig,
})
t.Cleanup(func() { client2.ForceStop() })
@@ -172,8 +172,8 @@ func TestSessionFsE2E(t *testing.T) {
suppliedFileContent := strings.Repeat("x", 100_000)
session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{
- OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
- CreateSessionFsHandler: createSessionFsHandler,
+ OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
+ CreateSessionFsProvider: createSessionFsHandler,
Tools: []copilot.Tool{
copilot.DefineTool("get_big_string", "Returns a large string",
func(_ struct{}, inv copilot.ToolInvocation) (string, error) {
@@ -191,7 +191,7 @@ func TestSessionFsE2E(t *testing.T) {
t.Fatalf("Failed to send message: %v", err)
}
- messages, err := session.GetMessages(t.Context())
+ messages, err := session.GetEvents(t.Context())
if err != nil {
t.Fatalf("Failed to get messages: %v", err)
}
@@ -217,8 +217,8 @@ func TestSessionFsE2E(t *testing.T) {
ctx.ConfigureForTest(t)
session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{
- OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
- CreateSessionFsHandler: createSessionFsHandler,
+ OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
+ CreateSessionFsProvider: createSessionFsHandler,
})
if err != nil {
t.Fatalf("Failed to create session: %v", err)
@@ -256,8 +256,8 @@ func TestSessionFsE2E(t *testing.T) {
ctx.ConfigureForTest(t)
session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{
- OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
- CreateSessionFsHandler: createSessionFsHandler,
+ OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
+ CreateSessionFsProvider: createSessionFsHandler,
})
if err != nil {
t.Fatalf("Failed to create session: %v", err)
@@ -298,8 +298,8 @@ func TestSessionFsE2E(t *testing.T) {
ctx.ConfigureForTest(t)
session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{
- OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
- CreateSessionFsHandler: createSessionFsHandler,
+ OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
+ CreateSessionFsProvider: createSessionFsHandler,
})
if err != nil {
t.Fatalf("Failed to create session: %v", err)
@@ -408,7 +408,7 @@ func (h *testSessionFsHandler) Stat(path string) (*copilot.SessionFsFileInfo, er
}, nil
}
-func (h *testSessionFsHandler) Mkdir(path string, recursive bool, mode *int) error {
+func (h *testSessionFsHandler) MakeDirectory(path string, recursive bool, mode *int) error {
fullPath := providerPath(h.root, h.sessionID, path)
perm := os.FileMode(0o777)
if mode != nil {
@@ -420,7 +420,7 @@ func (h *testSessionFsHandler) Mkdir(path string, recursive bool, mode *int) err
return os.Mkdir(fullPath, perm)
}
-func (h *testSessionFsHandler) Readdir(path string) ([]string, error) {
+func (h *testSessionFsHandler) ReadDirectory(path string) ([]string, error) {
entries, err := os.ReadDir(providerPath(h.root, h.sessionID, path))
if err != nil {
return nil, err
@@ -432,7 +432,7 @@ func (h *testSessionFsHandler) Readdir(path string) ([]string, error) {
return names, nil
}
-func (h *testSessionFsHandler) ReaddirWithTypes(path string) ([]rpc.SessionFsReaddirWithTypesEntry, error) {
+func (h *testSessionFsHandler) ReadDirectoryWithTypes(path string) ([]rpc.SessionFsReaddirWithTypesEntry, error) {
entries, err := os.ReadDir(providerPath(h.root, h.sessionID, path))
if err != nil {
return nil, err
@@ -451,7 +451,7 @@ func (h *testSessionFsHandler) ReaddirWithTypes(path string) ([]rpc.SessionFsRea
return result, nil
}
-func (h *testSessionFsHandler) Rm(path string, recursive bool, force bool) error {
+func (h *testSessionFsHandler) Remove(path string, recursive bool, force bool) error {
fullPath := providerPath(h.root, h.sessionID, path)
var err error
if recursive {
@@ -533,7 +533,7 @@ func TestSessionFsHandlerOperationsE2E(t *testing.T) {
sessionID := "handler-session"
handler := &testSessionFsHandler{root: providerRoot, sessionID: sessionID}
- if err := handler.Mkdir("/workspace/nested", true, nil); err != nil {
+ if err := handler.MakeDirectory("/workspace/nested", true, nil); err != nil {
t.Fatalf("Mkdir failed: %v", err)
}
@@ -575,7 +575,7 @@ func TestSessionFsHandlerOperationsE2E(t *testing.T) {
t.Errorf("Expected content 'hello world', got %q", content)
}
- entries, err := handler.Readdir("/workspace/nested")
+ entries, err := handler.ReadDirectory("/workspace/nested")
if err != nil {
t.Fatalf("Readdir failed: %v", err)
}
@@ -583,7 +583,7 @@ func TestSessionFsHandlerOperationsE2E(t *testing.T) {
t.Errorf("Expected entries to contain 'file.txt', got %v", entries)
}
- typedEntries, err := handler.ReaddirWithTypes("/workspace/nested")
+ typedEntries, err := handler.ReadDirectoryWithTypes("/workspace/nested")
if err != nil {
t.Fatalf("ReaddirWithTypes failed: %v", err)
}
@@ -616,7 +616,7 @@ func TestSessionFsHandlerOperationsE2E(t *testing.T) {
t.Errorf("Expected renamed content 'hello world', got %q", renamedContent)
}
- if err := handler.Rm("/workspace/nested/renamed.txt", false, false); err != nil {
+ if err := handler.Remove("/workspace/nested/renamed.txt", false, false); err != nil {
t.Fatalf("Rm failed: %v", err)
}
removed, err := handler.Exists("/workspace/nested/renamed.txt")
@@ -628,7 +628,7 @@ func TestSessionFsHandlerOperationsE2E(t *testing.T) {
}
// Force removing a missing path should succeed.
- if err := handler.Rm("/workspace/nested/missing.txt", false, true); err != nil {
+ if err := handler.Remove("/workspace/nested/missing.txt", false, true); err != nil {
t.Errorf("Rm with force on missing path should not error, got %v", err)
}
diff --git a/go/internal/e2e/session_fs_sqlite_e2e_test.go b/go/internal/e2e/session_fs_sqlite_e2e_test.go
index f73cf2e34..3d453f0a8 100644
--- a/go/internal/e2e/session_fs_sqlite_e2e_test.go
+++ b/go/internal/e2e/session_fs_sqlite_e2e_test.go
@@ -100,7 +100,7 @@ func (p *inMemorySqliteProvider) Stat(path string) (*copilot.SessionFsFileInfo,
return nil, fmt.Errorf("not found: %s", path)
}
-func (p *inMemorySqliteProvider) Mkdir(path string, recursive bool, mode *int) error {
+func (p *inMemorySqliteProvider) MakeDirectory(path string, recursive bool, mode *int) error {
p.mu.Lock()
defer p.mu.Unlock()
if recursive {
@@ -114,7 +114,7 @@ func (p *inMemorySqliteProvider) Mkdir(path string, recursive bool, mode *int) e
return nil
}
-func (p *inMemorySqliteProvider) Readdir(path string) ([]string, error) {
+func (p *inMemorySqliteProvider) ReadDirectory(path string) ([]string, error) {
p.mu.Lock()
defer p.mu.Unlock()
prefix := strings.TrimRight(path, "/") + "/"
@@ -143,7 +143,7 @@ func (p *inMemorySqliteProvider) Readdir(path string) ([]string, error) {
return result, nil
}
-func (p *inMemorySqliteProvider) ReaddirWithTypes(path string) ([]rpc.SessionFsReaddirWithTypesEntry, error) {
+func (p *inMemorySqliteProvider) ReadDirectoryWithTypes(path string) ([]rpc.SessionFsReaddirWithTypesEntry, error) {
p.mu.Lock()
defer p.mu.Unlock()
prefix := strings.TrimRight(path, "/") + "/"
@@ -176,7 +176,7 @@ func (p *inMemorySqliteProvider) ReaddirWithTypes(path string) ([]rpc.SessionFsR
return result, nil
}
-func (p *inMemorySqliteProvider) Rm(path string, recursive bool, force bool) error {
+func (p *inMemorySqliteProvider) Remove(path string, recursive bool, force bool) error {
p.mu.Lock()
defer p.mu.Unlock()
delete(p.files, path)
@@ -268,8 +268,8 @@ func TestSessionFsSqliteE2E(t *testing.T) {
sqliteCalls = nil
session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{
- OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
- CreateSessionFsHandler: createSessionFsHandler,
+ OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
+ CreateSessionFsProvider: createSessionFsHandler,
})
if err != nil {
t.Fatalf("Failed to create session: %v", err)
@@ -306,8 +306,8 @@ func TestSessionFsSqliteE2E(t *testing.T) {
sqliteCalls = nil
session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{
- OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
- CreateSessionFsHandler: createSessionFsHandler,
+ OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
+ CreateSessionFsProvider: createSessionFsHandler,
})
if err != nil {
t.Fatalf("Failed to create session: %v", err)
diff --git a/go/internal/e2e/streaming_fidelity_e2e_test.go b/go/internal/e2e/streaming_fidelity_e2e_test.go
index 2684306d7..189b61bf2 100644
--- a/go/internal/e2e/streaming_fidelity_e2e_test.go
+++ b/go/internal/e2e/streaming_fidelity_e2e_test.go
@@ -19,7 +19,7 @@ func TestStreamingFidelityE2E(t *testing.T) {
session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{
OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
- Streaming: true,
+ Streaming: copilot.Bool(true),
})
if err != nil {
t.Fatalf("Failed to create session with streaming: %v", err)
@@ -94,7 +94,7 @@ func TestStreamingFidelityE2E(t *testing.T) {
session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{
OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
- Streaming: false,
+ Streaming: copilot.Bool(false),
})
if err != nil {
t.Fatalf("Failed to create session: %v", err)
@@ -146,7 +146,7 @@ func TestStreamingFidelityE2E(t *testing.T) {
session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{
OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
- Streaming: false,
+ Streaming: copilot.Bool(false),
})
if err != nil {
t.Fatalf("Failed to create session: %v", err)
@@ -163,7 +163,7 @@ func TestStreamingFidelityE2E(t *testing.T) {
session2, err := newClient.ResumeSession(t.Context(), session.SessionID, &copilot.ResumeSessionConfig{
OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
- Streaming: true,
+ Streaming: copilot.Bool(true),
})
if err != nil {
t.Fatalf("Failed to resume session: %v", err)
@@ -216,7 +216,7 @@ func TestStreamingFidelityE2E(t *testing.T) {
session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{
OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
- Streaming: true,
+ Streaming: copilot.Bool(true),
})
if err != nil {
t.Fatalf("Failed to create session with streaming: %v", err)
@@ -232,7 +232,7 @@ func TestStreamingFidelityE2E(t *testing.T) {
session2, err := newClient.ResumeSession(t.Context(), session.SessionID, &copilot.ResumeSessionConfig{
OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
- Streaming: false,
+ Streaming: copilot.Bool(false),
})
if err != nil {
t.Fatalf("Failed to resume session: %v", err)
@@ -291,7 +291,7 @@ func TestStreamingFidelityE2E(t *testing.T) {
// the streaming pipeline — deltas still arrive and complete successfully.
session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{
OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
- Streaming: true,
+ Streaming: copilot.Bool(true),
ReasoningEffort: "high",
})
if err != nil {
@@ -343,10 +343,10 @@ func TestStreamingFidelityE2E(t *testing.T) {
t.Errorf("Expected assistant message to contain '255' (15*17), got %q", lastAssistantContent)
}
- // Verify the session was created with reasoning effort via GetMessages
- messages, err := session.GetMessages(t.Context())
+ // Verify the session was created with reasoning effort via GetEvents
+ messages, err := session.GetEvents(t.Context())
if err != nil {
- t.Fatalf("GetMessages failed: %v", err)
+ t.Fatalf("GetEvents failed: %v", err)
}
var sessionStartReasoningEffort string
for _, msg := range messages {
diff --git a/go/internal/e2e/suspend_e2e_test.go b/go/internal/e2e/suspend_e2e_test.go
index 957fb58c6..8ce0c1fb1 100644
--- a/go/internal/e2e/suspend_e2e_test.go
+++ b/go/internal/e2e/suspend_e2e_test.go
@@ -50,9 +50,7 @@ func TestSuspendE2E(t *testing.T) {
_, cliURL := startTcpServer(t, ctx)
client1 := ctx.NewClient(func(opts *copilot.ClientOptions) {
- opts.CLIUrl = cliURL
- opts.CLIPath = ""
- opts.TCPConnectionToken = sharedTcpToken
+ opts.Connection = copilot.UriConnection{URL: cliURL, ConnectionToken: sharedTcpToken}
})
t.Cleanup(func() { client1.ForceStop() })
@@ -76,9 +74,7 @@ func TestSuspendE2E(t *testing.T) {
client1.ForceStop()
client2 := ctx.NewClient(func(opts *copilot.ClientOptions) {
- opts.CLIUrl = cliURL
- opts.CLIPath = ""
- opts.TCPConnectionToken = sharedTcpToken
+ opts.Connection = copilot.UriConnection{URL: cliURL, ConnectionToken: sharedTcpToken}
})
t.Cleanup(func() { client2.ForceStop() })
diff --git a/go/internal/e2e/testharness/context.go b/go/internal/e2e/testharness/context.go
index d7d30e090..9055442a9 100644
--- a/go/internal/e2e/testharness/context.go
+++ b/go/internal/e2e/testharness/context.go
@@ -197,16 +197,17 @@ func (c *TestContext) Env() []string {
// Optional overrides can be applied to the default ClientOptions via the opts function.
func (c *TestContext) NewClient(opts ...func(*copilot.ClientOptions)) *copilot.Client {
options := &copilot.ClientOptions{
- CLIPath: c.CLIPath,
- Cwd: c.WorkDir,
- Env: c.Env(),
+ Connection: copilot.StdioConnection{Path: c.CLIPath},
+ Cwd: c.WorkDir,
+ Env: c.Env(),
}
for _, opt := range opts {
opt(options)
}
- if options.GitHubToken == "" && options.CLIUrl == "" {
+ _, externalRuntime := options.Connection.(copilot.UriConnection)
+ if options.GitHubToken == "" && !externalRuntime {
options.GitHubToken = defaultGitHubToken
}
diff --git a/go/internal/e2e/testharness/helper.go b/go/internal/e2e/testharness/helper.go
index 27cf77cb5..ca94d03ad 100644
--- a/go/internal/e2e/testharness/helper.go
+++ b/go/internal/e2e/testharness/helper.go
@@ -90,7 +90,7 @@ func GetNextEventOfType(session *copilot.Session, eventType copilot.SessionEvent
}
func getExistingFinalResponse(ctx context.Context, session *copilot.Session, alreadyIdle bool) (*copilot.SessionEvent, error) {
- messages, err := session.GetMessages(ctx)
+ messages, err := session.GetEvents(ctx)
if err != nil {
return nil, err
}
diff --git a/go/samples/chat.go b/go/samples/chat.go
index 1a1b7e203..2f34a243c 100644
--- a/go/samples/chat.go
+++ b/go/samples/chat.go
@@ -17,7 +17,7 @@ const reset = "\033[0m"
func main() {
ctx := context.Background()
cliPath := filepath.Join("..", "..", "nodejs", "node_modules", "@github", "copilot", "index.js")
- client := copilot.NewClient(&copilot.ClientOptions{CLIPath: cliPath})
+ client := copilot.NewClient(&copilot.ClientOptions{Connection: copilot.StdioConnection{Path: cliPath}})
if err := client.Start(ctx); err != nil {
panic(err)
}
diff --git a/go/samples/manual_tool_resume/main.go b/go/samples/manual_tool_resume/main.go
index 74b891b3a..1e0a23f5b 100644
--- a/go/samples/manual_tool_resume/main.go
+++ b/go/samples/manual_tool_resume/main.go
@@ -33,7 +33,7 @@ func manualTool() copilot.Tool {
func newClient() *copilot.Client {
cliPath := filepath.Join("..", "..", "nodejs", "node_modules", "@github", "copilot", "index.js")
- return copilot.NewClient(&copilot.ClientOptions{CLIPath: cliPath})
+ return copilot.NewClient(&copilot.ClientOptions{Connection: copilot.StdioConnection{Path: cliPath}})
}
func watchPermission(session *copilot.Session) (<-chan *copilot.PermissionRequestedData, func()) {
diff --git a/go/session.go b/go/session.go
index bc7e2ede9..067bd0314 100644
--- a/go/session.go
+++ b/go/session.go
@@ -63,9 +63,9 @@ type Session struct {
permissionMux sync.RWMutex
userInputHandler UserInputHandler
userInputMux sync.RWMutex
- exitPlanModeHandler ExitPlanModeHandler
+ exitPlanModeHandler ExitPlanModeRequestHandler
exitPlanModeMu sync.RWMutex
- autoModeSwitchHandler AutoModeSwitchHandler
+ autoModeSwitchHandler AutoModeSwitchRequestHandler
autoModeSwitchMu sync.RWMutex
hooks *SessionHooks
hooksMux sync.RWMutex
@@ -157,6 +157,14 @@ func (s *Session) Send(ctx context.Context, options MessageOptions) (string, err
return response.MessageID, nil
}
+// SendPrompt is a convenience wrapper for [Session.Send] that takes a plain
+// prompt string instead of a [MessageOptions] struct. Equivalent to:
+//
+// session.Send(ctx, copilot.MessageOptions{Prompt: prompt})
+func (s *Session) SendPrompt(ctx context.Context, prompt string) (string, error) {
+ return s.Send(ctx, MessageOptions{Prompt: prompt})
+}
+
// SendAndWait sends a message to this session and waits until the session becomes idle.
//
// This is a convenience method that combines [Session.Send] with waiting for
@@ -237,6 +245,15 @@ func (s *Session) SendAndWait(ctx context.Context, options MessageOptions) (*Ses
}
}
+// SendPromptAndWait is a convenience wrapper for [Session.SendAndWait] that
+// takes a plain prompt string instead of a [MessageOptions] struct. Equivalent
+// to:
+//
+// session.SendAndWait(ctx, copilot.MessageOptions{Prompt: prompt})
+func (s *Session) SendPromptAndWait(ctx context.Context, prompt string) (*SessionEvent, error) {
+ return s.SendAndWait(ctx, MessageOptions{Prompt: prompt})
+}
+
// On subscribes to events from this session.
//
// Events include assistant messages, tool executions, errors, and session state
@@ -363,13 +380,13 @@ func (s *Session) handleUserInputRequest(request UserInputRequest) (UserInputRes
return handler(request, invocation)
}
-func (s *Session) registerExitPlanModeHandler(handler ExitPlanModeHandler) {
+func (s *Session) registerExitPlanModeHandler(handler ExitPlanModeRequestHandler) {
s.exitPlanModeMu.Lock()
defer s.exitPlanModeMu.Unlock()
s.exitPlanModeHandler = handler
}
-func (s *Session) getExitPlanModeHandler() ExitPlanModeHandler {
+func (s *Session) getExitPlanModeHandler() ExitPlanModeRequestHandler {
s.exitPlanModeMu.RLock()
defer s.exitPlanModeMu.RUnlock()
return s.exitPlanModeHandler
@@ -384,13 +401,13 @@ func (s *Session) handleExitPlanModeRequest(request ExitPlanModeRequest) (ExitPl
return handler(request, ExitPlanModeInvocation{SessionID: s.SessionID})
}
-func (s *Session) registerAutoModeSwitchHandler(handler AutoModeSwitchHandler) {
+func (s *Session) registerAutoModeSwitchHandler(handler AutoModeSwitchRequestHandler) {
s.autoModeSwitchMu.Lock()
defer s.autoModeSwitchMu.Unlock()
s.autoModeSwitchHandler = handler
}
-func (s *Session) getAutoModeSwitchHandler() AutoModeSwitchHandler {
+func (s *Session) getAutoModeSwitchHandler() AutoModeSwitchRequestHandler {
s.autoModeSwitchMu.RLock()
defer s.autoModeSwitchMu.RUnlock()
return s.autoModeSwitchHandler
@@ -834,7 +851,7 @@ func (ui *SessionUI) Select(ctx context.Context, message string, options []strin
// Input shows a text input dialog. Returns the entered text, or empty string and
// false if the user declines/cancels.
-func (ui *SessionUI) Input(ctx context.Context, message string, opts *InputOptions) (string, bool, error) {
+func (ui *SessionUI) Input(ctx context.Context, message string, opts *UiInputOptions) (string, bool, error) {
if err := ui.session.assertElicitation(); err != nil {
return "", false, err
}
@@ -1147,7 +1164,7 @@ func rpcPermissionDecisionFromKind(kind rpc.PermissionDecisionKind) rpc.Permissi
}
}
-// GetMessages retrieves all events and messages from this session's history.
+// GetEvents retrieves all events from this session's history.
//
// This returns the complete conversation history including user messages,
// assistant responses, tool executions, and other session events in
@@ -1157,9 +1174,9 @@ func rpcPermissionDecisionFromKind(kind rpc.PermissionDecisionKind) rpc.Permissi
//
// Example:
//
-// events, err := session.GetMessages(context.Background())
+// events, err := session.GetEvents(context.Background())
// if err != nil {
-// log.Printf("Failed to get messages: %v", err)
+// log.Printf("Failed to get events: %v", err)
// return
// }
// for _, event := range events {
@@ -1167,16 +1184,16 @@ func rpcPermissionDecisionFromKind(kind rpc.PermissionDecisionKind) rpc.Permissi
// fmt.Println("Assistant:", d.Content)
// }
// }
-func (s *Session) GetMessages(ctx context.Context) ([]SessionEvent, error) {
+func (s *Session) GetEvents(ctx context.Context) ([]SessionEvent, error) {
result, err := s.client.Request("session.getMessages", sessionGetMessagesRequest{SessionID: s.SessionID})
if err != nil {
- return nil, fmt.Errorf("failed to get messages: %w", err)
+ return nil, fmt.Errorf("failed to get events: %w", err)
}
var response sessionGetMessagesResponse
if err := json.Unmarshal(result, &response); err != nil {
- return nil, fmt.Errorf("failed to unmarshal get messages response: %w", err)
+ return nil, fmt.Errorf("failed to unmarshal get events response: %w", err)
}
return response.Events, nil
}
@@ -1235,14 +1252,6 @@ func (s *Session) Disconnect() error {
return nil
}
-// Deprecated: Use [Session.Disconnect] instead. Destroy will be removed in a future release.
-//
-// Destroy closes this session and releases all in-memory resources.
-// Session data on disk is preserved for later resumption.
-func (s *Session) Destroy() error {
- return s.Disconnect()
-}
-
// Abort aborts the currently processing message in this session.
//
// Use this to cancel a long-running request. The session remains valid
diff --git a/go/session_fs_provider.go b/go/session_fs_provider.go
index f77a5317c..50922d7bc 100644
--- a/go/session_fs_provider.go
+++ b/go/session_fs_provider.go
@@ -34,16 +34,16 @@ type SessionFsProvider interface {
Stat(path string) (*SessionFsFileInfo, error)
// Mkdir creates a directory. If recursive is true, create parent directories as needed.
// mode is an optional POSIX-style permission mode (e.g., 0o755). Pass nil to use the OS default.
- Mkdir(path string, recursive bool, mode *int) error
+ MakeDirectory(path string, recursive bool, mode *int) error
// Readdir lists the names of entries in a directory.
// Return os.ErrNotExist if the directory does not exist.
- Readdir(path string) ([]string, error)
+ ReadDirectory(path string) ([]string, error)
// ReaddirWithTypes lists entries with type information.
// Return os.ErrNotExist if the directory does not exist.
- ReaddirWithTypes(path string) ([]rpc.SessionFsReaddirWithTypesEntry, error)
+ ReadDirectoryWithTypes(path string) ([]rpc.SessionFsReaddirWithTypesEntry, error)
// Rm removes a file or directory. If recursive is true, remove contents too.
// If force is true, do not return an error when the path does not exist.
- Rm(path string, recursive bool, force bool) error
+ Remove(path string, recursive bool, force bool) error
// Rename moves/renames a file or directory.
Rename(src string, dest string) error
}
@@ -152,14 +152,14 @@ func (a *sessionFsAdapter) Mkdir(request *rpc.SessionFsMkdirRequest) (*rpc.Sessi
m := int(*request.Mode)
mode = &m
}
- if err := a.provider.Mkdir(request.Path, recursive, mode); err != nil {
+ if err := a.provider.MakeDirectory(request.Path, recursive, mode); err != nil {
return toSessionFsError(err), nil
}
return nil, nil
}
func (a *sessionFsAdapter) Readdir(request *rpc.SessionFsReaddirRequest) (*rpc.SessionFsReaddirResult, error) {
- entries, err := a.provider.Readdir(request.Path)
+ entries, err := a.provider.ReadDirectory(request.Path)
if err != nil {
return &rpc.SessionFsReaddirResult{Error: toSessionFsError(err)}, nil
}
@@ -167,7 +167,7 @@ func (a *sessionFsAdapter) Readdir(request *rpc.SessionFsReaddirRequest) (*rpc.S
}
func (a *sessionFsAdapter) ReaddirWithTypes(request *rpc.SessionFsReaddirWithTypesRequest) (*rpc.SessionFsReaddirWithTypesResult, error) {
- entries, err := a.provider.ReaddirWithTypes(request.Path)
+ entries, err := a.provider.ReadDirectoryWithTypes(request.Path)
if err != nil {
return &rpc.SessionFsReaddirWithTypesResult{Error: toSessionFsError(err)}, nil
}
@@ -177,7 +177,7 @@ func (a *sessionFsAdapter) ReaddirWithTypes(request *rpc.SessionFsReaddirWithTyp
func (a *sessionFsAdapter) Rm(request *rpc.SessionFsRmRequest) (*rpc.SessionFsError, error) {
recursive := request.Recursive != nil && *request.Recursive
force := request.Force != nil && *request.Force
- if err := a.provider.Rm(request.Path, recursive, force); err != nil {
+ if err := a.provider.Remove(request.Path, recursive, force); err != nil {
return toSessionFsError(err), nil
}
return nil, nil
diff --git a/go/types.go b/go/types.go
index be3496c89..e97cb5a37 100644
--- a/go/types.go
+++ b/go/types.go
@@ -8,95 +8,128 @@ import (
"github.com/github/copilot-sdk/go/rpc"
)
-// ConnectionState represents the client connection state
-type ConnectionState string
+// connectionState is the internal client connection state.
+type connectionState string
const (
- StateDisconnected ConnectionState = "disconnected"
- StateConnecting ConnectionState = "connecting"
- StateConnected ConnectionState = "connected"
- StateError ConnectionState = "error"
+ stateDisconnected connectionState = "disconnected"
+ stateConnecting connectionState = "connecting"
+ stateConnected connectionState = "connected"
+ stateError connectionState = "error"
)
-// ClientOptions configures the CopilotClient
+// RuntimeConnection describes how a [Client] connects to the Copilot runtime.
+//
+// Construct one with a [StdioConnection], [TcpConnection], or [UriConnection]
+// literal and pass it via [ClientOptions.Connection]. When [ClientOptions.Connection]
+// is nil, the default is an empty [StdioConnection] (the SDK spawns the bundled
+// runtime and communicates over stdin/stdout).
+type RuntimeConnection interface {
+ runtimeConnection()
+}
+
+// StdioConnection spawns a runtime child process and communicates over its
+// stdin/stdout pipes. This is the default when no connection is configured.
+type StdioConnection struct {
+ // Path is the runtime executable. When empty, the bundled runtime is used.
+ Path string
+ // Args are extra command-line arguments inserted before SDK-managed args.
+ Args []string
+}
+
+func (StdioConnection) runtimeConnection() {}
+
+// TcpConnection spawns a runtime child process that listens on a TCP socket
+// and connects to it.
+type TcpConnection struct {
+ // Port is the TCP port the runtime listens on. 0 (the default) lets the
+ // runtime pick a free port; the chosen port is then available via
+ // [Client.RuntimePort] after [Client.Start] returns.
+ Port int
+ // ConnectionToken is an optional shared secret sent in the `connect`
+ // handshake. When empty, a UUID is generated automatically so the
+ // loopback listener is safe by default.
+ ConnectionToken string
+ // Path is the runtime executable. When empty, the bundled runtime is used.
+ Path string
+ // Args are extra command-line arguments inserted before SDK-managed args.
+ Args []string
+}
+
+func (TcpConnection) runtimeConnection() {}
+
+// UriConnection connects to an already-running runtime at the given URL.
+// The SDK does not spawn a process in this mode.
+type UriConnection struct {
+ // URL of the runtime. Accepts "port", "host:port", or a full URL such
+ // as "http://host:port".
+ URL string
+ // ConnectionToken authenticates the connection; must match what the
+ // remote runtime expects.
+ ConnectionToken string
+}
+
+func (UriConnection) runtimeConnection() {}
+
+// ClientOptions configures the [Client].
type ClientOptions struct {
- // CLIPath is the path to the Copilot CLI executable (default: "copilot")
- CLIPath string
- // CLIArgs are extra arguments to pass to the CLI executable (inserted before SDK-managed args)
- CLIArgs []string
- // Cwd is the working directory for the CLI process (default: "" = inherit from current process)
+ // Connection describes how to connect to the Copilot runtime. When nil,
+ // defaults to an empty [StdioConnection] (spawn the bundled runtime over
+ // stdio).
+ Connection RuntimeConnection
+ // Cwd is the working directory for the runtime process.
+ // If empty, inherits the current process's working directory.
Cwd string
- // CopilotHome is the base directory for Copilot data (session state, config, etc.).
- // Sets the COPILOT_HOME environment variable on the spawned CLI process.
- // When empty, the CLI defaults to ~/.copilot.
- // This does not affect where the Go SDK extracts the embedded CLI binary;
- // use embeddedcli.Config.Dir to control that install/cache location.
- // This option is only used when the SDK spawns the CLI process; it is ignored
- // when connecting to an external server via CLIUrl.
- CopilotHome string
- // Port for TCP transport (default: 0 = random port)
- Port int
- // UseStdio controls whether to use stdio transport instead of TCP.
- // Default: nil (use default = true, i.e. stdio). Use Bool(false) to explicitly select TCP.
- UseStdio *bool
- // TCPConnectionToken is the token sent in the `connect` handshake when using TCP transport.
- // Only meaningful in TCP mode. When the SDK spawns its own CLI in TCP mode and this is
- // empty, an auto-generated UUID is used so the loopback listener is safe by default.
- // Combining this with UseStdio=true is rejected (stdio is pre-authenticated by transport).
- TCPConnectionToken string
- // CLIUrl is the URL of an existing Copilot CLI server to connect to over TCP
- // Format: "host:port", "http://host:port", or just "port" (defaults to localhost)
- // Examples: "localhost:8080", "http://127.0.0.1:9000", "8080"
- // Mutually exclusive with CLIPath, UseStdio
- CLIUrl string
- // LogLevel for the CLI server
+ // BaseDirectory is the base directory for Copilot data (session state,
+ // config, etc.). Sets the COPILOT_HOME environment variable on the
+ // spawned runtime. When empty, the runtime defaults to ~/.copilot.
+ // This does not affect where the Go SDK extracts the embedded CLI
+ // binary; use embeddedcli.Config.Dir to control that install/cache
+ // location.
+ // Ignored when connecting to an existing runtime via [UriConnection].
+ BaseDirectory string
+ // LogLevel for the runtime. When empty (the default), the runtime
+ // uses its own default level; the SDK does not pass --log-level.
+ // Recognized values: "none", "error", "warning", "info", "debug", "all".
LogLevel string
- // AutoStart automatically starts the CLI server on first use (default: true).
- // Use Bool(false) to disable.
- AutoStart *bool
- // Deprecated: AutoRestart has no effect and will be removed in a future release.
- AutoRestart *bool
- // Env is the environment variables for the CLI process (default: inherits from current process).
- // Each entry is of the form "key=value".
- // If Env is nil, the new process uses the current process's environment.
- // If Env contains duplicate environment keys, only the last value in the
- // slice for each duplicate key is used.
+ // Env are the environment variables for the runtime process (default:
+ // inherits from current process). Each entry is of the form "KEY=VALUE".
+ // If Env contains duplicate keys, only the last value for each key is used.
Env []string
// GitHubToken is the GitHub token to use for authentication.
- // When provided, the token is passed to the CLI server via environment variable.
- // This takes priority over other authentication methods.
+ // When provided, the token is passed to the runtime via environment
+ // variable. This takes priority over other authentication methods.
GitHubToken string
- // UseLoggedInUser controls whether to use the logged-in user for authentication.
- // When true, the CLI server will attempt to use stored OAuth tokens or gh CLI auth.
- // When false, only explicit tokens (GitHubToken or environment variables) are used.
+ // UseLoggedInUser controls whether to use the logged-in user for
+ // authentication. When true, the runtime attempts to use stored OAuth
+ // tokens or gh CLI auth. When false, only explicit tokens (GitHubToken
+ // or environment variables) are used.
// Default: true (but defaults to false when GitHubToken is provided).
- // Use Bool(false) to explicitly disable.
UseLoggedInUser *bool
// OnListModels is a custom handler for listing available models.
- // When provided, client.ListModels() calls this handler instead of
- // querying the CLI server. Useful in BYOK mode to return models
- // available from your custom provider.
+ // When provided, [Client.ListModels] calls this handler instead of
+ // querying the runtime. Useful in BYOK mode to return models available
+ // from your custom provider.
OnListModels func(ctx context.Context) ([]ModelInfo, error)
// SessionFs configures a custom session filesystem provider.
// When provided, the client registers as the session filesystem provider
- // on connection, routing session-scoped file I/O through per-session handlers.
+ // on connection, routing session-scoped file I/O through per-session
+ // handlers.
SessionFs *SessionFsConfig
- // Telemetry configures OpenTelemetry integration for the Copilot CLI process.
- // When non-nil, COPILOT_OTEL_ENABLED=true is set and any populated fields
- // are mapped to the corresponding environment variables.
+ // Telemetry configures OpenTelemetry integration for the runtime.
+ // When non-nil, COPILOT_OTEL_ENABLED=true is set and any populated
+ // fields are mapped to the corresponding environment variables.
Telemetry *TelemetryConfig
- // SessionIdleTimeoutSeconds configures the server-wide session idle timeout in seconds.
- // Sessions without activity for this duration are automatically cleaned up.
- // Set to 0 or leave unset to disable (sessions live indefinitely).
- // This option is only used when the SDK spawns the CLI process; it is ignored
- // when connecting to an external server via CLIUrl.
+ // SessionIdleTimeoutSeconds configures the server-wide session idle
+ // timeout in seconds. Sessions without activity for this duration are
+ // automatically cleaned up. Set to 0 or leave unset to disable.
+ // Ignored when connecting to an existing runtime via [UriConnection].
SessionIdleTimeoutSeconds int
- // Remote enables remote session support (Mission Control integration).
- // When true, sessions in a GitHub repository working directory are
- // accessible from GitHub web and mobile.
- // This option is only used when the SDK spawns the CLI process; it is ignored
- // when connecting to an external server via CLIUrl.
- Remote bool
+ // EnableRemoteSessions enables remote session support (Mission Control
+ // integration). When true, sessions in a GitHub repository working
+ // directory are accessible from GitHub web and mobile.
+ // Ignored when connecting to an existing runtime via [UriConnection].
+ EnableRemoteSessions bool
}
// CloudSessionRepository is GitHub repository metadata associated with a cloud session.
@@ -319,8 +352,8 @@ type ExitPlanModeInvocation struct {
SessionID string
}
-// ExitPlanModeHandler handles exit-plan-mode requests from the agent.
-type ExitPlanModeHandler func(request ExitPlanModeRequest, invocation ExitPlanModeInvocation) (ExitPlanModeResult, error)
+// ExitPlanModeRequestHandler handles exit-plan-mode requests from the agent.
+type ExitPlanModeRequestHandler func(request ExitPlanModeRequest, invocation ExitPlanModeInvocation) (ExitPlanModeResult, error)
// AutoModeSwitchRequest represents a request to switch to auto mode after an eligible rate limit.
type AutoModeSwitchRequest struct {
@@ -333,16 +366,39 @@ type AutoModeSwitchInvocation struct {
SessionID string
}
-// AutoModeSwitchHandler handles auto-mode-switch requests from the agent.
-type AutoModeSwitchHandler func(request AutoModeSwitchRequest, invocation AutoModeSwitchInvocation) (AutoModeSwitchResponse, error)
+// AutoModeSwitchRequestHandler handles auto-mode-switch requests from the agent.
+type AutoModeSwitchRequestHandler func(request AutoModeSwitchRequest, invocation AutoModeSwitchInvocation) (AutoModeSwitchResponse, error)
// PreToolUseHookInput is the input for a pre-tool-use hook
type PreToolUseHookInput struct {
- SessionID string `json:"sessionId"`
- Timestamp int64 `json:"timestamp"`
- Cwd string `json:"cwd"`
- ToolName string `json:"toolName"`
- ToolArgs any `json:"toolArgs"`
+ SessionID string `json:"sessionId"`
+ Timestamp time.Time `json:"-"`
+ Cwd string `json:"cwd"`
+ ToolName string `json:"toolName"`
+ ToolArgs any `json:"toolArgs"`
+}
+
+// MarshalJSON implements json.Marshaler, emitting Timestamp as Unix milliseconds.
+func (h PreToolUseHookInput) MarshalJSON() ([]byte, error) {
+ type alias PreToolUseHookInput
+ return json.Marshal(&struct {
+ Timestamp int64 `json:"timestamp"`
+ alias
+ }{Timestamp: h.Timestamp.UnixMilli(), alias: alias(h)})
+}
+
+// UnmarshalJSON implements json.Unmarshaler, parsing Timestamp from Unix milliseconds.
+func (h *PreToolUseHookInput) UnmarshalJSON(data []byte) error {
+ type alias PreToolUseHookInput
+ aux := &struct {
+ Timestamp int64 `json:"timestamp"`
+ *alias
+ }{alias: (*alias)(h)}
+ if err := json.Unmarshal(data, aux); err != nil {
+ return err
+ }
+ h.Timestamp = time.UnixMilli(aux.Timestamp)
+ return nil
}
// PreToolUseHookOutput is the output for a pre-tool-use hook
@@ -359,12 +415,35 @@ type PreToolUseHandler func(input PreToolUseHookInput, invocation HookInvocation
// PostToolUseHookInput is the input for a post-tool-use hook
type PostToolUseHookInput struct {
- SessionID string `json:"sessionId"`
- Timestamp int64 `json:"timestamp"`
- Cwd string `json:"cwd"`
- ToolName string `json:"toolName"`
- ToolArgs any `json:"toolArgs"`
- ToolResult any `json:"toolResult"`
+ SessionID string `json:"sessionId"`
+ Timestamp time.Time `json:"-"`
+ Cwd string `json:"cwd"`
+ ToolName string `json:"toolName"`
+ ToolArgs any `json:"toolArgs"`
+ ToolResult any `json:"toolResult"`
+}
+
+// MarshalJSON implements json.Marshaler, emitting Timestamp as Unix milliseconds.
+func (h PostToolUseHookInput) MarshalJSON() ([]byte, error) {
+ type alias PostToolUseHookInput
+ return json.Marshal(&struct {
+ Timestamp int64 `json:"timestamp"`
+ alias
+ }{Timestamp: h.Timestamp.UnixMilli(), alias: alias(h)})
+}
+
+// UnmarshalJSON implements json.Unmarshaler, parsing Timestamp from Unix milliseconds.
+func (h *PostToolUseHookInput) UnmarshalJSON(data []byte) error {
+ type alias PostToolUseHookInput
+ aux := &struct {
+ Timestamp int64 `json:"timestamp"`
+ *alias
+ }{alias: (*alias)(h)}
+ if err := json.Unmarshal(data, aux); err != nil {
+ return err
+ }
+ h.Timestamp = time.UnixMilli(aux.Timestamp)
+ return nil
}
// PostToolUseHookOutput is the output for a post-tool-use hook
@@ -379,10 +458,33 @@ type PostToolUseHandler func(input PostToolUseHookInput, invocation HookInvocati
// UserPromptSubmittedHookInput is the input for a user-prompt-submitted hook
type UserPromptSubmittedHookInput struct {
- SessionID string `json:"sessionId"`
- Timestamp int64 `json:"timestamp"`
- Cwd string `json:"cwd"`
- Prompt string `json:"prompt"`
+ SessionID string `json:"sessionId"`
+ Timestamp time.Time `json:"-"`
+ Cwd string `json:"cwd"`
+ Prompt string `json:"prompt"`
+}
+
+// MarshalJSON implements json.Marshaler, emitting Timestamp as Unix milliseconds.
+func (h UserPromptSubmittedHookInput) MarshalJSON() ([]byte, error) {
+ type alias UserPromptSubmittedHookInput
+ return json.Marshal(&struct {
+ Timestamp int64 `json:"timestamp"`
+ alias
+ }{Timestamp: h.Timestamp.UnixMilli(), alias: alias(h)})
+}
+
+// UnmarshalJSON implements json.Unmarshaler, parsing Timestamp from Unix milliseconds.
+func (h *UserPromptSubmittedHookInput) UnmarshalJSON(data []byte) error {
+ type alias UserPromptSubmittedHookInput
+ aux := &struct {
+ Timestamp int64 `json:"timestamp"`
+ *alias
+ }{alias: (*alias)(h)}
+ if err := json.Unmarshal(data, aux); err != nil {
+ return err
+ }
+ h.Timestamp = time.UnixMilli(aux.Timestamp)
+ return nil
}
// UserPromptSubmittedHookOutput is the output for a user-prompt-submitted hook
@@ -397,11 +499,34 @@ type UserPromptSubmittedHandler func(input UserPromptSubmittedHookInput, invocat
// SessionStartHookInput is the input for a session-start hook
type SessionStartHookInput struct {
- SessionID string `json:"sessionId"`
- Timestamp int64 `json:"timestamp"`
- Cwd string `json:"cwd"`
- Source string `json:"source"` // "startup", "resume", "new"
- InitialPrompt string `json:"initialPrompt,omitempty"`
+ SessionID string `json:"sessionId"`
+ Timestamp time.Time `json:"-"`
+ Cwd string `json:"cwd"`
+ Source string `json:"source"` // "startup", "resume", "new"
+ InitialPrompt string `json:"initialPrompt,omitempty"`
+}
+
+// MarshalJSON implements json.Marshaler, emitting Timestamp as Unix milliseconds.
+func (h SessionStartHookInput) MarshalJSON() ([]byte, error) {
+ type alias SessionStartHookInput
+ return json.Marshal(&struct {
+ Timestamp int64 `json:"timestamp"`
+ alias
+ }{Timestamp: h.Timestamp.UnixMilli(), alias: alias(h)})
+}
+
+// UnmarshalJSON implements json.Unmarshaler, parsing Timestamp from Unix milliseconds.
+func (h *SessionStartHookInput) UnmarshalJSON(data []byte) error {
+ type alias SessionStartHookInput
+ aux := &struct {
+ Timestamp int64 `json:"timestamp"`
+ *alias
+ }{alias: (*alias)(h)}
+ if err := json.Unmarshal(data, aux); err != nil {
+ return err
+ }
+ h.Timestamp = time.UnixMilli(aux.Timestamp)
+ return nil
}
// SessionStartHookOutput is the output for a session-start hook
@@ -415,12 +540,35 @@ type SessionStartHandler func(input SessionStartHookInput, invocation HookInvoca
// SessionEndHookInput is the input for a session-end hook
type SessionEndHookInput struct {
- SessionID string `json:"sessionId"`
- Timestamp int64 `json:"timestamp"`
- Cwd string `json:"cwd"`
- Reason string `json:"reason"` // "complete", "error", "abort", "timeout", "user_exit"
- FinalMessage string `json:"finalMessage,omitempty"`
- Error string `json:"error,omitempty"`
+ SessionID string `json:"sessionId"`
+ Timestamp time.Time `json:"-"`
+ Cwd string `json:"cwd"`
+ Reason string `json:"reason"` // "complete", "error", "abort", "timeout", "user_exit"
+ FinalMessage string `json:"finalMessage,omitempty"`
+ Error string `json:"error,omitempty"`
+}
+
+// MarshalJSON implements json.Marshaler, emitting Timestamp as Unix milliseconds.
+func (h SessionEndHookInput) MarshalJSON() ([]byte, error) {
+ type alias SessionEndHookInput
+ return json.Marshal(&struct {
+ Timestamp int64 `json:"timestamp"`
+ alias
+ }{Timestamp: h.Timestamp.UnixMilli(), alias: alias(h)})
+}
+
+// UnmarshalJSON implements json.Unmarshaler, parsing Timestamp from Unix milliseconds.
+func (h *SessionEndHookInput) UnmarshalJSON(data []byte) error {
+ type alias SessionEndHookInput
+ aux := &struct {
+ Timestamp int64 `json:"timestamp"`
+ *alias
+ }{alias: (*alias)(h)}
+ if err := json.Unmarshal(data, aux); err != nil {
+ return err
+ }
+ h.Timestamp = time.UnixMilli(aux.Timestamp)
+ return nil
}
// SessionEndHookOutput is the output for a session-end hook
@@ -435,12 +583,35 @@ type SessionEndHandler func(input SessionEndHookInput, invocation HookInvocation
// ErrorOccurredHookInput is the input for an error-occurred hook
type ErrorOccurredHookInput struct {
- SessionID string `json:"sessionId"`
- Timestamp int64 `json:"timestamp"`
- Cwd string `json:"cwd"`
- Error string `json:"error"`
- ErrorContext string `json:"errorContext"` // "model_call", "tool_execution", "system", "user_input"
- Recoverable bool `json:"recoverable"`
+ SessionID string `json:"sessionId"`
+ Timestamp time.Time `json:"-"`
+ Cwd string `json:"cwd"`
+ Error string `json:"error"`
+ ErrorContext string `json:"errorContext"` // "model_call", "tool_execution", "system", "user_input"
+ Recoverable bool `json:"recoverable"`
+}
+
+// MarshalJSON implements json.Marshaler, emitting Timestamp as Unix milliseconds.
+func (h ErrorOccurredHookInput) MarshalJSON() ([]byte, error) {
+ type alias ErrorOccurredHookInput
+ return json.Marshal(&struct {
+ Timestamp int64 `json:"timestamp"`
+ alias
+ }{Timestamp: h.Timestamp.UnixMilli(), alias: alias(h)})
+}
+
+// UnmarshalJSON implements json.Unmarshaler, parsing Timestamp from Unix milliseconds.
+func (h *ErrorOccurredHookInput) UnmarshalJSON(data []byte) error {
+ type alias ErrorOccurredHookInput
+ aux := &struct {
+ Timestamp int64 `json:"timestamp"`
+ *alias
+ }{alias: (*alias)(h)}
+ if err := json.Unmarshal(data, aux); err != nil {
+ return err
+ }
+ h.Timestamp = time.UnixMilli(aux.Timestamp)
+ return nil
}
// ErrorOccurredHookOutput is the output for an error-occurred hook
@@ -476,8 +647,18 @@ type MCPServerConfig interface {
}
// MCPStdioServerConfig configures a local/stdio MCP server.
+//
+// The Tools field controls which tools from the server are exposed:
+// - nil (omitted from the wire): all tools (CLI default)
+// - &[]string{"*"}: explicit "all tools"
+// - &[]string{}: no tools
+// - &[]string{"foo","bar"}: only those tools
+//
+// The pointer-to-slice form is required so that a nil pointer (omitted from
+// the wire) is distinguishable from a non-nil pointer to an empty slice
+// (sent as `"tools": []`).
type MCPStdioServerConfig struct {
- Tools []string `json:"tools"`
+ Tools *[]string `json:"tools,omitempty"`
Timeout int `json:"timeout,omitempty"`
Command string `json:"command"`
Args []string `json:"args,omitempty"`
@@ -500,8 +681,10 @@ func (c MCPStdioServerConfig) MarshalJSON() ([]byte, error) {
}
// MCPHTTPServerConfig configures a remote MCP server (HTTP or SSE).
+//
+// See [MCPStdioServerConfig] for the semantics of the Tools field.
type MCPHTTPServerConfig struct {
- Tools []string `json:"tools"`
+ Tools *[]string `json:"tools,omitempty"`
Timeout int `json:"timeout,omitempty"`
URL string `json:"url"`
Headers map[string]string `json:"headers,omitempty"`
@@ -633,9 +816,10 @@ type SessionConfig struct {
// Tool operations will be relative to this directory.
WorkingDirectory string
// Streaming enables streaming of assistant message and reasoning chunks.
- // When true, assistant.message_delta and assistant.reasoning_delta events
- // with deltaContent are sent as the response is generated.
- Streaming bool
+ // When non-nil and true, assistant.message_delta and assistant.reasoning_delta
+ // events with deltaContent are sent as the response is generated.
+ // When nil, the runtime decides (currently defaults to non-streaming).
+ Streaming *bool
// IncludeSubAgentStreamingEvents includes sub-agent streaming events in the
// event stream. When true, streaming delta events from sub-agents (e.g.,
// assistant.message_delta, assistant.reasoning_delta, assistant.streaming_delta
@@ -680,9 +864,9 @@ type SessionConfig struct {
// handler. Equivalent to calling session.On(handler) immediately after creation,
// but executes earlier in the lifecycle so no events are missed.
OnEvent SessionEventHandler
- // CreateSessionFsHandler supplies a handler for session filesystem operations.
+ // CreateSessionFsProvider supplies a handler for session filesystem operations.
// This takes effect only when ClientOptions.SessionFs is configured.
- CreateSessionFsHandler func(session *Session) SessionFsProvider
+ CreateSessionFsProvider func(session *Session) SessionFsProvider
// Commands registers slash-commands for this session. Each command appears as
// /name in the CLI TUI for the user to invoke. The Handler is called when the
// command is executed.
@@ -691,12 +875,12 @@ type SessionConfig struct {
// When provided, the server may call back to this client for form-based UI dialogs
// (e.g. from MCP tools). Also enables the elicitation capability on the session.
OnElicitationRequest ElicitationHandler
- // OnExitPlanMode is a handler for exit-plan-mode requests from the server.
+ // OnExitPlanModeRequest is a handler for exit-plan-mode requests from the server.
// When provided, enables exitPlanMode.request callbacks for the session.
- OnExitPlanMode ExitPlanModeHandler
- // OnAutoModeSwitch is a handler for auto-mode-switch requests from the server.
+ OnExitPlanModeRequest ExitPlanModeRequestHandler
+ // OnAutoModeSwitchRequest is a handler for auto-mode-switch requests from the server.
// When provided, enables autoModeSwitch.request callbacks for the session.
- OnAutoModeSwitch AutoModeSwitchHandler
+ OnAutoModeSwitchRequest AutoModeSwitchRequestHandler
// GitHubToken is an optional per-session GitHub token used for authentication.
// When provided, the session authenticates as the token's owner instead of
// using the global client-level auth.
@@ -817,8 +1001,8 @@ type ElicitationContext struct {
// If the handler returns an error the SDK auto-cancels the request.
type ElicitationHandler func(ctx ElicitationContext) (ElicitationResult, error)
-// InputOptions configures a text input field for the Input convenience method.
-type InputOptions struct {
+// UiInputOptions configures a text input field for the Input convenience method.
+type UiInputOptions struct {
// Title label for the input field.
Title string
// Description text shown below the field.
@@ -893,9 +1077,10 @@ type ResumeSessionConfig struct {
// always loaded from the working directory regardless of this setting.
EnableConfigDiscovery bool
// Streaming enables streaming of assistant message and reasoning chunks.
- // When true, assistant.message_delta and assistant.reasoning_delta events
- // with deltaContent are sent as the response is generated.
- Streaming bool
+ // When non-nil and true, assistant.message_delta and assistant.reasoning_delta
+ // events with deltaContent are sent as the response is generated.
+ // When nil, the runtime decides (currently defaults to non-streaming).
+ Streaming *bool
// IncludeSubAgentStreamingEvents includes sub-agent streaming events in the
// event stream. When true, streaming delta events from sub-agents (e.g.,
// assistant.message_delta, assistant.reasoning_delta, assistant.streaming_delta
@@ -927,9 +1112,9 @@ type ResumeSessionConfig struct {
// RemoteSession controls per-session remote behavior.
// See SessionConfig.RemoteSession for details.
RemoteSession rpc.RemoteSessionMode
- // DisableResume, when true, skips emitting the session.resume event.
+ // SuppressResumeEvent, when true, skips emitting the session.resume event.
// Useful for reconnecting to a session without triggering resume-related side effects.
- DisableResume bool
+ SuppressResumeEvent bool
// ContinuePendingWork, when true, instructs the runtime to continue any tool calls
// or permission prompts that were still pending when the session was last suspended.
// When false (the default), the runtime treats pending work as interrupted on resume.
@@ -942,20 +1127,20 @@ type ResumeSessionConfig struct {
// OnEvent is an optional event handler registered before the session.resume RPC
// is issued, ensuring early events are delivered. See SessionConfig.OnEvent.
OnEvent SessionEventHandler
- // CreateSessionFsHandler supplies a handler for session filesystem operations.
+ // CreateSessionFsProvider supplies a handler for session filesystem operations.
// This takes effect only when ClientOptions.SessionFs is configured.
- CreateSessionFsHandler func(session *Session) SessionFsProvider
+ CreateSessionFsProvider func(session *Session) SessionFsProvider
// Commands registers slash-commands for this session. See SessionConfig.Commands.
Commands []CommandDefinition
// OnElicitationRequest is a handler for elicitation requests from the server.
// See SessionConfig.OnElicitationRequest.
OnElicitationRequest ElicitationHandler
- // OnExitPlanMode is a handler for exit-plan-mode requests from the server.
- // See SessionConfig.OnExitPlanMode.
- OnExitPlanMode ExitPlanModeHandler
- // OnAutoModeSwitch is a handler for auto-mode-switch requests from the server.
- // See SessionConfig.OnAutoModeSwitch.
- OnAutoModeSwitch AutoModeSwitchHandler
+ // OnExitPlanModeRequest is a handler for exit-plan-mode requests from the server.
+ // See SessionConfig.OnExitPlanModeRequest.
+ OnExitPlanModeRequest ExitPlanModeRequestHandler
+ // OnAutoModeSwitchRequest is a handler for auto-mode-switch requests from the server.
+ // See SessionConfig.OnAutoModeSwitchRequest.
+ OnAutoModeSwitchRequest AutoModeSwitchRequestHandler
}
type ProviderConfig struct {
// Type is the provider type: "openai", "azure", or "anthropic". Defaults to "openai".
@@ -984,11 +1169,11 @@ type ProviderConfig struct {
// custom fine-tune name) differs from ModelID.
// Falls back to ModelID, then SessionConfig.Model.
WireModel string `json:"wireModel,omitempty"`
- // MaxInputTokens overrides the resolved model's default max prompt tokens.
+ // MaxPromptTokens overrides the resolved model's default max prompt tokens.
// The runtime triggers conversation compaction before sending a request
// when the prompt (system message, history, tool definitions, user
// message) would exceed this limit.
- MaxInputTokens int `json:"maxPromptTokens,omitempty"`
+ MaxPromptTokens int `json:"maxPromptTokens,omitempty"`
// MaxOutputTokens overrides the resolved model's default max output
// tokens. When hit, the model stops generating and returns a truncated
// response.
@@ -1108,8 +1293,8 @@ type SessionListFilter struct {
// SessionMetadata contains metadata about a session
type SessionMetadata struct {
SessionID string `json:"sessionId"`
- StartTime string `json:"startTime"`
- ModifiedTime string `json:"modifiedTime"`
+ StartTime time.Time `json:"startTime"`
+ ModifiedTime time.Time `json:"modifiedTime"`
Summary *string `json:"summary,omitempty"`
IsRemote bool `json:"isRemote"`
Context *SessionContext `json:"context,omitempty"`
@@ -1135,9 +1320,9 @@ type SessionLifecycleEvent struct {
// SessionLifecycleEventMetadata contains optional metadata for lifecycle events
type SessionLifecycleEventMetadata struct {
- StartTime string `json:"startTime"`
- ModifiedTime string `json:"modifiedTime"`
- Summary *string `json:"summary,omitempty"`
+ StartTime time.Time `json:"startTime"`
+ ModifiedTime time.Time `json:"modifiedTime"`
+ Summary *string `json:"summary,omitempty"`
}
// SessionLifecycleHandler is a callback for session lifecycle events
diff --git a/go/types_test.go b/go/types_test.go
index 2d80d206c..1d201d2b8 100644
--- a/go/types_test.go
+++ b/go/types_test.go
@@ -159,7 +159,7 @@ func TestProviderConfig_JSONIncludesAllFields(t *testing.T) {
Headers: map[string]string{"Authorization": "Bearer provider-token"},
ModelID: "gpt-4o",
WireModel: "my-finetune-v3",
- MaxInputTokens: 100000,
+ MaxPromptTokens: 100000,
MaxOutputTokens: 4096,
}
diff --git a/test/scenarios/bundling/app-backend-to-server/go/main.go b/test/scenarios/bundling/app-backend-to-server/go/main.go
index d1fa1f898..6e0bc7f30 100644
--- a/test/scenarios/bundling/app-backend-to-server/go/main.go
+++ b/test/scenarios/bundling/app-backend-to-server/go/main.go
@@ -53,7 +53,7 @@ func chatHandler(w http.ResponseWriter, r *http.Request) {
}
client := copilot.NewClient(&copilot.ClientOptions{
- CLIUrl: cliURL(),
+ Connection: copilot.UriConnection{URL: cliURL()},
})
ctx := context.Background()
diff --git a/test/scenarios/bundling/app-direct-server/go/main.go b/test/scenarios/bundling/app-direct-server/go/main.go
index 447e99043..acdbaab76 100644
--- a/test/scenarios/bundling/app-direct-server/go/main.go
+++ b/test/scenarios/bundling/app-direct-server/go/main.go
@@ -16,7 +16,7 @@ func main() {
}
client := copilot.NewClient(&copilot.ClientOptions{
- CLIUrl: cliUrl,
+ Connection: copilot.UriConnection{URL: cliUrl},
})
ctx := context.Background()
diff --git a/test/scenarios/bundling/container-proxy/go/main.go b/test/scenarios/bundling/container-proxy/go/main.go
index 447e99043..acdbaab76 100644
--- a/test/scenarios/bundling/container-proxy/go/main.go
+++ b/test/scenarios/bundling/container-proxy/go/main.go
@@ -16,7 +16,7 @@ func main() {
}
client := copilot.NewClient(&copilot.ClientOptions{
- CLIUrl: cliUrl,
+ Connection: copilot.UriConnection{URL: cliUrl},
})
ctx := context.Background()
diff --git a/test/scenarios/sessions/streaming/go/main.go b/test/scenarios/sessions/streaming/go/main.go
index c6df2c28b..8a1c78efa 100644
--- a/test/scenarios/sessions/streaming/go/main.go
+++ b/test/scenarios/sessions/streaming/go/main.go
@@ -22,7 +22,7 @@ func main() {
session, err := client.CreateSession(ctx, &copilot.SessionConfig{
Model: "claude-haiku-4.5",
- Streaming: true,
+ Streaming: copilot.Bool(true),
})
if err != nil {
log.Fatal(err)
diff --git a/test/scenarios/tools/mcp-servers/go/main.go b/test/scenarios/tools/mcp-servers/go/main.go
index 72cbdc067..b1a1225f1 100644
--- a/test/scenarios/tools/mcp-servers/go/main.go
+++ b/test/scenarios/tools/mcp-servers/go/main.go
@@ -33,7 +33,7 @@ func main() {
mcpServers["example"] = copilot.MCPStdioServerConfig{
Command: cmd,
Args: args,
- Tools: []string{"*"},
+ Tools: &[]string{"*"},
}
}
diff --git a/test/scenarios/transport/reconnect/go/main.go b/test/scenarios/transport/reconnect/go/main.go
index f7f6cd152..fda142316 100644
--- a/test/scenarios/transport/reconnect/go/main.go
+++ b/test/scenarios/transport/reconnect/go/main.go
@@ -16,7 +16,7 @@ func main() {
}
client := copilot.NewClient(&copilot.ClientOptions{
- CLIUrl: cliUrl,
+ Connection: copilot.UriConnection{URL: cliUrl},
})
ctx := context.Background()
diff --git a/test/scenarios/transport/tcp/go/main.go b/test/scenarios/transport/tcp/go/main.go
index 447e99043..acdbaab76 100644
--- a/test/scenarios/transport/tcp/go/main.go
+++ b/test/scenarios/transport/tcp/go/main.go
@@ -16,7 +16,7 @@ func main() {
}
client := copilot.NewClient(&copilot.ClientOptions{
- CLIUrl: cliUrl,
+ Connection: copilot.UriConnection{URL: cliUrl},
})
ctx := context.Background()