Skip to content
Draft
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
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,18 @@ changes accumulate. Track in-flight protocol changes via PRs touching
- `JsonPrimitive` type alias (`string | number | boolean | null`) in `types/common/state.ts`.
- `session/activeClientRemoved` action to release a single active client from a
session by `clientId`.
- `SessionState.inputNeeded` — a session-level aggregate of outstanding input
requests across all chats, so a client can discover and answer elicitations,
tool confirmations, and client-tool execution requests from the session
channel without subscribing to individual chats. Each entry
(`SessionChatInputRequest`, `SessionToolConfirmationRequest`,
`SessionToolClientExecutionRequest`, unioned as `SessionInputRequest`) carries
the owning chat URI plus the identifiers needed to respond.
- `session/inputNeededSet` and `session/inputNeededRemoved` actions for the host
to upsert and remove `SessionState.inputNeeded` entries.
- `ToolCallConfirmationState` union (`ToolCallPendingConfirmationState |
ToolCallPendingResultConfirmationState`) for the tool call carried by
`SessionToolConfirmationRequest`.

### Changed

Expand Down
7 changes: 7 additions & 0 deletions clients/go/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ tag whose matching `## [X.Y.Z]` heading is missing from this file.
lightweight session-list presentation hints.
- `SessionActiveClientRemovedAction` (wire `session/activeClientRemoved`) to
release a single active client by `ClientId`.
- `SessionState.InputNeeded` — a session-level aggregate of outstanding input
requests across all chats (`SessionInputRequest` union with
`SessionChatInputRequest`, `SessionToolConfirmationRequest`, and
`SessionToolClientExecutionRequest`), plus the `SessionInputNeededSetAction`
(wire `session/inputNeededSet`) and `SessionInputNeededRemovedAction` (wire
`session/inputNeededRemoved`) actions and the `ToolCallConfirmationState`
union.

### Changed

Expand Down
33 changes: 33 additions & 0 deletions clients/go/ahp/reducers.go
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,18 @@ func customizationID(c ahptypes.Customization) (string, bool) {
return "", false
}

func sessionInputRequestID(r ahptypes.SessionInputRequest) (string, bool) {
switch v := r.Value.(type) {
case *ahptypes.SessionChatInputRequest:
return v.Id, true
case *ahptypes.SessionToolConfirmationRequest:
return v.Id, true
case *ahptypes.SessionToolClientExecutionRequest:
return v.Id, true
}
return "", false
}

func childCustomizationID(c ahptypes.ChildCustomization) (string, bool) {
switch v := c.Value.(type) {
case *ahptypes.AgentCustomization:
Expand Down Expand Up @@ -743,6 +755,27 @@ func ApplyActionToSession(state *ahptypes.SessionState, action ahptypes.StateAct
}
}
return ReduceOutcomeNoOp
case *ahptypes.SessionInputNeededSetAction:
id, ok := sessionInputRequestID(a.Request)
if !ok {
return ReduceOutcomeNoOp
}
for i := range state.InputNeeded {
if got, ok := sessionInputRequestID(state.InputNeeded[i]); ok && got == id {
state.InputNeeded[i] = a.Request
return ReduceOutcomeApplied
}
}
state.InputNeeded = append(state.InputNeeded, a.Request)
return ReduceOutcomeApplied
case *ahptypes.SessionInputNeededRemovedAction:
for i := range state.InputNeeded {
if got, ok := sessionInputRequestID(state.InputNeeded[i]); ok && got == a.Id {
state.InputNeeded = append(state.InputNeeded[:i], state.InputNeeded[i+1:]...)
return ReduceOutcomeApplied
}
}
return ReduceOutcomeNoOp
case *ahptypes.SessionCustomizationsChangedAction:
state.Customizations = append([]ahptypes.Customization(nil), a.Customizations...)
return ReduceOutcomeApplied
Expand Down
48 changes: 48 additions & 0 deletions clients/go/ahptypes/actions.generated.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ const (
ActionTypeSessionServerToolsChanged ActionType = "session/serverToolsChanged"
ActionTypeSessionActiveClientSet ActionType = "session/activeClientSet"
ActionTypeSessionActiveClientRemoved ActionType = "session/activeClientRemoved"
ActionTypeSessionInputNeededSet ActionType = "session/inputNeededSet"
ActionTypeSessionInputNeededRemoved ActionType = "session/inputNeededRemoved"
ActionTypeChatPendingMessageSet ActionType = "chat/pendingMessageSet"
ActionTypeChatPendingMessageRemoved ActionType = "chat/pendingMessageRemoved"
ActionTypeChatQueuedMessagesReordered ActionType = "chat/queuedMessagesReordered"
Expand Down Expand Up @@ -754,6 +756,38 @@ type SessionActiveClientRemovedAction struct {
ClientId string `json:"clientId"`
}

// A session-level input request was added or updated.
//
// Upsert semantics keyed by {@link SessionInputRequest.id | `request.id`}: the
// host dispatches this with the full {@link SessionInputRequest} to append a new
// entry to {@link SessionState.inputNeeded} or replace the existing entry with
// the same `id`.
//
// Server-originated: the host mirrors chat-level requests (elicitations, tool
// confirmations, client-tool executions) into the session aggregate so clients
// subscribed only to the session channel can discover them. Clients respond by
// dispatching the ordinary `chat/*` action to the entry's `chat` channel — see
// {@link SessionInputRequest}.
type SessionInputNeededSetAction struct {
Type ActionType `json:"type"`
// The input request to add or update, matched by `id`.
Request SessionInputRequest `json:"request"`
}

// A session-level input request was removed.
//
// Removes the entry identified by `id` from
// {@link SessionState.inputNeeded}; a no-op when no entry matches.
//
// Server-originated: the host dispatches this once the underlying request
// resolves (the user answers, the tool call is confirmed, or the client
// reports its result).
type SessionInputNeededRemovedAction struct {
Type ActionType `json:"type"`
// The `id` of the input request to remove.
Id string `json:"id"`
}

// The session's customizations have changed.
//
// Full-replacement semantics: the `customizations` array replaces the
Expand Down Expand Up @@ -1241,6 +1275,8 @@ func (*SessionChangesetsChangedAction) isStateAction() {}
func (*SessionServerToolsChangedAction) isStateAction() {}
func (*SessionActiveClientSetAction) isStateAction() {}
func (*SessionActiveClientRemovedAction) isStateAction() {}
func (*SessionInputNeededSetAction) isStateAction() {}
func (*SessionInputNeededRemovedAction) isStateAction() {}
func (*SessionCustomizationsChangedAction) isStateAction() {}
func (*SessionCustomizationToggledAction) isStateAction() {}
func (*SessionCustomizationUpdatedAction) isStateAction() {}
Expand Down Expand Up @@ -1534,6 +1570,18 @@ func (u *StateAction) UnmarshalJSON(data []byte) error {
return err
}
u.Value = &value
case "session/inputNeededSet":
var value SessionInputNeededSetAction
if err := json.Unmarshal(data, &value); err != nil {
return err
}
u.Value = &value
case "session/inputNeededRemoved":
var value SessionInputNeededRemovedAction
if err := json.Unmarshal(data, &value); err != nil {
return err
}
u.Value = &value
case "session/customizationsChanged":
var value SessionCustomizationsChangedAction
if err := json.Unmarshal(data, &value); err != nil {
Expand Down
Loading
Loading