root/progress notification#263
Conversation
Introduce a host→client notification, broadcast on the root channel, that reports progress while the host downloads a resource on the client's behalf — typically a multi-MB artifact fetched lazily the first time it is needed (today: an agent's native SDK/runtime, `kind: 'agent-sdk'`). Lets clients show a determinate (or indeterminate) progress indicator instead of a silent multi-second hang. The notification is intentionally resource-agnostic: a `kind` discriminant (open string) plus `resourceId` let the same channel report future downloads (additional agent runtimes, plugins, models, …) without a new method. The download is host-level — the artifact is shared across consumers and concurrent fetches are deduplicated into one download (one `downloadId`). Frames carry the lifecycle phase (started → throttled progress → completed/failed), `receivedBytes`, and optional `totalBytes` (present when the host knows the size up front, e.g. a Content-Length). The optional `session` field names the triggering session as informational context only. Lands in the unreleased 0.5.0 spec (NOTIFICATION_INTRODUCED_IN), with spec and per-client (TypeScript/Rust/Kotlin/Swift/Go) CHANGELOG entries. Re-exports the new `DownloadProgressParams` and `DownloadPhase` types from the TypeScript package root (types/index.ts). Registers the new types in the Swift/Rust/Kotlin/Go generators and commits the regenerated client sources + JSON schema. Additive and non-breaking. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
| * } | ||
| * ``` | ||
| */ | ||
| export interface DownloadProgressParams { |
There was a problem hiding this comment.
Download progress is very specific. I suggest we actually have a more generalized progress, maybe we borrow from MCP here. In their model progress messages are more generic (similary to our extension host progress) and can be associated with an RPC call, or not (in our case rather than including session in the progress, the progress would be associated with the 'create session' or subscribe call)
There was a problem hiding this comment.
thanks for the suggestion, made it more generic now
| * ```json | ||
| * { | ||
| * "jsonrpc": "2.0", | ||
| * "method": "root/downloadProgress", |
There was a problem hiding this comment.
What triggers a "download"? Is this really a global thing or is it blocking some other action that the client initiated?
I'm also interested in a more generic "progress" that can communicate a state like "Creating worktree...", I'm not sure whether that would fit with this or not
There was a problem hiding this comment.
E.g. when a client sends a message in a new session but that provider's agent SDK has not been downloaded yet, that triggers the server to download that agent SDK. This is for the Claude & Codex providers because their SDKs are not shipped with the product but are downloaded on demand. So we download them only when the client sends the first message in a new Claude/Codex session.
progress notification
05d5e69 to
64b1585
Compare
progress notificationprogress notification
| 'root/sessionAdded': { params: SessionAddedParams }; | ||
| 'root/sessionRemoved': { params: SessionRemovedParams }; | ||
| 'root/sessionSummaryChanged': { params: SessionSummaryChangedParams }; | ||
| 'progress': { params: ProgressParams }; |
There was a problem hiding this comment.
Let's define this as root/progress for routing
64b1585 to
c65a1df
Compare
progress notificationroot/progress notification
c65a1df to
c36f94a
Compare
root/progress notificationroot/progress notification
c36f94a to
b31f649
Compare
…ation Reviewer feedback: the notification was too download-specific. Replace it with a generic, operation-agnostic `root/progress` notification, correlated to its originating request by a `progressToken` the client supplies rather than naming a domain object like a session. - Replace `root/downloadProgress` / `DownloadProgressParams` / `DownloadPhase` with `root/progress` / `ProgressParams` (channel, progressToken, progress, total?, message?). Completion is signalled by `progress === total` (no phase/error on the wire); the host emits a terminal frame with `total === progress`. - Add `createSession.progressToken` — the opt-in token. The host echoes it on every frame so the client can attribute progress to that call (e.g. the lazy first-use download of an agent's native SDK that fires when the provisional session materializes on first message). - Route on the root channel as `root/progress`, alongside the other `root/*` notifications. - Update version map (`root/progress` introduced in 0.5.0), message-checks, and the `types/index.ts` re-exports. - Register the new type in the Swift/Rust/Kotlin/Go generators (dropping the removed `DownloadPhase` enum) and commit the regenerated clients + JSON schema. Refresh spec + per-client CHANGELOG entries. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
b31f649 to
78e4667
Compare
Why
Hosts fetch large native agent SDK/runtime binaries (70–95 MB) lazily on first use. Today that blocks for several seconds with nothing on the wire, so a client can only show a silent hang. This adds a generic host→client progress notification so clients can render a real progress indicator.
Related
Wire shape
A generic, operation-agnostic progress notification rather than a download-specific one. A client opts in per request by minting a
progressToken; the host echoes it on every frame so the client can correlate progress back to the call (and the UI awaiting it) without the protocol naming a domain object.Opt-in — new optional field on
createSession(CreateSessionParams):progressToken?root/progressframes are emitted. Unique across the client's active requests.Server→client notification on the root channel,
root/progress(ProgressParams):channelprogressTokenprogresstotal?Content-Length); absent ⇒ indeterminatemessage?{ "jsonrpc": "2.0", "method": "root/progress", "params": { "channel": "ahp-root://", "progressToken": "9b2c1f7e-4a0d-4e2b-8b1a-2f7e4a0d4e2b", "progress": 18874368, "total": 41957498 } }Completion is signalled by
progress === total— no phase/error on the wire. The host emits a terminal frame withtotal === progress(when the total was never known, it setstotalto the finalprogresson that frame); real failures surface via the existing session-failure path. Ephemeral (not replayed on reconnect); a client that misses the terminal frame should expire its indicator after an idle timeout.The token is anchored on
createSession, so it survives the provisional → materialized gap: the SDK download fires lazily when a session first materializes on its opening message, and the host fans the host-level download out to each waiting session's token.Because the notification says nothing about what is progressing, the same channel can report any future long-running operation (other runtimes, plugins, models) without a new method.
Introduced in the unreleased 0.5.0 spec (
NOTIFICATION_INTRODUCED_IN). Additive, non-breaking. Includes regenerated Rust/Kotlin/Swift/Go clients + JSON schema, and spec + per-client CHANGELOG entries.