From eb703e04303a3f503160bce8edaaff8f6c5b2675 Mon Sep 17 00:00:00 2001 From: Karl Krukow Date: Wed, 9 Sep 2026 06:35:52 -0700 Subject: [PATCH 1/2] feat(session): add typed message sources Port the stable upstream MessageSource contract and recertify parity through d8bbc9d without changing the runtime or release version. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/workflows/ci.yml | 2 +- CHANGELOG.md | 16 + doc/api/API.html | 6 + doc/api/github.copilot-sdk.session.html | 2 +- doc/reference/API.md | 8 + resources/github/copilot_sdk/api_surface.edn | 1 + src/github/copilot_sdk/session.clj | 64 ++-- src/github/copilot_sdk/specs.clj | 20 +- .../integration/message_source_test.clj | 211 +++++++++++ .../integration/stable_sync_1644e74_test.clj | 2 - .../integration/stable_sync_d8bbc9d_test.clj | 341 ++++++++++++++++++ .../stable_upstream_delta_d8bbc9d.edn | 328 +++++++++++++++++ 12 files changed, 962 insertions(+), 39 deletions(-) create mode 100644 test/github/copilot_sdk/integration/message_source_test.clj create mode 100644 test/github/copilot_sdk/integration/stable_sync_d8bbc9d_test.clj create mode 100644 test/resources/stable_upstream_delta_d8bbc9d.edn diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3c2664b..968791e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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 diff --git a/CHANGELOG.md b/CHANGELOG.md index cb68acc..a76cac8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +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-`. 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 + [`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)) - Recertified the complete stable Node SDK public surface through upstream commit [`1644e74578db3637bc7527951bac227aabbc0584`](https://github.com/github/copilot-sdk/commit/1644e74578db3637bc7527951bac227aabbc0584). diff --git a/doc/api/API.html b/doc/api/API.html index a00f1f3..88b2bc1 100644 --- a/doc/api/API.html +++ b/doc/api/API.html @@ -135,6 +135,7 @@

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 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. @@ -701,8 +702,13 @@

send!

: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) +
(copilot/send! session
+  {:prompt "Review this change"
+   :source {:agent-id "reviewer"}})
+

Attachment types:

diff --git a/doc/api/github.copilot-sdk.session.html b/doc/api/github.copilot-sdk.session.html index b9c7673..3b71770 100644 --- a/doc/api/github.copilot-sdk.session.html +++ b/doc/api/github.copilot-sdk.session.html @@ -115,7 +115,7 @@

Marked experimental — the upstream Node SDK exposes this only via the generated low-level commands.respondToQueuedCommand RPC (no high-level helper).

select!

(select! session message options)

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.

send!

(send! session opts)

Send a message to the session. Returns the message ID immediately (fire-and-forget).

-

