Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@
All notable changes to this project will be documented in this file. This change log follows the conventions of [keepachangelog.com](http://keepachangelog.com/).

## [Unreleased]
### Added (upstream sync)
- Session pre-registration: sessions are now created and registered in client state **before** the RPC call, preventing early events (e.g. `session.start`) from being dropped. Session IDs are generated client-side via `java.util.UUID/randomUUID` when not explicitly provided. On RPC failure, sessions are automatically cleaned up (upstream PR #664).
- `:on-event` optional handler in `create-session` and `resume-session` configs — a 1-arity function receiving event maps, registered before the RPC call so no events are missed. Equivalent to calling `subscribe-events` immediately after creation, but executes earlier in the lifecycle (upstream PR #664).
- `join-session` function — convenience for extensions running as child processes of the Copilot CLI. Reads `SESSION_ID` from environment, creates a child-process client, and resumes the session with `:disable-resume? true`. Returns `{:client ... :session ...}` (upstream PR #737).
- `:copilot/system.notification` event type — structured notification events with `:kind` discriminator (`agent_completed`, `shell_completed`, `shell_detached_completed`) (upstream PR #737).

### Changed
- `CopilotSession` record no longer includes `workspace-path` as a field. Use `(workspace-path session)` accessor which reads from mutable session state. This enables the pre-registration flow where workspace-path is set after the RPC response.

## [0.1.32.0] - 2026-03-10
### Added (v0.1.32 sync)
Expand Down
24 changes: 19 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ Key features:
- **Multi-session support** — Run multiple independent conversations concurrently
- **Session hooks** — Lifecycle callbacks for pre/post tool use, prompts, errors
- **User input handling** — Handle `ask_user` requests from the agent
- **Authentication options** — GitHub token auth or logged-in user
- **Event callbacks** — Register `:on-event` handlers to receive all session events
- **Child process mode** — Join existing sessions via `join-session` for extensions
- **Authentication options** — GitHub token auth or logged-in user

See [`examples/`](./examples/) for working code demonstrating common patterns.
Expand Down Expand Up @@ -133,19 +134,32 @@ See the [`examples/`](./examples/) directory for complete working examples:
| Example | Difficulty | Description |
|---------|------------|-------------|
| [`basic_chat.clj`](./examples/basic_chat.clj) | Beginner | Simple Q&A conversation with multi-turn context |
| [`helpers_query.clj`](./examples/helpers_query.clj) | Beginner | Stateless query API with blocking and streaming modes |
| [`reasoning_effort.clj`](./examples/reasoning_effort.clj) | Beginner | Control reasoning effort level |
| [`tool_integration.clj`](./examples/tool_integration.clj) | Intermediate | Custom tools that the LLM can invoke |
| [`multi_agent.clj`](./examples/multi_agent.clj) | Advanced | Multi-agent orchestration with core.async |
| [`config_skill_output.clj`](./examples/config_skill_output.clj) | Intermediate | Config dir, skills, and large output settings |
| [`permission_bash.clj`](./examples/permission_bash.clj) | Intermediate | Permission handling with bash |
| [`permission_bash.clj`](./examples/permission_bash.clj) | Intermediate | Permission handling with bash tool |
| [`session_events.clj`](./examples/session_events.clj) | Intermediate | Monitor session state events and their flow |
| [`session_resume.clj`](./examples/session_resume.clj) | Intermediate | Save and resume sessions by ID |
| [`file_attachments.clj`](./examples/file_attachments.clj) | Intermediate | Send file attachments for analysis |
| [`infinite_sessions.clj`](./examples/infinite_sessions.clj) | Intermediate | Infinite sessions with context compaction |
| [`lifecycle_hooks.clj`](./examples/lifecycle_hooks.clj) | Intermediate | Lifecycle hooks for tool use, prompts, errors |
| [`user_input.clj`](./examples/user_input.clj) | Intermediate | Handle ask_user requests from the agent |
| [`metadata_api.clj`](./examples/metadata_api.clj) | Intermediate | List sessions, tools, and quota |
| [`multi_agent.clj`](./examples/multi_agent.clj) | Advanced | Multi-agent orchestration with core.async |
| [`ask_user_failure.clj`](./examples/ask_user_failure.clj) | Advanced | User cancellation (Esc) with event tracing |
| [`mcp_local_server.clj`](./examples/mcp_local_server.clj) | Advanced | Model Context Protocol server integration |
| [`byok_provider.clj`](./examples/byok_provider.clj) | Advanced | Bring Your Own Key provider configuration |

Run examples:

```bash
clojure -A:examples -M -m basic-chat
clojure -A:examples -M -m helpers-query
clojure -A:examples -M -m tool-integration
clojure -A:examples -M -m session-events
clojure -A:examples -M -m multi-agent
clojure -A:examples -M -m config-skill-output
clojure -A:examples -M -m permission-bash
clojure -A:examples -M -m byok-provider
```

See [`examples/README.md`](./examples/README.md) for detailed walkthroughs and explanations.
Expand Down
1 change: 1 addition & 0 deletions doc/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,7 @@ Pass `:client-name` to identify your application in API requests (included in th
| **Session** | A conversation with context, model, and tools |
| **Tools** | Functions that Copilot can call in your code |
| **Events** | Streaming updates via core.async channels |
| **On-Event** | Optional callback receiving all session events, registered before RPC |
| **Helpers** | High-level stateless API with automatic lifecycle management |

### Comparison with JavaScript SDK
Expand Down
2 changes: 1 addition & 1 deletion doc/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Clojure SDK for programmatic control of the GitHub Copilot CLI via JSON-RPC.
## Getting Started

- [Getting Started](getting-started.md) — Step-by-step tutorial building a weather assistant
- [Examples](../examples/README.md) — 11 working examples with walkthroughs
- [Examples](../examples/README.md) — 17 working examples with walkthroughs

## Guides

Expand Down
40 changes: 40 additions & 0 deletions doc/reference/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,15 @@ or want to stop reading early without leaking session resources.

Explicitly shutdown the shared client. Safe to call multiple times.

### `client-info`

```clojure
(h/client-info)
;; => {:client-opts {:log-level :info, ...} :connected? true}
```

Get information about the current shared client state. Returns `nil` if no shared client exists, otherwise a map with `:client-opts` and `:connected?` keys.

---

## CopilotClient
Expand Down Expand Up @@ -243,6 +252,7 @@ Create a client and session together, ensuring both are cleaned up on exit.
| `:on-user-input-request` | fn | Handler for `ask_user` requests (see below) |
| `:hooks` | map | Lifecycle hooks (see below) |
| `:agent` | string | Name of a custom agent to activate at session start. Must match a name in `:custom-agents`. Equivalent to calling `agent.select` after creation. |
| `:on-event` | fn | Event handler (1-arg fn receiving event maps). Registered before the RPC call, guaranteeing early events like `session.start` are not missed. |

#### `resume-session`

Expand Down Expand Up @@ -304,6 +314,26 @@ Same config options as `resume-session`. Safe for use inside `go` blocks. On RPC
))
```

#### `join-session`

```clojure
(copilot/join-session config)
```

Join the current foreground session from an extension running as a child process of the Copilot CLI. Reads the `SESSION_ID` environment variable, creates a child-process client, and resumes the session with `:disable-resume?` defaulting to `true`.

Returns a map with `:client` and `:session` keys. The caller is responsible for stopping the client when done.

Throws if `SESSION_ID` is not set in the environment.

```clojure
(let [{:keys [client session]} (copilot/join-session
{:on-permission-request copilot/approve-all
:tools [my-tool]})]
;; use session...
(copilot/stop! client))
```

#### `ping`

```clojure
Expand Down Expand Up @@ -884,6 +914,15 @@ copilot/interaction-events
;; :copilot/external_tool.requested}
```

### `evt` — Event Keyword Helper

```clojure
(copilot/evt :session.info) ;; => :copilot/session.info
(copilot/evt :assistant.message) ;; => :copilot/assistant.message
```

Convert an unqualified event keyword to a namespace-qualified `:copilot/` keyword. Throws `IllegalArgumentException` if the keyword is not a valid event type.

### Event Reference

| Event Type | Description |
Expand Down Expand Up @@ -934,6 +973,7 @@ copilot/interaction-events
| `:copilot/hook.start` | Hook invocation started |
| `:copilot/hook.end` | Hook invocation finished |
| `:copilot/system.message` | System message emitted |
| `:copilot/system.notification` | System notification with structured `:kind` discriminator (e.g. `agent_completed`, `shell_completed`, `shell_detached_completed`) |
| `:copilot/permission.requested` | Permission request initiated |
| `:copilot/permission.completed` | Permission request resolved |
| `:copilot/user_input.requested` | User input requested from agent |
Expand Down
56 changes: 56 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ clojure -A:examples -X session-events/run
clojure -A:examples -X user-input/run
clojure -A:examples -X user-input/run-simple

# Ask user cancellation (simulates Esc)
clojure -A:examples -X ask-user-failure/run

# BYOK provider (requires API key, see example docs)
OPENAI_API_KEY=sk-... clojure -A:examples -X byok-provider/run
clojure -A:examples -X byok-provider/run :provider-name '"ollama"'
Expand Down Expand Up @@ -756,6 +759,59 @@ clojure -A:examples -X reasoning-effort/run :effort '"high"'

---

## Example 17: Ask User Failure (`ask_user_failure.clj`)

**Difficulty:** Intermediate
**Concepts:** User input cancellation, ask_user tool, error handling, event tracing

Demonstrates what happens when a user cancels an `ask_user` request (simulating pressing Esc). This is a 1:1 port of the upstream `basic-example.ts`.

### What It Demonstrates

- Handling user cancellation by throwing from `:on-user-input-request`
- Event tracing: subscribing to all events via `tap` on the session events mult
- Graceful degradation when the user skips a question
- Full event stream logging for debugging

### Usage

```bash
clojure -A:examples -X ask-user-failure/run
```

### Code Walkthrough

```clojure
(require '[clojure.core.async :refer [chan tap go-loop <!]])
(require '[github.copilot-sdk :as copilot])

;; Track cancelled requests
(let [cancelled-requests (atom [])]
(copilot/with-client [client]
(copilot/with-session [session client
{:on-permission-request copilot/approve-all
:model "claude-haiku-4.5"
:on-user-input-request
(fn [request _invocation]
(swap! cancelled-requests conj request)
;; Throwing simulates Esc — the SDK sends a failure
;; result back to the ask_user tool automatically.
(throw (RuntimeException. "User skipped question")))}]
;; Subscribe to all events for tracing
(let [events-ch (chan 256)]
(tap (copilot/events session) events-ch)
(go-loop []
(when-let [event (<! events-ch)]
(println event)
(recur)))

(let [result (copilot/send-and-wait! session
{:prompt "Use the ask_user tool to ask me to pick between 'Red' and 'Blue'."})]
(println "Response:" (get-in result [:data :content])))))))
```

---

## Clojure vs JavaScript Comparison

Here's how common patterns compare between the Clojure and JavaScript SDKs:
Expand Down
28 changes: 28 additions & 0 deletions src/github/copilot_sdk.clj
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
:copilot/hook.start
:copilot/hook.end
:copilot/system.message
:copilot/system.notification
:copilot/permission.requested
:copilot/permission.completed
:copilot/user_input.requested
Expand Down Expand Up @@ -555,6 +556,33 @@
[client session-id config]
(client/<resume-session client session-id config))

(defn join-session
"Join the current foreground session from an extension running as a child process.

Reads the SESSION_ID environment variable and connects to the parent CLI process
via stdio. Intended for extensions spawned by the Copilot CLI.

Config is the same as `resume-session` (`:on-permission-request` is **required**).
The `:disable-resume?` option defaults to true.

Returns a map with `:client` and `:session` keys. The caller is responsible for
stopping the client when done.

Throws if SESSION_ID is not set in the environment.

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

(let [{:keys [client session]} (copilot/join-session
{:on-permission-request copilot/approve-all
:tools [my-tool]})]
;; use session...
(copilot/stop! client))
```"
[config]
(client/join-session config))

(defn list-sessions
"List all available sessions.
Returns a vector of session metadata maps:
Expand Down
Loading
Loading