Add experimental command output deferral to thread item listings (#59601)

## What changed

Allow experimental clients to request `deferOutput: {"commandExecution": true}` in `thread/items/list` for a specific `turnId`. Completed commands with a recorded completion time, exit code zero, and nonempty saved output return `outputDeferred: true` and `item.aggregatedOutput: null`. Clients can retrieve the complete output through `thread/items/read` using the turn and item IDs.

Keep full output by default and for listings across turns. Make conversion from `ThreadItemEntry` to `ThreadItem` fallible so Rust consumers detect deferred output instead of treating an incomplete item as complete.

## Testing

Add integration coverage for experimental capability gating, default and disabled options, command status and output eligibility, listings across turns, deferred-item conversion, and full output retrieval by item ID.

GitOrigin-RevId: d6bfac34bb68f7e3b327fd5553270ed6d0fe87cf
This commit is contained in:
Brent Traut
2026-11-05 08:10:44 +00:00
committed by copyberry
parent edd0d89820
commit e7f32cf192
26 changed files with 415 additions and 67 deletions
@@ -5959,6 +5959,12 @@ class ContextCompactionThreadItem(BaseModel):
type: Annotated[Literal["contextCompaction"], Field(title="ContextCompactionThreadItemType")]
class ThreadItemOutputDeferral(BaseModel):
model_config = ConfigDict(
populate_by_name=True,
)
class ItemThreadItemsListAnchor(BaseModel):
model_config = ConfigDict(
populate_by_name=True,
@@ -5991,6 +5997,13 @@ class ThreadItemsListParams(BaseModel):
description="Opaque continuation cursor or an exclusive item anchor in the requested visible turn. An item anchor requires a non-empty `turnId`; ascending (the default) returns items after it, and descending returns items before it. Continue with the returned string cursor."
),
] = None
defer_output: Annotated[
ThreadItemOutputDeferral | None,
Field(
alias="deferOutput",
description="For a single turn, choose which saved outputs to fetch separately by item ID.",
),
] = None
limit: Annotated[int | None, Field(description="Optional item page size.", ge=0)] = None
sort_direction: Annotated[
SortDirection | None,