Skip to content

Commit fcad744

Browse files
krukowCopilot
andcommitted
refactor: ElicitationRequest → ElicitationContext single-arg handler (upstream PR #960)
Port upstream PR #960 cross-SDK consistency change: - Elicitation handler now takes single ElicitationContext arg instead of (request, ctx) — context includes :session-id alongside request fields - Rename ::elicitation-request spec to ::elicitation-context, add :session-id - Update all handler call sites, tests, example, and docs - BREAKING: handler signature change from (fn [request ctx]) to (fn [context]) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 6cb978b commit fcad744

7 files changed

Lines changed: 51 additions & 41 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ All notable changes to this project will be documented in this file. This change
33

44
## [Unreleased]
55

6+
### Changed (v0.2.1 sync)
7+
- **BREAKING**: Elicitation handler signature changed from 2-arg `(fn [request ctx])` to single-arg `(fn [context])`. The `ElicitationContext` map now includes `:session-id` alongside request fields (`:message`, `:requested-schema`, `:mode`, `:elicitation-source`, `:url`). Matches upstream cross-SDK consistency change (upstream PR #960). `::elicitation-request` spec renamed to `::elicitation-context`.
8+
69
### Added (v0.2.1 sync)
710
- **`remote-steerable?` field on `session.start` and `session.resume` events** — event data now includes optional `:remote-steerable?` boolean field indicating whether the session supports remote steering via Mission Control. Replaces previous `:steerable?` (upstream PRs #927, #908).
811
- **`get-session-metadata`** — new function on client for efficient O(1) session lookup by ID. Returns session metadata map if found, or `nil` if not found. Sends `session.getMetadata` JSON-RPC call. Shared `wire->session-metadata` helper extracted from `list-sessions` to eliminate duplication (upstream PR #899).

doc/reference/API.md

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ Create a client and session together, ensuring both are cleaned up on exit.
255255
| `:hooks` | map | Lifecycle hooks (see below) |
256256
| `: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. |
257257
| `: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. |
258-
| `:on-elicitation-request` | fn | Handler for elicitation requests from the agent. When provided, advertises `requestElicitation=true` and handles `elicitation.requested` broadcast events. Receives `(request ctx)` where request has `:message`, `:requested-schema`, `:mode`, `:elicitation-source`, `:url`. Returns an `ElicitationResult` map `{:action "accept"/"decline"/"cancel" :content {...}}`. See [Elicitation Provider](#elicitation-provider) |
258+
| `:on-elicitation-request` | fn | Handler for elicitation requests from the agent. When provided, advertises `requestElicitation=true` and handles `elicitation.requested` broadcast events. Single-arg handler receives an `ElicitationContext` map with `:session-id`, `:message`, `:requested-schema`, `:mode`, `:elicitation-source`, `:url`. Returns an `ElicitationResult` map `{:action "accept"/"decline"/"cancel" :content {...}}`. See [Elicitation Provider](#elicitation-provider) |
259259
| `:create-session-fs-handler` | fn | Factory for session filesystem handlers. Required when `:session-fs` is set on the client. Called as `(factory session)`, returns a map of FS handler functions. See [Session Filesystem](#session-filesystem) |
260260

261261
#### `resume-session`
@@ -1691,19 +1691,22 @@ Provide a handler for elicitation requests from the agent. This enables the SDK
16911691
(copilot/create-session client
16921692
{:on-permission-request copilot/approve-all
16931693
:on-elicitation-request
1694-
(fn [request {:keys [session-id]}]
1695-
;; request keys: :message, :requested-schema, :mode, :elicitation-source, :url
1696-
(println "Elicitation:" (:message request))
1694+
(fn [{:keys [session-id message requested-schema mode]}]
1695+
(println "Elicitation for session" session-id ":" message)
16971696
{:action "accept"
16981697
:content {:name "user-input"}})}))
16991698
```
17001699

1701-
The handler receives two arguments:
1700+
The handler receives a single `ElicitationContext` map:
17021701

1703-
| Argument | Description |
1704-
|----------|-------------|
1705-
| `request` | Map with `:message` (string), optional `:requested-schema` (JSON Schema map), `:mode` (`"form"` or `"url"`), `:elicitation-source` (string), `:url` (string) |
1706-
| `ctx` | Map with `:session-id` (string) |
1702+
| Key | Type | Description |
1703+
|-----|------|-------------|
1704+
| `:session-id` | string | Session that triggered the request |
1705+
| `:message` | string | What information is needed from the user |
1706+
| `:requested-schema` | map | JSON Schema describing form fields (optional) |
1707+
| `:mode` | string | `"form"` for structured input, `"url"` for browser redirect (optional) |
1708+
| `:elicitation-source` | string | Source that initiated the request, e.g. MCP server name (optional) |
1709+
| `:url` | string | URL to open in browser, url mode only (optional) |
17071710

17081711
Return an `ElicitationResult` map:
17091712

examples/elicitation_provider.clj

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,30 +15,31 @@
1515
(defn handle-elicitation
1616
"Handle an elicitation request from the runtime.
1717
In a real app this would render a UI dialog or open a browser.
18-
Here we print the request and auto-approve."
19-
[request {:keys [session-id]}]
18+
Here we print the request and auto-approve.
19+
Takes a single ElicitationContext map (upstream PR #960)."
20+
[{:keys [session-id message mode elicitation-source url requested-schema] :as _context}]
2021
(println "\n📋 Elicitation request received!")
2122
(println " Session:" session-id)
22-
(println " Message:" (:message request))
23-
(when-let [mode (:mode request)]
23+
(println " Message:" message)
24+
(when mode
2425
(println " Mode:" mode))
25-
(when-let [source (:elicitation-source request)]
26-
(println " Source:" source))
27-
(when-let [url (:url request)]
26+
(when elicitation-source
27+
(println " Source:" elicitation-source))
28+
(when url
2829
(println " URL:" url))
29-
(when-let [schema (:requested-schema request)]
30-
(println " Schema:" (pr-str schema)))
30+
(when requested-schema
31+
(println " Schema:" (pr-str requested-schema)))
3132

3233
;; Decide how to respond based on mode
33-
(case (:mode request)
34+
(case mode
3435
;; URL mode: the server wants us to open a browser
3536
"url"
3637
(do
3738
(println " → Auto-approving URL-based elicitation (would open browser)")
3839
{:action "accept"})
3940

4041
;; Form mode (or nil): the server wants form field values
41-
(if-let [props (get-in request [:requested-schema :properties])]
42+
(if-let [props (get-in requested-schema [:properties])]
4243
(do
4344
(println " → Auto-filling form fields:")
4445
(let [content (reduce-kv

src/github/copilot_sdk/client.clj

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -355,21 +355,22 @@
355355

356356
(defn- handle-v3-elicitation-requested!
357357
"Handle v3 elicitation.requested broadcast event.
358-
Calls the session's elicitation handler and responds via the
359-
session.ui.handlePendingElicitation RPC method.
358+
Builds an ElicitationContext (single-arg, includes session-id) and calls
359+
the session's elicitation handler. Responds via handlePendingElicitation RPC.
360360
If the handler fails, sends a cancel response to avoid hanging."
361361
[client session-id event]
362362
(let [data (:data event)
363363
request-id (:request-id data)]
364364
(when request-id
365365
(go
366366
(try
367-
(let [request {:message (:message data)
367+
(let [context {:session-id session-id
368+
:message (:message data)
368369
:requested-schema (:requested-schema data)
369370
:mode (:mode data)
370371
:elicitation-source (:elicitation-source data)
371372
:url (:url data)}
372-
result (<! (session/handle-elicitation-request! client session-id request))]
373+
result (<! (session/handle-elicitation-request! client session-id context))]
373374
(when result
374375
(let [conn (:connection-io @(:state client))]
375376
(when conn
@@ -1437,10 +1438,11 @@
14371438
:buffer-exhaustion-threshold (0.0-1.0, default 0.95)}
14381439
- :reasoning-effort - Reasoning effort level: \"low\", \"medium\", \"high\", or \"xhigh\" (PR #302)
14391440
- :on-user-input-request - Handler for ask_user requests (PR #269)
1440-
- :on-elicitation-request - Handler for elicitation requests from the agent (upstream PR #908).
1441+
- :on-elicitation-request - Handler for elicitation requests from the agent (upstream PRs #908, #960).
14411442
When provided, sends requestElicitation=true and enables the
1442-
elicitation capability. Handler is (fn [request ctx]) returning
1443-
an ElicitationResult map ({:action \"accept\" :content {...}}).
1443+
elicitation capability. Single-arg handler receives an ElicitationContext
1444+
map with :session-id, :message, :requested-schema, :mode,
1445+
:elicitation-source, :url. Returns an ElicitationResult map.
14441446
- :hooks - Lifecycle hooks map (PR #269):
14451447
{:on-pre-tool-use, :on-post-tool-use, :on-user-prompt-submitted,
14461448
:on-session-start, :on-session-end, :on-error-occurred}

src/github/copilot_sdk/session.clj

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -428,15 +428,16 @@
428428

429429
(defn handle-elicitation-request!
430430
"Handle an incoming elicitation.requested broadcast event.
431-
Calls the session's elicitation handler and returns a channel with the result.
431+
Calls the session's elicitation handler with a single ElicitationContext arg
432+
(includes :session-id alongside request fields). Returns a channel with the result.
432433
If the handler fails, returns {:action \"cancel\"} to avoid hanging requests."
433-
[client session-id request]
434+
[client session-id context]
434435
(async/thread-call
435436
(fn []
436437
(let [handler (:elicitation-handler (session-state client session-id))]
437438
(when handler
438439
(try
439-
(let [result (handler request {:session-id session-id})
440+
(let [result (handler context)
440441
result (if (channel? result) (<!! result) result)]
441442
(or result {:action "cancel"}))
442443
(catch Throwable t

src/github/copilot_sdk/specs.clj

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -327,13 +327,13 @@
327327
(s/def ::elicitation-params
328328
(s/keys :req-un [::message ::requested-schema]))
329329

330-
;; Elicitation request — inbound from server when this client is an elicitation provider
330+
;; Elicitation context — passed to :on-elicitation-request handler (upstream PR #960).
331+
;; Single-arg pattern: context includes session-id alongside request fields.
331332
;; Note: :mode here is "form"/"url" (different from message-options ::mode which is :enqueue/:immediate)
332-
;; We validate with s/and to avoid spec name collision.
333333
(s/def ::elicitation-source string?)
334334
(s/def ::url string?)
335-
(s/def ::elicitation-request
336-
(s/and (s/keys :req-un [::message]
335+
(s/def ::elicitation-context
336+
(s/and (s/keys :req-un [::session-id ::message]
337337
:opt-un [::requested-schema ::elicitation-source ::url])
338338
#(if-let [m (:mode %)]
339339
(contains? #{"form" "url"} m)

test/github/copilot_sdk/integration_test.clj

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1403,7 +1403,7 @@
14031403
(swap! requests conj {:method method :params params})))
14041404
session (sdk/create-session *test-client*
14051405
{:on-permission-request sdk/approve-all
1406-
:on-elicitation-request (fn [_req _ctx] {:action "cancel"})})
1406+
:on-elicitation-request (fn [_ctx] {:action "cancel"})})
14071407
create-rpcs (filter #(= "session.create" (:method %)) @requests)]
14081408
(is (= 1 (count create-rpcs)))
14091409
(when (seq create-rpcs)
@@ -1434,8 +1434,8 @@
14341434
session (sdk/create-session *test-client*
14351435
{:on-permission-request sdk/approve-all
14361436
:on-elicitation-request
1437-
(fn [request ctx]
1438-
(reset! handler-called {:request request :ctx ctx})
1437+
(fn [context]
1438+
(reset! handler-called context)
14391439
{:action "accept"
14401440
:content {:name "test-value"}})})
14411441
session-id (sdk/session-id session)]
@@ -1451,10 +1451,10 @@
14511451
:mode "form"
14521452
:elicitationSource "mcp-server"})
14531453
(is (.await rpc-latch 5 java.util.concurrent.TimeUnit/SECONDS))
1454-
;; Handler was called
1454+
;; Handler was called with ElicitationContext (single arg, includes session-id)
14551455
(is (some? @handler-called))
1456-
(is (= "Enter your name" (:message (:request @handler-called))))
1457-
(is (= session-id (:session-id (:ctx @handler-called))))
1456+
(is (= "Enter your name" (:message @handler-called)))
1457+
(is (= session-id (:session-id @handler-called)))
14581458
;; handlePendingElicitation RPC was sent with handler's result
14591459
(let [rpcs (filter #(= "session.ui.handlePendingElicitation" (:method %)) @requests)]
14601460
(is (= 1 (count rpcs)))
@@ -1474,7 +1474,7 @@
14741474
session (sdk/create-session *test-client*
14751475
{:on-permission-request sdk/approve-all
14761476
:on-elicitation-request
1477-
(fn [_req _ctx]
1477+
(fn [_ctx]
14781478
(throw (Exception. "UI unavailable")))})
14791479
session-id (sdk/session-id session)]
14801480
(swap! (:state *test-client*) assoc :negotiated-protocol-version 3)

0 commit comments

Comments
 (0)