Skip to content

sessions: add session-level inputNeeded aggregate#265

Draft
connor4312 wants to merge 2 commits into
mainfrom
session/input-needed-aggregate
Draft

sessions: add session-level inputNeeded aggregate#265
connor4312 wants to merge 2 commits into
mainfrom
session/input-needed-aggregate

Conversation

@connor4312

Copy link
Copy Markdown
Member

Summary

Surface every outstanding input request across a session's chats on the session channel, so a client (mobile app, tool-providing client, etc.) can discover and answer them without subscribing to each chat.

This is state-only by design: each aggregate entry carries the owning chat URI plus all identifiers needed to respond, so a client answers by dispatching the ordinary chat/* action to that chat's channel — no chat subscription, no new response actions.

What changed & why

  • SessionState.inputNeeded — host-managed aggregate of outstanding input across all chats, keyed by an opaque id.
  • SessionInputRequest union (discriminated on kind) with three variants:
    • SessionChatInputRequest — mirrors a chat's ChatInputRequest (elicitation).
    • SessionToolConfirmationRequest — a ToolCallConfirmationState awaiting confirmation, plus turnId.
    • SessionToolClientExecutionRequest — a running tool the session delegates to an active clientId.
  • ToolCallConfirmationState union (ToolCallPendingConfirmationState | ToolCallPendingResultConfirmationState) — an inline anonymous union isn't generatable across clients, so it's named.
  • session/inputNeededSet / session/inputNeededRemoved server actions (upsert / remove) + canonical reducer + 6 fixtures (keeps reducer branch coverage at 100%).
  • Cross-language: wired the new types/unions/actions through all four code generators and hand-ported the reducer logic into the Rust, Go, Kotlin, and Swift clients.
  • Docs/CHANGELOGs: new "Aggregated input requests" section in the session-channel spec; Unreleased entries in the root + all five client CHANGELOGs.

Reviewer notes

  • SessionToolClientExecutionRequest.toolCall is typed ToolCallState (documented as running) rather than ToolCallRunningState: a direct single-variant struct reference can't round-trip the status discriminant on the wire in the Rust/Go generators (the discriminant lives on the union wrapper). Happy to switch to a precise one-variant union if preferred.

Testing

  • Root suite: 273 tests, 100% reducer branch coverage; lint + changelog checks pass.
  • TS client (59), Rust, Go (build/vet/test), Swift (107) all pass. Generation is drift-free.
  • Kotlin not run locally (no JDK in this environment); the port mirrors the verified TS/Rust/Go/Swift reducers with symbol names confirmed against generated output — relying on CI.

connor4312 and others added 2 commits June 25, 2026 12:48
Surface every outstanding input request across a session's chats on the
session channel so a client (e.g. a mobile app or tool-providing client)
can discover and answer them without subscribing to individual chats.

What changed and why:

- Add `SessionState.inputNeeded` — a host-managed aggregate of outstanding
  input across all chats, keyed by an opaque `id`. Each entry carries the
  owning `chat` URI plus the identifiers needed to respond, so clients answer
  by dispatching the ordinary `chat/*` action to that chat's channel (no chat
  subscription required). State-only: no new response actions.
- Add the `SessionInputRequest` union with three variants:
  `SessionChatInputRequest` (mirrors a `ChatInputRequest`),
  `SessionToolConfirmationRequest` (a `ToolCallConfirmationState`), and
  `SessionToolClientExecutionRequest` (a running tool the session delegates
  to an active client).
- Add `ToolCallConfirmationState` union
  (`ToolCallPendingConfirmationState | ToolCallPendingResultConfirmationState`);
  an inline anonymous union isn't generatable across clients.
- Add `session/inputNeededSet` / `session/inputNeededRemoved` server actions
  (upsert/remove) plus the canonical reducer and 6 fixtures (100% branch
  coverage).
- Wire the new types/unions/actions through all four code generators and
  hand-port the reducer logic into the Rust, Go, Kotlin, and Swift clients.
- Document the aggregate in the session-channel spec and add Unreleased
  entries to the root and all five client CHANGELOGs.

Note: `SessionToolClientExecutionRequest.toolCall` is typed `ToolCallState`
(documented as `running`) rather than `ToolCallRunningState`, because a direct
single-variant struct reference can't round-trip the `status` discriminant on
the wire in the Rust/Go generators.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Reflow the `use crate::state::{...}` block in reducers.rs to satisfy
`cargo fmt --all -- --check`.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
/** A tool call awaiting parameter- or result-confirmation. */
ToolConfirmation = 'toolConfirmation',
/** A running tool the session wants an active client to execute. */
ToolClientExecution = 'toolClientExecution',

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think I'm following this part? Everything else makes sense

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An active client can be a participant of a session and provide its tools for all chats. But in order to know a chat needs is waiting for it to run a tool, it needs to be subscribed to that chat. This solves that case just like the confirmation/input request parts do

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants