Copilot Supercharged SDK for Elixir
Elixir SDK for the GitHub Copilot CLI. Communicates with the Copilot CLI server via JSON-RPC 2.0 over stdio.
Requirements
- Elixir 1.15+
-
The
copilotCLI installed and on your PATH (or specify the path via:cli_path) -
Authenticated via
copilot auth login
Installation
Add to your mix.exs dependencies:
def deps do
[
{:copilot_sdk_supercharged, path: "../elixir"} # or from Hex when published
]
endThen fetch dependencies:
mix deps.getQuick Start
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:
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:
- The parsed arguments (a map matching your JSON schema)
-
A
Copilot.Types.ToolInvocationstruct with session context
It can return:
- A plain string (wrapped as a success result)
-
A
Copilot.Types.ToolResultstruct (for full control) - Any other term (JSON-encoded as a success result)
Event Subscriptions
Subscribe to all events or specific event types:
# 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:
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:
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:
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
# 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)Custom Providers (BYOK)
Use your own API endpoint:
alias Copilot.Types.ProviderConfig
config = %SessionConfig{
provider: %ProviderConfig{
type: "openai",
base_url: "http://localhost:11434/v1",
api_key: "ollama"
},
model: "llama3"
}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
cd elixir
mix deps.get
mix run examples/basic_example.exsLicense
MIT - See LICENSE for details.