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)

## 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)
```

## 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"
}
```

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