Skip to content
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,19 @@ All notable changes to this project will be documented in this file. This change

## [Unreleased]

### Added (v0.2.1 sync)
- **Commands support** — register slash commands per-session via `:commands` option in session config. Each command definition has `:name`, optional `:description`, and a `:command-handler` function. Commands are sent on the wire (name + description) and executed via `command.execute` broadcast events with `session.commands.handlePendingCommand` RPC callback (upstream PR #906).
- **UI Elicitation convenience API** — new public functions `confirm!`, `select!`, `input!` wrap the existing `ui-elicitation!` with typed schemas. `capabilities` accessor returns host capabilities from session create/resume response. `elicitation-supported?` predicate checks if the host supports elicitation dialogs. All convenience methods throw with a clear error when elicitation is unsupported (upstream PR #906).
- **`COPILOT_CLI_PATH` env var fallback** — client constructor now checks `COPILOT_CLI_PATH` environment variable before defaulting to `"copilot"` when no explicit `:cli-path` or `:cli-url` is provided (upstream PR #906).
- **New event type** `session.custom_agents_updated` added to event type enum.
- New specs: `::command-definition`, `::commands`, `::session-capabilities`, `::elicitation-params`, `::elicitation-result`, `::input-options`.
- Function specs and instrumentation for `capabilities`, `elicitation-supported?`, `confirm!`, `select!`, `input!`.
- Integration tests for command wire format, command.execute routing, unknown command errors, handler errors, capabilities storage, and elicitation guards.

### Changed (v0.2.1 sync)
- `ui-elicitation!` no longer marked `^:experimental` — now asserts elicitation support before calling. Updated fdef to use `::elicitation-params` spec.
- Mock server `handle-request` now supports `session.commands.handlePendingCommand` RPC and allows request hooks to merge additional data into responses.

## [0.2.0.0] - 2026-03-23
### Added (v0.2.0 sync)
- **System message customize mode** — new `:customize` mode for `:system-message` enables section-level overrides of the Copilot system prompt. Ten configurable sections: `:identity`, `:tone`, `:tool-efficiency`, `:environment-context`, `:code-change-rules`, `:guidelines`, `:safety`, `:tool-instructions`, `:custom-instructions`, `:last-instructions`. Each section supports static actions (`:replace`, `:remove`, `:append`, `:prepend`) and transform callbacks (1-arity functions receiving current content, returning modified text). New `system-prompt-sections` constant exported from main namespace (upstream PR #816).
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ Add to your `deps.edn`:

```clojure
;; From Maven Central
io.github.copilot-community-sdk/copilot-sdk-clojure {:mvn/version "0.2.0.0"}
io.github.copilot-community-sdk/copilot-sdk-clojure {:mvn/version "0.2.1.0"}

;; Or git dependency
io.github.copilot-community-sdk/copilot-sdk-clojure {:git/url "https://github.com/copilot-community-sdk/copilot-sdk-clojure.git"
Expand Down
2 changes: 1 addition & 1 deletion build.clj
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
(:import [java.io File]))

(def lib 'io.github.copilot-community-sdk/copilot-sdk-clojure)
(def version "0.2.0.0")
(def version "0.2.1.0")
(def class-dir "target/classes")

(defn- try-sh
Expand Down
145 changes: 143 additions & 2 deletions doc/reference/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ Get information about the current shared client state. Returns `nil` if no share

| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `:cli-path` | string | `"copilot"` | Path to CLI executable |
| `:cli-path` | string | `"copilot"` | Path to CLI executable. Falls back to `COPILOT_CLI_PATH` env var when not set |
| `:cli-args` | vector | `[]` | Extra arguments prepended before SDK-managed flags |
| `:cli-url` | string | nil | URL of existing CLI server (e.g., `"localhost:8080"`). When provided, no CLI process is spawned |
| `:port` | number | `0` | Server port (0 = random) |
Expand Down Expand Up @@ -238,6 +238,7 @@ Create a client and session together, ensuring both are cleaned up on exit.
| `:excluded-tools` | vector | List of excluded tool names |
| `:provider` | map | Provider config for BYOK (see [BYOK docs](../auth/byok.md)). Required key: `:base-url`. Optional: `:provider-type` (`:openai`/`:azure`/`:anthropic`), `:wire-api` (`:completions`/`:responses`), `:api-key`, `:bearer-token`, `:azure-options` |
| `:mcp-servers` | map | MCP server configs keyed by server ID (see [MCP docs](../mcp/overview.md)). Local servers: `:mcp-command`, `:mcp-args`, `:mcp-tools`. Remote servers: `:mcp-server-type` (`:http`/`:sse`), `:mcp-url`, `:mcp-tools` |
| `:commands` | vector | Command definitions (slash commands). See [Commands](#commands) |
| `:custom-agents` | vector | Custom agent configs |
| `:on-permission-request` | fn | **Required.** Permission handler function. Use `copilot/approve-all` to approve everything. |
| `:streaming?` | boolean | Enable streaming deltas |
Expand Down Expand Up @@ -935,7 +936,106 @@ Get the client that owns this session.
| `session/compaction-compact!` | Trigger manual context compaction. |
| `session/shell-exec!` | Execute a shell command. |
| `session/shell-kill!` | Kill a running shell process. |
| `session/ui-elicitation!` | Request structured user input. |

---

## UI Elicitation

Request structured user input via interactive dialogs. Check host support before calling.

```clojure
(require '[github.copilot-sdk :as copilot])
```

### `capabilities`

```clojure
(copilot/capabilities session)
;; => {:ui {:elicitation true}}
```

Get the host capabilities map reported when the session was created or resumed.

### `elicitation-supported?`

```clojure
(copilot/elicitation-supported? session)
;; => true
```

Return `true` if the CLI host supports interactive elicitation dialogs.

### `confirm!`

```clojure
(copilot/confirm! session message)
```

Show a confirmation dialog. Returns `true` if the user confirms, `false` if they decline or cancel. Throws if elicitation is not supported.

```clojure
(when (copilot/elicitation-supported? session)
(when (copilot/confirm! session "Deploy to production?")
(println "Deploying...")))
```

### `select!`

```clojure
(copilot/select! session message options)
```

Show a selection dialog with the given options. Returns the selected value as a string, or `nil` if the user declines or cancels. Throws if elicitation is not supported.

```clojure
(when-let [env (copilot/select! session "Choose environment" ["staging" "production"])]
(println "Selected:" env))
```

### `input!`

```clojure
(copilot/input! session message)
(copilot/input! session message opts)
```

Show a text input dialog. Returns the entered text as a string, or `nil` if the user declines or cancels. Throws if elicitation is not supported.

**Options:**

| Key | Type | Description |
|-----|------|-------------|
| `:title` | string | Title for the input field |
| `:description` | string | Description text |
| `:min-length` | integer | Minimum input length |
| `:max-length` | integer | Maximum input length |
| `:format` | string | Input format (`"email"`, `"uri"`, `"date"`, `"date-time"`) |
| `:default` | string | Default value |

```clojure
(when-let [name (copilot/input! session "Enter your name"
{:min-length 1
:max-length 100})]
(println "Hello," name))
```

### `ui-elicitation!`

```clojure
(copilot/ui-elicitation! session params)
```

Raw elicitation request for custom JSON schemas. `params` is a map with `:message` and `:requested-schema` keys. Returns a map with `:action` (`"accept"`, `"decline"`, or `"cancel"`) and `:content`. Throws if elicitation is not supported.

```clojure
(copilot/ui-elicitation! session
{:message "Configure deployment"
:requested-schema {:type "object"
:properties {"env" {:type "string" :enum ["staging" "production"]}
"replicas" {:type "number" :default 3}}
:required ["env"]}})
;; => {:action "accept", :content {:env "staging", :replicas 3}}
```
Comment thread
krukow marked this conversation as resolved.

---

Expand Down Expand Up @@ -1056,6 +1156,7 @@ Convert an unqualified event keyword to a namespace-qualified `:copilot/` keywor
| `:copilot/session.mcp_servers_loaded` | MCP servers loaded for the session |
| `:copilot/session.mcp_server_status_changed` | MCP server status changed |
| `:copilot/session.extensions_loaded` | Extensions loaded for the session |
| `:copilot/session.custom_agents_updated` | Custom agents list updated |

### Example: Handling Events

Expand Down Expand Up @@ -1205,6 +1306,46 @@ Set `:overrides-built-in-tool true` to override a built-in tool (e.g., `grep`, `
(copilot/result-rejected "Invalid parameters")
```

### Commands

Register slash commands that users can invoke in the TUI. Define each command as a map with `:name`, `:description`, and `:command-handler`, then pass them via `:commands` in session config.
Comment thread
krukow marked this conversation as resolved.

```clojure
(def my-commands
[{:name "deploy"
:description "Deploy the current project"
:command-handler (fn [{:keys [session-id command-name args]}]
(println "Deploying with args:" args))}
{:name "status"
:description "Show project status"
:command-handler (fn [{:keys [session-id command-name args]}]
(println "All systems operational"))}])

(def session (copilot/create-session client
{:model "gpt-5.4"
:commands my-commands
:on-permission-request copilot/approve-all}))
```

**Command definition keys:**

| Key | Type | Required | Description |
|-----|------|----------|-------------|
| `:name` | string | yes | Command name (without leading slash) |
| `:description` | string | no | Description shown in TUI command list |
| `:command-handler` | fn | yes | Handler function |

The handler receives a context map:

| Key | Description |
|-----|-------------|
| `:session-id` | The session ID |
| `:command` | Full command string |
| `:command-name` | Matched command name |
| `:args` | Arguments after the command name |

The handler may return `nil` or a core.async channel (awaited automatically).

### System Message Customization

Control the system prompt:
Expand Down
87 changes: 85 additions & 2 deletions src/github/copilot_sdk.clj
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,8 @@
:copilot/session.skills_loaded
:copilot/session.mcp_servers_loaded
:copilot/session.mcp_server_status_changed
:copilot/session.extensions_loaded})
:copilot/session.extensions_loaded
:copilot/session.custom_agents_updated})

(def session-events
"Session lifecycle and state management events."
Expand Down Expand Up @@ -139,7 +140,8 @@
:copilot/session.skills_loaded
:copilot/session.mcp_servers_loaded
:copilot/session.mcp_server_status_changed
:copilot/session.extensions_loaded})
:copilot/session.extensions_loaded
:copilot/session.custom_agents_updated})

(def assistant-events
"Assistant response events."
Expand Down Expand Up @@ -834,6 +836,87 @@
[session]
(session/workspace-path session))

(defn capabilities
"Get the host capabilities reported when the session was created or resumed.
Returns a map, e.g. `{:ui {:elicitation true}}`.

Example:
```clojure
(copilot/capabilities session)
;; => {:ui {:elicitation true}}
```"
[session]
(session/capabilities session))

(defn elicitation-supported?
"Check if the CLI host supports interactive elicitation dialogs.

Example:
```clojure
(when (copilot/elicitation-supported? session)
(copilot/confirm! session \"Deploy to production?\"))
```"
[session]
(session/elicitation-supported? session))

(defn ui-elicitation!
"Request structured user input via an elicitation prompt.
params is a map with :message and :requested-schema keys.
Throws if the host does not support elicitation.

Example:
```clojure
(copilot/ui-elicitation! session
{:message \"Configure deployment\"
:requested-schema {:type \"object\"
:properties {\"env\" {:type \"string\" :enum [\"staging\" \"production\"]}}
:required [\"env\"]}})
```"
[session params]
(session/ui-elicitation! session params))

(defn confirm!
"Show a confirmation dialog and return the user's boolean answer.
Returns false if the user declines or cancels.
Throws if the host does not support elicitation.

Example:
```clojure
(when (copilot/confirm! session \"Deploy to production?\")
(println \"Deploying...\"))
```"
[session message]
(session/confirm! session message))

(defn select!
"Show a selection dialog with the given options.
Returns the selected value as a string, or nil if the user declines/cancels.
Throws if the host does not support elicitation.

Example:
```clojure
(when-let [env (copilot/select! session \"Choose environment\" [\"staging\" \"production\"])]
(println \"Selected:\" env))
```"
[session message options]
(session/select! session message options))

(defn input!
"Show a text input dialog. Returns the entered text, or nil if the user
declines/cancels. opts is an optional map with :title, :description,
:min-length, :max-length, :format, and :default keys.
Throws if the host does not support elicitation.

Example:
```clojure
(when-let [name (copilot/input! session \"Enter your name\")]
(println \"Hello,\" name))
```"
([session message]
(session/input! session message))
([session message opts]
(session/input! session message opts)))

(defn get-current-model
"Get the current model for this session.
Returns the model ID string, or nil if none set.
Expand Down
Loading
Loading