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
15 changes: 11 additions & 4 deletions .github/workflows/ci-python.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: Python CI

on:
push:
branches: [main]
branches: [ main ]
paths:
- "pctx-py/**"
- ".github/workflows/ci-python.yaml"
Expand All @@ -21,7 +21,7 @@ defaults:
working-directory: pctx-py

jobs:
lint:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
Expand All @@ -37,19 +37,26 @@ jobs:
python-version: "3.10"

- name: Install dependencies
run: uv sync --group dev
id: install
run: uv sync --all-extras --all-groups

- name: Check formatting
if: ${{ !cancelled() && steps.install.outcome == 'success' }}
run: uv run ruff format --check .

- name: Run linter
if: ${{ !cancelled() && steps.install.outcome == 'success' }}
run: uv run ruff check .

- name: Type check
if: ${{ !cancelled() && steps.install.outcome == 'success' }}
run: uv run ty check src

test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13"]
python-version: [ "3.10", "3.11", "3.12", "3.13" ]
steps:
- uses: actions/checkout@v4

Expand Down
26 changes: 20 additions & 6 deletions pctx-py/Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.PHONY: help install test test-integration format build docs
.PHONY: help install test test-integration fmt lint typecheck check build docs

# Default target - show help when running just 'make'
.DEFAULT_GOAL := help
Expand All @@ -7,17 +7,20 @@ help:
@echo "pctx-py dev scripts"
@echo ""
@echo "Available targets:"
@echo " make install - Install dev dependencies and all extras with uv"
@echo " make install - Install all dependency groups and all extras with uv"
@echo " make test - Run Python client tests"
@echo " make test-integration - Run Python client tests with integration testing"
@echo " make format - Format and lint Python code with ruff"
@echo " make fmt - Format and auto-fix lints with ruff"
@echo " make lint - Lint Python code with ruff"
@echo " make typecheck - Type-check Python code with ty"
@echo " make check - Run lint, typecheck, and tests"
@echo " make build - Build Python package (resolves symlinks before build)"
@echo " make docs - Build Python Sphinx documentation"
@echo ""

# Install with dev deps and all extras
# Install with all dependency groups (dev, docs) and all extras
install:
@uv sync --all-extras
@uv sync --all-extras --all-groups

# Run Python client tests
test:
Expand All @@ -27,9 +30,20 @@ test:
test-integration:
@uv run pytest tests/ --integration -v

format:
fmt:
@uv run ruff format . && uv run ruff check . --fix

# Lint with ruff (no auto-fix)
lint:
@uv run ruff check .

# Type-check with ty
typecheck:
@uv run ty check src

# Run lint, typecheck, and tests
check: lint typecheck test

# Build Python package (resolves _tool_descriptions/data symlink before build, restores after)
build:
@../scripts/build-python.sh
Expand Down
1 change: 1 addition & 0 deletions pctx-py/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ dev = [
"litellm>=1.80.8",
"langchain-openai>=0.3.0",
"mcp>=1.25.0",
"ty>=0.0.34",
]

[tool.ruff.lint.pydocstyle]
Expand Down
16 changes: 8 additions & 8 deletions pctx-py/src/pctx_client/_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,13 @@
from crewai.tools import BaseTool as CrewAiBaseTool
from langchain_core.tools import BaseTool as LangchainBaseTool
from pydantic_ai.tools import Tool as PydanticAITool
from Stemmer import Stemmer
from Stemmer import Stemmer # ty: ignore[unresolved-import]
except ImportError:
pass

try:
from bm25s import BM25, tokenize
from Stemmer import Stemmer
from Stemmer import Stemmer # ty: ignore[unresolved-import]
except ImportError:
pass

Expand Down Expand Up @@ -864,7 +864,7 @@ def claude_agent_sdk_tools(
"Claude Agent SDK is not installed. Install it with: pip install pctx[claude]"
) from e

def _text_content_block(val: str) -> dict:
def _text_content_block(val: str) -> dict[str, Any]:
return {"content": [{"type": "text", "text": val}]}

# build all tools
Expand All @@ -873,7 +873,7 @@ def _text_content_block(val: str) -> dict:
get_tool_description("execute_bash", overrides=descriptions),
ExecuteBashInput.model_json_schema(),
)
async def execute_bash(args: dict[str, Any]) -> str:
async def execute_bash(args: dict[str, Any]) -> dict[str, Any]:
tool_input = ExecuteBashInput(**args)
bash_out = await self.execute_bash(tool_input.command)
return _text_content_block(bash_out.markdown())
Expand All @@ -885,7 +885,7 @@ async def execute_bash(args: dict[str, Any]) -> str:
),
ExecuteTypescriptInput.model_json_schema(),
)
async def execute_typescript(args: dict[str, Any]) -> str:
async def execute_typescript(args: dict[str, Any]) -> dict[str, Any]:
tool_input = ExecuteTypescriptInput(**args)
exec_out = await self.execute_typescript(
tool_input.code, disclosure=disclosure
Expand All @@ -897,7 +897,7 @@ async def execute_typescript(args: dict[str, Any]) -> str:
get_tool_description("list_functions", overrides=descriptions),
{"type": "object"},
)
async def list_functions(_args: dict[str, Any]) -> str:
async def list_functions(_args: dict[str, Any]) -> dict[str, Any]:
listed = await self.list_functions()
return _text_content_block(listed.code)

