Add thread ID inclusion filters to thread/list (#61446)

## Why

Clients reconciling saved thread IDs need to retrieve matching summaries within the current list filters, including threads beyond the first page of history.

## What changed

- Add `includedThreadIds` with up to 100 entries, applied before pagination alongside existing filters. Exclusions take precedence, and results retain the requested list sort.
- Treat omitted or null inclusions as unrestricted and an empty array as matching nothing. Reject invalid IDs and oversized arrays without truncating them.
- Use indexed metadata and primary-key lookups for local inclusion queries, without scanning rollouts for repairs. IDs absent from the index do not match.
- Update protocol schemas, generated bindings, and documentation, including guidance to verify server support before treating missing saved IDs as removals.

## Testing

Add coverage for saved IDs across project moves and archiving, pagination, exclusions, validation limits, and combinations with search, account, and relationship filters. Add a SQLite query-plan test for primary-key lookups across scopes.

GitOrigin-RevId: 90b02a12aee5716f872a9d5d6168d23cf86c6333
This commit is contained in:
Brent Traut
2026-11-14 02:23:37 +00:00
committed by copyberry
parent e9919f4b4e
commit 32cec881b7
48 changed files with 468 additions and 46 deletions
@@ -11157,6 +11157,13 @@ class ThreadListParams(BaseModel):
description="Thread IDs to exclude before applying the result limit. Up to 100 entries; invalid IDs or a larger list are rejected, never truncated. Send the same exclusions on each page. Omitted, null, or empty means no exclusions.",
),
] = None
included_thread_ids: Annotated[
list[str] | None,
Field(
alias="includedThreadIds",
description="Restrict results to up to 100 thread IDs before pagination. Omit or send null for unrestricted results. An empty list matches nothing. Exclusions still apply. Repeat the same IDs on each page. The local store uses indexed metadata for this filter.",
),
] = None
limit: Annotated[
int | None,
Field(description="Optional page size; defaults to a reasonable server-side value.", ge=0),
@@ -11210,7 +11217,7 @@ class ThreadListParams(BaseModel):
bool | None,
Field(
alias="useStateDbOnly",
description="If true, return from the state DB without scanning JSONL rollouts to repair thread metadata. Omitted or false preserves scan-and-repair behavior.",
description="If true, the local store returns indexed metadata without scanning JSONL rollouts for repairs. Requests with `includedThreadIds` always use indexed metadata.",
),
] = None