Skip to content

feat: sync to upstream copilot-sdk v1.0.1 (open-canvases snapshot, schema 1.0.61)#136

Merged
krukow merged 7 commits into
mainfrom
krukow/upstream-sync-v1-0-1
Jun 12, 2026
Merged

feat: sync to upstream copilot-sdk v1.0.1 (open-canvases snapshot, schema 1.0.61)#136
krukow merged 7 commits into
mainfrom
krukow/upstream-sync-v1-0-1

Conversation

@krukow

@krukow krukow commented Jun 12, 2026

Copy link
Copy Markdown
Collaborator

Syncs the Clojure SDK to upstream github/copilot-sdk v1.0.1 (CLI @github/copilot 1.0.61). The marquee port is the per-session open-canvases snapshot from upstream PR #1604; the rest is schema-driven codegen and a few reactive spec adds.

What this PR does

  • Open-canvases snapshot. A new (copilot/open-canvases session) getter returns the in-memory vector of currently-open canvases for a session, mirroring upstream CopilotSession.openCanvases. Initialized from the session.resume RPC response (session.create does NOT populate it — matches upstream client.ts); maintained by the session.canvas.opened (upsert by :instance-id, position-preserving) and session.canvas.closed (remove) event handlers; mutations happen before event publish so observers see consistent state.
  • :open-canvases resume config. Callers can seed the snapshot when reconnecting via :open-canvases on resume-session / join-session, matching upstream ResumeSessionConfig.openCanvases.
  • Schema 1.0.57 → 1.0.61. New :copilot/session.canvas.closed event (added to public event-types, kept off session-events to match the existing canvas treatment). Reactive spec follow-ups for new optional fields: :events-file-size-bytes on resume/shutdown, :api-call-id on assistant.message, :temporary on hook.progress, :at / :cron / :tz on schedule_created (with :interval-ms relaxed to optional).

Non-obvious nuance reviewers will want

  • Strict canvas upsert (mirrors upstream isOpenCanvasInstance). A session.canvas.opened payload missing any of :instance-id, :extension-id, :canvas-id, :reopen (must be boolean), or :availability (must be "ready" or "stale") is a no-op with a warn log. Upstream is strict here precisely because the snapshot drives reconnect behavior; lax acceptance would let half-formed entries leak across resumes. remove-open-canvas! only requires :instance-id (a closed event with junk extra fields should still close cleanly).
  • :input keys preserved verbatim, in both directions. The canvas :input map is opaque caller data ({ [k: string]: unknown }) — kebab-casing the keys would silently corrupt it. Inbound: protocol/preserve-event-opaque-fields carves :input out before wire->clj recurses, both for the session.canvas.opened event and for each entry in session.resume's openCanvases[]. Outbound: client/canvas-instance->wire deep-stringifies :input keys before they enter clj->wire, since cske/transform-keys only transforms keyword keys (string keys pass through). Net result: :user_id, :myKey, and nested mixed-case keys round-trip through the CLI unchanged.
  • session.create does not populate the snapshot. Only resume. This matches upstream client.ts:1340-1360 exactly — a fresh session has no canvases by definition, and adding the wire field at create-time would diverge from upstream.
  • ::at is pos-int?, not number?. at is an epoch-ms timestamp; non-integer or non-positive values are invalid by construction. Tighter than the raw schema's number because the schema can't express "positive integer ms" but the contract does.
  • No upstream behavior is gated by protocol version for canvas events — they're plain session events, not v3 broadcasts.

Validation

bb ci:full (incl. E2E + examples) green. 14 new canvas/v1.0.1 integration tests cover the snapshot lifecycle, strict-validation rejection cases, :input preservation in both directions, the new optional event-data fields, and the outbound resume config wire shape.

Upstream PRs

  • #1597 — schema 1.0.57 → 1.0.60 codegen
  • #1604 — canvas snapshot behavior (the substantive port)
  • #1612 — schema 1.0.60 → 1.0.61 codegen

Generated via Copilot on behalf of @krukow

