diff --git a/CHANGELOG.md b/CHANGELOG.md index 845b78f..2c4146e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ All notable changes to this project will be documented in this file. This change ## [Unreleased] -### Added (v1.0.7-preview.2 post-release sync) +### Added (v1.0.7 sync) - **Opaque tool-definition metadata** — port of upstream [PR #1864](https://github.com/github/copilot-sdk/pull/1864). `define-tool` and `define-tool-from-spec` accept an optional `:metadata` map and forward its @@ -26,6 +26,14 @@ All notable changes to this project will be documented in this file. This change `:promo` maps (`:ends-at`, with optional `:id`, `:discount-percent`, and `:message`). No new public session event type was added. +### Added (post-v1.0.7 sync) +- **Per-custom-agent reasoning effort** — port of + [upstream PR #1981](https://github.com/github/copilot-sdk/pull/1981). Custom + agent maps accept optional `:agent-reasoning-effort` values `"low"`, + `"medium"`, `"high"`, or `"xhigh"`. Session create and resume send it as the + exact `reasoningEffort` wire field. When absent, the field is omitted and does + not inherit the session-level `:reasoning-effort`. + ## [1.0.7-preview.2.1] - 2026-07-15 ### Added - **API-surface drift guard** ([#120](https://github.com/copilot-community-sdk/copilot-sdk-clojure/issues/120)) — diff --git a/README.md b/README.md index 7f65c5d..277e723 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ Add to your `deps.edn`: ```clojure ;; From Maven Central -io.github.copilot-community-sdk/copilot-sdk-clojure {:mvn/version "1.0.7-preview.2.1"} +io.github.copilot-community-sdk/copilot-sdk-clojure {:mvn/version "1.0.7.0"} ;; Or git dependency io.github.copilot-community-sdk/copilot-sdk-clojure {:git/url "https://github.com/copilot-community-sdk/copilot-sdk-clojure.git" diff --git a/build.clj b/build.clj index 2e833ce..8d930d2 100644 --- a/build.clj +++ b/build.clj @@ -7,7 +7,7 @@ (:import [java.io File])) (def lib 'io.github.copilot-community-sdk/copilot-sdk-clojure) -(def version "1.0.7-preview.2.1") +(def version "1.0.7.0") (def class-dir "target/classes") (defn- try-sh diff --git a/doc/api/API.html b/doc/api/API.html index f31b5c2..f2f3990 100644 --- a/doc/api/API.html +++ b/doc/api/API.html @@ -203,7 +203,7 @@

with-clien :exp-assignments map (Internal) Opaque experiment flight assignments. Keys are source-defined flight ids and are forwarded verbatim (string keys bypass kebab→camel conversion). Serialized as expAssignments. (upstream PR #1750) :mcp-servers map MCP server configs keyed by server ID (see MCP docs). Local (stdio) servers: :mcp-command, :mcp-args, :mcp-tools. Remote (HTTP/SSE) servers: :mcp-server-type (:http/:sse), :mcp-url, :mcp-tools. Spec aliases: ::mcp-stdio-server = ::mcp-local-server, ::mcp-http-server = ::mcp-remote-server :commands vector Command definitions (slash commands). See Commands - :custom-agents vector Custom agent configs. Each agent map: :agent-name (required), :agent-prompt (required), :agent-display-name, :agent-description, :agent-tools, :agent-infer?, :agent-skills (vector of strings), :agent-model (string, e.g. "claude-haiku-4.5"; when set the runtime tries this model for the agent, falling back to the parent session model — upstream PR #1309), :mcp-servers + :custom-agents vector Custom agent configs. Each agent map: :agent-name (required), :agent-prompt (required), :agent-display-name, :agent-description, :agent-tools, :agent-infer?, :agent-skills (vector of strings), :agent-model (string, e.g. "claude-haiku-4.5"; when set the runtime tries this model for the agent, falling back to the parent session model — upstream PR #1309), :agent-reasoning-effort ("low", "medium", "high", or "xhigh"), :mcp-servers. :agent-reasoning-effort is serialized as reasoningEffort on both session.create and session.resume. When omitted, no per-agent override is sent; the backend chooses its default rather than inheriting the parent session’s effort. :default-agent map Built-in/default agent config. Use {:excluded-tools [...]} to hide tools from the default agent while leaving them available to custom agents :on-permission-request fn Permission handler function. Optional (upstream PR #1308). When omitted, permission requests are not auto-resolved; resolve them manually via handle-pending-permission-request!. Use copilot/approve-all to approve everything. :streaming? boolean Enable streaming deltas @@ -1813,10 +1813,11 @@

:tools [repo-index-tool] :custom-agents [{:agent-name "repo-auditor" :agent-prompt "Audit repository changes." - :agent-tools ["repo_index_search"]}] + :agent-tools ["repo_index_search"] + :agent-reasoning-effort "high"}] :default-agent {:excluded-tools ["repo_index_search"]}})) -