Expand All @@ -906,7 +906,7 @@ async def list_functions(_args: dict[str, Any]) -> str:
get_tool_description("get_function_details", overrides=descriptions),
GetFunctionDetailsInput.model_json_schema(),
)
async def get_function_details(args: dict[str, Any]) -> str:
async def get_function_details(args: dict[str, Any]) -> dict[str, Any]:
tool_input = GetFunctionDetailsInput(**args)
details = await self.get_function_details(tool_input.functions)
return _text_content_block(details.code)
Expand All @@ -920,7 +920,7 @@ class SearchFunctionsInput(BaseModel):
get_tool_description("search_functions", overrides=descriptions),
SearchFunctionsInput.model_json_schema(),
)
async def search_functions(args: dict[str, Any]) -> str:
async def search_functions(args: dict[str, Any]) -> dict[str, Any]:
print(f"Claude fn called search_functions: {args}")
tool_input = SearchFunctionsInput(**args)
functions = await self.search_functions(tool_input.query, tool_input.k)
Expand Down
6 changes: 3 additions & 3 deletions pctx-py/src/pctx_client/_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ class BaseTool(BaseModel):
),
)

_input_validator: Draft202012Validator | None = PrivateAttr(default=None)
_output_validator: Draft202012Validator | None = PrivateAttr(default=None)
_input_validator: Draft202012Validator | None = PrivateAttr(default=None) # ty: ignore[invalid-type-form]
_output_validator: Draft202012Validator | None = PrivateAttr(default=None) # ty: ignore[invalid-type-form]

@model_validator(mode="after")
def _compile_schema_validators(self) -> "BaseTool":
Expand Down Expand Up @@ -130,7 +130,7 @@ def from_func(

docstring = parse_docstring(textwrap.dedent(description or func.__doc__ or ""))

name_ = name or func.__name__
name_ = name or func.__name__ # ty: ignore[unresolved-attribute]

if input_schema is None:
in_schema = infer_input_model(f"{name_}_Input", func, docstring=docstring)
Expand Down
4 changes: 2 additions & 2 deletions pctx-py/src/pctx_client/_websocket_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ async def _connect(self, code_mode_session: str):
try:
headers = {
"x-code-mode-session": code_mode_session,
"x-pctx-api-key": self._api_key,
"x-pctx-api-key": self._api_key or "",
}
self.ws = await websockets.connect(self.url, additional_headers=headers)
except Exception as e:
Expand Down Expand Up @@ -143,7 +143,7 @@ async def execute_typescript(
request = ExecuteCodeRequest(
id=request_id,
method="execute_typescript",
params=ExecuteCodeParams(code=code, style=disclosure),
params=ExecuteCodeParams(code=code, disclosure=disclosure),
)

try:
Expand Down
7 changes: 4 additions & 3 deletions pctx-py/src/pctx_client/descriptions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def get_tool_description(
tool_name: ToolName,
disclosure: ToolDisclosure | ToolDisclosureName = ToolDisclosure.CATALOG,
version: int = 1,
overrides: dict[ToolName, str] = None,
overrides: dict[ToolName, str] | None = None,
) -> str:
"""Load a tool description string from the data directory.

Expand All @@ -47,11 +47,12 @@ def get_tool_description(
if overrides is not None and tool_name in overrides:
return overrides[tool_name]

path_segment = tool_name
if tool_name == "execute_typescript":
tool_name = f"{tool_name}_{disclosure.value}"
path_segment = f"{tool_name}_{disclosure.value}"

return (
files("pctx_client.descriptions.data.tools") / tool_name / f"v{version}.txt"
files("pctx_client.descriptions.data.tools") / path_segment / f"v{version}.txt"
).read_text(encoding="utf-8")


Expand Down
2 changes: 1 addition & 1 deletion pctx-py/src/pctx_client/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class ToolDisclosure(str, Enum):
FS = "filesystem"
# SIDECAR = "sidecar" # <--- not fully supported by session server yet

def contains_tool(self, tool_name: ToolName) -> bool:
def contains_tool(self, tool_name: str) -> bool:
if self == ToolDisclosure.CATALOG:
allowed = {
"get_function_details",
Expand Down
26 changes: 26 additions & 0 deletions pctx-py/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading