Skip to content
Merged
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
21 changes: 21 additions & 0 deletions .github/instructions/general-instructions.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,24 @@ applyTo: 'types/**/*.ts'
## Finalizing changes

Before declaring a protocol repo change complete, run `npm run generate` and `npm run test` from the repo root. Resolve any generated-output, typecheck, lint, test, or generator issues those commands expose before handing the change back.

## Running toolchains you don't have locally

A spec change ripples into every `clients/<lang>/` mirror, so you often need to build/test a client whose toolchain isn't installed on your machine. **Don't skip verification** — run it in a container with `podman`. Use these pinned images so every agent verifies against the same environment (add a row when you containerize another toolchain so the images stay consistent):

| Toolchain | Image | Notes |
| --- | --- | --- |
| JDK 17 (Kotlin client) | `docker.io/library/eclipse-temurin:17-jdk` | Gradle comes from the repo's `./gradlew` wrapper. |

Mount the **repo root** (the shared conformance fixtures under `types/test-cases/` are resolved by walking up from the working directory, so a client-dir-only mount fails) and keep build caches out of `$HOME`. Example — run the Kotlin client's tests:

```sh
podman run --rm \
-e GRADLE_USER_HOME=/tmp/gradle-home \
-v "$PWD":/workspace \
-w /workspace/clients/kotlin \
docker.io/library/eclipse-temurin:17-jdk \
./gradlew test --no-daemon --console=plain
```

On macOS this needs a running `podman machine` (`podman machine start`). Build artifacts (`clients/kotlin/build/`, `.gradle/`) are git-ignored.
18 changes: 18 additions & 0 deletions clients/go/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ tag whose matching `## [X.Y.Z]` heading is missing from this file.

### Added

- `ChatDraftChangedAction` (wire `chat/draftChanged`) and `ChatState.Draft`
(`*Message`) for syncing a chat's in-progress input draft; `ApplyActionToChat`
sets or clears `state.Draft` without stamping `ModifiedAt`.
- `Message.Model` and `Message.Agent` optional fields recording the model /
agent selection a message was composed with.
- `ProgressParams` struct (wire `root/progress`) — a generic progress notification
correlated by a `ProgressToken` (added on `CreateSessionParams`).
Used today for the lazy first-use download of an agent's native SDK.
Expand All @@ -28,6 +33,16 @@ tag whose matching `## [X.Y.Z]` heading is missing from this file.

### Changed

- `SessionState` no longer embeds a `Summary` sub-struct; its metadata fields
(`Provider`, `Title`, `Status`, `Activity`, `Project`, `WorkingDirectory`,
`Annotations`) are now inlined directly on `SessionState`, which no longer
carries `Model`, `Agent`, `CreatedAt`, or `ModifiedAt`. `ApplyActionToSession`
reads and writes these flat fields and no longer stamps a session
`ModifiedAt`.
- `SessionSummary` is now a root-only catalog struct; its `CreatedAt` /
`ModifiedAt` are ISO-8601 strings (previously numeric) and it no longer
carries `Model` / `Agent`.
- `ChatState` and `ChatSummary` no longer carry `Model` / `Agent`.
- `SessionState.ActiveClients` (`[]SessionActiveClient`, required) replaces the
single pointer `SessionState.ActiveClient`; `ApplyActionToSession` upserts and
removes entries keyed by `ClientId`.
Expand All @@ -42,6 +57,9 @@ tag whose matching `## [X.Y.Z]` heading is missing from this file.

### Removed

- `SessionModelChangedAction` (wire `session/modelChanged`) and
`SessionAgentChangedAction` (wire `session/agentChanged`); session model /
agent are no longer part of the protocol surface.
- `SessionActiveClientToolsChangedAction`. An active client now updates its
published tools by re-dispatching `SessionActiveClientSetAction` with its
full, updated entry.
Expand Down
32 changes: 7 additions & 25 deletions clients/go/ahp/reducers.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,10 +162,6 @@ func refreshSummaryStatus(state *ahptypes.ChatState) {
state.Status = summaryStatus(state, nil)
}

func touchSessionModified(state *ahptypes.SessionState) {
state.Summary.ModifiedAt = nowMs()
}

