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
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,21 @@ All notable changes to this project will be documented in this file. This change
### Added
- `:no-result` permission outcome — extensions can attach to sessions without actively answering permission requests by returning `{:kind :no-result}` from their `:on-permission-request` handler. On v3 protocol, the `handlePendingPermissionRequest` RPC is skipped; on v2, an error is propagated to the CLI (upstream PR #802).

### Added (v0.1.33 sync)
- `:skip-permission?` option on tool definitions — when `true`, the tool executes without triggering a permission prompt. Sent as `skipPermission: true` in the wire protocol (upstream PR #808).
- OpenTelemetry support: new `:telemetry` client option (map with `:otlp-endpoint`, `:file-path`, `:exporter-type`, `:source-name`, `:capture-content?`) configures OTel environment variables on the spawned CLI process. New `:on-get-trace-context` client option (0-arity fn returning `{:traceparent ... :tracestate ...}`) enables W3C Trace Context propagation into `session.create`, `session.resume`, and `session.send` RPCs (upstream PR #785).
- Tool invocations now receive `:traceparent` and `:tracestate` fields in the invocation context map when the CLI provides them (upstream PR #785).
- Optional `:reasoning-effort` parameter in `switch-model!` and `set-model!` — pass `{:reasoning-effort "high"}` as a third argument to set reasoning effort when switching models (upstream PR #712).
- New event data fields from upstream codegen update (upstream PR #796):
- `session.start` event: `:reasoning-effort`, `:already-in-use?`, `:host-type`, `:head-commit`, `:base-commit` optional fields
- `session.resume` event: new `::session.resume-data` spec with `:event-count`, `:selected-model`, `:reasoning-effort`, `:already-in-use?`, `:host-type`, `:head-commit`, `:base-commit`
- `session.model_change` event: new `::session.model_change-data` spec with `:new-model`, `:previous-model`, `:reasoning-effort`, `:previous-reasoning-effort`
- `user.message` event: new `:blob` attachment type with `:data` (base64), `:mime-type`, optional `:display-name`

### Changed (v0.1.33 sync)
- `join-session` now makes `:on-permission-request` **optional**. When omitted, a default handler returns `{:kind :no-result}`, leaving any pending permission request unanswered. This matches the upstream `JoinSessionConfig` where `onPermissionRequest` is optional (upstream PR #802).
- `:auto-restart?` client option is **deprecated** and has no effect. The auto-restart/reconnect behavior has been removed across all official SDKs. The option is retained for backward compatibility but will be removed in a future release (upstream PR #803).

## [0.1.32.0] - 2026-03-12
### 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).
Expand Down
19 changes: 14 additions & 5 deletions src/github/copilot_sdk.clj
Original file line number Diff line number Diff line change
Expand Up @@ -170,8 +170,10 @@
- :use-stdio? - Use stdio transport (default: true)
- :log-level - :none :error :warning :info :debug :all
- :auto-start? - Auto-start on first use (default: true)
- :auto-restart? - Auto-restart on crash (default: true)
- :auto-restart? - **DEPRECATED**: This option has no effect and will be removed in a future release.
- :env - Environment variables map
- :telemetry - OpenTelemetry config map with :otlp-endpoint, :file-path, :exporter-type, :source-name, :capture-content?
- :on-get-trace-context - Zero-arg fn returning {:traceparent ... :tracestate ...}

Example:
```clojure
Expand Down Expand Up @@ -810,14 +812,18 @@
(defn switch-model!
"Switch the model for this session.
The new model takes effect for the next message. Conversation history is preserved.
Optional opts map with `:reasoning-effort` (\"low\", \"medium\", \"high\", \"xhigh\").
Returns the new model ID string, or nil.

Example:
```clojure
(copilot/switch-model! session \"claude-sonnet-4.5\")
(copilot/switch-model! session \"claude-sonnet-4.5\" {:reasoning-effort \"high\"})
```"
[session model-id]
(session/switch-model! session model-id))
([session model-id]
(session/switch-model! session model-id))
([session model-id opts]
(session/switch-model! session model-id opts)))

(defn set-model!
"Alias for switch-model!. Matches the upstream SDK's setModel() API.
Expand All @@ -826,9 +832,12 @@
Example:
```clojure
(copilot/set-model! session \"gpt-4.1\")
(copilot/set-model! session \"gpt-4.1\" {:reasoning-effort \"high\"})
```"
[session model-id]
(session/set-model! session model-id))
([session model-id]
(session/set-model! session model-id))
([session model-id opts]
(session/set-model! session model-id opts)))

(defn log!
"Log a message to the session timeline.
Expand Down
97 changes: 53 additions & 44 deletions src/github/copilot_sdk/client.clj
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,21 @@
(def ^:private sdk-protocol-version-max 3)
(def ^:private sdk-protocol-version-min 2)

(defn- get-trace-context
"Call the user-provided trace context provider. Returns {} when no provider
is configured or returns a non-map value. Only :traceparent and :tracestate
keys are retained to prevent accidental override of RPC params."
[provider]
(if-not provider
{}
(try
(let [ctx (provider)]
(if (map? ctx)
(select-keys ctx [:traceparent :tracestate])
{}))
(catch Throwable _
{}))))

(defn- parse-cli-url
"Parse CLI URL into {:host :port}."
[url]
Expand Down Expand Up @@ -47,7 +62,7 @@
:use-stdio? true
:log-level :info
:auto-start? true
:auto-restart? true
:auto-restart? false
:notification-queue-size 4096
:router-queue-size 4096
:tool-timeout-ms 120000
Expand Down Expand Up @@ -94,7 +109,6 @@
:router-thread nil
:router-running? false
:stopping? false
:restarting? false
:force-stopping? false
:models-cache nil ; nil, promise, or vector of models (cleared on stop)
:lifecycle-handlers {}
Expand All @@ -113,15 +127,17 @@
- :use-stdio? - Use stdio transport (default: true)
- :log-level - :none :error :warning :info :debug :all
- :auto-start? - Auto-start on first use (default: true)
- :auto-restart? - Auto-restart on crash (default: true)
- :auto-restart? - **DEPRECATED**: This option has no effect and will be removed in a future release.
- :notification-queue-size - Max queued protocol notifications (default: 4096)
- :router-queue-size - Max queued non-session notifications (default: 4096)
- :tool-timeout-ms - Timeout for tool calls that return a channel (default: 120000)
- :env - Environment variables map
- :github-token - GitHub token for authentication (sets COPILOT_SDK_AUTH_TOKEN env var)
- :use-logged-in-user? - Whether to use logged-in user auth (default: true, false when github-token provided)
- :is-child-process? - When true, SDK is a child of an existing Copilot CLI process and uses stdio to communicate with it (no process spawning)
- :on-list-models - Zero-arg fn returning a seq of model info maps; bypasses the RPC call and does not require start!"
- :on-list-models - Zero-arg fn returning a seq of model info maps; bypasses the RPC call and does not require start!
- :telemetry - OpenTelemetry config map with optional keys :otlp-endpoint, :file-path, :exporter-type, :source-name, :capture-content?
- :on-get-trace-context - Zero-arg fn returning {:traceparent ... :tracestate ...} for distributed trace propagation"
([]
(client {}))
([opts]
Expand Down Expand Up @@ -184,7 +200,9 @@
:actual-host (or host "localhost")
:state (atom (assoc (initial-state port) :options final-opts))}
(:on-list-models opts)
(assoc :on-list-models (:on-list-models opts))))))
(assoc :on-list-models (:on-list-models opts))
(:on-get-trace-context opts)
(assoc :on-get-trace-context (:on-get-trace-context opts))))))

(defn state
"Get the current connection state."
Expand Down Expand Up @@ -216,12 +234,15 @@
request-id (:request-id data)
tool-name (:tool-name data)
tool-call-id (:tool-call-id data)
arguments (:arguments data)]
arguments (:arguments data)
traceparent (:traceparent data)
tracestate (:tracestate data)]
(when (and request-id tool-name)
(go
(try
(let [tool-response (<! (session/handle-tool-call!
client session-id tool-call-id tool-name arguments))
client session-id tool-call-id tool-name arguments
:traceparent traceparent :tracestate tracestate))
result (:result tool-response)
conn (:connection-io @(:state client))]
(when conn
Expand Down Expand Up @@ -407,35 +428,10 @@
{:handler handler :event-type event-type})
(fn [] (swap! (:state client) update :lifecycle-handlers dissoc id))))))

(defn- mark-restarting!
"Atomically mark the client as restarting. Returns true if this caller won."
[client]
(let [state-atom (:state client)]
(loop []
(let [state @state-atom]
(if (or (:stopping? state) (:restarting? state))
false
(if (compare-and-set! state-atom state (assoc state :restarting? true))
true
(recur)))))))

(defn- maybe-reconnect!
"Attempt a stop/start cycle when auto-restart is enabled."
[client reason]
(let [state @(:state client)]
(when (and (:auto-restart? (:options client))
(= :connected (:status state))
(not (:stopping? state)))
(when (mark-restarting! client)
(log/warn "Auto-restart triggered:" reason)
(async/thread
(try
(stop! client)
(start! client)
(catch Exception e
(log/error "Auto-restart failed: " (ex-message e)))
(finally
(swap! (:state client) assoc :restarting? false))))))))
"No-op: auto-restart is deprecated and has no effect (upstream PR #803)."
[_client _reason]
nil)

(defn- watch-process-exit!
"Trigger auto-restart when the managed CLI process exits."
Expand Down Expand Up @@ -683,8 +679,8 @@
Blocks until connected or throws on error.

Thread safety: do not call start! and stop! concurrently from different
threads. The :stopping? and :restarting? flags guard against concurrent
auto-restart, but explicit concurrent calls are unsupported."
threads. The :stopping? flag guards against concurrent calls, but explicit
concurrent calls are unsupported."
[client]
(when-not (= :connected (:status @(:state client)))
(log/info "Starting Copilot client...")
Expand Down Expand Up @@ -1111,7 +1107,9 @@
:description (:tool-description t)
:parameters (:tool-parameters t)}
(some? (:overrides-built-in-tool t))
(assoc :overridesBuiltInTool (:overrides-built-in-tool t))))
(assoc :overridesBuiltInTool (:overrides-built-in-tool t))
(some? (:skip-permission? t))
(assoc :skipPermission (:skip-permission? t))))
(:tools config)))
wire-sys-msg (when-let [sm (:system-message config)]
(cond
Expand Down Expand Up @@ -1163,7 +1161,9 @@
:description (:tool-description t)
:parameters (:tool-parameters t)}
(some? (:overrides-built-in-tool t))
(assoc :overridesBuiltInTool (:overrides-built-in-tool t))))
(assoc :overridesBuiltInTool (:overrides-built-in-tool t))
(some? (:skip-permission? t))
(assoc :skipPermission (:skip-permission? t))))
(:tools config)))
wire-sys-msg (when-let [sm (:system-message config)]
(cond
Expand Down Expand Up @@ -1257,7 +1257,8 @@
(ensure-connected! client)
(let [{:keys [connection-io]} @(:state client)
session-id (or (:session-id config) (str (java.util.UUID/randomUUID)))
params (assoc (build-create-session-params config) :session-id session-id)
trace-ctx (get-trace-context (:on-get-trace-context client))
params (merge trace-ctx (assoc (build-create-session-params config) :session-id session-id))
;; Pre-register session before RPC so early events are captured
session (pre-register-session client session-id config)]
(try
Expand Down Expand Up @@ -1310,7 +1311,8 @@
{:config config})))
(ensure-connected! client)
(let [{:keys [connection-io]} @(:state client)
params (build-resume-session-params session-id config)
trace-ctx (get-trace-context (:on-get-trace-context client))
params (merge trace-ctx (build-resume-session-params session-id config))
;; Pre-register session before RPC so early events are captured
session (pre-register-session client session-id config)]
(try
Expand Down Expand Up @@ -1345,7 +1347,8 @@
(ensure-connected! client)
(let [{:keys [connection-io]} @(:state client)
session-id (or (:session-id config) (str (java.util.UUID/randomUUID)))
params (assoc (build-create-session-params config) :session-id session-id)
trace-ctx (get-trace-context (:on-get-trace-context client))
params (merge trace-ctx (assoc (build-create-session-params config) :session-id session-id))
;; Pre-register session before RPC so early events are captured
session (pre-register-session client session-id config)
rpc-ch (proto/send-request connection-io "session.create" params)]
Expand Down Expand Up @@ -1398,7 +1401,8 @@
{:config config})))
(ensure-connected! client)
(let [{:keys [connection-io]} @(:state client)
params (build-resume-session-params session-id config)
trace-ctx (get-trace-context (:on-get-trace-context client))
params (merge trace-ctx (build-resume-session-params session-id config))
;; Pre-register session before RPC so early events are captured
session (pre-register-session client session-id config)
rpc-ch (proto/send-request connection-io "session.resume" params)]
Expand All @@ -1423,7 +1427,10 @@
Reads the SESSION_ID environment variable and connects to the parent CLI process
via stdio. This is intended for extensions spawned by the Copilot CLI.

Config is the same as resume-session (`:on-permission-request` is **required**).
Config is the same as resume-session. `:on-permission-request` is **optional**;
when omitted, a default handler is used that returns `{:kind :no-result}`, leaving any
pending permission request unanswered (appropriate for most extensions that use
`:skip-permission?` on their tools or do not require permission handling).
The `:disable-resume?` option defaults to true.

Returns a map with :client and :session keys. The caller is responsible for
Expand All @@ -1438,6 +1445,8 @@
{})))
(let [c (client {:is-child-process? true})
merged-config (cond-> config
(not (contains? config :on-permission-request))
(assoc :on-permission-request (constantly {:kind :no-result}))
(not (contains? config :disable-resume?))
(assoc :disable-resume? true))]
(try
Expand Down
12 changes: 7 additions & 5 deletions src/github/copilot_sdk/instrument.clj
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@
:ret ::specs/events-ch)

(s/fdef github.copilot-sdk.client/join-session
:args (s/cat :config ::specs/resume-session-config)
:args (s/cat :config ::specs/join-session-config)
:ret (s/keys :req-un [::specs/client ::specs/session]))

(s/fdef github.copilot-sdk.client/list-sessions
Expand Down Expand Up @@ -217,13 +217,15 @@

(s/fdef github.copilot-sdk.session/switch-model!
:args (s/cat :session ::specs/session
:model-id string?)
:ret ::specs/model-id)
:model-id string?
:opts (s/? (s/nilable (s/keys :opt-un [::specs/reasoning-effort]))))
:ret (s/nilable ::specs/model-id))

(s/fdef github.copilot-sdk.session/set-model!
:args (s/cat :session ::specs/session
:model-id string?)
:ret ::specs/model-id)
:model-id string?
:opts (s/? (s/nilable (s/keys :opt-un [::specs/reasoning-effort]))))
:ret (s/nilable ::specs/model-id))

(s/fdef github.copilot-sdk.session/log!
:args (s/cat :session ::specs/session
Expand Down
17 changes: 16 additions & 1 deletion src/github/copilot_sdk/process.clj
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,22 @@
;; Set github token in environment if provided (PR #237).
;; Explicit github-token should take precedence over env.
(when github-token
(.put env-map "COPILOT_SDK_AUTH_TOKEN" github-token)))
(.put env-map "COPILOT_SDK_AUTH_TOKEN" github-token))

;; Set OpenTelemetry environment variables if telemetry is configured (upstream PR #785)
(when-let [telemetry (:telemetry opts)]
(.put env-map "COPILOT_OTEL_ENABLED" "true")
(when-let [v (:otlp-endpoint telemetry)]
(.put env-map "OTEL_EXPORTER_OTLP_ENDPOINT" v))
(when-let [v (:file-path telemetry)]
(.put env-map "COPILOT_OTEL_FILE_EXPORTER_PATH" v))
(when-let [v (:exporter-type telemetry)]
(.put env-map "COPILOT_OTEL_EXPORTER_TYPE" v))
(when-let [v (:source-name telemetry)]
(.put env-map "COPILOT_OTEL_SOURCE_NAME" v))
(when (some? (:capture-content? telemetry))
(.put env-map "OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT"
(str (:capture-content? telemetry))))))

;; Configure stdio — use explicit PIPE redirects for all three streams.
;; On Windows, the JVM's ProcessImpl sets CREATE_NO_WINDOW when none of the
Expand Down
Loading
Loading