feat: the client grows two sides β documents and chat each pick their home - #424
Merged
Conversation
β¦ home
One client, two independent switches: api_key decides where documents
live (the PageIndex cloud, or the local store); a configured chat model
decides who answers (your own model in your process, or the managed
cloud chat). Their free combination opens the bridge β cloud documents,
your model β and the fourth cell stays unspellable.
- index=/chat= slots: string shorthand or grouped dict, 1:1 with the
flat arguments; one spelling per side, sides mix freely
- optional "type" everywhere (top-level and in either dict): always
omittable, checked against the content, meaningful alone β
type="cloud" is a keyless cloud spelling
- PAGEINDEX_API_KEY is read only when the code explicitly says cloud
(PageIndexCloudClient(), type="cloud", "pageindex-cloud",
{"type": "cloud"}); a bare PageIndexClient() stays local
- bare mode words ("cloud", "local", β¦) are reserved: they error
with the real spellings instead of silently parsing as model names
- bridge chat runs the in-process agent over the live cloud MCP tools
and instructions; doc_id targets at the prompt level; citations stay
managed-only; an auth-shaped backend failure explains whose
credentials run the model
- typed shapes (IndexConfig, ChatConfig) ship as optional annotations
Every previously working program is byte-for-byte unchanged: the only
behavioral deltas are error paths β reworded guidance, and the
api_key+chat_model combination graduating from an error into the
bridge.
rejojer
force-pushed
the
feat/client-config
branch
from
August 24, 2026 10:55
b951fdb to
c5cd78d
Compare
Member
Author
Code reviewNo issues found. Checked for bugs and CLAUDE.md compliance. π€ Generated with Claude Code |
- .env keys reach all four keyless-cloud spellings: utils' import-time
load_dotenv now runs before every PAGEINDEX_API_KEY read
- an empty chat-side value ("", {}) errors instead of silently selecting
own-model chat on the default model; None-valued slot keys mean absent,
exactly like the flat arguments
- _local_chat derives from chat_model, so a post-construction assignment
switches the whole client, never half of it
- model= beside a slot gets the split guidance (index_model=/chat_model=)
instead of "two spellings of the same thing"
- the messages door wraps provider failures through _model_backend_error,
and 401s count as auth-shaped even without "api key" in the text
- keyless-cloud hints name the spelling that actually combines; slot
strings are stripped; wrong-typed values raise PageIndexAPIError
- retrieve_model/chat_backend docs drop the stale "Local mode only";
the local-scope refusal no longer claims bridge tools are server-scoped
Claude-Session: https://claude.ai/code/session_01RnBVRYfvzSZhNewbD7qni7
β¦its chat side
- type= beside index= now does what the docstring promises: agreement
passes, disagreement errors, and a mistyped value reports the
vocabulary error instead of a spelling collision
- PageIndexCloudClient grows the chat-side arguments (chat=, chat_model,
retrieve_model, chat_backend), so "pin the index side" is literally
true and the chat surfaces' construct-with-chat_model guidance is
followable on it
- the four chat doors' doc_id entries carry the enforcement split the
config helpers already state (local: tool-layer allowlist; cloud:
prompt-level / server-side)
- types.py stops claiming slot keys share the flat names β the side
prefix is factored out, index={"model"} is index_model=
Claude-Session: https://claude.ai/code/session_01LJ6FLPk8LN4GN4tfswjS1J
β¦, hints name a chat= model - the three framework-missing errors said "in local mode", which is wrong on a bridge client (cloud documents + own model) β they now explain the dependency the way the surfaces do: your own chat model - the construct-with guidance reads "(or a chat= model)": a bare chat="pageindex-cloud" is also chat= but selects the managed side - the mechanical Local-only β Own-model-chat-only substitution left orphan fragments and two overlong lines; those paragraphs re-flowed Claude-Session: https://claude.ai/code/session_01LJ6FLPk8LN4GN4tfswjS1J
β¦ists - a managed-chat cloud client stores chat_model/chat_backend as None, so the documented attribute reads instead of raising AttributeError; _local_chat derives from "is a chat model configured" - McpBridge caches tools/list per session β every chat turn rebuilds the tool set, and the round trip was pure latency; the 404 session-expiry reset drops the cache with the session - run_messages builds tools before the transport: on a bridge client that build is network I/O, and a failure there stranded a per-call anthropic client ahead of the try/finally Claude-Session: https://claude.ai/code/session_01RnBVRYfvzSZhNewbD7qni7
Member
Author
Code reviewNo issues found. Checked for bugs and CLAUDE.md compliance. π€ Generated with Claude Code |
"type" is Python's own word β a builtin, and "data type" beside the
TypedDict shapes; "mode" is what the SDK already calls the two sides
("local mode", "cloud mode"). Same grammar everywhere the declaration
appears: the top-level argument, the index dict, the chat dict, the
typed shapes. The rename also frees the builtin inside the constructor,
so the shape-check error names the offending class through type() again.
"type" in a slot dict is now an ordinary unknown key.
With the declaration key spelled mode=, 'index="cloud" is not a mode
word' contradicted its own remedy, index={"mode": "cloud"} β "cloud" is
exactly a mode value. The four bare strings are reserved words; the
message now says so.
β¦s chat-lane only
- McpBridge.list_tools caches only a non-empty list β a transient blank
(a deploy blip, a gate misconfiguration) would otherwise run every later
turn with zero tools while the instructions still name them, and only a
404 session reset could clear it
- the 401 architecture note appends "drop the chat model configuration"
only on the chat lane: responses() and messages() refuse a client
without an own model, so on those lanes the exit sent the caller in a
circle
- CloudIndexConfig says api_key is omittable only while mode: "cloud"
stays β index={} refuses as an empty dict rather than reading the env
Claude-Session: https://claude.ai/code/session_01E1op9sc12bv7EeY27ZhWns
β¦ the cwd - McpBridge.list_tools no longer caches: the tool set is built once per SDK call (Agent(tools=...) ahead of Runner.run; build_anthropic_tools ahead of tool_runner), not per model turn, so the cache saved one round trip per later call while a mid-pagination 404 replayed a dead cursor into a duplicated (and cached) list, and the list went out by reference across a lock dropped between miss and store - utils.load_dotenv searches upward from the cwd: a bare load_dotenv() walked up from utils.py, which is site-packages for an installed SDK, so the four keyless-cloud spellings never saw a project-root .env; the package-relative walk stays as the fallback - the emptiness guard strips strings: chat_model=" " selected own-model chat, the silent flip the guard's own comment rules out - the _local_chat comment stops advertising post-construction assignment as a full mode switch Claude-Session: https://claude.ai/code/session_01836Yrt6Swz3RmRnysnRGSo
Member
Author
Code reviewNo issues found. Checked for bugs and CLAUDE.md compliance. π€ Generated with Claude Code |
β¦ords; a blank chat_model stays managed
- PageIndexLocalClient takes index= and chat=, PageIndexCloudClient takes
index= β the grouped spelling of the flat vocabulary each already took;
their refusals name the class and an exit that class can take, and the
mode cross-check runs before any environment read
- "cloud" and "local" are accepted wherever "pageindex-cloud" was (index=,
chat=, mode=, {"mode": ...}), case- and whitespace-insensitive; "hosted"
and "managed" still refuse, pointing at the real word
- every spelling strips its strings, and the slot spellings' type/empty
errors name the slot key (index["model"]), not the flat argument
- _local_chat treats a blank chat_model as managed: the constructor
refuses "", so assignment agrees instead of opening the bridge on a
nameless model; openai_agent_config carries no model then either
- an empty MCP tools/list raises like empty instructions does β a
zero-tool agent would answer from the model's own knowledge silently
- enable_citations names the real gate (managed vs own chat), not
"cloud-only", on a cloud own-model client
- pageindex/py.typed: the exported config TypedDicts reach installed
type-checked callers
Claude-Session: https://claude.ai/code/session_01GZLsJ6jmAQvgotcbhQv85Q
| return value | ||
|
|
||
|
|
||
| _CloudKey = Union[str, Callable[[], str], None] |
| """An empty tools/list must raise like empty instructions does: a | ||
| zero-tool agent answers from the model's own knowledge instead of | ||
| the documents, with nothing to signal it.""" | ||
| import pageindex.mcp_bridge as mcp_bridge |
as_openai_tools() and openai_agent_config() need the agents package, which the "without frameworks" CI legs do not install β the same importorskip every other test on those doors already carries. Claude-Session: https://claude.ai/code/session_01GZLsJ6jmAQvgotcbhQv85Q
Member
Author
Code reviewNo issues found. Checked for bugs and CLAUDE.md compliance. π€ Generated with Claude Code |
This was referenced Aug 26, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
One client, two independent switches:
api_keydecides where your documents live; a configured chat model decides who answers. Their free combination opens the third mode β cloud documents, your own model β and the impossible fourth (local documents + managed chat) stays unspellable.Each step of that ladder changes one thing.
chat_modelmeans the same thing everywhere β your model, on your keys, in your process;api_keymoves your documents, never your model.New configuration surface
index=/chat=slots β string shorthand or grouped dict, 1:1 with the flat arguments (same names, same meanings). One spelling per side; sides mix freely."mode"β top-level (mode="cloud") or inside either dict. Always omittable, checked against the content (disagreement errors), meaningful alone:mode="cloud"/index={"mode": "cloud"}are keyless cloud spellings.PAGEINDEX_API_KEYis read only when the code explicitly says cloud βPageIndexCloudClient()(now the shortest env-key construction),mode="cloud","cloud"/"pageindex-cloud",{"mode": "cloud"}. A barePageIndexClient()stays local no matter what the environment holds."cloud"and"local"are accepted wherever"pageindex-cloud"is (index=,chat=,mode=,{"mode": ...}), any case/whitespace; the near-synonyms"hosted"/"managed"error pointing at the real word instead of silently parsing as a model name.PageIndexLocalClient(index=..., chat=...)andPageIndexCloudClient(index=...)accept the grouped spelling of the flat vocabulary they already take; a slot that picks the other side is refused in the class's name, and the mode cross-check runs before any environment read.IndexConfig/CloudIndexConfig/LocalIndexConfig/ChatConfigship as optional annotations (Literal-typedmodefields give checkers a discriminated union). The constructor keeps accepting plain dicts. The package shipspy.typed, so installed callers' checkers see them.index["model"]), not the flat argument.The bridge (cloud documents + your model)
The same in-process document-QA engine that local mode runs, fed by the live cloud MCP tool set and the MCP server's own instructions.
chat(),chat_completions(),responses()andmessages()all work. Semantics, documented on the surfaces:doc_idtargets at the prompt level (the tool-layer allowlist is local-store only).enable_citationsstays managed-only.Compatibility
Not strictly additive:
chat_backend={},index_backend={},storage_path="",model=""β and every other value that configures nothing ("", whitespace-only strings,{}) β now refuse at construction; main accepted them in local mode.client.retrieve_model = m(0.2.9's write path) now selects own-model chat instead of doing nothing (a blank value keeps the managed chat);api_key + chat_modelandapi_key + chat_backendgraduate from a constructor error into the bridge.Tests
432 passing (main's 393 untouched, 39 new). New coverage: constructor matrix, slot/mode/env rules, mode words, the pinned classes' slots, and bridge execution on all three chat lanes against a fake MCP bridge. A 28,000-combination constructor sweep confirms every input either builds a consistent client or raises
PageIndexAPIErrorβ never a third kind of failure. Construction touches no network; clients stay picklable.