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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
uses: actions/checkout@v6
with:
repository: github/copilot-sdk
ref: 1644e74578db3637bc7527951bac227aabbc0584
ref: d8bbc9dd7a6167d4806780f405d8ce74add1cc7c
path: .upstream/copilot-sdk
persist-credentials: false
fetch-depth: 0
Expand Down
21 changes: 14 additions & 7 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,23 @@ All notable changes to this project will be documented in this file. This change

## [Unreleased]

### Added (upstream parity)
- Added stable message provenance to every send API. Use `:source :user`,
`:source :system`, or `:source {:agent-id "..."}`; identified agents
preserve the opaque ID exactly and serialize it as `agent-<id>`. Omission
sends no source, while explicit `nil` and unsupported values are rejected.
([upstream PR #2573](https://github.com/github/copilot-sdk/pull/2573))

### Changed (upstream parity)
- Recertified the complete stable Node SDK public surface through upstream
commit
[`1644e74578db3637bc7527951bac227aabbc0584`](https://github.com/github/copilot-sdk/commit/1644e74578db3637bc7527951bac227aabbc0584).
The `nodejs/` tree and package metadata are byte-for-byte identical to the
`v1.0.13` oracle, so no API, wire, schema, runtime, generated source, or
version changes were required. The intervening commits update Java release
metadata and add the generated v1.0.13 changelog; live Auto-tier switching
and sandbox bypass remain explicitly experimental.
([upstream PR #2545](https://github.com/github/copilot-sdk/pull/2545))
[`d8bbc9dd7a6167d4806780f405d8ce74add1cc7c`](https://github.com/github/copilot-sdk/commit/d8bbc9dd7a6167d4806780f405d8ce74add1cc7c).
The only stable delta is typed message provenance on `send`; the runtime
schema and generated sources remain unchanged. ARM64 musl transport coverage
is language-specific, and the existing experimental exclusions remain
outside the stable Clojure API.
([upstream PR #2548](https://github.com/github/copilot-sdk/pull/2548),
[upstream PR #2573](https://github.com/github/copilot-sdk/pull/2573))

### Added (v1.0.13 sync)
- Added stable `:client-info` identity on the connection handshake, with
Expand Down
6 changes: 6 additions & 0 deletions doc/api/API.html
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ <h3><a href="#naming-and-shape-differences-vs-the-official-sdk" id="naming-and-s
<tbody>
<tr><td> <code>:disable-resume?</code> </td><td> <code>suppressResumeEvent</code> </td><td> Config key on <code>resume-session</code> / <code>join-session</code>. When true, skips emitting the <code>session.resume</code> event. Defaults to <code>true</code> in <code>join-session</code> (matching upstream), <code>false</code> elsewhere. </td></tr>
<tr><td> <code>:max-input-tokens</code> </td><td> <code>maxPromptTokens</code> </td><td> BYOK provider/model config key (input/prompt token cap). Serialized back to <code>maxPromptTokens</code> on the wire. </td></tr>
<tr><td> <code>:source {:agent-id "reviewer"}</code> </td><td> <code>source: "agent-reviewer"</code> </td><td> <code>send</code> message provenance. Fixed sources use <code>:user</code> or <code>:system</code>; identified agents use a map so the opaque ID is preserved exactly before the <code>agent-</code> wire prefix is added. </td></tr>
<tr><td> <code>join-session</code> return <code>{:client :session}</code> </td><td> <code>joinSession()</code> returns <code>CopilotSession</code> </td><td> Clojure has no implicit/global client, so it returns both so the caller can own the client lifecycle. See <a href="#join-session"><code>join-session</code></a>. </td></tr>
<tr><td> <code>join-session</code> return <code>:granted-environment-variables</code> </td><td> <code>joinSession()</code> writes grants to <code>process.env</code> </td><td> The JVM cannot portably mutate process environment variables. When an extension requests environment access, Clojure returns approved values as a string-keyed map filtered to the exact requested names. </td></tr>
</tbody>
Expand Down Expand Up @@ -701,8 +702,13 @@ <h4><a href="#send" id="send"></a><code>send!</code></h4>
<tr><td> <code>:agent-mode</code> </td><td> keyword </td><td> <code>#{:interactive :plan :autopilot :shell}</code>. Per-message agent mode. Wire-encoded as <code>agentMode</code>. (upstream PR #1438) </td></tr>
<tr><td> <code>:display-prompt</code> </td><td> string </td><td> Alternate prompt shown in the timeline UI instead of <code>:prompt</code>. Useful when the model-facing prompt contains machinery or context that should not be surfaced to the end user. Wire-encoded as <code>displayPrompt</code>. (upstream PR #1470) </td></tr>
<tr><td> <code>:request-headers</code> </td><td> map </td><td> Extra HTTP headers (string→string) forwarded to the model provider for this request. Merged with provider-level <code>:headers</code>. (upstream PR #1094) </td></tr>
<tr><td> <code>:source</code> </td><td> keyword or map </td><td> Optional message provenance. Use <code>:user</code>, <code>:system</code>, or <code>{:agent-id "..."}</code>. Agent IDs are opaque strings, including the empty string, and serialize as <code>agent-&lt;id&gt;</code>. Omission sends no wire key; explicit <code>nil</code> is invalid. Remote backends may echo the source locally without forwarding it end to end. (<a href="https://github.com/github/copilot-sdk/pull/2573">upstream PR #2573</a>) </td></tr>
</tbody>
</table>
<pre><code class="language-clojure">(copilot/send! session
{:prompt "Review this change"
:source {:agent-id "reviewer"}})
</code></pre>
<p><strong>Attachment types:</strong></p>
<table>
<thead>
Expand Down
2 changes: 1 addition & 1 deletion doc/api/github.copilot-sdk.session.html
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@
<p>Marked experimental — the upstream Node SDK exposes this only via the generated low-level <code>commands.respondToQueuedCommand</code> RPC (no high-level helper).</p>
</div></div></div><div class="public anchor" id="var-select.21"><h3>select!</h3><div class="usage"><code>(select! session message options)</code></div><div class="doc"><div class="markdown"><p>Show a selection dialog with the given options. Returns the selected value as a string, or nil if the user declines/cancels. Throws if the host does not support elicitation.</p>
</div></div></div><div class="public anchor" id="var-send.21"><h3>send!</h3><div class="usage"><code>(send! session opts)</code></div><div class="doc"><div class="markdown"><p>Send a message to the session. Returns the message ID immediately (fire-and-forget).</p>
<p>Options: - :prompt - The message text (required) - :attachments - Vector of attachments (file/directory/selection) - :mode - :enqueue (default) or :immediate - :agent-mode - <strong>Optional</strong>. One of :interactive (default), :plan, :autopilot, or :shell. Selects the agent mode for this turn (upstream PR #1438). - :display-prompt - <strong>Optional</strong>. String shown in the session timeline instead of the model <code>:prompt</code> (e.g., when the model prompt is augmented with internal context that should not be shown to end users). (upstream PR #1470) - :request-headers - Optional map of HTTP headers forwarded to the upstream LLM on this send (upstream PR #1094). Keys and values must both be strings (do not use Clojure keywords — they would be camelized by the wire-conversion layer).</p>
<p>Options: - :prompt - The message text (required) - :attachments - Vector of attachments (file/directory/selection) - :mode - :enqueue (default) or :immediate - :agent-mode - <strong>Optional</strong>. One of :interactive (default), :plan, :autopilot, or :shell. Selects the agent mode for this turn (upstream PR #1438). - :display-prompt - <strong>Optional</strong>. String shown in the session timeline instead of the model <code>:prompt</code> (e.g., when the model prompt is augmented with internal context that should not be shown to end users). (upstream PR #1470) - :source - <strong>Optional</strong>. Message provenance: :user, :system, or {:agent-id string}. Agent IDs are opaque and are sent as <code>agent-&lt;id&gt;</code>. Omission sends no source. (upstream PR #2573) - :request-headers - Optional map of HTTP headers forwarded to the upstream LLM on this send (upstream PR #1094). Keys and values must both be strings (do not use Clojure keywords — they would be camelized by the wire-conversion layer).</p>
</div></div></div><div class="public anchor" id="var-send-and-wait.21"><h3>send-and-wait!</h3><div class="usage"><code>(send-and-wait! session opts)</code><code>(send-and-wait! session opts timeout-ms)</code></div><div class="doc"><div class="markdown"><p>Send a message and wait until the session becomes idle. Returns the final assistant message event, or nil if none received. Serialized per session to avoid mixing concurrent sends. An idle event whose wire <code>:mode</code> is the string <code>"autopilot"</code> is a nonterminal turn boundary, so the wait continues.</p>
<p>Options: same as send!</p>
<p>Additional options: - :timeout-ms - Timeout in milliseconds (default: 60000). The 2-arity form reads this from <code>opts</code>; an explicit numeric value overrides the default. A <code>nil</code> value (in <code>opts</code> or as the positional 3-arity argument) disables the deadline and waits indefinitely for <code>session.idle</code>/<code>session.error</code>. In every case <code>:timeout-ms</code> is stripped from <code>opts</code> before the underlying <code>session.send</code>, so it is never forwarded on the wire.</p>
Expand Down
8 changes: 8 additions & 0 deletions doc/reference/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ adapted to Clojure idioms. When translating Node.js examples, use this map:
|---------|----------------------|-------|
| `:disable-resume?` | `suppressResumeEvent` | Config key on `resume-session` / `join-session`. When true, skips emitting the `session.resume` event. Defaults to `true` in `join-session` (matching upstream), `false` elsewhere. |
| `:max-input-tokens` | `maxPromptTokens` | BYOK provider/model config key (input/prompt token cap). Serialized back to `maxPromptTokens` on the wire. |
| `:source {:agent-id "reviewer"}` | `source: "agent-reviewer"` | `send` message provenance. Fixed sources use `:user` or `:system`; identified agents use a map so the opaque ID is preserved exactly before the `agent-` wire prefix is added. |
| `join-session` return `{:client :session}` | `joinSession()` returns `CopilotSession` | Clojure has no implicit/global client, so it returns both so the caller can own the client lifecycle. See [`join-session`](#join-session). |
| `join-session` return `:granted-environment-variables` | `joinSession()` writes grants to `process.env` | The JVM cannot portably mutate process environment variables. When an extension requests environment access, Clojure returns approved values as a string-keyed map filtered to the exact requested names. |

Expand Down Expand Up @@ -968,6 +969,13 @@ Send a message to the session. Returns immediately with the message ID.
| `:agent-mode` | keyword | `#{:interactive :plan :autopilot :shell}`. Per-message agent mode. Wire-encoded as `agentMode`. (upstream PR #1438) |
| `:display-prompt` | string | Alternate prompt shown in the timeline UI instead of `:prompt`. Useful when the model-facing prompt contains machinery or context that should not be surfaced to the end user. Wire-encoded as `displayPrompt`. (upstream PR #1470) |
| `:request-headers` | map | Extra HTTP headers (string→string) forwarded to the model provider for this request. Merged with provider-level `:headers`. (upstream PR #1094) |
| `:source` | keyword or map | Optional message provenance. Use `:user`, `:system`, or `{:agent-id "..."}`. Agent IDs are opaque strings, including the empty string, and serialize as `agent-<id>`. Omission sends no wire key; explicit `nil` is invalid. Remote backends may echo the source locally without forwarding it end to end. ([upstream PR #2573](https://github.com/github/copilot-sdk/pull/2573)) |

```clojure
(copilot/send! session
{:prompt "Review this change"
:source {:agent-id "reviewer"}})
```

**Attachment types:**

Expand Down
1 change: 1 addition & 0 deletions resources/github/copilot_sdk/api_surface.edn
Original file line number Diff line number Diff line change
Expand Up @@ -812,6 +812,7 @@
:github.copilot-sdk.specs/memory-reason
:github.copilot-sdk.specs/message
:github.copilot-sdk.specs/message-id
:github.copilot-sdk.specs/message-source
:github.copilot-sdk.specs/messages-cleared
:github.copilot-sdk.specs/metadata
:github.copilot-sdk.specs/metrics
Expand Down
64 changes: 32 additions & 32 deletions src/github/copilot_sdk/session.clj
Original file line number Diff line number Diff line change
Expand Up @@ -2086,6 +2086,32 @@
(let [{:keys [session-id client]} session]
(:config (session-state client session-id))))

(defn- message-source->wire
[source]
(if (keyword? source)
(name source)
(str "agent-" (:agent-id source))))

(defn- build-send-params
[client session-id opts]
(let [wire-attachments (when (:attachments opts)
(util/attachments->wire (:attachments opts)))
trace-ctx (when-let [provider (:on-get-trace-context client)]
(try (let [ctx (provider)]
(when (map? ctx)
(select-keys ctx [:traceparent :tracestate])))
(catch Throwable _ nil)))]
(cond-> {:session-id session-id
:prompt (:prompt opts)}
trace-ctx (merge trace-ctx)
wire-attachments (assoc :attachments wire-attachments)
(:mode opts) (assoc :mode (name (:mode opts)))
(:agent-mode opts) (assoc :agent-mode (name (:agent-mode opts)))
(some? (:display-prompt opts)) (assoc :display-prompt (:display-prompt opts))
(:request-headers opts) (assoc :request-headers (:request-headers opts))
(contains? opts :source) (assoc :source
(message-source->wire (:source opts))))))

(defn- prepare-send-request
[session opts]
(when-not (s/valid? ::specs/send-options opts)
Expand All @@ -2096,24 +2122,9 @@
(log/debug "send! called for session " session-id " with prompt: " (subs (str (:prompt opts)) 0 (min 50 (count (str (:prompt opts))))) "...")
(when (session-disconnected? client session-id)
(throw (ex-info "Session has been disconnected" {:session-id session-id})))
(let [conn (connection-io client)
wire-attachments (when (:attachments opts)
(util/attachments->wire (:attachments opts)))
trace-ctx (when-let [provider (:on-get-trace-context client)]
(try (let [ctx (provider)]
(when (map? ctx)
(select-keys ctx [:traceparent :tracestate])))
(catch Throwable _ nil)))
params (cond-> {:session-id session-id
:prompt (:prompt opts)}
trace-ctx (merge trace-ctx)
wire-attachments (assoc :attachments wire-attachments)
(:mode opts) (assoc :mode (name (:mode opts)))
(:agent-mode opts) (assoc :agent-mode (name (:agent-mode opts)))
(some? (:display-prompt opts)) (assoc :display-prompt (:display-prompt opts))
(:request-headers opts) (assoc :request-headers (:request-headers opts)))]
(let [conn (connection-io client)]
{:connection conn
:params params
:params (build-send-params client session-id opts)
:session-id session-id})))

(defn ^:no-doc send-with-timeout!
Expand Down Expand Up @@ -2142,6 +2153,9 @@
instead of the model `:prompt` (e.g., when the model
prompt is augmented with internal context that should
not be shown to end users). (upstream PR #1470)
- :source - **Optional**. Message provenance: :user, :system, or
{:agent-id string}. Agent IDs are opaque and are sent as
`agent-<id>`. Omission sends no source. (upstream PR #2573)
- :request-headers - Optional map of HTTP headers forwarded to the
upstream LLM on this send (upstream PR #1094).
Keys and values must both be strings (do not use
Expand Down Expand Up @@ -2373,21 +2387,7 @@
(tap event-mult event-ch)
;; Send message via channel-based RPC (no blocking)
(let [conn (connection-io client)
wire-attachments (when (:attachments opts)
(util/attachments->wire (:attachments opts)))
trace-ctx (when-let [provider (:on-get-trace-context client)]
(try (let [ctx (provider)]
(when (map? ctx)
(select-keys ctx [:traceparent :tracestate])))
(catch Throwable _ nil)))
params (cond-> {:session-id session-id
:prompt (:prompt opts)}
trace-ctx (merge trace-ctx)
wire-attachments (assoc :attachments wire-attachments)
(:mode opts) (assoc :mode (name (:mode opts)))
(:agent-mode opts) (assoc :agent-mode (name (:agent-mode opts)))
(some? (:display-prompt opts)) (assoc :display-prompt (:display-prompt opts))
(:request-headers opts) (assoc :request-headers (:request-headers opts)))
params (build-send-params client session-id opts)
response-ch (proto/send-request conn "session.send" params)
[result port] (if deadline-ch
(async/alts! [response-ch deadline-ch])
Expand Down
20 changes: 17 additions & 3 deletions src/github/copilot_sdk/specs.clj
Original file line number Diff line number Diff line change
Expand Up @@ -1647,10 +1647,24 @@
;; (upstream PR #1470, post-v1.0.0-beta.4).
(s/def ::display-prompt string?)

(defn- message-source?
[source]
(or (contains? #{:user :system} source)
(and (map? source)
(= #{:agent-id} (set (keys source)))
(string? (:agent-id source)))))

;; Stable message provenance (upstream PR #2573). Fixed sources use keywords;
;; identified agents use a map because agent IDs are opaque strings and may be
;; empty or contain characters that are unsuitable for keywords.
(s/def ::message-source message-source?)

(s/def ::send-options
(s/keys :req-un [::prompt]
:opt-un [::attachments ::mode ::timeout-ms ::request-headers
::agent-mode ::display-prompt]))
(s/and
(s/keys :req-un [::prompt]
:opt-un [::attachments ::mode ::timeout-ms ::request-headers
::agent-mode ::display-prompt])
#(optional-field? % :source message-source?)))

;; :timeout-ms as used in option maps for send-async / <send! /
;; send-async-with-id / send-and-wait! allows nil to "disable" the timeout per
Expand Down
Loading