Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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 @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. This change

## [Unreleased]

### Added (post-v1.0.0-beta.4 sync)
- **`:agent-model` on custom-agent configs** — Custom agent maps in
`:custom-agents` now accept an optional `:agent-model` string (e.g.
`"claude-haiku-4.5"`). When set, the runtime attempts to use that model
for the agent, falling back to the parent session model if unavailable.
Forwarded on the wire as `agentModel` on each entry in `customAgents`
for both `session.create` and `session.resume`. (upstream PR #1309)

### Notes (v1.0.0-beta.4 sync)
Upstream `v1.0.0-beta.4` shipped no new Node.js SDK API surface relative to
`v1.0.0-beta.3` — every SDK-visible change in the upstream diff
Expand Down
2 changes: 1 addition & 1 deletion doc/reference/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ Create a client and session together, ensuring both are cleaned up on exit.
| `: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`, `:headers` (map of HTTP header name→value, sent with each provider request — upstream PR #1094), `:model-id` (string — the model identifier to send to the provider; overrides session `:model`), `:wire-model` (string — model name as sent on the provider wire when it differs from `:model-id`), `:max-input-tokens` (integer — input/prompt token cap; serialized as wire `maxPromptTokens`), `:max-output-tokens` (integer — output token cap). The four override fields were added in upstream PR #966 |
| `: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), `: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), `:mcp-servers` |
| `: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 | **Required.** Permission handler function. Use `copilot/approve-all` to approve everything. |
| `:streaming?` | boolean | Enable streaming deltas |
Expand Down
6 changes: 5 additions & 1 deletion src/github/copilot_sdk/specs.clj
Original file line number Diff line number Diff line change
Expand Up @@ -274,11 +274,15 @@
(s/def ::agent-prompt ::non-blank-string)
(s/def ::agent-infer? boolean?)
(s/def ::agent-skills (s/coll-of string?))
;; Model identifier for the agent (e.g. "claude-haiku-4.5"). When set, the
;; 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)

(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]))
::mcp-servers ::agent-infer? ::agent-skills ::agent-model]))

(s/def ::custom-agents (s/coll-of ::custom-agent))

Expand Down
51 changes: 51 additions & 0 deletions test/github/copilot_sdk/integration_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -3356,6 +3356,57 @@
agent (first (:customAgents create-params))]
(is (= ["my-skill"] (:agentSkills agent))))))

;; --- Per-agent model field (upstream PR #1309) ------------------------------

(deftest test-custom-agent-model-spec
(testing "::custom-agent spec accepts optional :agent-model field"
(is (s/valid? :github.copilot-sdk.specs/custom-agent
{:agent-name "test" :agent-prompt "You are helpful"}))
(is (s/valid? :github.copilot-sdk.specs/custom-agent
{:agent-name "test" :agent-prompt "You are helpful"
:agent-model "claude-haiku-4.5"}))
(is (not (s/valid? :github.copilot-sdk.specs/custom-agent
{:agent-name "test" :agent-prompt "You are helpful"
:agent-model 42}))
":agent-model must be a string when provided")))

(deftest test-custom-agent-model-on-wire
(testing "model field is sent on wire in session.create and session.resume (upstream PR #1309)"
(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 "haiku-agent"
:agent-prompt "Hello"
:agent-model "claude-haiku-4.5"}]})
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 "haiku-agent-2"
:agent-prompt "Hi"
:agent-model "gpt-5.4"}]})
create-params (get @seen "session.create")
resume-params (get @seen "session.resume")]
(is (= "claude-haiku-4.5"
(get-in create-params [:customAgents 0 :agentModel])))
(is (= "gpt-5.4"
(get-in resume-params [:customAgents 0 :agentModel]))))))

(deftest test-custom-agent-model-omitted-when-not-set
(testing ":agent-model 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 "no-model"
:agent-prompt "Hi"}]})
agent (first (get-in @seen ["session.create" :customAgents]))]
(is (not (contains? agent :agentModel))))))

;; --- Default agent config (upstream PR #1098) --------------------------------

(deftest test-default-agent-excluded-tools-on-wire
Expand Down
Loading