The default agent cannot call repo_index_search. The repo-auditor custom agent can still call it because custom-agent tool assignment is independent of :default-agent.

+

The default agent cannot call repo_index_search. The repo-auditor custom agent can still call it because custom-agent tool assignment is independent of :default-agent. Its reasoning effort is overridden to "high"; omit :agent-reasoning-effort to let the backend choose its default rather than inherit the parent session’s effort.

Config Directory and Skills

config-dir overrides where the CLI reads its config and state (e.g., ~/.copilot). It does not define custom agents. Custom agents are provided via :custom-agents.

(def session (copilot/create-session client
diff --git a/doc/api/getting-started.html b/doc/api/getting-started.html
index efc7030..2fc11a5 100644
--- a/doc/api/getting-started.html
+++ b/doc/api/getting-started.html
@@ -25,7 +25,7 @@ 

Prerequisites

Step 1: Add the SDK Dependency

Add to your deps.edn:

-
{:deps {io.github.copilot-community-sdk/copilot-sdk-clojure {:mvn/version "1.0.7-preview.2.1"}}}
+
{:deps {io.github.copilot-community-sdk/copilot-sdk-clojure {:mvn/version "1.0.7.0"}}}
 

Or use as a Git dependency:

{:deps {io.github.copilot-community-sdk/copilot-sdk-clojure
diff --git a/doc/getting-started.md b/doc/getting-started.md
index 16fd61b..d9a8976 100644
--- a/doc/getting-started.md
+++ b/doc/getting-started.md
@@ -34,7 +34,7 @@ copilot --version
 Add to your `deps.edn`:
 
 ```clojure
-{:deps {io.github.copilot-community-sdk/copilot-sdk-clojure {:mvn/version "1.0.7-preview.2.1"}}}
+{:deps {io.github.copilot-community-sdk/copilot-sdk-clojure {:mvn/version "1.0.7.0"}}}
 ```
 
 Or use as a Git dependency:
diff --git a/doc/reference/API.md b/doc/reference/API.md
index 56731a6..369330a 100644
--- a/doc/reference/API.md
+++ b/doc/reference/API.md
@@ -291,7 +291,7 @@ Create a client and session together, ensuring both are cleaned up on exit.
 | `:exp-assignments` | map | (Internal) Opaque experiment flight assignments. Keys are source-defined flight ids and are forwarded verbatim (string keys bypass kebab→camel conversion). Serialized as `expAssignments`. (upstream PR #1750) |
 | `:mcp-servers` | map | MCP server configs keyed by server ID (see [MCP docs](../mcp/overview.md)). Local (stdio) servers: `:mcp-command`, `:mcp-args`, `:mcp-tools`. Remote (HTTP/SSE) servers: `:mcp-server-type` (`:http`/`:sse`), `:mcp-url`, `:mcp-tools`. Spec aliases: `::mcp-stdio-server` = `::mcp-local-server`, `::mcp-http-server` = `::mcp-remote-server` |
 | `:commands` | vector | Command definitions (slash commands). See [Commands](#commands) |
-| `:custom-agents` | vector | Custom agent configs. Each agent map: `:agent-name` (required), `:agent-prompt` (required), `:agent-display-name`, `:agent-description`, `:agent-tools`, `:agent-infer?`, `:agent-skills` (vector of strings), `:agent-model` (string, e.g. `"claude-haiku-4.5"`; when set the runtime tries this model for the agent, falling back to the parent session model — upstream PR #1309), `:mcp-servers` |
+| `:custom-agents` | vector | Custom agent configs. Each agent map: `:agent-name` (required), `:agent-prompt` (required), `:agent-display-name`, `:agent-description`, `:agent-tools`, `:agent-infer?`, `:agent-skills` (vector of strings), `:agent-model` (string, e.g. `"claude-haiku-4.5"`; when set the runtime tries this model for the agent, falling back to the parent session model — upstream PR #1309), `:agent-reasoning-effort` (`"low"`, `"medium"`, `"high"`, or `"xhigh"`), `:mcp-servers`. `:agent-reasoning-effort` is serialized as `reasoningEffort` on both `session.create` and `session.resume`. When omitted, no per-agent override is sent; the backend chooses its default rather than inheriting the parent session's effort. |
 | `:default-agent` | map | Built-in/default agent config. Use `{:excluded-tools [...]}` to hide tools from the default agent while leaving them available to custom agents |
 | `:on-permission-request` | fn | Permission handler function. **Optional** (upstream PR #1308). When omitted, permission requests are not auto-resolved; resolve them manually via `handle-pending-permission-request!`. Use `copilot/approve-all` to approve everything. |
 | `:streaming?` | boolean | Enable streaming deltas |
@@ -2327,11 +2327,12 @@ Hide tools from the built-in/default agent while keeping them available to custo
      :tools [repo-index-tool]
      :custom-agents [{:agent-name "repo-auditor"
                       :agent-prompt "Audit repository changes."
-                      :agent-tools ["repo_index_search"]}]
+                      :agent-tools ["repo_index_search"]
+                      :agent-reasoning-effort "high"}]
      :default-agent {:excluded-tools ["repo_index_search"]}}))
 ```
 
-The default agent cannot call `repo_index_search`. The `repo-auditor` custom agent can still call it because custom-agent tool assignment is independent of `:default-agent`.
+The default agent cannot call `repo_index_search`. The `repo-auditor` custom agent can still call it because custom-agent tool assignment is independent of `:default-agent`. Its reasoning effort is overridden to `"high"`; omit `:agent-reasoning-effort` to let the backend choose its default rather than inherit the parent session's effort.
 
 ### Config Directory and Skills
 
diff --git a/resources/github/copilot_sdk/api_surface.edn b/resources/github/copilot_sdk/api_surface.edn
index 4215850..3154ce9 100644
--- a/resources/github/copilot_sdk/api_surface.edn
+++ b/resources/github/copilot_sdk/api_surface.edn
@@ -92,6 +92,7 @@
   :github.copilot-sdk.specs/agent-model
   :github.copilot-sdk.specs/agent-name
   :github.copilot-sdk.specs/agent-prompt
+  :github.copilot-sdk.specs/agent-reasoning-effort
   :github.copilot-sdk.specs/agent-skills
   :github.copilot-sdk.specs/agent-tool-names
   :github.copilot-sdk.specs/agent-tools
diff --git a/src/github/copilot_sdk/client.clj b/src/github/copilot_sdk/client.clj
index fd9f256..560668f 100644
--- a/src/github/copilot_sdk/client.clj
+++ b/src/github/copilot_sdk/client.clj
@@ -1925,6 +1925,15 @@
     (some? (:metadata t))
     (assoc :metadata (:metadata t))))
 
+(defn- custom-agent->wire
+  "Convert a custom agent to its wire shape for session.create / session.resume.
+   The idiomatic `:agent-reasoning-effort` key maps to the upstream SDK's
+   `reasoningEffort` field and is omitted when absent."
+  [agent]
+  (let [reasoning-effort (:agent-reasoning-effort agent)]
+    (cond-> (util/clj->wire (dissoc agent :agent-reasoning-effort))
+      (some? reasoning-effort) (assoc :reasoningEffort reasoning-effort))))
+
 (defn- config-defaults-for-mode
   "Mode-specific session config defaults spread UNDER the caller's config
    (caller's values always win). Mirrors upstream `configDefaultsForMode`
@@ -2067,7 +2076,7 @@
         wire-mcp-servers (when-let [servers (:mcp-servers config)]
                            (util/mcp-servers->wire servers))
         wire-custom-agents (when-let [agents (:custom-agents config)]
-                             (mapv util/clj->wire agents))
+                             (mapv custom-agent->wire agents))
         wire-default-agent (when-let [agent (:default-agent config)]
                              (util/clj->wire agent))
         wire-infinite-sessions (when-let [is (:infinite-sessions config)]
@@ -2252,7 +2261,7 @@
         wire-mcp-servers (when-let [servers (:mcp-servers config)]
                            (util/mcp-servers->wire servers))
         wire-custom-agents (when-let [agents (:custom-agents config)]
-                             (mapv util/clj->wire agents))
+                             (mapv custom-agent->wire agents))
         wire-default-agent (when-let [agent (:default-agent config)]
                              (util/clj->wire agent))
         wire-infinite-sessions (when-let [is (:infinite-sessions config)]
diff --git a/src/github/copilot_sdk/specs.clj b/src/github/copilot_sdk/specs.clj
index c8b2b1a..18d4aeb 100644
--- a/src/github/copilot_sdk/specs.clj
+++ b/src/github/copilot_sdk/specs.clj
@@ -408,6 +408,7 @@
 ;; Custom agent configuration
 ;; -----------------------------------------------------------------------------
 
+(s/def ::reasoning-effort #{"low" "medium" "high" "xhigh"})
 (s/def ::agent-name ::non-blank-string)
 (s/def ::agent-display-name string?)
 (s/def ::agent-description string?)
@@ -419,11 +420,15 @@
 ;; runtime will attempt to use this model for the agent, falling back to the
 ;; parent session model if unavailable. Upstream PR #1309.
 (s/def ::agent-model ::non-blank-string)
+;; Per-agent override of the model's reasoning effort. The nested API key keeps
+;; the established agent prefix; the wire serializer emits `reasoningEffort`.
+(s/def ::agent-reasoning-effort ::reasoning-effort)
 
 (s/def ::custom-agent
   (s/keys :req-un [::agent-name ::agent-prompt]
           :opt-un [::agent-display-name ::agent-description ::agent-tools
-                   ::mcp-servers ::agent-infer? ::agent-skills ::agent-model]))
+                   ::mcp-servers ::agent-infer? ::agent-skills ::agent-model
+                   ::agent-reasoning-effort]))
 
 (s/def ::custom-agents (s/coll-of ::custom-agent))
 
@@ -630,9 +635,6 @@
 (s/def ::memory
   (s/keys :req-un [::enabled]))
 
-;; Reasoning effort support (PR #302)
-(s/def ::reasoning-effort #{"low" "medium" "high" "xhigh"})
-
 ;; Hooks and user input handlers (PR #269)
 (s/def ::on-user-input-request fn?)
 (s/def ::on-pre-tool-use fn?)
diff --git a/test/github/copilot_sdk/integration_test.clj b/test/github/copilot_sdk/integration_test.clj
index 5ee2318..8d46267 100644
--- a/test/github/copilot_sdk/integration_test.clj
+++ b/test/github/copilot_sdk/integration_test.clj
@@ -5232,6 +5232,54 @@
           agent (first (get-in @seen ["session.create" :customAgents]))]
       (is (not (contains? agent :agentModel))))))
 
+;; --- Per-agent reasoning effort (upstream PR #1981) -------------------------
+
+(deftest test-custom-agent-reasoning-effort-spec
+  (testing "::custom-agent spec validates optional :agent-reasoning-effort"
+    (is (s/valid? :github.copilot-sdk.specs/custom-agent
+                  {:agent-name "test" :agent-prompt "You are helpful"
+                   :agent-reasoning-effort "high"}))
+    (is (not (s/valid? :github.copilot-sdk.specs/custom-agent
+                       {:agent-name "test" :agent-prompt "You are helpful"
+                        :agent-reasoning-effort "max"})))))
+
+(deftest test-custom-agent-reasoning-effort-on-wire
+  (testing "reasoning effort is sent on wire in session.create and session.resume"
+    (let [seen (atom {})
+          _ (mock/set-request-hook! *mock-server* (fn [method params]
+                                                    (when (#{"session.create" "session.resume"} method)
+                                                      (swap! seen assoc method params))))
+          _ (sdk/create-session *test-client*
+                                {:on-permission-request sdk/approve-all
+                                 :custom-agents [{:agent-name "reasoning-agent"
+                                                  :agent-prompt "Hello"
+                                                  :agent-reasoning-effort "high"}]})
+          session-id (sdk/get-last-session-id *test-client*)
+          _ (sdk/resume-session *test-client* session-id
+                                {:on-permission-request sdk/approve-all
+                                 :custom-agents [{:agent-name "reasoning-agent-2"
+                                                  :agent-prompt "Hi"
+                                                  :agent-reasoning-effort "low"}]})
+          create-agent (first (get-in @seen ["session.create" :customAgents]))
+          resume-agent (first (get-in @seen ["session.resume" :customAgents]))]
+      (is (= "high" (:reasoningEffort create-agent)))
+      (is (= "low" (:reasoningEffort resume-agent)))
+      (is (not (contains? create-agent :agentReasoningEffort)))
+      (is (not (contains? resume-agent :agentReasoningEffort))))))
+
+(deftest test-custom-agent-reasoning-effort-omitted-when-not-set
+  (testing ":agent-reasoning-effort is omitted from wire when not provided"
+    (let [seen (atom {})
+          _ (mock/set-request-hook! *mock-server* (fn [method params]
+                                                    (when (= "session.create" method)
+                                                      (swap! seen assoc method params))))
+          _ (sdk/create-session *test-client*
+                                {:on-permission-request sdk/approve-all
+                                 :custom-agents [{:agent-name "default-reasoning"
+                                                  :agent-prompt "Hi"}]})
+          agent (first (get-in @seen ["session.create" :customAgents]))]
+      (is (not (contains? agent :reasoningEffort))))))
+
 ;; --- Default agent config (upstream PR #1098) --------------------------------
 
 (deftest test-default-agent-excluded-tools-on-wire