func touchChatModified(state *ahptypes.ChatState) {
state.ModifiedAt = nowISOString()
}
Expand Down Expand Up @@ -599,6 +595,9 @@ func ApplyActionToChat(state *ahptypes.ChatState, action ahptypes.StateAction) R
}
state.QueuedMessages = reordered
return ReduceOutcomeApplied
case *ahptypes.ChatDraftChangedAction:
state.Draft = a.Draft
return ReduceOutcomeApplied
}
return ReduceOutcomeOutOfScope
}
Expand All @@ -616,12 +615,6 @@ func mergeChatSummaryPartial(summary *ahptypes.ChatSummary, changes ahptypes.Par
if changes.ModifiedAt != nil {
summary.ModifiedAt = *changes.ModifiedAt
}
if changes.Model != nil {
summary.Model = changes.Model
}
if changes.Agent != nil {
summary.Agent = changes.Agent
}
if changes.Origin != nil {
summary.Origin = changes.Origin
}
Expand Down Expand Up @@ -677,26 +670,16 @@ func ApplyActionToSession(state *ahptypes.SessionState, action ahptypes.StateAct
state.DefaultChat = a.DefaultChat
return ReduceOutcomeApplied
case *ahptypes.SessionTitleChangedAction:
state.Summary.Title = a.Title
touchSessionModified(state)
return ReduceOutcomeApplied
case *ahptypes.SessionModelChangedAction:
model := a.Model
state.Summary.Model = &model
touchSessionModified(state)
return ReduceOutcomeApplied
case *ahptypes.SessionAgentChangedAction:
state.Summary.Agent = a.Agent
touchSessionModified(state)
state.Title = a.Title
return ReduceOutcomeApplied
case *ahptypes.SessionIsReadChangedAction:
state.Summary.Status = withStatusFlag(state.Summary.Status, ahptypes.SessionStatusIsRead, a.IsRead)
state.Status = withStatusFlag(state.Status, ahptypes.SessionStatusIsRead, a.IsRead)
return ReduceOutcomeApplied
case *ahptypes.SessionIsArchivedChangedAction:
state.Summary.Status = withStatusFlag(state.Summary.Status, ahptypes.SessionStatusIsArchived, a.IsArchived)
state.Status = withStatusFlag(state.Status, ahptypes.SessionStatusIsArchived, a.IsArchived)
return ReduceOutcomeApplied
case *ahptypes.SessionActivityChangedAction:
state.Summary.Activity = a.Activity
state.Activity = a.Activity
return ReduceOutcomeApplied
case *ahptypes.SessionChangesetsChangedAction:
if a.Changesets == nil {
Expand All @@ -718,7 +701,6 @@ func ApplyActionToSession(state *ahptypes.SessionState, action ahptypes.StateAct
for k, v := range a.Config {
state.Config.Values[k] = v
}
touchSessionModified(state)
return ReduceOutcomeApplied
case *ahptypes.SessionMetaChangedAction:
state.Meta = a.Meta
Expand Down
63 changes: 25 additions & 38 deletions clients/go/ahptypes/actions.generated.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,13 @@ const (
ActionTypeSessionTitleChanged ActionType = "session/titleChanged"
ActionTypeChatUsage ActionType = "chat/usage"
ActionTypeChatReasoning ActionType = "chat/reasoning"
ActionTypeSessionModelChanged ActionType = "session/modelChanged"
ActionTypeSessionAgentChanged ActionType = "session/agentChanged"
ActionTypeSessionServerToolsChanged ActionType = "session/serverToolsChanged"
ActionTypeSessionActiveClientSet ActionType = "session/activeClientSet"
ActionTypeSessionActiveClientRemoved ActionType = "session/activeClientRemoved"
ActionTypeChatPendingMessageSet ActionType = "chat/pendingMessageSet"
ActionTypeChatPendingMessageRemoved ActionType = "chat/pendingMessageRemoved"
ActionTypeChatQueuedMessagesReordered ActionType = "chat/queuedMessagesReordered"
ActionTypeChatDraftChanged ActionType = "chat/draftChanged"
ActionTypeChatInputRequested ActionType = "chat/inputRequested"
ActionTypeChatInputAnswerChanged ActionType = "chat/inputAnswerChanged"
ActionTypeChatInputCompleted ActionType = "chat/inputCompleted"
Expand Down Expand Up @@ -586,6 +585,23 @@ type ChatQueuedMessagesReorderedAction struct {
Order []string `json:"order"`
}

// The chat's draft input changed.
//
// Clients MAY periodically sync their local input state — the message the user
// is composing, including its {@link Message.model | model} /
// {@link Message.agent | agent} selection and attachments — into the chat's
// {@link ChatState.draft | `draft`} so it survives reloads and is visible to
// other clients viewing the same chat. Eager syncing is **not** required;
// clients SHOULD debounce and MAY sync only at convenient points. Set `draft`
// to `undefined` to clear it (e.g. once the message is sent).
//
// A client is only allowed to draft {@link MessageKind.User} messages.
type ChatDraftChangedAction struct {
Type ActionType `json:"type"`
// New draft message, or `undefined` to clear it
Draft *Message `json:"draft,omitempty"`
}

// A session requested input from the user.
//
// Full-request upsert semantics: the `request` replaces any existing request
Expand Down Expand Up @@ -639,28 +655,6 @@ type ChatTruncatedAction struct {
TurnId *string `json:"turnId,omitempty"`
}

// Model changed for this session.
type SessionModelChangedAction struct {
Type ActionType `json:"type"`
// New model selection
Model ModelSelection `json:"model"`
}

// Custom agent selection changed for this session.
//
// Omitting `agent` (or setting it to `undefined`) clears the selection and
// resets the session to no selected custom agent (provider default behavior).
//
// When a turn is currently active, the server MUST defer the change until
// the active turn completes, then apply it for the next turn (same rule as
// {@link SessionModelChangedAction | `session/modelChanged`}).
type SessionAgentChangedAction struct {
Type ActionType `json:"type"`
// New agent selection, or `undefined` to clear the selection and reset the
// session to no selected custom agent.
Agent *AgentSelection `json:"agent,omitempty"`
}

// The read state of the session changed.
//
// Dispatched by a client to mark a session as read (e.g. after viewing it)
Expand Down Expand Up @@ -1228,12 +1222,11 @@ func (*ChatReasoningAction) isStateAction() {}
func (*ChatPendingMessageSetAction) isStateAction() {}
func (*ChatPendingMessageRemovedAction) isStateAction() {}
func (*ChatQueuedMessagesReorderedAction) isStateAction() {}
func (*ChatDraftChangedAction) isStateAction() {}
func (*ChatInputRequestedAction) isStateAction() {}
func (*ChatInputAnswerChangedAction) isStateAction() {}
func (*ChatInputCompletedAction) isStateAction() {}
func (*ChatTruncatedAction) isStateAction() {}
func (*SessionModelChangedAction) isStateAction() {}
func (*SessionAgentChangedAction) isStateAction() {}
func (*SessionIsReadChangedAction) isStateAction() {}
func (*SessionIsArchivedChangedAction) isStateAction() {}
func (*SessionActivityChangedAction) isStateAction() {}
Expand Down Expand Up @@ -1456,6 +1449,12 @@ func (u *StateAction) UnmarshalJSON(data []byte) error {
return err
}
u.Value = &value
case "chat/draftChanged":
var value ChatDraftChangedAction
if err := json.Unmarshal(data, &value); err != nil {
return err
}
u.Value = &value
case "chat/inputRequested":
var value ChatInputRequestedAction
if err := json.Unmarshal(data, &value); err != nil {
Expand All @@ -1480,18 +1479,6 @@ func (u *StateAction) UnmarshalJSON(data []byte) error {
return err
}
u.Value = &value
case "session/modelChanged":
var value SessionModelChangedAction
if err := json.Unmarshal(data, &value); err != nil {
return err
}
u.Value = &value
case "session/agentChanged":
var value SessionAgentChangedAction
if err := json.Unmarshal(data, &value); err != nil {
return err
}
u.Value = &value
case "session/isReadChanged":
var value SessionIsReadChangedAction
if err := json.Unmarshal(data, &value); err != nil {
Expand Down
10 changes: 0 additions & 10 deletions clients/go/ahptypes/commands.generated.go
Original file line number Diff line number Diff line change
Expand Up @@ -231,12 +231,6 @@ type CreateSessionParams struct {
Channel URI `json:"channel"`
// Agent provider ID
Provider *string `json:"provider,omitempty"`
// Model selection (ID and optional model-specific configuration)
Model *ModelSelection `json:"model,omitempty"`
// Initial custom agent selection for the new session.
//
// Omit to start the session with no custom agent selected (provider default).
Agent *AgentSelection `json:"agent,omitempty"`
// Working directory for the session
WorkingDirectory *URI `json:"workingDirectory,omitempty"`
// Fork from an existing session. The new session is populated with content
Expand Down Expand Up @@ -289,10 +283,6 @@ type CreateChatParams struct {
Chat URI `json:"chat"`
// Optional initial message for the new chat.
InitialMessage *Message `json:"initialMessage,omitempty"`
// Optional per-chat model override.
Model *ModelSelection `json:"model,omitempty"`
// Optional per-chat agent override.
Agent *AgentSelection `json:"agent,omitempty"`
// Optional source chat and turn to fork from.
Source *ChatForkSource `json:"source,omitempty"`
}
Expand Down
4 changes: 0 additions & 4 deletions clients/go/ahptypes/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,6 @@ type PartialChatSummary struct {
Activity *string `json:"activity,omitempty"`
// Last modification timestamp (ISO 8601, e.g. `"2025-03-10T18:42:03.123Z"`)
ModifiedAt *string `json:"modifiedAt,omitempty"`
// Optional per-chat model override (defaults to the session's model)
Model *ModelSelection `json:"model,omitempty"`
// Optional per-chat agent override (defaults to the session's agent)
Agent *AgentSelection `json:"agent,omitempty"`
// How this chat came into existence
Origin *ChatOrigin `json:"origin,omitempty"`
// Optional per-chat working directory.
Expand Down
29 changes: 11 additions & 18 deletions clients/go/ahptypes/notifications.generated.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,8 +207,6 @@ type OtlpExportMetricsParams struct {

// PartialSessionSummary is the partial equivalent of SessionSummary — every field is optional for delta updates.
type PartialSessionSummary struct {
// Session URI
Resource *URI `json:"resource,omitempty"`
// Agent provider ID
Provider *string `json:"provider,omitempty"`
// Session title
Expand All @@ -217,34 +215,29 @@ type PartialSessionSummary struct {
Status *SessionStatus `json:"status,omitempty"`
// Human-readable description of what the session is currently doing
Activity *string `json:"activity,omitempty"`
// Creation timestamp
CreatedAt *int64 `json:"createdAt,omitempty"`
// Last modification timestamp
ModifiedAt *int64 `json:"modifiedAt,omitempty"`
// Server-owned project for this session
Project *ProjectInfo `json:"project,omitempty"`
// Currently selected model
Model *ModelSelection `json:"model,omitempty"`
// Currently selected custom agent.
//
// Absent (`undefined`) means no custom agent is selected for this session
// — the session uses the provider's default behavior.
Agent *AgentSelection `json:"agent,omitempty"`
// The default working directory URI for this session. Individual chats
// MAY override via {@link ChatSummary.workingDirectory | their own
// `workingDirectory`}; this field acts as the fallback for any chat that
// does not.
WorkingDirectory *URI `json:"workingDirectory,omitempty"`
// Aggregate summary of file changes associated with this session. Servers
// may populate this to give clients a quick at-a-glance view of the
// session's footprint (e.g., for list rendering) without requiring the
// client to subscribe to a changeset.
Changes *ChangesSummary `json:"changes,omitempty"`
// Lightweight summary of this session's inline annotations channel
// (`ahp-session:/<uuid>/annotations`). Surfaced so badge UI can render
// annotation / entry counts without subscribing. Absent when the session
// does not expose an annotations channel.
Annotations *AnnotationsSummary `json:"annotations,omitempty"`
// Session URI
Resource *URI `json:"resource,omitempty"`
// Creation timestamp (ISO 8601, e.g. `"2025-03-10T18:42:03.123Z"`)
CreatedAt *string `json:"createdAt,omitempty"`
// Last modification timestamp (ISO 8601, e.g. `"2025-03-10T18:42:03.123Z"`)
ModifiedAt *string `json:"modifiedAt,omitempty"`
// Aggregate summary of file changes associated with this session. Servers
// may populate this to give clients a quick at-a-glance view of the
// session's footprint (e.g., for list rendering) without requiring the
// client to subscribe to a changeset.
Changes *ChangesSummary `json:"changes,omitempty"`
// Lightweight server-defined metadata clients may use for the session
// presentation. The protocol does not interpret these values; producers
// SHOULD keep the payload small because summaries appear in session lists
Expand Down
Loading
Loading