Fixes the cloud/local contract mismatches from the PR #272 review
(verified against the official API docs — the cloud API has no
folder/collection endpoints publicly, GET /docs supports limit<=100
with offset):
- query_stream: emit a terminal answer_done event with the full answer
(same contract as the local backend); raise CloudAPIError instead of
disguising HTTP errors as answer events; move the initial connect
inside try so a connection failure can no longer strand the consumer
awaiting a sentinel that never arrives
- _request: rewind file objects before retrying so a transient 5xx/429
during upload no longer re-sends an empty multipart body; carry the
HTTP status on CloudAPIError (status_code) and keep the last status
in the max-retries error
- list_documents: paginate with limit/offset instead of a hard-coded
limit=100, so >100-doc collections are no longer silently truncated
(whole-collection queries rely on this list)
- folders: treat only 403/404 as "folders unavailable" (warned via
warnings.warn instead of an invisible logger.warning, matched on
status_code instead of a "403" substring); transient errors now
propagate instead of being permanently cached as folder_id=None
- error taxonomy parity: cloud doc endpoints map HTTP 404 to
DocumentNotFoundError; local get_document raises DocumentNotFoundError
instead of returning {}; local delete_document raises on missing
doc_id instead of silently deleting nothing
- cloud get_document warns that include_text is not supported instead
of silently ignoring it
Adds regression tests for each fix (11 new tests).
Claude-Session: https://claude.ai/code/session_01Kx5DgKbhK1N8autqXH8SmS
- Collection.query and Backend.query/query_stream accept doc_ids as
str, list[str] or None. Single str is normalized to [str] inside each
backend; bare [] is rejected with ValueError at both layers.
- wrap_with_doc_context wraps the scoped doc list in <docs>...</docs>
and SCOPED_SYSTEM_PROMPT instructs the agent to treat that block as
data, not instructions (defense against prompt injection via
auto-generated doc_description).
- _require_cloud_api now distinguishes api_key="" from api_key=None;
the former gives a targeted error pointing at the empty-string vs
fall-back-to-local situation when legacy SDK methods are called.
- Legacy PageIndexClient.list_documents docstring spells out the
return-shape difference vs collection.list_documents() to flag a
silent migration footgun (paginated dict with id/name keys vs plain
list[dict] with doc_id/doc_name keys).
- Remove dead CloudBackend.get_agent_tools stub (not on the Backend
protocol; only ever returned an empty AgentTools()) and the
SYSTEM_PROMPT alias (OPEN_/SCOPED_SYSTEM_PROMPT are the explicit
names now).
- README quick start and streaming example now pass doc_ids; new
multi-document section shows both str and list forms.
- examples/demo_query_modes.py exercises all five query-mode cases
(single-doc, multi-doc with/without env var, scoped single, scoped
multi) for manual verification.