Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pageindex/agent_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -1503,7 +1503,7 @@ def _tool_specs(client, include_management: bool = False, doc_ids=None,
"""(name, description, schema, invoke) per tool, for adapters that take
the wire schema verbatim. ``invoke`` returns (envelope_text, is_error).
Schemas are copies (frameworks keep the dict by reference). ``doc_ids``
is the local chat scope; cloud scoping is server-side."""
is the local chat scope."""
_require_local_scope(client, doc_ids)
if getattr(client, "api_key", None):
bridge = _cloud_bridge(client, gated=not include_management)
Expand Down
29 changes: 12 additions & 17 deletions pageindex/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ def _resolve_index_slot(index) -> "tuple[_CloudKey, dict[str, Any]]":
raise PageIndexAPIError(
"index is an empty string — pass a local index model name, "
'or "cloud".')
if isinstance(index, dict):
if isinstance(index, Mapping):
# None-valued keys mean "absent", exactly like the flat arguments.
conf = {name: value for name, value in index.items()
if value is not None}
Expand Down Expand Up @@ -195,7 +195,7 @@ def _resolve_chat_slot(chat) -> "tuple[Optional[str], dict[str, Any]]":
raise PageIndexAPIError(
"chat is an empty string — pass a model name, or "
'"cloud" for the managed chat.')
if isinstance(chat, dict):
if isinstance(chat, Mapping):
# None-valued keys mean "absent", exactly like the flat arguments.
conf = {name: value for name, value in chat.items()
if value is not None}
Expand Down Expand Up @@ -308,8 +308,9 @@ class PageIndexClient:
``index_model`` covers this.
retrieve_model (str, optional): Legacy name for ``chat_model`` —
same meaning everywhere, cloud clients included.
storage_path (str, optional): Local mode only — directory where
indexed documents are stored. Defaults to ``./.pageindex``.
storage_path (str or os.PathLike, optional): Local mode only —
directory where indexed documents are stored. Defaults to
``./.pageindex``.
index_backend (dict, optional): Local mode only — connection
overrides for the indexing lane's LLM calls. Keys are
LiteLLM's own connection params — ``api_key``, ``api_base``,
Expand Down Expand Up @@ -1201,8 +1202,7 @@ def agent_tools(
the full ``/mcp`` list (upload, delete, ...).
doc_id: Local only — restrict the tools to this document ID
(or list of IDs), enforced at the tool layer: out-of-scope
lookups return NOT_FOUND. Raises on cloud, where scoping
is server-side.
lookups return NOT_FOUND. Raises on cloud.
"""
from .agent_tools import build_agent_tools
return build_agent_tools(self, include_management, doc_ids=doc_id)
Expand Down Expand Up @@ -1245,8 +1245,7 @@ def as_openai_tools(self, include_management: bool = False,
for server-side tool execution (OpenAI models only).
doc_id: Local only — restrict the tools to this document ID
(or list of IDs), enforced at the tool layer: out-of-scope
lookups return NOT_FOUND. Raises on cloud, where scoping
is server-side.
lookups return NOT_FOUND. Raises on cloud.
"""
from .integrations.openai_agents import build_openai_tools
return build_openai_tools(self, include_management, hosted,
Expand Down Expand Up @@ -1297,8 +1296,7 @@ def openai_agent_config(
Args:
doc_id: Document ID or list of IDs to target, as in
``agent_instructions``. Local: also enforced at the tool
layer, not just prompted. Cloud: prompt-level targeting
(tool scoping is server-side).
layer, not just prompted. Cloud: prompt-level targeting.
include_management (bool): Also expose tools that modify the
library.
model: Backend model name; overrides the local default. Same
Expand Down Expand Up @@ -1386,8 +1384,7 @@ def as_anthropic_tools(self, include_management: bool = False,
sync and async runners each accept only their own flavor.
doc_id: Local only — restrict the tools to this document ID
(or list of IDs), enforced at the tool layer: out-of-scope
lookups return NOT_FOUND. Raises on cloud, where scoping
is server-side.
lookups return NOT_FOUND. Raises on cloud.
"""
from .integrations.anthropic_sdk import build_anthropic_tools
return build_anthropic_tools(self, include_management, asynchronous,
Expand Down Expand Up @@ -1428,8 +1425,7 @@ def anthropic_runner_config(
default).
doc_id: Document ID or list of IDs to target, as in
``agent_instructions``. Local: also enforced at the tool
layer, not just prompted. Cloud: prompt-level targeting
(tool scoping is server-side).
layer, not just prompted. Cloud: prompt-level targeting.
include_management (bool): Also expose tools that modify the
library.
asynchronous (bool): Build async runnables for
Expand Down Expand Up @@ -1475,7 +1471,7 @@ def as_claude_mcp(self, include_management: bool = False,
same way at registration (requires ``claude-agent-sdk``;
``pip install 'pageindex[claude]'``). ``doc_id`` (local only)
restricts those tools to that document ID (or list), enforced at
the tool layer; it raises on cloud, where scoping is server-side.
the tool layer; it raises on cloud.
``server_name`` names the in-process server — match it to the key
you register the entry under (cloud entries carry no name).

Expand Down Expand Up @@ -1520,8 +1516,7 @@ def claude_agent_config(
Args:
doc_id: Document ID or list of IDs to target, as in
``agent_instructions``. Local: also enforced at the tool
layer, not just prompted. Cloud: prompt-level targeting
(tool scoping is server-side).
layer, not just prompted. Cloud: prompt-level targeting.
include_management (bool): Also allow tools that modify the
library.
server_name (str): Key the server is registered under;
Expand Down
2 changes: 1 addition & 1 deletion pageindex/local_chat.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Managed local chat: document-QA agents over the local tools."""
"""Own-model chat: document-QA agents over the local or cloud agent tools."""
from __future__ import annotations

import asyncio
Expand Down
9 changes: 9 additions & 0 deletions tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,15 @@ def test_slot_flat_equivalence(tmp_path):
assert getattr(flat, attr) == getattr(slot, attr), attr


def test_slots_take_any_mapping():
"""The slots are typed Mapping so the exported TypedDicts pass a
checker; the resolvers must accept what the annotation admits — and
read-only proxies prove they never mutate the caller's mapping."""
client = PageIndexClient(index=types.MappingProxyType({"api_key": "pi-k"}),
chat=types.MappingProxyType({"model": "m"}))
assert (client.api_key, client.chat_model) == ("pi-k", "m")


def test_same_side_double_spelling_rejected():
for kwargs in ({"api_key": "k", "index": {"api_key": "k"}},
{"index": "m", "index_model": "m"},
Expand Down
Loading