Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
137 commits
Select commit Hold shift + click to select a range
6b343f5
feat: agent tools — the cloud MCP tool contract on the client
rejojer Aug 9, 2026
8737799
fix: agent tools review — next_steps order, resolve caching, error se…
rejojer Aug 10, 2026
2ba9035
fix: agent tools review 2 — bridge thread safety, browse paging, meta…
rejojer Aug 10, 2026
3f13158
fix: agent tools review 3 — page-span cap, duplicate names, wait resi…
rejojer Aug 10, 2026
40b1706
feat: surface the stored document name from submit_document
rejojer Aug 10, 2026
bf9e6da
fix: add missing page_list kwarg in duplicate-name test mock
rejojer Aug 11, 2026
dece6e6
revert: keep README.md unchanged from main — SDK section deferred
rejojer Aug 11, 2026
3c37cdc
feat: serve cloud agent instructions live from the MCP server
rejojer Aug 11, 2026
50fd618
fix: local relevance sort answers honestly instead of imitating
rejojer Aug 11, 2026
f1301e7
docs: note the cloud+Claude instructions duplication trade-off in as_…
rejojer Aug 11, 2026
20ed81f
fix: unsupported-capability envelopes say local-mode-yet, point to cloud
rejojer Aug 11, 2026
8dc929f
fix: local tool descriptions pre-announce cloud-only capabilities
rejojer Aug 11, 2026
2b929ee
refactor: localized tool guidance replaces the appended LOCAL MODE note
rejojer Aug 11, 2026
e790c37
feat: hide cloud-only parameters from the local tool surface
rejojer Aug 11, 2026
1fa3eb7
fix: incremental-review findings — bridge cache, guards, envelope drift
rejojer Aug 11, 2026
63b767f
chore: keep the demo's doc_id cache file out of the repo
rejojer Aug 11, 2026
6c9fe2e
test: live envelope field-parity guard against cloud response drift
rejojer Aug 11, 2026
a45b554
feat: local chat — three protocol surfaces over the agent tools (v0.2…
rejojer Aug 11, 2026
daac9d2
fix: local-chat review findings — truncation, serialization, streams
rejojer Aug 11, 2026
4590dd8
feat: as_anthropic_tools — Anthropic tool-runner export, both modes
rejojer Aug 11, 2026
02022df
fix: as_anthropic_tools review findings — async flavor, schema isolation
rejojer Aug 11, 2026
adb2f1f
docs: doc_id is per-call table-setting — keep it identical across a c…
rejojer Aug 11, 2026
3f1919b
feat: every chat surface takes a bare query string
rejojer Aug 11, 2026
d28a8af
feat: messages() defaults max_tokens to 4096
rejojer Aug 11, 2026
2de3b18
fix: raise messages() max_tokens default to 8192
rejojer Aug 11, 2026
2607a86
fix: restore per-extra skip markers the string-input tests displaced
rejojer Aug 11, 2026
d87fa89
fix: close 17 findings from the v0.2.10 max review
rejojer Aug 12, 2026
dbe5857
feat: one-call config bundles for every bring-your-own-framework surface
rejojer Aug 12, 2026
b135711
fix: three more review findings — partial-read reporting, reply corre…
rejojer Aug 12, 2026
eb1a230
feat: gate the config-handoff surfaces by the read-only MCP endpoint
rejojer Aug 12, 2026
5f35a33
fix: chat_completions wraps framework exceptions like responses()
rejojer Aug 12, 2026
9d16dbf
fix: same-name documents in different folders no longer refuse doc_id…
rejojer Aug 12, 2026
ec58189
Revert "fix: same-name documents in different folders no longer refus…
rejojer Aug 12, 2026
001493c
docs: state the name-uniqueness invariant in library terms
rejojer Aug 12, 2026
ec1fd84
docs: trim doc_targeting_block docstring to the contract
rejojer Aug 12, 2026
b14af33
fix: compress out-of-range page lists in get_page_content
rejojer Aug 12, 2026
f523485
docs: messages() marks only the managed prefix with cache_control
rejojer Aug 12, 2026
166c60c
docs: _stream_sync cancels on close, not on abandonment
rejojer Aug 12, 2026
18171d6
fix: raise the openai-agents floor to 0.14.0
rejojer Aug 12, 2026
9f67fdd
fix: enforce doc_id at the tool layer in the framework config helpers
rejojer Aug 12, 2026
66912c6
fix: two chat findings — final-turn append and cache-key seeding
rejojer Aug 12, 2026
31c9150
fix: six review findings — pagination, compat, and containment
rejojer Aug 12, 2026
32c4940
fix: five secondary review findings — containment and guards
rejojer Aug 12, 2026
ce1deaf
fix: three review findings — protocol honesty, model echo, containment
rejojer Aug 12, 2026
adeb209
fix: config bundles use the scoped shadow check their tools earned
rejojer Aug 12, 2026
2b62194
docs: as_openai_tools' remote-MCP note moves to the Cloud paragraph
rejojer Aug 12, 2026
b997e3b
fix: nine review findings — argument coercion, scope, and honest enve…
rejojer Aug 12, 2026
bcd0bf4
fix: two chat findings — protocol terminal states, provider error types
rejojer Aug 12, 2026
bc25f72
fix: guided failure for unknown LiteLLM providers, non-object call_to…
rejojer Aug 12, 2026
031d411
fix: wrap litellm import in PageIndexAPIError when not installed
rejojer Aug 12, 2026
1c863fa
fix: silence CodeQL findings — merge implicit string concat, drop unu…
rejojer Aug 12, 2026
15eecee
fix: two external review findings — init-notification race, SDK floor
rejojer Aug 12, 2026
ce0dbf0
refactor: drop the unused exc parameter from _wrap_max_turns
rejojer Aug 12, 2026
f58cca1
fix: raise the anthropic and openai-agents floors past broken releases
rejojer Aug 12, 2026
c88f9d0
test: cover max_turns wrapping on every chat surface
rejojer Aug 12, 2026
eebed64
fix: four review findings — envelope size honesty, contained tool errors
rejojer Aug 12, 2026
9001ef9
fix: conformant responses() envelope — official output, transcript in…
rejojer Aug 13, 2026
4888986
fix: stale anthropic>=0.84.0 hints — the supported floor is 0.108.0
rejojer Aug 13, 2026
f521fe7
docs: disclose the bridge's binary-stub behavior on the two image-adv…
rejojer Aug 13, 2026
56c28b7
refactor: trim the envelope-change comments to the essential constraint
rejojer Aug 13, 2026
fee6890
fix: declare the real python floor — >=3.10
rejojer Aug 13, 2026
525caa4
chore: trim rationale comments from this session's commits
rejojer Aug 13, 2026
db78209
chore: trim non-essential comments across the PR
rejojer Aug 13, 2026
1a97218
fix: break the phantom exception chain in _run_sync
rejojer Aug 13, 2026
d3880c6
feat: Flash with full optimization becomes the default local indexing…
rejojer Aug 13, 2026
97d4c41
feat: chat() — the answer-out front door over chat_completions
rejojer Aug 14, 2026
73c6a00
feat: cache-mark the managed prefix on anthropic-routed LiteLLM models
rejojer Aug 14, 2026
9f14287
refactor: cache predicate asks litellm's own provider resolution
rejojer Aug 14, 2026
97cfc06
feat: extend cache marking to Claude on Bedrock and Vertex — both liv…
rejojer Aug 14, 2026
797d545
fix: point anthropic-extra users at messages(); guard the two silent …
rejojer Aug 14, 2026
a098dd7
docs: the max-tokens table is a closed set — litellm's map prunes EOL…
rejojer Aug 14, 2026
152ddbe
chore: trim the max-tokens docstring to the contract
rejojer Aug 14, 2026
f059853
docs: state the local text-only history contract — cloud forwards too…
rejojer Aug 14, 2026
c78c01f
feat: openai-agents becomes a base dependency — the chat engine ships…
rejojer Aug 14, 2026
75a59c8
docs: drop the demo's install step — openai-agents ships with the SDK…
rejojer Aug 14, 2026
ad239a9
feat: the chat lane routes every model through LiteLLM — bare names i…
rejojer Aug 16, 2026
82e3354
refactor: no prefix-triggered direct lane — chat model names are Lite…
rejojer Aug 16, 2026
1ecc322
fix: third-audit findings — extra_body naming drift, litellm floor hint
rejojer Aug 16, 2026
0307a2c
test: pin the bundle door's Agents-SDK model grammar; rename the cach…
rejojer Aug 16, 2026
d411e5a
refactor: name the retrieve_model helper for its reason — the Agents …
rejojer Aug 16, 2026
4ee6a5e
test: the bundle-grammar test skips without openai-agents, like its f…
rejojer Aug 16, 2026
7244ee4
feat: index_model + chat_model — two-knob model surface with full leg…
rejojer Aug 16, 2026
d0ca51d
feat: --index-model on the CLI; README flag docs follow
rejojer Aug 16, 2026
96c5330
feat: default chat model becomes gpt-5.6-sol
rejojer Aug 16, 2026
a9f5cb0
chore: trim the model-keys comment to the constraint
rejojer Aug 16, 2026
c7c3f6c
feat: per-door reasoning passthrough — reasoning_effort / reasoning /…
rejojer Aug 16, 2026
7ee64aa
feat: extra_body escape hatch on the three protocol doors
rejojer Aug 16, 2026
c5e8326
chore: litellm floor 1.84 -> 1.97.0
rejojer Aug 16, 2026
347fdd1
feat: named top_p/max_tokens on the chat door, max_output_tokens on r…
rejojer Aug 16, 2026
8742101
chore: the missing-key error names chat_model as the other exit
rejojer Aug 16, 2026
6e7d826
fix: rebuild litellm's Message/Delta types on Python 3.10
rejojer Aug 16, 2026
4d345bd
feat: chat() takes reasoning_effort — the front door's one thinking knob
rejojer Aug 16, 2026
7f9b1a4
feat: backend connection overrides; extra_headers on every local door
rejojer Aug 17, 2026
e13332d
docs: constructor Args list gains index_backend / chat_backend
rejojer Aug 17, 2026
0553ac2
chore: three audit leftovers
rejojer Aug 17, 2026
4430eba
chore: narrow the anthropic wrap to TypeError; key advice names chat_…
rejojer Aug 17, 2026
7cbc3be
fix: the bundle's per-call model override speaks chat_model's grammar
rejojer Aug 17, 2026
57a77f6
fix: support pypdfium2 5.x (GetFontName removed, bookmark API changed)
rejojer Aug 17, 2026
6a9104a
fix: one openai client per backend, not one per call
rejojer Aug 17, 2026
c4ffa31
fix: reassemble UTF-16 surrogate pairs from PDFium's FPDFText_GetUnicode
rejojer Aug 17, 2026
8e059aa
fix: CLI flash precheck resolves the default model; summarize_tree st…
rejojer Aug 17, 2026
78d44b4
fix: the indexing lane routes every model through LiteLLM — direct la…
rejojer Aug 17, 2026
a8f6ffd
fix: four review findings — empty doc_id refused, backend spelling, c…
rejojer Aug 17, 2026
959be4a
fix: seven review findings across the bridge, envelope, stream, store…
rejojer Aug 17, 2026
4cdc90e
chore: trim rationale comments to the essentials — the why lives in t…
rejojer Aug 17, 2026
d429fe4
chore: deprecation notice when the CHATGPT_API_KEY alias fires
rejojer Aug 17, 2026
7720558
fix: connection reuse, one preload thread, runner max_turns gate, des…
rejojer Aug 17, 2026
d0007c2
refactor: the image-sentence edit anchors on the tool name, not the w…
rejojer Aug 17, 2026
535d75b
refactor: five cleanup findings — one synthesizer, one parser, one lo…
rejojer Aug 17, 2026
28eaab8
chore: dotenv-proof the chat missing-key test; page_num falsy-zero re…
rejojer Aug 17, 2026
228426d
fix: close the pypdfium2 dual-semantics window — floor >=5, sentinel …
rejojer Aug 18, 2026
5434111
fix: the missing-key pre-check covers only OpenAI-shaped models
rejojer Aug 18, 2026
86d6833
docs: page_index_flash's opening claim scoped to what is actually LLM…
rejojer Aug 18, 2026
2570cfa
chore: __init__.py diff carries only the lines the feature needed
rejojer Aug 18, 2026
03ffab3
fix: ten review findings — swallowed 401s, key gate, tool envelopes, …
rejojer Aug 18, 2026
1995842
chore: drop LocalAPI's dead retrieve_model plumbing
rejojer Aug 18, 2026
db3a007
fix: optimize='full' without a key fails fast with a guided error
rejojer Aug 18, 2026
7abf88a
fix: five review findings — one doc fetch, safe id reads, docstrings …
rejojer Aug 18, 2026
c129c2e
fix: guard _usage_sums against None token counts from non-standard ba…
rejojer Aug 18, 2026
86c257d
fix: raise non-stream chat_completions timeout to 600s
rejojer Aug 18, 2026
fc2dd3c
fix: three review findings — page budget in emitted units, demo cache…
rejojer Aug 18, 2026
b988fc6
docs: two review wording fixes — agent_tools() read-only default, REA…
rejojer Aug 18, 2026
39b13b5
fix: annotate mixed-type dict literals for Pyright
rejojer Aug 18, 2026
3b71c70
fix: three review findings — surrogate census, bridge bool coercion, …
rejojer Aug 18, 2026
4563223
fix: nine approved review cleanups + arm the live drift tests
rejojer Aug 18, 2026
50d86bf
fix: drop mcp internal API usage that broke on mcp 2.0
rejojer Aug 18, 2026
16bd56f
fix: three review findings — transport-error fidelity, cache key doc …
rejojer Aug 18, 2026
c57da99
fix: re-arm the claude_agent_config doc-scope guard, correct a stale …
rejojer Aug 18, 2026
594c03d
fix: stop spawn workers from re-running unguarded caller scripts
rejojer Aug 18, 2026
914dc43
fix: ten review findings — doc_id scoping, honest chat envelopes, thi…
rejojer Aug 19, 2026
49a24e1
fix: twelve max-review findings — shadow guard restored, caller trans…
rejojer Aug 19, 2026
0667e3b
ci: dev tags publish to PyPI only — skip the GitHub Release
rejojer Aug 19, 2026
c0c715c
fix: six review findings — poison docs rejected, lone surrogates scru…
rejojer Aug 20, 2026
3504664
fix: drop the inverted cache-seeding kwarg, true up five doc contracts
rejojer Aug 20, 2026
5c48ad8
chore: trim the new comments to essentials
rejojer Aug 20, 2026
f4fc566
fix: openai_agent_config bundles the Claude cache marks its doc promised
rejojer Aug 20, 2026
a15e915
feat: model_settings and name become openai_agent_config parameters
rejojer Aug 20, 2026
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
6 changes: 5 additions & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ name: Publish to PyPI
# 2. git push origin v0.2.9
# 3. This workflow derives the version from the tag, injects it into
# pyproject.toml, builds, publishes to PyPI via OIDC trusted publishing
# (no stored secret), and creates a GitHub Release with generated notes.
# (no stored secret), and creates a GitHub Release with generated notes
# (dev tags publish to PyPI only — no GitHub Release).
#
# The tag must be a PEP 440 version with a leading `v`:
# v0.2.9 v0.2.9rc1 v0.2.9.dev1
Expand Down Expand Up @@ -76,6 +77,9 @@ jobs:
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # release/v1.14.0

- name: Create GitHub Release
# dev builds need an explicit pin to install; a Release for them
# only doubles the feed next to the stable that follows.
if: ${{ !contains(github.ref_name, 'dev') }}
uses: softprops/action-gh-release@718ea10b132b3b2eba29c1007bb80653f286566b # v3.0.1
with:
tag_name: ${{ github.ref_name }}
Expand Down
5 changes: 4 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ jobs:
python-version: ${{ matrix.python-version }}
cache: pip
- run: pip install -r requirements.txt pytest
- if: matrix.agent-frameworks == 'without'
# requirements.txt carries it; this leg tests the no-framework paths
run: pip uninstall -y openai-agents
- if: matrix.agent-frameworks == 'with'
run: pip install openai-agents claude-agent-sdk
run: pip install openai-agents claude-agent-sdk anthropic
- run: python -m pytest -q
15 changes: 6 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,10 +173,11 @@ python3 run_pageindex.py --pdf_path /path/to/your/document.pdf
<details>
<summary>Optional parameters</summary>
<br>
You can customize the processing with additional optional arguments:
You can customize the processing with additional optional arguments (the structure-tuning flags from <code>--toc-check-pages</code> down require <code>--mode standard</code>):

```
--model LLM model to use (default: gpt-4o-2024-11-20)
--mode Processing mode: flash (default) or standard
--index-model LLM model used to index the document (default: gpt-5.6-luna)
--toc-check-pages Pages to check for table of contents (default: 20)
--max-pages-per-node Max pages per node (default: 10)
--max-tokens-per-node Max tokens per node (default: 20000)
Expand All @@ -199,23 +200,19 @@ python3 run_pageindex.py --md_path /path/to/your/document.md
</details>

> ### ⚡ PageIndex Flash *(preview)*
> **PageIndex Flash** ([`pageindex/flash`](pageindex/flash)) generates tree structures from PDFs in seconds. Structure extraction is purely heuristic-based, no LLM needed. LLM is only used to generate node summaries.
> **PageIndex Flash** ([`pageindex/flash`](pageindex/flash)) generates tree structures from PDFs in seconds. Structure extraction is purely heuristic-based, no LLM needed. An LLM is used only for node summaries and the optimization's expansion pass.
>
> ```bash
> python3 run_pageindex.py --flash --pdf_path /path/to/your/document.pdf
> python3 run_pageindex.py --mode flash --pdf_path /path/to/your/document.pdf
> ```
>
> Add `--optimize` to refine the tree structure for more efficient retrieval (with an LLM expansion pass).
> Tree optimization for retrieval (a deterministic merge, then an LLM expansion pass) is on by default; pass `--optimize off` to disable.

## 🚀 Agentic Vectorless RAG: An Example

For a simple, end-to-end **agentic vectorless RAG** example using **self-hosted PageIndex** (with OpenAI Agents SDK), see [`examples/agentic_vectorless_rag_demo.py`](examples/agentic_vectorless_rag_demo.py).

```bash
# Install optional dependency
pip3 install openai-agents

# Run the demo
python3 examples/agentic_vectorless_rag_demo.py
```

Expand Down
66 changes: 17 additions & 49 deletions examples/agentic_vectorless_rag_demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,33 +6,33 @@
chunking, PageIndex builds a hierarchical tree index and uses agentic LLM
reasoning for human-like, context-aware retrieval.

Agent tools:
- get_document() — document metadata (status, page count, etc.)
- get_document_structure() — tree structure index of a document
- get_page_content() — retrieve text content of specific pages
The agent tools come straight from the SDK — ``client.as_openai_tools()``
exposes the PageIndex tool contract (browse_documents, get_document,
get_document_structure, get_page_content) and ``client.agent_instructions()``
provides the retrieval playbook, so the whole agent is a few lines. Swap
``PageIndexLocalClient()`` for ``PageIndexCloudClient(api_key=...)`` and the
same code runs against the cloud.

Steps:
1 — Index a PDF locally and view its tree structure index
2 — View document metadata
3 — Ask a question (agent reasons over the index and auto-calls tools)

Requirements: pip install openai-agents; OPENAI_API_KEY in the environment.
Requirements: pip install pageindex; OPENAI_API_KEY in the environment.
"""
import sys
import json
import asyncio
import concurrent.futures
from pathlib import Path
import requests

sys.path.insert(0, str(Path(__file__).parent.parent))

from agents import Agent, Runner, function_tool, set_tracing_disabled
from agents.model_settings import ModelSettings
from agents import Agent, Runner, set_tracing_disabled
from agents.stream_events import RawResponsesStreamEvent, RunItemStreamEvent
from openai.types.responses import ResponseTextDeltaEvent, ResponseReasoningSummaryTextDeltaEvent

from pageindex import PageIndexClient
from pageindex import PageIndexLocalClient
import pageindex.utils as utils

PDF_URL = "https://arxiv.org/pdf/2603.15031"
Expand All @@ -41,48 +41,18 @@
PDF_PATH = _EXAMPLES_DIR / "documents" / "attention-residuals.pdf"
STORAGE_PATH = _EXAMPLES_DIR / ".pageindex"

AGENT_SYSTEM_PROMPT = """
You are PageIndex, a document QA assistant.
TOOL USE:
- Call get_document() first to confirm status and page count.
- Call get_document_structure() to identify relevant page ranges.
- Call get_page_content(pages="5-7") with tight ranges; never fetch the whole document.
- Before each tool call, output one short sentence explaining the reason.
Answer based only on tool output. Be concise.
"""


def query_agent(client: PageIndexClient, doc_id: str, prompt: str, verbose: bool = False) -> str:
def query_agent(client: PageIndexLocalClient, doc_id: str, prompt: str, verbose: bool = False) -> str:
"""Run a document QA agent using the OpenAI Agents SDK.

Streams text output token-by-token and returns the full answer string.
Tool calls are always printed; verbose=True also prints arguments and output previews.
"""

@function_tool
def get_document() -> str:
"""Get document metadata: status, page count, name, and description."""
return json.dumps(client.get_document(doc_id))

@function_tool
def get_document_structure() -> str:
"""Get the document's full tree structure (without text) to find relevant sections."""
return json.dumps(client.get_document_structure(doc_id), ensure_ascii=False)

@function_tool
def get_page_content(pages: str) -> str:
"""
Get the text content of specific pages.
Use tight ranges: e.g. '5-7' for pages 5 to 7, '3,8' for pages 3 and 8, '12' for page 12.
"""
return json.dumps(client.get_page_content(doc_id, pages), ensure_ascii=False)

agent = Agent(
name="PageIndex",
instructions=AGENT_SYSTEM_PROMPT,
tools=[get_document, get_document_structure, get_page_content],
model=getattr(client, "retrieve_model", None),
# model_settings=ModelSettings(reasoning={"effort": "low", "summary": "auto"}), # Uncomment to enable reasoning
**client.openai_agent_config(
doc_id=doc_id,
# model_settings=ModelSettings(reasoning={"effort": "low", "summary": "auto"}), # from agents.model_settings import ModelSettings
),
)

async def _run():
Expand Down Expand Up @@ -152,21 +122,19 @@ async def _run():
print("Download complete.\n")

# Setup: local mode — no PageIndex API key needed, your LLM key does the work
client = PageIndexClient(storage_path=str(STORAGE_PATH))
client = PageIndexLocalClient(storage_path=str(STORAGE_PATH))

# Step 1: Index PDF and view tree structure
print("=" * 60)
print("Step 1: Index PDF and view tree structure")
print("=" * 60)
doc_id = next(
(doc["id"] for doc in client.list_documents(limit=100)["documents"]
if doc["name"] == PDF_PATH.name),
None,
)
if doc["name"] == PDF_PATH.name), None)
if doc_id:
print(f"\nLoaded cached doc_id: {doc_id}")
else:
doc_id = client.submit_document(str(PDF_PATH))["doc_id"]
doc_id = client.submit_document(str(PDF_PATH), wait=True)["doc_id"]
print(f"\nIndexed. doc_id: {doc_id}")
print("\nTree Structure (top-level sections):")
structure = client.get_tree(doc_id, node_summary=True)["result"]
Expand Down
7 changes: 4 additions & 3 deletions pageindex/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@
"optimize_tree": ".tree_optimize",
"md_to_tree": ".page_index_md",
}
_SUBMODULES = {"client", "cloud_api", "errors", "flash", "local_api",
"local_store", "page_index_classic", "page_index_md", "tree_optimize",
"utils"}
_SUBMODULES = {"agent_tools", "client", "cloud_api", "errors", "flash",
"integrations", "local_api", "local_chat", "local_store",
"mcp_bridge", "page_index_classic", "page_index_md",
"tree_optimize", "utils"}


def __getattr__(name):
Expand Down
10 changes: 10 additions & 0 deletions pageindex/_version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
"""Installed-package version, shared by every surface that reports it upstream."""
from __future__ import annotations


def sdk_version() -> str:
try:
from importlib.metadata import version
return version("pageindex")
except Exception:
return "0.0.0"
Loading