Options: - :prompt - The message text (required) - :attachments - Vector of attachments (file/directory/selection) - :mode - :enqueue (default) or :immediate - :agent-mode - Optional. One of :interactive (default), :plan, :autopilot, or :shell. Selects the agent mode for this turn (upstream PR #1438). - :display-prompt - Optional. String shown in the session timeline 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) - :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).

+

Options: - :prompt - The message text (required) - :attachments - Vector of attachments (file/directory/selection) - :mode - :enqueue (default) or :immediate - :agent-mode - Optional. One of :interactive (default), :plan, :autopilot, or :shell. Selects the agent mode for this turn (upstream PR #1438). - :display-prompt - Optional. String shown in the session timeline 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 Clojure keywords — they would be camelized by the wire-conversion layer).

send-and-wait!

(send-and-wait! session opts)(send-and-wait! session opts timeout-ms)

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 :mode is the string "autopilot" is a nonterminal turn boundary, so the wait continues.

Options: same as send!

Additional options: - :timeout-ms - Timeout in milliseconds (default: 60000). The 2-arity form reads this from opts; an explicit numeric value overrides the default. A nil value (in opts or as the positional 3-arity argument) disables the deadline and waits indefinitely for session.idle/session.error. In every case :timeout-ms is stripped from opts before the underlying session.send, so it is never forwarded on the wire.

diff --git a/doc/reference/API.md b/doc/reference/API.md index c5e2b18..e32ffac 100644 --- a/doc/reference/API.md +++ b/doc/reference/API.md @@ -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. | @@ -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-`. 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:** diff --git a/resources/github/copilot_sdk/api_surface.edn b/resources/github/copilot_sdk/api_surface.edn index c87ec0c..bfe0eaf 100644 --- a/resources/github/copilot_sdk/api_surface.edn +++ b/resources/github/copilot_sdk/api_surface.edn @@ -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 diff --git a/src/github/copilot_sdk/session.clj b/src/github/copilot_sdk/session.clj index 3181d55..627ade9 100644 --- a/src/github/copilot_sdk/session.clj +++ b/src/github/copilot_sdk/session.clj @@ -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) @@ -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! @@ -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-`. 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 @@ -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]) diff --git a/src/github/copilot_sdk/specs.clj b/src/github/copilot_sdk/specs.clj index d2034e6..44cfd2c 100644 --- a/src/github/copilot_sdk/specs.clj +++ b/src/github/copilot_sdk/specs.clj @@ -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 / {:prompt "hello"} + (not= omitted source) (assoc :source source) + (not= omitted mode) (assoc :mode mode))) + +(defn- assert-wire-options + [params source mode wire-source] + (if (= omitted wire-source) + (is (not (contains? params :source))) + (is (= wire-source (:source params)))) + (if (= omitted mode) + (is (not (contains? params :mode))) + (is (= (name mode) (:mode params))))) + +(deftest message-source-spec-contract + (testing "stable message sources use one canonical Clojure shape" + (doseq [{:keys [source]} (remove #(= omitted (:source %)) source-cases)] + (is (s/valid? ::specs/message-source source))) + (doseq [source [nil + false + true + "" + "user" + "agent-reviewer" + {} + {:agent-id nil} + {:agent-id 42} + {:agent-id "reviewer" :extra true}]] + (is (not (s/valid? ::specs/message-source source)) + (str "must reject " (pr-str source)))))) + +(deftest send-message-source-wire-contract + (let [requests (atom []) + _ (mock/set-request-hook! + *mock-server* + (fn [method params] + (when (= "session.send" method) + (swap! requests conj params)))) + session (sdk/create-session + *test-client* + {:on-permission-request sdk/approve-all})] + (doseq [{:keys [label source wire-source]} source-cases + mode delivery-modes] + (testing (str label " with delivery mode " mode) + (sdk/send! session (send-opts source mode)) + (assert-wire-options (last @requests) source mode wire-source))))) + +(deftest async-message-source-wire-contract + (let [requests (atom []) + _ (mock/set-request-hook! + *mock-server* + (fn [method params] + (when (= "session.send" method) + (swap! requests conj params)))) + session (sdk/create-session + *test-client* + {:on-permission-request sdk/approve-all})] + (doseq [{:keys [label source wire-source]} source-cases + mode delivery-modes] + (testing (str label " with delivery mode " mode) + (let [request-count (count @requests) + events-ch (sdk/send-async session (send-opts source mode))] + (await-atom! requests + #(> (count %) request-count) + "async session.send request" + 2000) + (assert-wire-options (last @requests) source mode wire-source) + (await-event-type! events-ch :copilot/session.idle 2000)))))) + +(deftest message-source-coexists-with-send-options + (let [seen (atom nil) + _ (mock/set-request-hook! + *mock-server* + (fn [method params] + (when (= "session.send" method) + (reset! seen params)))) + session (sdk/create-session + *test-client* + {:on-permission-request sdk/approve-all})] + (sdk/send! session + {:prompt "model prompt" + :source {:agent-id "reviewer"} + :attachments [{:type :file :path "/tmp/context.txt"}] + :mode :immediate + :agent-mode :plan + :display-prompt "Visible prompt" + :request-headers {"X-Trace-Id" "trace-1"}}) + (is (= "agent-reviewer" (:source @seen))) + (is (= [{:type "file" :path "/tmp/context.txt"}] + (:attachments @seen))) + (is (= "immediate" (:mode @seen))) + (is (= "plan" (:agentMode @seen))) + (is (= "Visible prompt" (:displayPrompt @seen))) + (is (= "trace-1" + (get-in @seen [:requestHeaders (keyword "X-Trace-Id")]))))) + +(deftest explicit-nil-message-source-is-rejected + (let [requests (atom []) + _ (mock/set-request-hook! + *mock-server* + (fn [method params] + (when (= "session.send" method) + (swap! requests conj params)))) + session (sdk/create-session + *test-client* + {:on-permission-request sdk/approve-all})] + (is (thrown-with-msg? + clojure.lang.ExceptionInfo + #"Invalid send options" + (sdk/send! session {:prompt "hello" :source nil}))) + (is (empty? @requests)))) + +(deftest message-source-preserves-trace-context + (let [seen (atom nil) + _ (mock/set-request-hook! + *mock-server* + (fn [method params] + (when (= "session.send" method) + (reset! seen params)))) + trace {:traceparent + "00-fedcba0987654321fedcba0987654321-abcdef1234567890-01" + :tracestate "vendor=source"} + sdk-client + (assoc *test-client* :on-get-trace-context (constantly trace)) + session + (sdk/create-session + sdk-client + {:session-id "trace-session" + :on-permission-request sdk/approve-all})] + (sdk/send! session {:prompt "hello" :source :system}) + (is (= "system" (:source @seen))) + (is (= (:traceparent trace) (:traceparent @seen))) + (is (= (:tracestate trace) (:tracestate @seen))))) + +(deftest message-source-round-trips-on-live-and-historical-events + (let [session (sdk/create-session + *test-client* + {:on-permission-request sdk/approve-all}) + session-id (sdk/session-id session) + live-events (sdk/subscribe-events session) + event-data {:content "hello" + :source "agent-reviewer"}] + (try + (mock/send-session-event! + *mock-server* + session-id + :user.message + event-data) + (let [event (await-event-type! + live-events + :copilot/user.message + 2000)] + (is (= "agent-reviewer" (get-in event [:data :source]))) + (is (s/valid? ::specs/user.message-data (:data event)))) + + (mock/set-session-messages! + *mock-server* + session-id + [{:type "user.message" + :id "event-1" + :timestamp "2026-09-09T12:00:00Z" + :parentId nil + :data event-data}]) + (let [[event] (sdk/get-messages session)] + (is (= :copilot/user.message (:type event))) + (is (= "agent-reviewer" (get-in event [:data :source]))) + (is (s/valid? ::specs/user.message-data (:data event)))) + (finally + (sdk/unsubscribe-events! session live-events))))) diff --git a/test/github/copilot_sdk/integration/stable_sync_1644e74_test.clj b/test/github/copilot_sdk/integration/stable_sync_1644e74_test.clj index 8b31af1..58af375 100644 --- a/test/github/copilot_sdk/integration/stable_sync_1644e74_test.clj +++ b/test/github/copilot_sdk/integration/stable_sync_1644e74_test.clj @@ -163,8 +163,6 @@ :coercion-source-changed? false}} schema)) (is (= "1.0.83" (str/trim (slurp ".copilot-schema-version")))) - (is (str/includes? (slurp ".github/workflows/ci.yml") - (str "ref: " (:target-commit upstream)))) (is (= {:sdk "1.0.13.0" :changed? false :release-required? false} diff --git a/test/github/copilot_sdk/integration/stable_sync_d8bbc9d_test.clj b/test/github/copilot_sdk/integration/stable_sync_d8bbc9d_test.clj new file mode 100644 index 0000000..9249dc0 --- /dev/null +++ b/test/github/copilot_sdk/integration/stable_sync_d8bbc9d_test.clj @@ -0,0 +1,341 @@ +(ns github.copilot-sdk.integration.stable-sync-d8bbc9d-test + "Executable certification for the exact upstream 1644e74..d8bbc9d delta." + (:require [clojure.data.json :as json] + [clojure.edn :as edn] + [clojure.java.io :as io] + [clojure.java.shell :as sh] + [clojure.set :as set] + [clojure.string :as str] + [clojure.test :refer [deftest is testing]]) + (:import [java.math BigInteger] + [java.nio.file Files] + [java.security MessageDigest])) + +(def ^:private report-resource + "resources/stable_upstream_delta_d8bbc9d.edn") + +(def ^:private historical-report-resource + "resources/stable_upstream_delta_1644e74.edn") + +(def ^:private expected-commit-classifications + [["d5c9d06d8c4118530083848d9c3fa9d615c0a5c4" :language-specific] + ["d8bbc9dd7a6167d4806780f405d8ce74add1cc7c" :stable-public]]) + +(def ^:private expected-path-classification-counts + {:language-specific 11 :stable-public 33}) + +(def ^:private expected-stable-delta-ids + #{:session/message-source}) + +(def ^:private required-exclusion-ids + #{:events/completion-receipt + :events/fusion-phase-routing + :generated/send-request-source-values + :sandbox/allow-bypass + :session/live-auto-tier-switching}) + +(def ^:private allowed-classifications + #{:experimental :generated-only :language-specific :stable-public}) + +(def ^:private upstream-validation-enabled? + (= "true" (System/getenv "COPILOT_UPSTREAM_VALIDATION"))) + +(defn- read-resource + [resource] + (some-> resource io/resource slurp edn/read-string)) + +(defn- sha256-bytes + [bytes] + (let [digest (MessageDigest/getInstance "SHA-256")] + (format "%064x" (BigInteger. 1 (.digest digest bytes))))) + +(defn- sha256-file + [file] + (sha256-bytes (Files/readAllBytes (.toPath file)))) + +(defn- sha256-lines + [lines] + (sha256-bytes (.getBytes (str (str/join "\n" lines) "\n") "UTF-8"))) + +(defn- resolve-upstream + [] + (let [{:keys [exit out err]} + (sh/sh "bash" + ".github/skills/update-upstream/scripts/resolve-upstream.sh")] + (when-not (zero? exit) + (throw (ex-info "Could not resolve the upstream checkout" + {:exit exit :stderr err}))) + (str/trim out))) + +(def ^:private upstream-repo + (delay + (when upstream-validation-enabled? + (resolve-upstream)))) + +(defn- git-output + [upstream & args] + (let [{:keys [exit out err]} + (apply sh/sh (concat ["git" "-C" upstream] args))] + (when-not (zero? exit) + (throw (ex-info "Could not inspect the upstream checkout" + {:args args :exit exit :stderr err}))) + out)) + +(defn- git-lines + [upstream & args] + (->> (str/split-lines (apply git-output upstream args)) + (remove str/blank?) + vec)) + +(defn- declaration-symbols + [source] + (into #{} + (map second) + (re-seq + #"(?m)^export\s+(?:(?:declare|abstract)\s+)*(?:type|interface|class|enum|const|(?:async\s+)?function)\s+([A-Za-z_$][A-Za-z0-9_$]*)" + source))) + +(defn- export-list-symbols + [source] + (into + #{} + (comp + (map second) + (map #(str/replace % #"(?s)/\*.*?\*/|//[^\n]*" "")) + (mapcat #(str/split % #",")) + (map str/trim) + (remove str/blank?) + (map #(str/replace % #"^type\s+" "")) + (map #(last (str/split % #"\s+as\s+"))) + (map str/trim)) + (re-seq #"(?s)export(?:\s+type)?\s*\{(.*?)\}\s*from" source))) + +(defn- exported-symbols + [source] + (set/union (declaration-symbols source) + (export-list-symbols source))) + +(defn- class-source + [source class-name] + (let [pattern + (re-pattern + (str "(?ms)^export class " + (java.util.regex.Pattern/quote class-name) + "\\b.*?\\{(.*?)^\\}")) + body (second (re-find pattern source))] + (when-not body + (throw (ex-info "Expected exported class was not found" + {:class-name class-name}))) + body)) + +(defn- public-class-methods + [source class-name] + (->> (re-seq + #"(?m)^ ((?:(?:private|public|protected|async|static|override|readonly)\s+)*)(?:(?:get|set)\s+)?(\[Symbol\.[A-Za-z_$][A-Za-z0-9_$]*\]|[A-Za-z_$][A-Za-z0-9_$]*)\s*\(" + (class-source source class-name)) + (remove #(re-find #"\b(?:private|protected)\b" (second %))) + (map #(nth % 2)) + set)) + +(deftest parity-resource-is-complete-and-self-consistent + (let [report (read-resource report-resource) + historical-report (read-resource historical-report-resource)] + (is (some? report) "The d8bbc9d parity evidence must be committed") + (is (some? historical-report) + "The historical 1644e74 oracle must remain available") + (when (and report historical-report) + (let [{:keys [upstream historical-oracle commit-classifications + changed-paths public-surface-audit stable-delta-ids + stable-deltas intentional-exclusions source-evidence + schema version]} report + commits (mapv :commit commit-classifications) + path-entries (:entries changed-paths) + paths (mapv :path path-entries) + exclusions (set (map :id intentional-exclusions))] + (is (= "1644e74578db3637bc7527951bac227aabbc0584" + (:base-commit upstream))) + (is (= "d8bbc9dd7a6167d4806780f405d8ce74add1cc7c" + (:target-commit upstream))) + (is (= "f13e4a2cc7e4e220974d2333142234e162a3252e" + (:base-tag-commit upstream))) + (is (= 2 (:commit-count upstream) (count commits))) + (is (= expected-commit-classifications + (mapv (juxt :commit :classification) + commit-classifications))) + (is (= (:commit-sequence-sha256 upstream) + (sha256-lines commits))) + (is (every? #(and (contains? allowed-classifications + (:classification %)) + (keyword? (:status %)) + (string? (:subject %)) + (not (str/blank? (:subject %))) + (string? (:reason %)) + (not (str/blank? (:reason %))) + (nat-int? (:changed-path-count %)) + (re-matches #"[0-9a-f]{64}" + (:changed-paths-sha256 %))) + commit-classifications)) + (is (= 44 (:count changed-paths) (count path-entries))) + (is (= paths (vec (sort paths)))) + (is (= 44 (count (set paths)))) + (is (= expected-path-classification-counts + (:classification-counts changed-paths) + (frequencies (map :classification path-entries)))) + (is (= (:sha256 changed-paths) + (sha256-lines paths))) + (is (= expected-stable-delta-ids stable-delta-ids)) + (is (= expected-stable-delta-ids + (set (map :id stable-deltas)))) + (is (= required-exclusion-ids exclusions)) + (is (empty? (:unclassified-deltas public-surface-audit))) + (is (= #{:message-source-export + :message-source-type + :message-source-option + :message-source-send + :message-source-tests} + (set (keys source-evidence)))) + (is (= historical-report-resource (:resource historical-oracle))) + (is (= (:sha256 historical-oracle) + (sha256-file + (io/file "test/resources/stable_upstream_delta_1644e74.edn")))) + (is (= (:base-commit upstream) + (get-in historical-report [:upstream :target-commit]))) + (is (= {:runtime-pin "1.0.83" + :changed? false + :reason + "The runtime SendRequest source field predates this stable SDK addition; generated protocol and event sources are byte-for-byte unchanged."} + schema)) + (is (= "1.0.83" (str/trim (slurp ".copilot-schema-version")))) + (is (str/includes? (slurp ".github/workflows/ci.yml") + (str "ref: " (:target-commit upstream)))) + (is (= {:sdk "1.0.13.0" + :changed? false + :release-required? false + :next-release "1.0.13.1"} + version)) + (is (str/includes? (slurp "build.clj") + "(def version \"1.0.13.0\")")))))) + +(deftest exact-upstream-range-and-public-surface-are-pinned + (when-let [report (read-resource report-resource)] + (when-let [upstream @upstream-repo] + (let [{:keys [base-commit base-tag base-tag-commit target-commit + target-package-version runtime-version]} + (:upstream report) + expected-commits (mapv :commit (:commit-classifications report)) + expected-paths (mapv :path (get-in report [:changed-paths :entries])) + {:keys [source-blobs baseline-node-tree target-node-tree + baseline-generated-tree target-generated-tree + baseline-test-tree target-test-tree]} + (:public-surface-audit report) + target-surface (:target-public-surface report) + package-json + (json/read-str + (git-output upstream "show" + (str target-commit ":nodejs/package.json"))) + read-source + (fn [path] + (git-output upstream "show" (str target-commit ":" path))) + index-symbols + (exported-symbols (read-source "nodejs/src/index.ts")) + event-symbols + (exported-symbols + (read-source "nodejs/src/generated/session-events.ts")) + type-symbols + (exported-symbols (read-source "nodejs/src/types.ts")) + client-methods + (public-class-methods + (read-source "nodejs/src/client.ts") + "CopilotClient") + session-methods + (public-class-methods + (read-source "nodejs/src/session.ts") + "CopilotSession")] + (is (= expected-commits + (git-lines upstream "rev-list" "--reverse" + (str base-commit ".." target-commit)))) + (is (= expected-paths + (vec + (sort + (git-lines upstream "diff" "--name-only" + (str base-commit ".." target-commit)))))) + (is (= base-tag-commit + (str/trim (git-output upstream "rev-parse" base-tag)))) + (is (= target-package-version (get package-json "version"))) + (is (= runtime-version (get package-json "copilotCliVersion"))) + (doseq [{:keys [commit subject changed-path-count + changed-paths-sha256]} + (:commit-classifications report)] + (let [paths + (sort + (git-lines upstream "diff-tree" "--no-commit-id" + "--name-only" "-r" commit))] + (testing commit + (is (= subject + (str/trim + (git-output upstream "show" "-s" "--format=%s" commit)))) + (is (= changed-path-count (count paths))) + (is (= changed-paths-sha256 (sha256-lines paths)))))) + (is (= baseline-node-tree + (str/trim + (git-output upstream "rev-parse" + (str base-commit ":nodejs"))))) + (is (= target-node-tree + (str/trim + (git-output upstream "rev-parse" + (str target-commit ":nodejs"))))) + (is (= baseline-generated-tree + (str/trim + (git-output upstream "rev-parse" + (str base-commit ":nodejs/src/generated"))))) + (is (= target-generated-tree + (str/trim + (git-output upstream "rev-parse" + (str target-commit ":nodejs/src/generated"))))) + (is (= baseline-test-tree + (str/trim + (git-output upstream "rev-parse" + (str base-commit ":nodejs/test"))))) + (is (= target-test-tree + (str/trim + (git-output upstream "rev-parse" + (str target-commit ":nodejs/test"))))) + (doseq [[path {:keys [base target]}] source-blobs] + (testing path + (is (= base + (str/trim + (git-output upstream "rev-parse" + (str base-commit ":" path))))) + (is (= target + (str/trim + (git-output upstream "rev-parse" + (str target-commit ":" path))))))) + (is (= (get-in target-surface [:package-root :explicit-symbol-count]) + (count index-symbols))) + (is (= (get-in target-surface [:package-root :explicit-symbols-sha256]) + (sha256-lines (sort index-symbols)))) + (let [all-symbols (set/union index-symbols event-symbols)] + (is (= (get-in target-surface [:package-root :symbol-count]) + (count all-symbols))) + (is (= (get-in target-surface [:package-root :symbols-sha256]) + (sha256-lines (sort all-symbols))))) + (is (= (get-in target-surface [:types :symbol-count]) + (count type-symbols))) + (is (= (get-in target-surface [:types :symbols-sha256]) + (sha256-lines (sort type-symbols)))) + (is (= (get-in target-surface [:classes :client :method-count]) + (count client-methods))) + (is (= (get-in target-surface [:classes :client :methods-sha256]) + (sha256-lines (sort client-methods)))) + (is (= (get-in target-surface [:classes :session :method-count]) + (count session-methods))) + (is (= (get-in target-surface [:classes :session :methods-sha256]) + (sha256-lines (sort session-methods)))) + (doseq [[_ {:keys [path symbol symbols]}] + (:source-evidence report) + :let [source (read-source path)]] + (testing path + (if symbol + (is (str/includes? source symbol)) + (doseq [expected symbols] + (is (str/includes? source expected)))))))))) diff --git a/test/resources/stable_upstream_delta_d8bbc9d.edn b/test/resources/stable_upstream_delta_d8bbc9d.edn new file mode 100644 index 0000000..2faa07a --- /dev/null +++ b/test/resources/stable_upstream_delta_d8bbc9d.edn @@ -0,0 +1,328 @@ +{:upstream + {:repository "github/copilot-sdk" + :base-commit "1644e74578db3637bc7527951bac227aabbc0584" + :target-commit "d8bbc9dd7a6167d4806780f405d8ce74add1cc7c" + :base-tag "v1.0.13" + :base-tag-commit "f13e4a2cc7e4e220974d2333142234e162a3252e" + :target-position :post-v1.0.13-main + :target-package-version "0.0.0-dev" + :runtime-version "1.0.83" + :commit-count 2 + :commit-sequence-sha256 + "dc02f407b1f686e320f1f39dc3606c41bf195d59a622350eb7b04023e64b060b"} + + :historical-oracle + {:resource "resources/stable_upstream_delta_1644e74.edn" + :sha256 + "c4840042bd6f3894e535fa5e60eec75238a7a07fd9f906442c9952a4b1879773" + :policy + "Immutable complete public-surface inventory at the prior exact pin; this report certifies only baseline..target."} + + :commit-classifications + [{:commit "d5c9d06d8c4118530083848d9c3fa9d615c0a5c4" + :subject "Add ARM64 musl coverage across SDK transports (#2548)" + :source-url "https://github.com/github/copilot-sdk/pull/2548" + :classification :language-specific + :status :reviewed-no-port + :reason + "Adds CI, .NET test, and Node native-package coverage for ARM64 musl without changing a stable SDK contract." + :changed-path-count 11 + :changed-paths-sha256 + "3cb269b2f28c08f5c25835cf7320610d9d40baaeeca2f116d996dab6b19834c5"} + {:commit "d8bbc9dd7a6167d4806780f405d8ce74add1cc7c" + :subject "Add typed user, system, and agent message sources across SDKs (#2573)" + :source-url "https://github.com/github/copilot-sdk/pull/2573" + :classification :stable-public + :status :ported + :reason + "Adds stable typed message provenance to MessageOptions and forwards it through CopilotSession.send." + :changed-path-count 33 + :changed-paths-sha256 + "5ccebd23fbc63688c2e2d148c477ef32a5aeb03d58b79dcd676a92801862fc07"}] + + :changed-paths + {:count 44 + :sha256 + "c94f7b435769cf318391cea5d79e723fbbeecf8eadafaadf73c2ab5b0a2c9545" + :classification-counts {:language-specific 11 :stable-public 33} + :entries + [{:path ".github/actions/run-alpine-tests/action.yml" + :classification :language-specific} + {:path ".github/workflows/dotnet-sdk-tests.yml" + :classification :language-specific} + {:path ".github/workflows/go-sdk-tests.yml" + :classification :language-specific} + {:path ".github/workflows/nodejs-sdk-tests.yml" + :classification :language-specific} + {:path ".github/workflows/python-sdk-tests.yml" + :classification :language-specific} + {:path ".github/workflows/required-checks.yml" + :classification :language-specific} + {:path ".github/workflows/rust-sdk-tests.yml" + :classification :language-specific} + {:path "docs/features/steering-and-queueing.md" + :classification :stable-public} + {:path "dotnet/README.md" :classification :stable-public} + {:path "dotnet/src/Session.cs" :classification :stable-public} + {:path "dotnet/src/Types.cs" :classification :stable-public} + {:path "dotnet/test/Unit/ClientSessionLifetimeTests.cs" + :classification :stable-public} + {:path "dotnet/test/Unit/CloneTests.cs" :classification :stable-public} + {:path "dotnet/test/Unit/MSBuildTargetsTests.cs" + :classification :language-specific} + {:path "dotnet/test/Unit/RuntimeWrapperTests.cs" + :classification :language-specific} + {:path "dotnet/test/Unit/SerializationTests.cs" + :classification :stable-public} + {:path "go/README.md" :classification :stable-public} + {:path "go/message_source_test.go" :classification :stable-public} + {:path "go/session.go" :classification :stable-public} + {:path "go/types.go" :classification :stable-public} + {:path "java/README.md" :classification :stable-public} + {:path "java/sdk/src/main/java/com/github/copilot/CopilotSession.java" + :classification :stable-public} + {:path "java/sdk/src/main/java/com/github/copilot/rpc/MessageOptions.java" + :classification :stable-public} + {:path "java/sdk/src/main/java/com/github/copilot/rpc/MessageSource.java" + :classification :stable-public} + {:path "java/sdk/src/main/java/com/github/copilot/rpc/SendMessageRequest.java" + :classification :stable-public} + {:path "java/sdk/src/test/java/com/github/copilot/MessageSourceTest.java" + :classification :stable-public} + {:path "nodejs/README.md" :classification :stable-public} + {:path "nodejs/package-lock.json" :classification :language-specific} + {:path "nodejs/package.json" :classification :language-specific} + {:path "nodejs/src/index.ts" :classification :stable-public} + {:path "nodejs/src/session.ts" :classification :stable-public} + {:path "nodejs/src/types.ts" :classification :stable-public} + {:path "nodejs/test/message-source.test.ts" + :classification :stable-public} + {:path "nodejs/tsconfig.test.json" :classification :stable-public} + {:path "python/README.md" :classification :stable-public} + {:path "python/copilot/__init__.py" :classification :stable-public} + {:path "python/copilot/session.py" :classification :stable-public} + {:path "python/test_session.py" :classification :stable-public} + {:path "rust/README.md" :classification :stable-public} + {:path "rust/src/rpc.rs" :classification :stable-public} + {:path "rust/src/session.rs" :classification :stable-public} + {:path "rust/src/types.rs" :classification :stable-public} + {:path "rust/tests/e2e/copilot_request_handler.rs" + :classification :stable-public} + {:path "rust/tests/session_test.rs" :classification :stable-public}]} + + :target-public-surface + {:package-root + {:path "nodejs/src/index.ts" + :explicit-symbol-count 199 + :explicit-symbols-sha256 + "051a48c091329ccb9e386c396c5a0cf03a2fc30c21fad4222150cd64626291cc" + :star-exports + [{:module "./generated/session-events.js" + :path "nodejs/src/generated/session-events.ts" + :symbol-count 529 + :symbols-sha256 + "89c603979efe106904751acd486988d398e1f11d8dfdb248c02b3fd473547d34"}] + :symbol-count 719 + :symbols-sha256 + "3d6b3bd7dba3f4ab82eddc0d78a32f54260d41c6ed526e776a7edf33cc6dcad1" + :classification-counts {:stable-public 564 :experimental 150 :internal 5} + :delta {:stable-public #{"MessageSource"} + :experimental #{} + :removed #{}} + :provenance + {:baseline + {:resource "resources/stable_upstream_delta_f13e4a2.edn" + :symbol-count 718}}} + :types + {:path "nodejs/src/types.ts" + :symbol-count 207 + :symbols-sha256 + "ac251977aae64ea16c156a4d8d7a10dab4d5a3a89bd201bf5856459cd668aeff" + :delta {:stable-public #{"MessageSource"} + :experimental #{} + :removed #{}}} + :classes + {:client + {:path "nodejs/src/client.ts" + :class-name "CopilotClient" + :method-count 20 + :methods-sha256 + "bcf31a8e931df3230402c11670d9e14dd03b90564fb6e7192356d746edd273e6"} + :session + {:path "nodejs/src/session.ts" + :class-name "CopilotSession" + :method-count 41 + :methods-sha256 + "e3d8d11b11ae017b7485e25197283d41f64aac2be2a582c73cc96be3685480bb" + :properties {:stable-public #{"sessionId"} + :experimental #{"factory"} + :internal #{"clientSessionApis"}}}} + :extension + {:path "nodejs/src/extension.ts" + :symbol-count 39 + :symbols-sha256 + "13316e0859171d9c8be79a0bdbe2626b9c21a1947dae04a24fc51ddede17b52c" + :stable-public #{"ExtensionInfo" "JoinSessionConfig" "JsonValue" "joinSession"}} + :tool-set + {:path "nodejs/src/toolSet.ts" + :symbol-count 2 + :symbols-sha256 + "99ef0910d8d65bfb0074e664122cd7d9642aa6c89d8d476bb26d0a9ce1d748ca" + :stable-public #{"BuiltInTools" "ToolSet"}} + :relevant-stable-tests ["nodejs/test/message-source.test.ts"]} + + :public-surface-audit + {:inspected + ["nodejs/package.json" + "nodejs/src/index.ts" + "nodejs/src/types.ts" + "nodejs/src/client.ts" + "nodejs/src/session.ts" + "nodejs/src/extension.ts" + "nodejs/src/toolSet.ts" + "nodejs/src/factory.ts" + "nodejs/src/canvas.ts" + "nodejs/src/generated" + "nodejs/test"] + :source-blobs + {"nodejs/package.json" + {:base "783c4d53900c7176005ca1fac80b95fe7081a543" + :target "ee02f32fb6d09e7414c4ca4807d252b93f0d581c"} + "nodejs/src/index.ts" + {:base "2007679d619831273bac2dc7e58b2969c181b812" + :target "6251df4fc766e40730a57d5f668a91efbd5bed73"} + "nodejs/src/types.ts" + {:base "0f15749f716af0b942183fb6557d539ba9b450d4" + :target "068e0f0a583424d528e27b08647f5e6026b4e0e6"} + "nodejs/src/client.ts" + {:base "eb92cf0bed34e5ac6bf4e208365fb8be047c43f0" + :target "eb92cf0bed34e5ac6bf4e208365fb8be047c43f0"} + "nodejs/src/session.ts" + {:base "b7fc7837a5ac17f3625d6fa6cb6382da2dd3a640" + :target "4c2be1429921b0c6cb397b2d45266c1b41b3dc7d"} + "nodejs/src/extension.ts" + {:base "ac0ccdb7ce3102cabc98e8457f4c7f684b76fa28" + :target "ac0ccdb7ce3102cabc98e8457f4c7f684b76fa28"} + "nodejs/src/toolSet.ts" + {:base "559e9234e5b178036d88a7045a632bd16a97f534" + :target "559e9234e5b178036d88a7045a632bd16a97f534"} + "nodejs/src/factory.ts" + {:base "6212f462b4aff716417baf2180b9a8b887c12ad1" + :target "6212f462b4aff716417baf2180b9a8b887c12ad1"} + "nodejs/src/canvas.ts" + {:base "aeb1f00ec42926977c010df2471123dc18ddddd3" + :target "aeb1f00ec42926977c010df2471123dc18ddddd3"}} + :baseline-node-tree "f506a0e89c7920149e520da2e8a43a41d7459d16" + :target-node-tree "feecf04fc34de66db918972850d26a93e27c92b3" + :baseline-generated-tree "33841e17abf78f49e06d8af6dce3e8d72a3827f6" + :target-generated-tree "33841e17abf78f49e06d8af6dce3e8d72a3827f6" + :baseline-test-tree "19124b3cd602bf7d04079c4f76fbc2822160046d" + :target-test-tree "87fdb6360171ca6a5f3b58f2bf6aa2c906c67347" + :changed-node-files + ["nodejs/README.md" + "nodejs/package-lock.json" + "nodejs/package.json" + "nodejs/src/index.ts" + "nodejs/src/session.ts" + "nodejs/src/types.ts" + "nodejs/test/message-source.test.ts" + "nodejs/tsconfig.test.json"] + :stable-public-deltas [:session/message-source] + :unclassified-deltas []} + + :stable-delta-ids #{:session/message-source} + :stable-deltas + [{:id :session/message-source + :classification :stable-public + :status :ported + :evidence + [:message-source-export + :message-source-type + :message-source-option + :message-source-send + :message-source-tests] + :inventory-items + [[:exported-symbol "nodejs/src/index.ts" "MessageSource"] + [:exported-symbol "nodejs/src/types.ts" "MessageSource"] + [:interface-field "MessageOptions" "source"] + [:builder "CopilotSession.send" "source"]] + :clojure-paths + ["src/github/copilot_sdk/specs.clj" + "src/github/copilot_sdk/session.clj" + "resources/github/copilot_sdk/api_surface.edn" + "doc/reference/API.md"] + :tests + ["test/github/copilot_sdk/integration/message_source_test.clj"] + :contract + "Optional :source accepts :user, :system, or {:agent-id string}; agent IDs are opaque and may be empty. The field is omitted when absent, explicit nil is rejected, and all send paths emit user/system or agent- as the top-level session.send source string."}] + + :intentional-exclusions + [{:id :session/live-auto-tier-switching + :classification :experimental + :decision :exclude + :status :approved + :reason + "The existing setAutoTier and related result/event surface remains explicitly experimental."} + {:id :events/completion-receipt + :classification :experimental + :decision :exclude + :status :approved + :reason + "The completion receipt event family remains generated wire evidence only."} + {:id :events/fusion-phase-routing + :classification :experimental + :decision :exclude + :status :approved + :reason + "HydraFusion phase activity and routing remain explicitly experimental."} + {:id :sandbox/allow-bypass + :classification :experimental + :decision :exclude + :status :approved + :reason + "sandbox.allowBypass remains outside the stable official SDK contract."} + {:id :generated/send-request-source-values + :classification :generated-only + :decision :exclude + :status :approved + :reason + "Generated SendRequest accepts broader command-* and schedule-* source strings, but the stable Node MessageSource type exposes only user, system, and agent-*."}] + + :source-evidence + {:message-source-export + {:path "nodejs/src/index.ts" + :symbol "MessageSource," + :kind :added} + :message-source-type + {:path "nodejs/src/types.ts" + :symbol "export type MessageSource = \"user\" | \"system\" | `agent-${string}`;" + :kind :added} + :message-source-option + {:path "nodejs/src/types.ts" + :symbol "source?: MessageSource;" + :kind :added} + :message-source-send + {:path "nodejs/src/session.ts" + :symbol "source: options.source," + :kind :added} + :message-source-tests + {:path "nodejs/test/message-source.test.ts" + :symbols + ["omits source when sending a plain human prompt" + "preserves the wire payload with delivery mode %s" + "preserves attachments, display text, headers, agent mode and tracing" + "allows sendAndWait to finish without assistant output in mode %s" + "propagates an RPC failure from sendAndWait" + "propagates a session error from sendAndWait"]}} + + :schema + {:runtime-pin "1.0.83" + :changed? false + :reason + "The runtime SendRequest source field predates this stable SDK addition; generated protocol and event sources are byte-for-byte unchanged."} + + :version + {:sdk "1.0.13.0" + :changed? false + :release-required? false + :next-release "1.0.13.1"}} From 5641595c5d31d95191b808ce52367e952bd3e223 Mon Sep 17 00:00:00 2001 From: Karl Krukow Date: Wed, 9 Sep 2026 06:43:13 -0700 Subject: [PATCH 2/2] docs: consolidate upstream certification note Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- CHANGELOG.md | 9 --------- 1 file changed, 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a76cac8..bb13e61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,15 +20,6 @@ All notable changes to this project will be documented in this file. This change 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)) -- 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)) ### Added (v1.0.13 sync) - Added stable `:client-info` identity on the connection handshake, with