Current section

Files

Jump to

README.md

# Copilot Supercharged SDK for Elixir
Elixir SDK for the [GitHub Copilot CLI](https://github.com/jeremiahjordanisaacson/copilot-sdk-supercharged). Communicates with the Copilot CLI server via JSON-RPC 2.0 over stdio.
## Requirements
- Elixir 1.15+
- The `copilot` CLI installed and on your PATH (or specify the path via `:cli_path`)
- Authenticated via `copilot auth login`
## Installation
Add to your `mix.exs` dependencies:
```elixir
def deps do
[
{:copilot_sdk_supercharged, path: "../elixir"} # or from Hex when published
]
end
```
Then fetch dependencies:
```bash
mix deps.get
```
## Quick Start
```elixir
alias Copilot.Client
alias Copilot.Session
alias Copilot.DefineTool
alias Copilot.Types.{CopilotClientOptions, SessionConfig, MessageOptions}
# Start the client (spawns the CLI process automatically)
{:ok, client} = Client.start_link(%CopilotClientOptions{log_level: "info"})
# Create a session
{:ok, session} = Client.create_session(client, %SessionConfig{})
# Subscribe to events
Session.on(session, fn event ->
if event["type"] == "assistant.message" do
IO.puts("Assistant: " <> event["data"]["content"])
end
end)
# Send a message and wait for the response
{:ok, response} = Session.send_and_wait(session, %MessageOptions{prompt: "What is 2+2?"})
IO.puts("Response: #{response["data"]["content"]}")
# Clean up
Session.destroy(session)
Client.stop(client)
```
## Architecture
The SDK consists of the following modules:
| Module | Description |
|---|---|
| `Copilot.Client` | Main client GenServer. Spawns the CLI process, manages the JSON-RPC connection, and provides session lifecycle operations (create, resume, delete, list). |
| `Copilot.Session` | Session GenServer. Sends messages, subscribes to events, handles tool calls, permissions, user input, and hooks. |
| `Copilot.JsonRpcClient` | Low-level JSON-RPC 2.0 client using Erlang `Port` for stdio communication with Content-Length header framing. |
| `Copilot.Types` | All type definitions as Elixir structs with typespecs. |
| `Copilot.DefineTool` | Helper for defining tools to expose to the CLI. |
| `Copilot.SdkProtocolVersion` | Protocol version constant (must match the server). |
## Defining Tools
Tools allow the assistant to call custom functions you define. Use `Copilot.DefineTool.define/2`:
```elixir
tool = Copilot.DefineTool.define("get_weather",
description: "Get the current weather for a city.",
parameters: %{
"type" => "object",
"properties" => %{
"city" => %{"type" => "string", "description" => "City name"}
},
"required" => ["city"]
},
handler: fn %{"city" => city}, _invocation ->
"The weather in #{city} is sunny, 72F."
end
)
{:ok, session} = Client.create_session(client, %SessionConfig{tools: [tool]})
```
The handler function receives two arguments:
1. The parsed arguments (a map matching your JSON schema)
2. A `Copilot.Types.ToolInvocation` struct with session context
It can return:
- A plain string (wrapped as a success result)
- A `Copilot.Types.ToolResult` struct (for full control)
- Any other term (JSON-encoded as a success result)
## Image Generation
Request image responses using `response_format` and `image_options`:
```elixir
response = Copilot.Session.send_and_wait(session, %Copilot.Types.MessageOptions{
prompt: "Generate a sunset over mountains",
response_format: :image,
image_options: %{size: "1024x1024", quality: "hd", style: "natural"}
})
```
## Event Subscriptions
Subscribe to all events or specific event types:
```elixir
# All events
ref = Session.on(session, fn event -> IO.inspect(event) end)
# Specific event type
ref = Session.on(session, "assistant.message", fn event ->
IO.puts(event["data"]["content"])
end)
# Unsubscribe
Session.off(session, ref)
```
### Common Event Types
| Event Type | Description |
|---|---|
| `session.start` | Session started |
| `session.idle` | Session finished processing |
| `session.error` | An error occurred |
| `user.message` | User message recorded |
| `assistant.message` | Final assistant response |
| `assistant.message_delta` | Streaming response chunk (when streaming enabled) |
| `tool.execution_start` | Tool execution started |
| `tool.execution_complete` | Tool execution finished |
## Permissions
Handle permission requests from the server:
```elixir
alias Copilot.Types.{PermissionRequest, PermissionRequestResult}
config = %SessionConfig{
on_permission_request: fn %PermissionRequest{kind: kind}, _ctx ->
IO.puts("Permission requested: #{kind}")
%PermissionRequestResult{kind: :approved}
end
}
```
## User Input (ask_user)
Handle user input requests from the agent:
```elixir
alias Copilot.Types.{UserInputRequest, UserInputResponse}
config = %SessionConfig{
on_user_input_request: fn %UserInputRequest{question: q}, _ctx ->
answer = IO.gets("#{q} > ") |> String.trim()
%UserInputResponse{answer: answer, was_freeform: true}
end
}
```
## Hooks
Intercept session lifecycle events with hooks:
```elixir
alias Copilot.Types.SessionHooks
config = %SessionConfig{
hooks: %SessionHooks{
on_pre_tool_use: fn input, _ctx ->
IO.puts("About to use tool: #{input["toolName"]}")
%{"permissionDecision" => "allow"}
end,
on_post_tool_use: fn input, _ctx ->
IO.puts("Tool completed: #{input["toolName"]}")
nil
end
}
}
```
## Session Management
```elixir
# List all sessions
{:ok, sessions} = Client.list_sessions(client)
# Resume a previous session
{:ok, session} = Client.resume_session(client, session_id)
# Delete a session
:ok = Client.delete_session(client, session_id)
# Get the last session ID
{:ok, last_id} = Client.get_last_session_id(client)
# List available models
{:ok, models} = Client.list_models(client)
```
### Session Idle Timeout
Configure automatic session cleanup after a period of inactivity:
```elixir
{:ok, client} = CopilotClient.start_link(session_idle_timeout_seconds: 300)
```
### SessionFs (Persistent Session Filesystem)
SessionFs provides a virtual filesystem scoped to each session, enabling persistent state across compaction boundaries and session resumes.
```elixir
{:ok, client} = CopilotClient.start_link(
session_fs: %{
initial_cwd: "/repo",
session_state_path: "/tmp/state",
conventions: "posix"
}
)
```
### Session Metadata
Retrieve metadata about a session (model, creation time, status):
```elixir
{:ok, meta} = CopilotClient.get_session_metadata(client, "session-123")
```
### Skills and Sub-Agent Orchestration
Register skill directories and control sub-agent behavior:
```elixir
{:ok, session} = CopilotClient.create_session(client,
skill_directories: ["./skills"],
disabled_skills: ["test-skill"],
include_sub_agent_streaming_events: true
)
```
- `skill_directories` - directories to scan for skill definitions
- `disabled_skills` - skills to exclude from the session
- `include_sub_agent_streaming_events` - receive streaming events from sub-agents
## Custom Providers (BYOK)
Use your own API endpoint:
```elixir
alias Copilot.Types.ProviderConfig
config = %SessionConfig{
provider: %ProviderConfig{
type: "openai",
base_url: "http://localhost:11434/v1",
api_key: "ollama"
},
model: "llama3"
}
```
## Recent Features (v2.4–v2.5)
Fields added in the v2.4 and v2.5 upstream syncs. These are `%SessionConfig{}`,
`%CopilotClientOptions{}`, or `%MessageOptions{}` fields (all under
`Copilot.Types`).
**v2.5.0**
- Reasoning effort — `reasoning_effort`
- Tool search — `tool_search`
- Session rewind — `rewind_enabled`
- Additional directories — `additional_directories`
- Disabled MCP servers — `disabled_mcp_servers`
- GitHub MCP tool config — `github_mcp_tool_config`
- Canvas provider — `canvas_provider`
- Custom agents local-only — `custom_agents_local_only`
- Experimental mode — `experimental_mode`
- Content exclusion — `content_exclusion`
- User-prompt-transformed hook — `SessionHooks.on_user_prompt_transformed`
- Permission decision context — `PermissionRequestResult.decision_context`
- Agent-factory args schema — `CustomAgentConfig.args_schema`
- Built-in plugin directories — `CopilotClientOptions.builtin_plugin_directories`
- In-process FFI transport — `CopilotClientOptions.in_process`
**v2.4.0**
- BYOK bearer-token provider — `CopilotClientOptions.bearer_token_provider`
- MCP OAuth token handler — `on_mcp_auth_request`
- HTTP request handler — `CopilotClientOptions.request_handler`
- Session citations — `enable_citations`
- Excluded built-in agents — `excluded_builtin_agents`
- Session spending limits — `Copilot.Types.SessionLimitsConfig` (`max_ai_credits`)
- Session memory — `Copilot.Types.MemoryConfiguration`
- OTLP protocol — `otlp_protocol`
- WebSocket responses — `enable_web_socket_responses`
- Experiment assignments — `exp_assignments`
- Tool defer loading — `Copilot.Types.ToolDefer` (`auto/0`, `never/0`)
- System-message sections — `SystemMessageSection.preamble/0`, `SystemMessageSection.preserve/0`
- Post-tool-use / pre-MCP hooks — `on_post_tool_use`, `on_pre_mcp_tool_call`
- Message agent mode / display prompt — `MessageOptions.agent_mode`, `MessageOptions.display_prompt`
- GitHub attachment variants — `GitHubAttachment.github_commit/0`, `GitHubAttachment.github_repository/0`
Reasoning effort, content exclusion, extra directories, and rewind:
```elixir
{:ok, session} = Client.create_session(client, %SessionConfig{
reasoning_effort: "high",
content_exclusion: true,
additional_directories: ["../shared", "../docs"],
rewind_enabled: true,
disabled_mcp_servers: ["playwright"]
})
```
Tool search and experimental mode:
```elixir
config = %SessionConfig{
tool_search: %{"enabled" => true},
experimental_mode: true,
custom_agents_local_only: true
}
```
Session spending limits, memory, and citations:
```elixir
alias Copilot.Types.{SessionConfig, SessionLimitsConfig, MemoryConfiguration}
config = %SessionConfig{
session_limits: %SessionLimitsConfig{max_ai_credits: 5.0},
memory: %MemoryConfiguration{enabled: true},
enable_citations: true,
otlp_protocol: "http/protobuf"
}
```
BYOK bearer-token provider and in-process transport (client options):
```elixir
{:ok, client} = Client.start_link(%CopilotClientOptions{
bearer_token_provider: fn _args -> "ghs_ephemeral_token" end,
builtin_plugin_directories: ["./plugins"],
in_process: false
})
```
## Protocol Version
The SDK protocol version must match the CLI server's version. The current version is **2**. Version mismatches will produce a clear error message on connection.
## Running the Example
```bash
cd elixir
mix deps.get
mix run examples/basic_example.exs
```
## License
MIT - See [LICENSE](../LICENSE) for details.