krukow and others added 3 commits June 12, 2026 13:24
Sync to upstream copilot-sdk v1.0.1. Pinned @github/copilot schema
bumped from 1.0.57 to 1.0.61 (upstream PRs #1597, #1612). Regenerated
generated/event_specs.clj from the new session-events schema.

Schema diffs picked up:
- New CanvasClosedEvent + CanvasClosedData ({canvasId, extensionId,
  instanceId}, ephemeral)
- ResumeData and ShutdownData gain optional eventsFileSizeBytes
- ScheduleCreatedData gains optional at, cron, tz; intervalMs becomes
  optional
- AssistantMessageData gains optional apiCallId
- HookEndError gains optional source
- HookProgressData gains optional temporary

Project version bumped to 1.0.1.0 to match upstream parity.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add a per-session in-memory snapshot of currently-open canvases, mirroring
the upstream Node.js client's openCanvasInstances behavior.

State machine
- Initialize :open-canvases [] alongside :capabilities at session creation.
- Internal mutators set-open-canvases! / upsert-open-canvas! /
  and validates strictly (mirrors upstream isOpenCanvasInstance:
  required :instance-id, :extension-id, :canvas-id, :reopen boolean,
  :availability "ready"|"stale"); bad payloads log a warn and no-op.
  Remove only requires :instance-id.
- Public getter open-canvases returns the immutable snapshot vector.

Wiring (client.clj)
- session.canvas.opened -> upsert-open-canvas! before publishing event,
  so observers see consistent state (matches the capabilities.changed
  pattern at lines 612-614).
- session.canvas.closed -> remove-open-canvas! before publishing.
- session.resume responses populate the snapshot via set-open-canvases!
  on both sync and async resume paths. session.create does NOT populate
  it (matches upstream client.ts).
- :open-canvases accepted in resume-session / join-session config to
  seed the snapshot when reconnecting (upstream ResumeSessionConfig).
  canvas-instance->wire produces an explicit camelCase keyword wire
  shape and stringifies caller-supplied :input keys deeply so they
  bypass csk on outbound — caller-defined opaque keys (e.g. :user_id)
  round-trip verbatim.

Opaque :input preservation (protocol.clj)
- session.canvas.opened event :input map is preserved verbatim through
  preserve-event-opaque-fields.
- session.resume response openCanvases[] :input maps are restored
  positionally in normalize-incoming after wire->clj recursion.

Specs
- :copilot/session.canvas.closed added to the public ::event-type set.
- ::open-canvas-instance / ::open-canvases (open-keys for forward-compat).
- :open-canvases added to resume-session-config-keys, ::resume-session-config,
  ::join-session-config :opt-un.
- ::at tightened to pos-int? (epoch-ms).
- ::session.schedule_created-data: :interval-ms moved to :opt-un, plus
  new :at, :cron, :tz fields.
- :events-file-size-bytes nat-int? on resume/shutdown :opt-un.
- :api-call-id on assistant.message :opt-un.
- :temporary boolean? on hook.progress :opt-un.

Public surface (copilot_sdk.clj)
- :copilot/session.canvas.closed added to event-types (not session-events,
  matching the existing canvas treatment from 1.0.0).
- session/open-canvases re-exported at the top-level namespace.

Tests (integration_test.clj)
- 14 new canvas/v1.0.1 tests:
  - canvas.closed registration + idiom data spec
  - open-canvases initialized from resume
  - opened event upserts in place (same instanceId replaces)
  - closed event removes from snapshot
  - malformed payload no-op (idempotent)
  - session.create does NOT populate snapshot
  - schedule data with cron-only / :at variants pass spec
  - eventsFileSizeBytes / apiCallId / temporary accepted
  - opened event :input preserved verbatim (incl. snake_case)
  - resume response openCanvases[] :input preserved verbatim
  - upsert strict validation (4 rejection cases + 1 happy path)
  - outbound :open-canvases resume config wire shape preserves :input
  - ::at spec rejects non-positive / non-integer values
- mock_server.clj: set-resume-response-extras! helper added for
  shaping resume responses with custom fields.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- API.md: open-canvases section explains the strict upsert contract
  (required fields per upstream isOpenCanvasInstance), :input verbatim
  preservation, and how to seed the snapshot via :open-canvases on
  resume-session / join-session. Adds :open-canvases row to the
  config table.
- CHANGELOG.md: split v1.0.1 sync entries into the schema-driven
  baseline and the review-driven follow-up (strict upsert validation,
  :input preservation, ::at tightening, :open-canvases config).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 12, 2026 11:26

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Syncs the Clojure Copilot SDK to upstream github/copilot-sdk v1.0.1 / schema 1.0.61, adding the upstream per-session open-canvases snapshot API and updating event/spec coverage to match new schema fields.

Changes:

  • Add per-session open-canvases snapshot (open-canvases getter), initialize it from session.resume, and keep it updated via session.canvas.opened / session.canvas.closed events.
  • Extend resume/join config with :open-canvases and preserve opaque canvas :input keys verbatim across wire conversion.
  • Bump pinned schema/version artifacts and update generated wire specs, curated specs, docs, tests, changelog, and library version.
Show a summary per file
File Description
test/github/copilot_sdk/mock_server.clj Allows tests to inject extra fields into session.resume responses (e.g. openCanvases).
test/github/copilot_sdk/integration_test.clj Adds integration coverage for canvas snapshot lifecycle and new optional fields/spec relaxations.
test/github/copilot_sdk/codegen_test.clj Updates schema-envelope fixtures to include session.canvas.closed.
src/github/copilot_sdk/specs.clj Adds/relaxes curated idiom specs for new schema fields and canvas data structures.
src/github/copilot_sdk/session.clj Introduces per-session :open-canvases state and snapshot mutation helpers + public accessor.
src/github/copilot_sdk/protocol.clj Preserves opaque :input keys for canvas opened events and resume openCanvases[].
src/github/copilot_sdk/instrument.clj Adds fdef instrumentation for session/open-canvases.
src/github/copilot_sdk/generated/event_specs.clj Regenerates wire specs to schema 1.0.61 (new event + optional fields).
src/github/copilot_sdk/client.clj Applies canvas state mutations before publish; sends/receives open-canvases on resume/join.
src/github/copilot_sdk.clj Exposes new event type and public open-canvases API entry point.
schemas/session-events.schema.json Updates vendored schema JSON (1.0.61) including canvas.closed + new fields.
schemas/README.md Updates pinned schema version note.
README.md Bumps dependency version to 1.0.1.0.
doc/reference/API.md Documents :open-canvases config and the open-canvases accessor + event semantics.
doc/getting-started.md Bumps dependency version to 1.0.1.0.
CHANGELOG.md Adds v1.0.1 sync + follow-up entries describing canvases and schema changes.
build.clj Bumps project version to 1.0.1.0.
.copilot-schema-version Bumps pinned @github/copilot schema version to 1.0.61.

Copilot's findings

  • Files reviewed: 17/19 changed files
  • Comments generated: 2

Comment thread src/github/copilot_sdk/client.clj Outdated
Comment thread src/github/copilot_sdk/session.clj
Address GitHub Copilot Code Review feedback on PR #136:

1. canvas-instance->wire (client.clj): keep wrapping fields in idiomatic
   kebab-case keywords so util/clj->wire camelCases them exactly once,
   instead of relying on csk's idempotence on already-camelCase keys.
   Behavior unchanged; opaque :input keys still preserved verbatim via
   stringify-keys-deep.

2. remove-open-canvas! (session.clj): tighten validation to require a
   non-blank string :instance-id, matching the strict check in
   upsert-open-canvas! and the ::instance-id non-blank-string spec.
   A non-string (e.g. numeric) :instance-id now logs a warn and no-ops
   instead of being silently treated as a valid removal key.

3. Regression test for the non-string :instance-id rejection.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot's findings

  • Files reviewed: 17/19 changed files
  • Comments generated: 1

Comment thread src/github/copilot_sdk/client.clj Outdated
Address GitHub Copilot Code Review feedback round 2 on PR #136:
build-resume-session-params now gates on (contains? config :open-canvases)
instead of (seq …), so an explicit empty vector is sent verbatim — matching
upstream client.ts which forwards config.openCanvases directly. Allows
callers to deliberately seed/clear the snapshot on resume.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot's findings

  • Files reviewed: 17/19 changed files
  • Comments generated: 3

Comment thread src/github/copilot_sdk/client.clj
Comment thread test/github/copilot_sdk/mock_server.clj Outdated
Comment thread src/github/copilot_sdk/instrument.clj Outdated
Address GitHub Copilot Code Review feedback round 3 on PR #136:

* stringify-keys-deep now uses (subs (str k) 1) instead of (name k) so
  namespaced keyword keys in opaque :input payloads (e.g. :my.app/user_id)
  are preserved as 'my.app/user_id' on the wire — not silently truncated
  to 'user_id'. This honors the verbatim-keys contract for caller-supplied
  data. Added regression test test-resume-config-open-canvases-namespaced-input-keys.

* test/mock_server.clj handle-session-resume now merges :resume-response-extras
  before {:sessionId session-id} so the canonical response key wins —
  prevents tests from accidentally producing invalid responses by including
  :sessionId in extras.

* instrument.clj: tightened session/open-canvases fdef :ret from
  (s/coll-of map?) to ::specs/open-canvases so instrumentation enforces
  the canvas-instance shape on the snapshot getter.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot's findings

  • Files reviewed: 17/19 changed files
  • Comments generated: 1

Comment thread src/github/copilot_sdk/session.clj Outdated
Address GitHub Copilot Code Review feedback round 4 on PR #136:
set-open-canvases! (called after session.resume) was storing the CLI's
openCanvases payload verbatim, bypassing the same valid-open-canvas-instance?
check that gates live session.canvas.opened upserts. A malformed CLI
response could leak invalid entries into the snapshot and trip
instrumentation on (open-canvases session).

Now: non-sequential values become [], and entries failing
valid-open-canvas-instance? are dropped with a single warning carrying
the dropped count. Snapshot invariant holds for all callers.

Added regression test test-open-canvases-resume-sanitizes-invalid-entries
mixing missing :reopen, missing :availability, and bad availability values.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot's findings

  • Files reviewed: 17/19 changed files
  • Comments generated: 0 new

@krukow krukow merged commit 74543ad into main Jun 12, 2026
3 checks passed
@krukow krukow deleted the krukow/upstream-sync-v1-0-1 branch June 12, 2026 12:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants