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

Installation

Add to your mix.exs dependencies:

def deps do
  [
    {:copilot_sdk_supercharged, path: "../elixir"}  # or from Hex when published
  ]
end

Then fetch dependencies:

mix deps.get

Quick 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:

  1. The parsed arguments (a map matching your JSON schema)
  2. A Copilot.Types.ToolInvocation struct with session context

It can return:

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.exs

License

MIT - See LICENSE for details.