Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ changes accumulate. Track in-flight protocol changes via PRs touching

### Added

- `root/downloadProgress` (`DownloadProgressParams`, `DownloadPhase`) host→client
notification reporting progress while the host downloads a resource on the
client's behalf (e.g. an agent's native SDK fetched lazily on first use), so
clients can show a progress indicator instead of a silent multi-second hang.
- `SessionModelInfo.maxOutputTokens` and `SessionModelInfo.maxPromptTokens`
optional fields for communicating model token limits.
- `SessionSummary._meta` optional provider metadata field for lightweight
Expand Down
3 changes: 3 additions & 0 deletions clients/go/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ tag whose matching `## [X.Y.Z]` heading is missing from this file.

### Added

- `DownloadProgressParams` struct and `DownloadPhase` (wire
`root/downloadProgress`) for reporting host-side resource download progress
(e.g. an agent's native SDK fetched lazily on first use).
- `SessionModelInfo.MaxOutputTokens` and `SessionModelInfo.MaxPromptTokens`
optional fields for communicating model token limits.
- `SessionSummary.Meta` (wire `_meta`) optional provider metadata field for
Expand Down
78 changes: 78 additions & 0 deletions clients/go/ahptypes/notifications.generated.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,20 @@ const (
AuthRequiredReasonExpired AuthRequiredReason = "expired"
)

// Lifecycle phase of a single download.
type DownloadPhase string

const (
// The download has begun; no bytes received yet.
DownloadPhaseStarted DownloadPhase = "started"
// A throttled progress sample with bytes received so far.
DownloadPhaseProgress DownloadPhase = "progress"
// Terminal success frame; the resource is fully downloaded.
DownloadPhaseCompleted DownloadPhase = "completed"
// Terminal failure frame; see {@link DownloadProgressParams.error}.
DownloadPhaseFailed DownloadPhase = "failed"
)

// ─── Notification Payloads ────────────────────────────────────────────

// Broadcast to all clients subscribed to the root channel when a new session
Expand Down Expand Up @@ -85,6 +99,70 @@ type SessionSummaryChangedParams struct {
Changes PartialSessionSummary `json:"changes"`
}

// Broadcast on the root channel 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 progress indicator instead of a silent multi-second hang.
//
// The notification is intentionally **resource-agnostic** so the same channel
// can report future downloads (additional agent runtimes, plugins, models, …)
// without a new method. The `kind` discriminant categorizes the resource and
// `resourceId` identifies it within that kind; clients that don't care can show
// a single generic indicator driven by `displayName` + the byte counts.
//
// This is **host-level**, not session state: the artifact is shared across every
// consumer and the host deduplicates concurrent fetches into one download (one
// `downloadId`). The optional `session` field names the session whose action
// triggered the fetch, purely as context — a client MAY attribute the progress
// to that session's row, or show a single global indicator and ignore it.
//
// Semantics:
//
// - Frames for one download share a stable `downloadId`. The first frame a
// client observes for a `downloadId` begins the indicator even if it is not
// `phase: 'started'` (a client that connects mid-download may miss the
// `started` frame).
// - `receivedBytes` is monotonically non-decreasing within a `downloadId`.
// `totalBytes` is present only when the host knows the size up front
// (e.g. a `Content-Length`); when absent the client SHOULD show an
// indeterminate indicator.
// - Exactly one terminal frame (`phase: 'completed'` or `'failed'`) ends a
// download. `error` carries a short, non-localized reason on failure.
// - Like all notifications this is ephemeral and is **not** replayed on
// reconnect. A client that never receives a terminal frame (the download
// finished while it was disconnected) SHOULD expire the indicator after an
// idle timeout.
// - The brand noun is carried in `displayName`; clients own the surrounding
// (localized) template, e.g. `"Downloading {displayName}… {pct}%"`.
type DownloadProgressParams struct {
// Channel URI this notification belongs to (the root channel)
Channel URI `json:"channel"`
// Stable id for one download. Coalesces the frames of a single fetch and
// distinguishes concurrent downloads (e.g. two resources at once).
DownloadId string `json:"downloadId"`
// Category of resource being downloaded. An open string (not a closed enum)
// so new resource types can be reported without a protocol bump. Known
// values today: `'agent-sdk'` (an agent's native SDK/runtime).
Kind string `json:"kind"`
// Id of the resource within its {@link kind}, e.g. the provider id `'claude'`
// or `'codex'` for an `'agent-sdk'` download.
ResourceId string `json:"resourceId"`
// Human-readable brand name for display, e.g. `'Claude'`. The host supplies
// the noun; the client owns the surrounding localized template.
DisplayName string `json:"displayName"`
// Lifecycle phase of this frame.
Phase DownloadPhase `json:"phase"`
// Bytes written so far. Monotonically non-decreasing within a `downloadId`.
ReceivedBytes int64 `json:"receivedBytes"`
// Total bytes when known (e.g. from `Content-Length`); omitted ⇒ indeterminate.
TotalBytes *int64 `json:"totalBytes,omitempty"`
// Session whose action triggered the fetch, if any. Informational only —
// the download is host-level and shared across sessions.
Session *URI `json:"session,omitempty"`
// Short, non-localized failure reason; present only when `phase: 'failed'`.
Error *string `json:"error,omitempty"`
}

// Sent by the server when a protected resource requires (re-)authentication.
//
// This notification MAY be associated with any channel — for example, an
Expand Down
3 changes: 3 additions & 0 deletions clients/kotlin/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ versions (`*-SNAPSHOT`) are explicitly rejected by the publish pipeline; bump

### Added

- `DownloadProgressParams` data class and `DownloadPhase` enum (wire
`root/downloadProgress`) for reporting host-side resource download progress
(e.g. an agent's native SDK fetched lazily on first use).
- `SessionModelInfo.maxOutputTokens` and `SessionModelInfo.maxPromptTokens`
optional fields for communicating model token limits.
- `SessionSummary.meta` (`_meta` on the wire) optional provider metadata field
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,33 @@ enum class AuthRequiredReason {
EXPIRED
}

/**
* Lifecycle phase of a single download.
*/
@Serializable
enum class DownloadPhase {
/**
* The download has begun; no bytes received yet.
*/
@SerialName("started")
STARTED,
/**
* A throttled progress sample with bytes received so far.
*/
@SerialName("progress")
PROGRESS,
/**
* Terminal success frame; the resource is fully downloaded.
*/
@SerialName("completed")
COMPLETED,
/**
* Terminal failure frame; see {@link DownloadProgressParams.error}.
*/
@SerialName("failed")
FAILED
}

// ─── Notification Types ─────────────────────────────────────────────────────

@Serializable
Expand Down Expand Up @@ -83,6 +110,56 @@ data class SessionSummaryChangedParams(
val changes: PartialSessionSummary
)

@Serializable
data class DownloadProgressParams(
/**
* Channel URI this notification belongs to (the root channel)
*/
val channel: String,
/**
* Stable id for one download. Coalesces the frames of a single fetch and
* distinguishes concurrent downloads (e.g. two resources at once).
*/
val downloadId: String,
/**
* Category of resource being downloaded. An open string (not a closed enum)
* so new resource types can be reported without a protocol bump. Known
* values today: `'agent-sdk'` (an agent's native SDK/runtime).
*/
val kind: String,
/**
* Id of the resource within its {@link kind}, e.g. the provider id `'claude'`
* or `'codex'` for an `'agent-sdk'` download.
*/
val resourceId: String,
/**
* Human-readable brand name for display, e.g. `'Claude'`. The host supplies
* the noun; the client owns the surrounding localized template.
*/
val displayName: String,
/**
* Lifecycle phase of this frame.
*/
val phase: DownloadPhase,
/**
* Bytes written so far. Monotonically non-decreasing within a `downloadId`.
*/
val receivedBytes: Long,
/**
* Total bytes when known (e.g. from `Content-Length`); omitted ⇒ indeterminate.
*/
val totalBytes: Long? = null,
/**
* Session whose action triggered the fetch, if any. Informational only —
* the download is host-level and shared across sessions.
*/
val session: String? = null,
/**
* Short, non-localized failure reason; present only when `phase: 'failed'`.
*/
val error: String? = null
)

@Serializable
data class AuthRequiredParams(
/**
Expand Down
3 changes: 3 additions & 0 deletions clients/rust/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ matching `## [X.Y.Z]` heading is missing from this file.

### Added

- `DownloadProgressParams` struct and `DownloadPhase` enum (wire
`root/downloadProgress`) for reporting host-side resource download progress
(e.g. an agent's native SDK fetched lazily on first use).
- `SessionModelInfo.maxOutputTokens` and `SessionModelInfo.maxPromptTokens`
optional fields for communicating model token limits.
- `SessionSummary.meta` (`_meta` on the wire) optional provider metadata field
Expand Down
86 changes: 86 additions & 0 deletions clients/rust/crates/ahp-types/src/notifications.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,23 @@ pub enum AuthRequiredReason {
Expired,
}

/// Lifecycle phase of a single download.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum DownloadPhase {
/// The download has begun; no bytes received yet.
#[serde(rename = "started")]
Started,
/// A throttled progress sample with bytes received so far.
#[serde(rename = "progress")]
Progress,
/// Terminal success frame; the resource is fully downloaded.
#[serde(rename = "completed")]
Completed,
/// Terminal failure frame; see {@link DownloadProgressParams.error}.
#[serde(rename = "failed")]
Failed,
}

// ─── Notification Payloads ────────────────────────────────────────────

/// Broadcast to all clients subscribed to the root channel when a new session
Expand Down Expand Up @@ -96,6 +113,75 @@ pub struct SessionSummaryChangedParams {
pub changes: PartialSessionSummary,
}

/// Broadcast on the root channel 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 progress indicator instead of a silent multi-second hang.
///
/// The notification is intentionally **resource-agnostic** so the same channel
/// can report future downloads (additional agent runtimes, plugins, models, …)
/// without a new method. The `kind` discriminant categorizes the resource and
/// `resourceId` identifies it within that kind; clients that don't care can show
/// a single generic indicator driven by `displayName` + the byte counts.
///
/// This is **host-level**, not session state: the artifact is shared across every
/// consumer and the host deduplicates concurrent fetches into one download (one
/// `downloadId`). The optional `session` field names the session whose action
/// triggered the fetch, purely as context — a client MAY attribute the progress
/// to that session's row, or show a single global indicator and ignore it.
///
/// Semantics:
///
/// - Frames for one download share a stable `downloadId`. The first frame a
/// client observes for a `downloadId` begins the indicator even if it is not
/// `phase: 'started'` (a client that connects mid-download may miss the
/// `started` frame).
/// - `receivedBytes` is monotonically non-decreasing within a `downloadId`.
/// `totalBytes` is present only when the host knows the size up front
/// (e.g. a `Content-Length`); when absent the client SHOULD show an
/// indeterminate indicator.
/// - Exactly one terminal frame (`phase: 'completed'` or `'failed'`) ends a
/// download. `error` carries a short, non-localized reason on failure.
/// - Like all notifications this is ephemeral and is **not** replayed on
/// reconnect. A client that never receives a terminal frame (the download
/// finished while it was disconnected) SHOULD expire the indicator after an
/// idle timeout.
/// - The brand noun is carried in `displayName`; clients own the surrounding
/// (localized) template, e.g. `"Downloading {displayName}… {pct}%"`.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DownloadProgressParams {
/// Channel URI this notification belongs to (the root channel)
pub channel: Uri,
/// Stable id for one download. Coalesces the frames of a single fetch and
/// distinguishes concurrent downloads (e.g. two resources at once).
pub download_id: String,
/// Category of resource being downloaded. An open string (not a closed enum)
/// so new resource types can be reported without a protocol bump. Known
/// values today: `'agent-sdk'` (an agent's native SDK/runtime).
pub kind: String,
/// Id of the resource within its {@link kind}, e.g. the provider id `'claude'`
/// or `'codex'` for an `'agent-sdk'` download.
pub resource_id: String,
/// Human-readable brand name for display, e.g. `'Claude'`. The host supplies
/// the noun; the client owns the surrounding localized template.
pub display_name: String,
/// Lifecycle phase of this frame.
pub phase: DownloadPhase,
/// Bytes written so far. Monotonically non-decreasing within a `downloadId`.
pub received_bytes: i64,
/// Total bytes when known (e.g. from `Content-Length`); omitted ⇒ indeterminate.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub total_bytes: Option<i64>,
/// Session whose action triggered the fetch, if any. Informational only —
/// the download is host-level and shared across sessions.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub session: Option<Uri>,
/// Short, non-localized failure reason; present only when `phase: 'failed'`.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
}

/// Sent by the server when a protected resource requires (re-)authentication.
///
/// This notification MAY be associated with any channel — for example, an
Expand Down
Loading
Loading