Upstream sync: v1.0.0-beta.10 round 6 (schema 1.0.56-1, cloud-no-id, multitenancy)#113
Merged
Conversation
Regenerated wire layer (event_specs.clj, coerce.clj) for upstream tags
v1.0.0-beta.9 and v1.0.0-beta.10. Schema diff surfaces:
- contextTier ("long_context" | "default" | nil) on session.start,
session.resume, session.model_change event data.
- workingDirectory on external_tool.requested event data.
- Three new session event types: hook.progress,
session.autopilot_objective_changed, session.permissions_changed.
- Autopilot objective status enum widened to include "active",
"paused", "cap_reached", "completed".
- Many new SessionConfigBase fields (mcpOAuthTokenStorage,
embeddingCacheStorage, pluginDirectories, multitenancy flags,
reasoningSummary, contextTier, displayPrompt, agentMode, etc.) —
exposed on the public API in a follow-up commit.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- :copilot/hook.progress (ephemeral progress from long-running hooks) - :copilot/session.autopilot_objective_changed - :copilot/session.permissions_changed Added to the public event-types and session-events sets, plus fixture entries in codegen_test for the two with hand-written curated data specs (added in the follow-up specs commit). Curated specs land in specs.clj so they're not re-generated when the schema is regenerated. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds an optional {:on-response-inline (fn [result])} option to
send-request and send-request!. The callback is invoked synchronously
in the JSON-RPC reader thread, before the response is delivered to the
result channel and before the next inbound message is dispatched.
This is the building block for upstream PR #1479 (server-assigned
sessionId for cloud sessions): the SDK uses this callback to register
the session under the server-returned id atomically with respect to
session-scoped notifications that may arrive immediately after the
response on the wire.
The callback runs in a try/catch so any exception is logged and the
result is still delivered to the caller. Callers must keep the callback
fast and non-blocking — it executes on the single reader thread.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- :agent-mode — keyword in #{:interactive :plan :autopilot :shell},
wire-encoded as agentMode. Per-message agent mode (upstream PR #1438).
- :display-prompt — string shown in the timeline UI instead of the
model-facing :prompt. Wire-encoded as displayPrompt
(upstream PR #1470).
Applied to both send! and <send-async* so blocking and core.async
call paths behave identically.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Session config additions, all optional, accepted on both create and
resume unless noted (closes pre-existing parity gaps from earlier
upstream releases as well):
- :mcp-oauth-token-storage — #{:persistent :in-memory}.
Wire-encoded as the string key "mcpOAuthTokenStorage" (bypassing the
default kebab→camel converter, which would lower-case OAuth).
(upstream PR #1326)
- :embedding-cache-storage, :skip-embedding-retrieval,
:organization-custom-instructions,
:enable-on-demand-instruction-discovery, :enable-file-hooks,
:enable-host-git-operations, :enable-session-store, :enable-skills
— per-session multitenancy granular flags. (upstream PR #1474)
- :plugin-directories — extra plugin dirs loaded even when
:enable-config-discovery is false. (upstream PR #1482)
- :reasoning-summary — #{:none :concise :detailed} (parity gap).
- :context-tier — #{:default :long-context}, wire-encoded as
contextTier with values "default"/"long_context" via an explicit
case table (parity gap).
- :large-output on resume — was already accepted on create; now also
forwarded on resume to match upstream client.ts:1308 (parity gap).
PR #1479 — server-assigned sessionId for cloud sessions:
When :cloud is set and :session-id is omitted from create-session /
<create-session, the SDK now omits sessionId from session.create and
captures the server-assigned id from the response. Registration runs
inside an inline-response callback on the protocol reader thread so
that any session-scoped notification arriving immediately after the
response is correctly routed to the freshly-registered session.
Extracted three helpers used by both sync and async create/resume:
- ensure-session-fs-handler-factory! validates the factory is present
BEFORE the RPC (fail-fast, prevents deadlock from the reader-thread
callback throwing).
- install-session-fs-handler! is the post-RPC factory invocation.
- make-create-session-inline-callback builds the cloud-no-id reader
callback with full partial-registration cleanup.
Curated event-data specs for the two round-6 event types with
hand-shaped payloads:
- ::hook.progress-data — :message string.
- ::session.permissions_changed-data — :allow-all-permissions and
:disable-permissions booleans.
23 new integration tests cover all of the above, including wire-format
assertions for the camelCase / kebab / underscore boundaries.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CHANGELOG: round 6 [Unreleased] entries for MessageOptions additions (agentMode, displayPrompt), mcpOAuthTokenStorage, multitenancy flags, pluginDirectories, cloud-no-id (PR #1479), reasoningSummary / contextTier / resume largeOutput parity gaps, three new event types, and the schema bump. Deferred items called out: PR #1428 (multitenancy client mode), the configDir → configDirectory rename (PR #1482), Canvas runtime, MCP Apps enableMcpApps. doc/reference/API.md: new session-config options table entries with wire-key annotations and upstream PR references; new event types in the event-type table; cloud-no-id behaviour noted on :cloud; resume table mentions :large-output forwarded on session.resume; send! options table gains :agent-mode and :display-prompt. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
Pull request overview
This PR syncs the Clojure SDK with upstream Copilot SDK schema 1.0.56-1, adding new session config/message options, new schema event types, and cloud session server-assigned ID handling.
Changes:
- Adds round 6 config/message options such as storage modes, multitenancy flags, plugin directories,
:agent-mode, and:display-prompt. - Implements cloud
session.createwithout caller-suppliedsessionIdusing an inline JSON-RPC response callback. - Regenerates schema/event specs and updates tests, API reference, and changelog for new upstream events and options.
Show a summary per file
| File | Description |
|---|---|
.copilot-schema-version |
Advances pinned schema version to 1.0.56-1. |
CHANGELOG.md |
Documents round 6 additions and deferred work. |
doc/reference/API.md |
Updates API reference for new options and event types. |
schemas/README.md |
Updates documented schema pin. |
schemas/api.schema.json |
Updates upstream API schema snapshot. |
schemas/session-events.schema.json |
Updates upstream session event schema snapshot. |
src/github/copilot_sdk.clj |
Adds new public event types and session event grouping entries. |
src/github/copilot_sdk/client.clj |
Adds config wire encoding and cloud server-assigned session ID flow. |
src/github/copilot_sdk/generated/event_specs.clj |
Regenerates event specs for schema 1.0.56-1. |
src/github/copilot_sdk/protocol.clj |
Adds inline response callback support for JSON-RPC requests. |
src/github/copilot_sdk/session.clj |
Forwards new per-message send options. |
src/github/copilot_sdk/specs.clj |
Adds specs for new config keys, send options, and event data. |
test/github/copilot_sdk/codegen_test.clj |
Adds schema fixture coverage for new event data. |
test/github/copilot_sdk/integration_test.clj |
Adds integration/spec tests for new wire options and cloud session ID behavior. |
Copilot's findings
- Files reviewed: 13/14 changed files
- Comments generated: 6
- docs(reasoning-summary): correct CHANGELOG/API.md from keyword-enum
to string enum (#{"none" "concise" "detailed"}) — matches the actual
spec and tests, and the existing :reasoning-effort string pattern.
- docs(event-payloads): correct session.autopilot_objective_changed
data (:operation #{"create" "update" "delete"} required, :id integer
optional, :status optional — no :objective field) and
session.permissions_changed data (:allow-all-permissions plus
:previous-allow-all-permissions — no :disable-permissions field) to
match the actual schema/specs.
- docs(hook.progress): clarify that the curated ::hook.progress-data
spec is just :message — :session-id/:timestamp are envelope fields,
not data fields.
- docs(config-directory aliases): the :config-directory and
:output-directory aliases ARE added in this release (non-breaking);
moved from Deferred to a new Added bullet. Deferred now scoped to
the future breaking removal of the legacy spellings.
- test(codegen fixture): add session.autopilot_objective_changed
fixture so generated-data-specs-accept-wire-payloads exercises the
new required :operation and optional :id/:status shape.
- test(context-tier): rewrite the false-confidence
test-spec-session-resume-data-context-tier — the curated
::session.resume-data does not declare :context-tier, so the prior
assertion passed trivially. Rewritten as
test-generated-session-resume-data-context-tier asserting against
the generated wire spec (the layer that actually carries the field
as nilable "default"/"long_context" string).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
| (when (and (string? returned-id) | ||
| (not (str/blank? returned-id)) | ||
| (not= returned-id session-id)) | ||
| (throw (ex-info "session.create returned a sessionId that differs from the caller-supplied id" |
Collaborator
Author
There was a problem hiding this comment.
Generated via Copilot on behalf of @krukow
Valid — fixed in 9a791d0. The standard path is (or caller-session-id (str (java.util.UUID/randomUUID))) (client.clj:1881), so when the SDK generates the id, "caller-supplied" is misleading. Reworded to "differs from the requested id", which is accurate for both paths — the {:requested session-id :returned returned-id} ex-data already carries the actual ids for diagnostic purposes.
- API.md :context-tier row: document nil case for explicit clearing,
distinct from omitting the key (matches spec at specs.clj:620 which
is (s/nilable #{:default :long-context})).
- client.clj session.create id-mismatch error: reword from
'caller-supplied' to 'requested' so the message is accurate for both
the standard caller-supplied path and the SDK-generated path
(sync site at L1894 and async site at L2082).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Round 6's PR #1438 port added a second ::agent-mode s/def at the ::send-options site, but ::agent-mode already exists from PR #1286 for ::user.message-data. The two defs were identical sets, so behaviour was unchanged, but the duplicate would drift if upstream widens or narrows the enum. Keep the single def alongside the surrounding turn-options block and broaden the comment to cover both uses (per-turn send option AND inbound user.message echo). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The caller-facing ::agent-mode keyword set (#{:interactive :plan
:autopilot :shell}) belongs on the SEND side (::send-options): callers
pass a keyword and session/send! coerces via (name kw) to the wire
string. But ::user.message-data was also referencing ::agent-mode in
its :opt-un — and wire->clj on an inbound user.message event keeps the
echoed agentMode as a wire string ("interactive", ...), not a
keyword. That meant a valid inbound event with agentMode would fail
curated validation of ::user.message-data. This was latent — no test
exercised it — and predates round 6, but round 6's PR #1438 port made
the symmetry assumption explicit.
Fix:
- Drop ::agent-mode from ::user.message-data :opt-un. The generated
wire spec validates the string enum upstream of curated specs.
- Update the ::agent-mode docstring to scope it explicitly to
caller-side ::send-options and note the wire/idiom asymmetry.
- Add a regression test that round-trips wire-shaped user.message
events with each agentMode value through wire->clj and validates
against the idiom spec.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
C12 (client.clj:1753 inline callback deadlock risk): The cloud-no-id inline-response callback invokes the user-supplied :create-session-fs-handler factory on the JSON-RPC reader thread. If the factory blocks (issues another RPC, waits on a session event), the reader thread deadlocks — not just for this session, but for the whole connection. The internal callback docstring already noted this; promote the contract to the public surface so users actually see it. - create-session and <create-session docstrings now spell out the sessionFs + cloud-no-id contract: the factory MUST be a fast, non-blocking constructor and MUST NOT call back into the SDK. - Note that this constraint is only active on the cloud-no-id path; factories on standard / cloud-with-id / resume paths run on the caller's thread and may block freely. Architectural mitigations (timeouts around the factory, deferring install off the reader thread) were considered and rejected: timeouts hide the bug, and deferring install opens a race where session-scoped notifications arrive before the fs handler is registered. C13 (client.clj:2018 async cloud-no-id untested): The async <create-session cloud-no-id branch has its own promise/cleanup logic and uses the 4-arity proto/send-request options path, neither of which is exercised by the existing sync cloud-no-id tests. Added two coverage tests that mirror the sync ones: - test-async-cloud-session-omits-session-id-on-wire: asserts sessionId is omitted on wire and the delivered session adopts the server-assigned id (mock prefix "session-"). - test-async-cloud-session-with-caller-supplied-id-is-sent: asserts the caller-supplied id is forwarded on the wire and adopted as the delivered session's id. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The 4-arity form is [conn method params timeout-ms] — no opts. Only
the 5-arity form accepts an opts map. Reword the docstring so callers
trying to pass {:on-response-inline ...} target the right arity.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Syncs the Clojure SDK with upstream
github/copilot-sdkfor tagsv1.0.0-beta.9andv1.0.0-beta.10. Schema pin advances1.0.55-1→1.0.56-1. Closes round 6 of the rolling upstream-sync project.This is a non-breaking addition-only sync. All new config keys and message options are optional, and the cloud-no-id path activates only when
:cloudis set and:session-idis omitted (an explicit caller-supplied id keeps the prior behaviour exactly).Session plan:
~/.copilot/session-state/b794e17b-2c5a-42ed-90fe-63b823312966/plan.md(local; key decisions captured in this PR description).Ported upstream PRs
mcpOAuthTokenStorageconfigclient.clj,specs.clj, API.mdagentModeonMessageOptionssession.clj,specs.clj, API.mddisplayPromptonMessageOptionssession.clj,specs.clj, API.mdembeddingCacheStorageclient.clj,specs.clj, API.mdsessionIdfor cloud sessionsprotocol.clj,client.clj, helpers, API.mdpluginDirectoriesconfigclient.clj,specs.clj, API.mdParity gaps closed (existed in
SessionConfigBaseprior to this window):reasoningSummary(#{:none :concise :detailed})contextTier(#{:default :long-context}, wire"default"/"long_context")largeOutputnow forwarded onsession.resume(was already accepted on create)Three new session events (auto-picked-up by the regenerated wire spec; added to the public
event-typesset; curateddataspecs for the two with hand-shaped payloads)::copilot/hook.progress:copilot/session.autopilot_objective_changed:copilot/session.permissions_changedDeferred (will be addressed in follow-up rounds)
mode = "empty" | "copilot-cli",ToolSet,toolFilterPrecedence, ambient flags viasession.options.update). Per User input in the planning phase, this gets a dedicated future plan and sync round of its own.configDir→configDirectoryandoutputDir→outputDirectoryrename (PR #1482 tail): wire keys stay the same; deferring the Clojure-side option-key rename to a coordinated breaking-rename release alongside other rename PRs.enableMcpApps: continue to defer as experimental coupled surfaces.Cloud-no-id design notes (PR #1479)
The upstream Node.js SDK omits
sessionIdfromsession.createwhen the caller doesn't supply one and:cloudis set, then registers the session under the server-returned id. This is necessary because the server is the source of truth for cloud session ids.The Clojure port faces an ordering challenge: any session-scoped notifications that arrive immediately after the response must find the session registered. The protocol's reader thread processes responses synchronously, then continues reading. The implementation adds an inline-response callback option to
protocol/send-request({:on-response-inline (fn [result])}) that runs in the reader thread before the result is delivered downstream. The client's callback:sessionIdis a non-blank string (errors otherwise).If anything throws, the callback unwinds any partial registration via a tracked
registered-idatom before delivering an error result. The session-fs factory is validated upfront (ensure-session-fs-handler-factory!) before the RPC, so a missing factory cannot reach the reader-thread callback. The callback body is wrapped intry/catchin the protocol so reader-thread health is never compromised by a misbehaving client callback.Three helpers (
ensure-session-fs-handler-factory!,install-session-fs-handler!,make-create-session-inline-callback) are shared between the sync and async create/resume paths so the same cleanup guarantees apply everywhere.Wire-format gotchas (verified)
:mcp-oauth-token-storagewould camelCase tomcpOauthTokenStorage(lowercaseo), which the CLI does not accept. Build-params bypasses the default converter with the literal string wire key"mcpOAuthTokenStorage"(the conversion layer preserves non-keyword keys).:in-memoryvalue →"in-memory"via(name kw), not csk (which would mangle to"inMemory").:context-tier :long-context→"long_context"(underscore) via an explicit case mapping.:config-directoryand:output-directoryClojure-side options round-trip to the legacy wire keysconfigDir/outputDir(deferred Clojure-side rename, see Deferred section).Validation
bb test(unit + integration): 300 tests / 1439 assertions / 0 failures / 0 errorsbb ci(no E2E): passesbb ci:full(with E2E): one pre-existing flake intest-e2e-blob-attachment(30 s LLM timeout on the vision model) — reproduces on cleanmain, not a regression./run-all-examples.sh: all examples passbb validate-docs: cleanCode review
Two parallel multi-model reviews (Claude Opus 4.7-high and GPT-5.5) were run on the full change set with focused review areas (cloud-no-id correctness, wire conversion accuracy, spec completeness, API parity, test coverage, concurrency, DRY).
::join-session-configmissing::large-outputin:opt-undespite:large-outputbeing in its closed-keys set (Opus)::context-tier(keyword spec) lifted into::session.resume-datacurated spec, but inbound events carry wire string"long_context"; no coerce entry — fails the curated spec (GPT-5.5)::context-tierfrom curated::session.resume-datato match the round-5 pattern (::session.model_change-datadeliberately did not liftcontext-tierinto the curated layer). The wire-level generated spec still covers it.registered-idcleanup atom (in the same commit)Both reviewers' final recommendations after fixes: cloud-no-id flow is structurally sound, all wire conversions verified correct, spec coverage matches API parity, tests assert wire-format at the right level.
Commits
chore(schema): bump copilot CLI schema 1.0.55-1 → 1.0.56-1feat(events): expose 3 new round-6 event types on the public APIfeat(protocol): add inline-response callback on send-requestfeat(session): forward :agent-mode and :display-prompt on send!feat(client): port round 6 session config + cloud-no-id (PR #1479)docs: round 6 changelog and API referenceEach commit individually builds and passes tests.