diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index de0fcb0..15dc7e6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -48,7 +48,7 @@ jobs: - name: Update version in __init__.py run: | VERSION="${{ steps.version.outputs.version }}" - sed -i "s/^__version__ = \".*\"/__version__ = \"$VERSION\"/" sentience/__init__.py + sed -i "s/^__version__ = \".*\"/__version__ = \"$VERSION\"/" predicate/__init__.py - name: Verify extension files are present run: | @@ -56,12 +56,12 @@ jobs: # Check required extension files exist REQUIRED_FILES=( - "sentience/extension/manifest.json" - "sentience/extension/content.js" - "sentience/extension/background.js" - "sentience/extension/injected_api.js" - "sentience/extension/pkg/sentience_core.js" - "sentience/extension/pkg/sentience_core_bg.wasm" + "predicate/extension/manifest.json" + "predicate/extension/content.js" + "predicate/extension/background.js" + "predicate/extension/injected_api.js" + "predicate/extension/pkg/sentience_core.js" + "predicate/extension/pkg/sentience_core_bg.wasm" ) MISSING_FILES=() @@ -81,7 +81,7 @@ jobs: fi # Verify findTextRect function exists in injected_api.js - if ! grep -q "findTextRect:" sentience/extension/injected_api.js; then + if ! grep -q "findTextRect:" predicate/extension/injected_api.js; then echo "❌ Error: findTextRect function not found in injected_api.js" echo "The extension may be out of date. Please sync the extension before releasing." exit 1 @@ -89,7 +89,7 @@ jobs: echo "βœ… All extension files verified" echo "πŸ“¦ Extension files that will be included:" - find sentience/extension -type f | sort + find predicate/extension -type f | sort - name: Build package run: | @@ -117,10 +117,10 @@ jobs: # Check for required extension files in the wheel REQUIRED_IN_WHEEL=( - "sentience/extension/manifest.json" - "sentience/extension/injected_api.js" - "sentience/extension/pkg/sentience_core.js" - "sentience/extension/pkg/sentience_core_bg.wasm" + "predicate/extension/manifest.json" + "predicate/extension/injected_api.js" + "predicate/extension/pkg/sentience_core.js" + "predicate/extension/pkg/sentience_core_bg.wasm" ) MISSING_IN_WHEEL=() @@ -140,14 +140,14 @@ jobs: fi # Verify findTextRect is in the packaged injected_api.js - if ! grep -q "findTextRect:" sentience/extension/injected_api.js; then + if ! grep -q "findTextRect:" predicate/extension/injected_api.js; then echo "❌ Error: findTextRect not found in packaged injected_api.js" exit 1 fi echo "βœ… All extension files verified in built package" echo "πŸ“¦ Extension files found in wheel:" - find sentience/extension -type f | sort + find predicate/extension -type f | sort # Cleanup rm -rf "$TEMP_DIR" @@ -166,11 +166,11 @@ jobs: tag_name: v${{ steps.version.outputs.version }} name: Release v${{ steps.version.outputs.version }} body: | - Release v${{ steps.version.outputs.version }} of sentienceapi + Release v${{ steps.version.outputs.version }} of predicatelabs ## Installation ```bash - pip install sentienceapi==${{ steps.version.outputs.version }} + pip install predicatelabs==${{ steps.version.outputs.version }} ``` draft: false prerelease: false diff --git a/.github/workflows/sync-extension.yml b/.github/workflows/sync-extension.yml index 4746ac3..c3aa3a7 100644 --- a/.github/workflows/sync-extension.yml +++ b/.github/workflows/sync-extension.yml @@ -1,4 +1,4 @@ -name: Sync Extension from sentience-chrome +name: Sync Extension from predicate-chrome on: repository_dispatch: @@ -6,7 +6,7 @@ on: workflow_dispatch: inputs: release_tag: - description: 'Release tag from sentience-chrome (e.g., v1.0.0)' + description: 'Release tag from predicate-chrome (e.g., v1.0.0)' required: true type: string schedule: @@ -147,7 +147,7 @@ jobs: if: steps.release.outputs.skip != 'true' run: | # Target directory in sdk-python (inside the package source) - TARGET_DIR="sentience/extension" + TARGET_DIR="predicate/extension" # Ensure target directory exists and is clean rm -rf "$TARGET_DIR" @@ -172,7 +172,7 @@ jobs: if: steps.release.outputs.skip != 'true' id: changes run: | - git add sentience/extension/ + git add predicate/extension/ if git diff --staged --quiet; then echo "No changes detected." @@ -196,10 +196,10 @@ jobs: uses: peter-evans/create-pull-request@v5 with: token: ${{ secrets.PR_TOKEN || secrets.GITHUB_TOKEN }} - commit-message: "chore: sync extension files from sentience-chrome ${{ steps.release.outputs.tag }}" + commit-message: "chore: sync extension files from predicate-chrome ${{ steps.release.outputs.tag }}" title: "Sync Extension: ${{ steps.release.outputs.tag }}" body: | - This PR syncs extension files from sentience-chrome release ${{ steps.release.outputs.tag }}. + This PR syncs extension files from predicate-chrome release ${{ steps.release.outputs.tag }}. **Files updated:** - Extension manifest and scripts diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4734810..3bd6a8d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -33,15 +33,15 @@ jobs: shell: bash run: | pip cache purge || true - pip uninstall -y sentienceapi || true + pip uninstall -y predicatelabs || true # Aggressively clean any bytecode cache (cross-platform Python approach) python -c "import pathlib; [p.unlink() for p in pathlib.Path('.').rglob('*.pyc')]" || true python -c "import pathlib, shutil; [shutil.rmtree(p) for p in pathlib.Path('.').rglob('__pycache__') if p.is_dir()]" || true - # Also clean .pyc files in sentience package specifically - find sentience -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || python -c "import pathlib, shutil; [shutil.rmtree(p) for p in pathlib.Path('sentience').rglob('__pycache__') if p.is_dir()]" || true - find sentience -name "*.pyc" -delete 2>/dev/null || python -c "import pathlib; [p.unlink() for p in pathlib.Path('sentience').rglob('*.pyc')]" || true + # Also clean .pyc files in predicate package specifically + find predicate -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || python -c "import pathlib, shutil; [shutil.rmtree(p) for p in pathlib.Path('predicate').rglob('__pycache__') if p.is_dir()]" || true + find predicate -name "*.pyc" -delete 2>/dev/null || python -c "import pathlib; [p.unlink() for p in pathlib.Path('predicate').rglob('*.pyc')]" || true # Ensure source uses assert_ (no auto-rewrite). - python -c "import sys; import io; sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace') if sys.platform == 'win32' else sys.stdout; content = open('sentience/agent_runtime.py', 'r', encoding='utf-8').read(); assert 'self.assertTrue(' not in content, 'Source file still has self.assertTrue('; print('OK: Source file verified: uses self.assert_()')" + python -c "import sys; import io; sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace') if sys.platform == 'win32' else sys.stdout; content = open('predicate/agent_runtime.py', 'r', encoding='utf-8').read(); assert 'self.assertTrue(' not in content, 'Source file still has self.assertTrue('; print('OK: Source file verified: uses self.assert_()')" # Force reinstall to ensure latest code pip install --no-cache-dir --force-reinstall -e ".[dev]" @@ -55,10 +55,10 @@ jobs: git branch --show-current || echo "Detached HEAD" echo "" echo "=== Verify editable install (should point to local source) ===" - python -c "import sentience; import os; fpath = sentience.__file__; print(f'Package location: {fpath}'); print(f'Is in current dir: {os.path.abspath(fpath).startswith(os.getcwd())}'); print(f'Points to source: {os.path.exists(fpath)}')" + python -c "import predicate; import os; fpath = predicate.__file__; print(f'Package location: {fpath}'); print(f'Is in current dir: {os.path.abspath(fpath).startswith(os.getcwd())}'); print(f'Points to source: {os.path.exists(fpath)}')" echo "" echo "=== Source file line 345 (from repo) ===" - sed -n '345p' sentience/agent_runtime.py || python -c "with open('sentience/agent_runtime.py', 'r') as f: lines = f.readlines(); print(lines[344] if len(lines) > 344 else 'NOT FOUND')" + sed -n '345p' predicate/agent_runtime.py || python -c "with open('predicate/agent_runtime.py', 'r') as f: lines = f.readlines(); print(lines[344] if len(lines) > 344 else 'NOT FOUND')" echo "" echo "=== Installed package assert_done (from imported module) ===" python << 'PYEOF' @@ -72,11 +72,11 @@ jobs: sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace') sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace') - from sentience.agent_runtime import AgentRuntime + from predicate.agent_runtime import AgentRuntime # Verify it's using local source - import sentience - pkg_path = os.path.abspath(sentience.__file__) + import predicate + pkg_path = os.path.abspath(predicate.__file__) cwd = os.getcwd() if not pkg_path.startswith(cwd): print(f'WARNING: Package is not from local source!') @@ -120,13 +120,13 @@ jobs: # Check agent_runtime.py line 345 print("=== Checking agent_runtime.py line 345 ===") - with open('sentience/agent_runtime.py', 'r', encoding='utf-8') as f: + with open('predicate/agent_runtime.py', 'r', encoding='utf-8') as f: lines = f.readlines() print(''.join(lines[339:350])) # Verify assert_ method exists print("\n=== Verifying assert_ method exists ===") - with open('sentience/agent_runtime.py', 'r', encoding='utf-8') as f: + with open('predicate/agent_runtime.py', 'r', encoding='utf-8') as f: lines = f.readlines() for i, line in enumerate(lines, 1): if 'def assert_' in line: @@ -135,7 +135,7 @@ jobs: # Check for problematic assertTrue patterns (should NOT exist) print("\n=== Checking for assertTrue patterns (should NOT exist) ===") import re - with open('sentience/agent_runtime.py', 'r', encoding='utf-8') as f: + with open('predicate/agent_runtime.py', 'r', encoding='utf-8') as f: content = f.read() # Check for self.assertTrue( - this is the bug if re.search(r'self\.assertTrue\s*\(', content): @@ -156,14 +156,14 @@ jobs: - name: Type check with mypy continue-on-error: true run: | - mypy sentience --ignore-missing-imports --no-strict-optional + mypy predicate --ignore-missing-imports --no-strict-optional - name: Check code style continue-on-error: true run: | - black --check sentience tests --line-length=100 - isort --check-only --profile black sentience tests - flake8 sentience tests --max-line-length=100 --extend-ignore=E203,W503,E501 --max-complexity=15 + black --check predicate tests --line-length=100 + isort --check-only --profile black predicate tests + flake8 predicate tests --max-line-length=100 --extend-ignore=E203,W503,E501 --max-complexity=15 - name: Build extension (if needed) if: runner.os != 'Windows' @@ -192,7 +192,7 @@ jobs: sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace') # Check the source file directly (not the installed package) - file_path = 'sentience/agent_runtime.py' + file_path = 'predicate/agent_runtime.py' if not os.path.exists(file_path): print(f'WARNING: {file_path} not found!') sys.exit(0) # Don't fail if file doesn't exist @@ -248,7 +248,7 @@ jobs: print("=== Final Pre-Test Verification ===") # First, verify the source file directly - source_file = 'sentience/agent_runtime.py' + source_file = 'predicate/agent_runtime.py' print(f"=== Checking source file: {source_file} ===") if not os.path.exists(source_file): print(f"ERROR: Source file {source_file} not found!") @@ -282,11 +282,11 @@ jobs: # Now check the installed package print("\n=== Checking installed package ===") - import sentience.agent_runtime + import predicate.agent_runtime # Verify it's using local source (editable install) - import sentience - pkg_path = os.path.abspath(sentience.__file__) + import predicate + pkg_path = os.path.abspath(predicate.__file__) cwd = os.getcwd() if not pkg_path.startswith(cwd): print(f'WARNING: Package is not from local source!') @@ -296,7 +296,7 @@ jobs: else: print(f'OK: Package is from local source: {pkg_path}') - src = inspect.getsource(sentience.agent_runtime.AgentRuntime.assert_done) + src = inspect.getsource(predicate.agent_runtime.AgentRuntime.assert_done) print("assert_done method source:") print(src) diff --git a/.gitignore b/.gitignore index fde0371..e11dc3c 100644 --- a/.gitignore +++ b/.gitignore @@ -61,6 +61,6 @@ Thumbs.db extension-temp/ playground/ -# Allow bundled extension assets under sentience/extension/dist -!sentience/extension/dist/ -!sentience/extension/dist/** +# Allow bundled extension assets under predicate/extension/dist +!predicate/extension/dist/ +!predicate/extension/dist/** diff --git a/CHANGELOG.md b/CHANGELOG.md index 113c113..91d6f00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,22 +10,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added #### Agent Tracing & Debugging -- **New Module: `sentience.tracing`** - Built-in tracing infrastructure for debugging and analyzing agent behavior +- **New Module: `predicate.tracing`** - Built-in tracing infrastructure for debugging and analyzing agent behavior - `Tracer` class for recording agent execution - `TraceSink` abstract base class for custom trace storage - `JsonlTraceSink` for saving traces to JSONL files - `TraceEvent` dataclass for structured trace events - Trace events: `step_start`, `snapshot`, `llm_query`, `action`, `step_end`, `error` -- **New Module: `sentience.agent_config`** - Centralized agent configuration +- **New Module: `predicate.agent_config`** - Centralized agent configuration - `AgentConfig` dataclass with defaults for snapshot limits, LLM settings, screenshot options -- **New Module: `sentience.utils`** - Snapshot digest utilities +- **New Module: `predicate.utils`** - Snapshot digest utilities - `compute_snapshot_digests()` - Generate SHA256 fingerprints for loop detection - `canonical_snapshot_strict()` - Digest including element text - `canonical_snapshot_loose()` - Digest excluding text (layout only) - `sha256_digest()` - Hash computation helper -- **New Module: `sentience.formatting`** - LLM prompt formatting +- **New Module: `predicate.formatting`** - LLM prompt formatting - `format_snapshot_for_llm()` - Convert snapshots to LLM-friendly text format -- **Schema File: `sentience/schemas/trace_v1.json`** - JSON Schema for trace events, bundled with package +- **Schema File: `predicate/schemas/trace_v1.json`** - JSON Schema for trace events, bundled with package #### Enhanced SentienceAgent - Added optional `tracer` parameter to `SentienceAgent.__init__()` for execution tracking @@ -40,8 +40,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Fixed linting errors across multiple files: - - `sentience/cli.py` - Removed unused variable `code` (F841) - - `sentience/inspector.py` - Removed unused imports (F401) + - `predicate/cli.py` - Removed unused variable `code` (F841) + - `predicate/inspector.py` - Removed unused imports (F401) - `tests/test_inspector.py` - Removed unused `pytest` import (F401) - `tests/test_recorder.py` - Removed unused imports (F401) - `tests/test_smart_selector.py` - Removed unused `pytest` import (F401) @@ -87,13 +87,13 @@ agent.act("Click the button") # Now traced! ```python # Tracing -from sentience.tracing import Tracer, JsonlTraceSink, TraceEvent, TraceSink +from predicate.tracing import Tracer, JsonlTraceSink, TraceEvent, TraceSink # Configuration -from sentience.agent_config import AgentConfig +from predicate.agent_config import AgentConfig # Utilities -from sentience.utils import ( +from predicate.utils import ( compute_snapshot_digests, canonical_snapshot_strict, canonical_snapshot_loose, @@ -101,7 +101,7 @@ from sentience.utils import ( ) # Formatting -from sentience.formatting import format_snapshot_for_llm +from predicate.formatting import format_snapshot_for_llm ``` ### Notes diff --git a/MANIFEST.in b/MANIFEST.in index 6dadf9c..5213d23 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,2 +1,2 @@ -include sentience/schemas/*.json -recursive-include sentience/extension *.js *.json *.wasm *.d.ts +include predicate/schemas/*.json +recursive-include predicate/extension *.js *.json *.wasm *.d.ts diff --git a/README.md b/README.md index 2da0e43..97f505d 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -# Sentience Python SDK +# Predicate Python SDK > **A verification & control layer for AI agents that operate browsers** -Sentience is built for **AI agent developers** who already use Playwright / CDP / browser-use / LangGraph and care about **flakiness, cost, determinism, evals, and debugging**. +Predicate is built for **AI agent developers** who already use Playwright / CDP / browser-use / LangGraph and care about **flakiness, cost, determinism, evals, and debugging**. Often described as *Jest for Browser AI Agents* - but applied to end-to-end agent runs (not unit tests). @@ -10,7 +10,7 @@ The core loop is: > **Agent β†’ Snapshot β†’ Action β†’ Verification β†’ Artifact** -## What Sentience is +## What Predicate is - A **verification-first runtime** (`AgentRuntime`) for browser agents - Treats the browser as an adapter (Playwright / CDP / browser-use); **`AgentRuntime` is the product** @@ -19,7 +19,7 @@ The core loop is: - Enables **local LLM small models (3B-7B)** for browser automation (privacy, compliance, and cost control) - Keeps vision models **optional** (use as a fallback when DOM/snapshot structure falls short, e.g. ``) -## What Sentience is not +## What Predicate is not - Not a browser driver - Not a Playwright replacement @@ -28,13 +28,13 @@ The core loop is: ## Install ```bash -pip install sentienceapi +pip install predicatelabs playwright install chromium ``` ## Conceptual example (why this exists) -In Sentience, agents don’t β€œhope” an action worked. +In Predicate, agents don’t β€œhope” an action worked. - **Every step is gated by verifiable UI assertions** - If progress can’t be proven, the run **fails with evidence** (trace + artifacts) @@ -47,9 +47,9 @@ This is the smallest useful pattern: snapshot β†’ assert β†’ act β†’ assert-done ```python import asyncio -from sentience import AgentRuntime, AsyncSentienceBrowser -from sentience.tracing import JsonlTraceSink, Tracer -from sentience.verification import exists, url_contains +from predicate import AgentRuntime, AsyncSentienceBrowser +from predicate.tracing import JsonlTraceSink, Tracer +from predicate.verification import exists, url_contains async def main() -> None: @@ -80,13 +80,13 @@ if __name__ == "__main__": ## SentienceDebugger: attach to your existing agent framework (sidecar mode) -If you already have an agent loop (LangGraph, browser-use, custom planner/executor), you can keep it and attach Sentience as a **verifier + trace layer**. +If you already have an agent loop (LangGraph, browser-use, custom planner/executor), you can keep it and attach Predicate as a **verifier + trace layer**. -Key idea: your agent still decides and executes actions β€” Sentience **snapshots and verifies outcomes**. +Key idea: your agent still decides and executes actions β€” Predicate **snapshots and verifies outcomes**. ```python -from sentience import SentienceDebugger, create_tracer -from sentience.verification import exists, url_contains +from predicate import SentienceDebugger, create_tracer +from predicate.verification import exists, url_contains async def run_existing_agent(page) -> None: @@ -108,10 +108,10 @@ async def run_existing_agent(page) -> None: ## SDK-driven full loop (snapshots + actions) -If you want Sentience to drive the loop end-to-end, you can use the SDK primitives directly: take a snapshot, select elements, act, then verify. +If you want Predicate to drive the loop end-to-end, you can use the SDK primitives directly: take a snapshot, select elements, act, then verify. ```python -from sentience import SentienceBrowser, snapshot, find, click, type_text, wait_for +from predicate import SentienceBrowser, snapshot, find, click, type_text, wait_for def login_example() -> None: @@ -168,10 +168,10 @@ def login_example() -> None: ## ToolRegistry (LLM-callable tools) -Sentience can expose a **typed tool surface** for agents (with tool-call tracing). +Predicate can expose a **typed tool surface** for agents (with tool-call tracing). ```python -from sentience.tools import ToolRegistry, register_default_tools +from predicate.tools import ToolRegistry, register_default_tools registry = ToolRegistry() register_default_tools(registry, runtime) # or pass a ToolContext @@ -185,7 +185,7 @@ tools_for_llm = registry.llm_tools() Chrome permission prompts are outside the DOM and can be invisible to snapshots. Prefer setting a policy **before navigation**. ```python -from sentience import AsyncSentienceBrowser, PermissionPolicy +from predicate import AsyncSentienceBrowser, PermissionPolicy policy = PermissionPolicy( default="clear", @@ -205,7 +205,7 @@ If your backend supports it, you can also use ToolRegistry permission tools (`gr If a flow is expected to download a file, assert it explicitly: ```python -from sentience.verification import download_completed +from predicate.verification import download_completed runtime.assert_(download_completed("report.csv"), label="download_ok", required=True) ``` @@ -215,16 +215,16 @@ runtime.assert_(download_completed("report.csv"), label="download_ok", required= - **Manual driver CLI** (inspect clickables, click/type/press quickly): ```bash -sentience driver --url https://example.com +predicate driver --url https://example.com ``` -- **Verification + artifacts + debugging with time-travel traces (Sentience Studio demo)**: +- **Verification + artifacts + debugging with time-travel traces (Predicate Studio demo)**: If the video tag doesn’t render in your GitHub README view, use this link: [`sentience-studio-demo.mp4`](https://github.com/user-attachments/assets/7ffde43b-1074-4d70-bb83-2eb8d0469307) -- **Sentience SDK Documentation**: https://www.sentienceapi.com/docs +- **Predicate SDK Documentation**: https://predicatelabs.dev/docs ## Integrations (examples) diff --git a/docs/QUERY_DSL.md b/docs/QUERY_DSL.md index c391eaa..1701aad 100644 --- a/docs/QUERY_DSL.md +++ b/docs/QUERY_DSL.md @@ -18,7 +18,7 @@ The Sentience Query DSL (Domain-Specific Language) allows you to find elements o ## Quick Start ```python -from sentience import SentienceBrowser, snapshot, query, find +from predicate import SentienceBrowser, snapshot, query, find with SentienceBrowser() as browser: browser.page.goto("https://example.com") diff --git a/examples/agent_layers_demo.py b/examples/agent_layers_demo.py index f41f84d..acdaab6 100644 --- a/examples/agent_layers_demo.py +++ b/examples/agent_layers_demo.py @@ -27,7 +27,7 @@ def demo_layer1_direct_sdk(): print("LAYER 1: Direct SDK Usage (Full Control)") print("=" * 70) - from sentience import SentienceBrowser, click, find, press, snapshot, type_text + from predicate import SentienceBrowser, click, find, press, snapshot, type_text with SentienceBrowser(headless=False) as browser: # Navigate @@ -67,8 +67,8 @@ def demo_layer2_sentience_agent(): print("LAYER 2: SentienceAgent (Technical Commands)") print("=" * 70) - from sentience import SentienceAgent, SentienceBrowser - from sentience.llm_provider import OpenAIProvider + from predicate import SentienceAgent, SentienceBrowser + from predicate.llm_provider import OpenAIProvider # Initialize browser = SentienceBrowser(headless=False) @@ -103,8 +103,8 @@ def demo_layer3_conversational_agent(): print("LAYER 3: ConversationalAgent (Natural Language)") print("=" * 70) - from sentience import ConversationalAgent, SentienceBrowser - from sentience.llm_provider import OpenAIProvider + from predicate import ConversationalAgent, SentienceBrowser + from predicate.llm_provider import OpenAIProvider # Initialize browser = SentienceBrowser(headless=False) @@ -133,8 +133,8 @@ def demo_layer3_with_local_llm(): print("LAYER 3: ConversationalAgent with Local LLM (Zero Cost)") print("=" * 70) - from sentience import ConversationalAgent, SentienceBrowser - from sentience.llm_provider import LocalLLMProvider + from predicate import ConversationalAgent, SentienceBrowser + from predicate.llm_provider import LocalLLMProvider # Initialize with local LLM browser = SentienceBrowser(headless=False) diff --git a/examples/agent_runtime_captcha_strategies.py b/examples/agent_runtime_captcha_strategies.py index dbe1855..da88d88 100644 --- a/examples/agent_runtime_captcha_strategies.py +++ b/examples/agent_runtime_captcha_strategies.py @@ -1,7 +1,7 @@ import asyncio import os -from sentience import ( +from predicate import ( AgentRuntime, AsyncSentienceBrowser, CaptchaOptions, @@ -9,7 +9,7 @@ HumanHandoffSolver, VisionSolver, ) -from sentience.tracing import JsonlTraceSink, Tracer +from predicate.tracing import JsonlTraceSink, Tracer async def notify_webhook(ctx) -> None: diff --git a/examples/agent_runtime_verification.py b/examples/agent_runtime_verification.py index 92bddfc..8e802d8 100644 --- a/examples/agent_runtime_verification.py +++ b/examples/agent_runtime_verification.py @@ -21,10 +21,10 @@ import asyncio import os -from sentience import AsyncSentienceBrowser -from sentience.agent_runtime import AgentRuntime -from sentience.tracing import JsonlTraceSink, Tracer -from sentience.verification import all_of, exists, not_exists, url_contains, url_matches +from predicate import AsyncSentienceBrowser +from predicate.agent_runtime import AgentRuntime +from predicate.tracing import JsonlTraceSink, Tracer +from predicate.verification import all_of, exists, not_exists, url_contains, url_matches async def main(): diff --git a/examples/asserts/eventually_min_confidence.py b/examples/asserts/eventually_min_confidence.py index 674c9af..176f9b5 100644 --- a/examples/asserts/eventually_min_confidence.py +++ b/examples/asserts/eventually_min_confidence.py @@ -10,9 +10,9 @@ import asyncio import os -from sentience import AgentRuntime, AsyncSentienceBrowser -from sentience.tracing import JsonlTraceSink, Tracer -from sentience.verification import exists +from predicate import AgentRuntime, AsyncSentienceBrowser +from predicate.tracing import JsonlTraceSink, Tracer +from predicate.verification import exists async def main() -> None: diff --git a/examples/asserts/state_assertions.py b/examples/asserts/state_assertions.py index 4ec5ecb..d8180a0 100644 --- a/examples/asserts/state_assertions.py +++ b/examples/asserts/state_assertions.py @@ -11,9 +11,9 @@ import asyncio import os -from sentience import AgentRuntime, AsyncSentienceBrowser -from sentience.tracing import JsonlTraceSink, Tracer -from sentience.verification import ( +from predicate import AgentRuntime, AsyncSentienceBrowser +from predicate.tracing import JsonlTraceSink, Tracer +from predicate.verification import ( exists, is_checked, is_disabled, diff --git a/examples/asserts/vision_fallback.py b/examples/asserts/vision_fallback.py index 9c74410..ea83271 100644 --- a/examples/asserts/vision_fallback.py +++ b/examples/asserts/vision_fallback.py @@ -13,10 +13,10 @@ import asyncio import os -from sentience import AgentRuntime, AsyncSentienceBrowser -from sentience.llm_provider import OpenAIProvider -from sentience.tracing import JsonlTraceSink, Tracer -from sentience.verification import exists +from predicate import AgentRuntime, AsyncSentienceBrowser +from predicate.llm_provider import OpenAIProvider +from predicate.tracing import JsonlTraceSink, Tracer +from predicate.verification import exists async def main() -> None: diff --git a/examples/async_api_demo.py b/examples/async_api_demo.py index 81b478a..8203aa9 100644 --- a/examples/async_api_demo.py +++ b/examples/async_api_demo.py @@ -15,7 +15,7 @@ import os # Import async API functions -from sentience.async_api import ( +from predicate.async_api import ( AsyncSentienceBrowser, click_async, find, @@ -23,7 +23,7 @@ snapshot_async, type_text_async, ) -from sentience.models import SnapshotOptions, Viewport +from predicate.models import SnapshotOptions, Viewport async def basic_async_example(): diff --git a/examples/auth_injection_agent.py b/examples/auth_injection_agent.py index 95ff702..e1b6457 100644 --- a/examples/auth_injection_agent.py +++ b/examples/auth_injection_agent.py @@ -30,8 +30,8 @@ import argparse import os -from sentience import SentienceAgent, SentienceBrowser, save_storage_state -from sentience.llm_provider import OpenAIProvider +from predicate import SentienceAgent, SentienceBrowser, save_storage_state +from predicate.llm_provider import OpenAIProvider def example_inject_storage_state(): @@ -51,7 +51,7 @@ def example_inject_storage_state(): print(" 2. Use save_storage_state() to save the session") print("\n Example code:") print(" ```python") - print(" from sentience import SentienceBrowser, save_storage_state") + print(" from predicate import SentienceBrowser, save_storage_state") print(" browser = SentienceBrowser()") print(" browser.start()") print(" browser.goto('https://example.com')") diff --git a/examples/basic_agent.py b/examples/basic_agent.py index a14ac3e..aa4a89c 100644 --- a/examples/basic_agent.py +++ b/examples/basic_agent.py @@ -4,7 +4,7 @@ import os -from sentience import SentienceBrowser, snapshot +from predicate import SentienceBrowser, snapshot def main(): diff --git a/examples/basic_agent_async.py b/examples/basic_agent_async.py index 6559ec2..3967f9f 100644 --- a/examples/basic_agent_async.py +++ b/examples/basic_agent_async.py @@ -6,8 +6,8 @@ import asyncio import os -from sentience.async_api import AsyncSentienceBrowser, SentienceAgentAsync -from sentience.llm_provider import LLMProvider, LLMResponse +from predicate.async_api import AsyncSentienceBrowser, SentienceAgentAsync +from predicate.llm_provider import LLMProvider, LLMResponse # Simple mock LLM provider for demonstration diff --git a/examples/browser-use/README.md b/examples/browser-use/README.md index 5299d51..a2dd2f8 100644 --- a/examples/browser-use/README.md +++ b/examples/browser-use/README.md @@ -16,13 +16,13 @@ This directory contains examples for integrating [Sentience](https://github.com/ Install both packages together using the optional dependency: ```bash -pip install "sentienceapi[browser-use]" +pip install "predicatelabs[browser-use]" ``` Or install separately: ```bash -pip install sentienceapi browser-use +pip install predicatelabs browser-use ``` ## Quick Start @@ -33,8 +33,8 @@ pip install sentienceapi browser-use ```python from browser_use import BrowserSession, BrowserProfile -from sentience import get_extension_dir -from sentience.backends import SentienceContext, TopElementSelector +from predicate import get_extension_dir +from predicate.backends import SentienceContext, TopElementSelector # Setup browser with Sentience extension profile = BrowserProfile( @@ -72,8 +72,8 @@ if state: For fine-grained control over snapshots and actions: ```python -from sentience import find, query, get_extension_dir -from sentience.backends import BrowserUseAdapter, snapshot, click, type_text +from predicate import find, query, get_extension_dir +from predicate.backends import BrowserUseAdapter, snapshot, click, type_text # Create adapter and backend adapter = BrowserUseAdapter(session) diff --git a/examples/browser-use/agent_runtime_browser_use.py b/examples/browser-use/agent_runtime_browser_use.py index 1944f48..a1305eb 100644 --- a/examples/browser-use/agent_runtime_browser_use.py +++ b/examples/browser-use/agent_runtime_browser_use.py @@ -20,11 +20,11 @@ import asyncio import os -from sentience import get_extension_dir -from sentience.agent_runtime import AgentRuntime -from sentience.backends import BrowserUseAdapter -from sentience.tracing import JsonlTraceSink, Tracer -from sentience.verification import all_of, exists, not_exists, url_contains, url_matches +from predicate import get_extension_dir +from predicate.agent_runtime import AgentRuntime +from predicate.backends import BrowserUseAdapter +from predicate.tracing import JsonlTraceSink, Tracer +from predicate.verification import all_of, exists, not_exists, url_contains, url_matches # browser-use imports (requires: pip install browser-use) try: diff --git a/examples/browser-use/integration.py b/examples/browser-use/integration.py index cddadf7..dc63974 100644 --- a/examples/browser-use/integration.py +++ b/examples/browser-use/integration.py @@ -6,10 +6,10 @@ using Sentience's snapshot-based grounding instead of coordinate estimation. Requirements: - pip install "sentienceapi[browser-use]" python-dotenv + pip install "predicatelabs[browser-use]" python-dotenv Or install separately: - pip install sentienceapi browser-use python-dotenv + pip install predicatelabs browser-use python-dotenv Usage: python examples/browser-use/integration.py @@ -22,8 +22,8 @@ from dotenv import load_dotenv # Sentience imports -from sentience import find, get_extension_dir, query -from sentience.backends import ( +from predicate import find, get_extension_dir, query +from predicate.backends import ( BrowserUseAdapter, CachedSnapshot, ExtensionNotLoadedError, @@ -240,7 +240,7 @@ async def example_with_sentience_context() -> None: print("Extension path:", get_extension_dir()) print() print("To run with a real browser:") - print(' 1. pip install "sentienceapi[browser-use]" python-dotenv') + print(' 1. pip install "predicatelabs[browser-use]" python-dotenv') print(" 2. Uncomment the browser-use imports and code sections") print(" 3. Run: python examples/browser-use/integration.py") diff --git a/examples/click_rect_demo.py b/examples/click_rect_demo.py index d472c20..bee434d 100644 --- a/examples/click_rect_demo.py +++ b/examples/click_rect_demo.py @@ -4,7 +4,7 @@ import os -from sentience import SentienceBrowser, click_rect, find, snapshot +from predicate import SentienceBrowser, click_rect, find, snapshot def main(): diff --git a/examples/find_text_demo.py b/examples/find_text_demo.py index 45b6177..bd7a852 100644 --- a/examples/find_text_demo.py +++ b/examples/find_text_demo.py @@ -8,7 +8,7 @@ 4. Handle multiple matches and filter by viewport visibility """ -from sentience import SentienceBrowser, click_rect, find_text_rect +from predicate import SentienceBrowser, click_rect, find_text_rect def main(): diff --git a/examples/hello.py b/examples/hello.py index 6499252..fe0ea0e 100644 --- a/examples/hello.py +++ b/examples/hello.py @@ -4,7 +4,7 @@ import os -from sentience import SentienceBrowser +from predicate import SentienceBrowser def main(): diff --git a/examples/hello_async.py b/examples/hello_async.py index 63cf150..9bebb08 100644 --- a/examples/hello_async.py +++ b/examples/hello_async.py @@ -5,7 +5,7 @@ import asyncio import os -from sentience.async_api import AsyncSentienceBrowser +from predicate.async_api import AsyncSentienceBrowser async def main(): diff --git a/examples/human_cursor_click_demo.py b/examples/human_cursor_click_demo.py index 5182860..31a3563 100644 --- a/examples/human_cursor_click_demo.py +++ b/examples/human_cursor_click_demo.py @@ -7,7 +7,7 @@ from __future__ import annotations -from sentience import CursorPolicy, SentienceBrowser, click, find, snapshot +from predicate import CursorPolicy, SentienceBrowser, click, find, snapshot def main() -> None: diff --git a/examples/lang-chain/README.md b/examples/lang-chain/README.md index 2dedf51..6857d7c 100644 --- a/examples/lang-chain/README.md +++ b/examples/lang-chain/README.md @@ -5,7 +5,7 @@ These examples show how to use Sentience as a **tool layer** inside LangChain an Install: ```bash -pip install sentienceapi[langchain] +pip install predicatelabs[langchain] ``` Examples: diff --git a/examples/lang-chain/langchain_tools_demo.py b/examples/lang-chain/langchain_tools_demo.py index 232f569..781cc67 100644 --- a/examples/lang-chain/langchain_tools_demo.py +++ b/examples/lang-chain/langchain_tools_demo.py @@ -2,7 +2,7 @@ Example: Build Sentience LangChain tools (async-only). Install: - pip install sentienceapi[langchain] + pip install predicatelabs[langchain] Run: python examples/lang-chain/langchain_tools_demo.py @@ -15,8 +15,8 @@ import asyncio -from sentience import AsyncSentienceBrowser -from sentience.integrations.langchain import ( +from predicate import AsyncSentienceBrowser +from predicate.integrations.langchain import ( SentienceLangChainContext, build_sentience_langchain_tools, ) diff --git a/examples/lang-chain/langgraph_self_correcting_graph.py b/examples/lang-chain/langgraph_self_correcting_graph.py index 4daa905..4f81163 100644 --- a/examples/lang-chain/langgraph_self_correcting_graph.py +++ b/examples/lang-chain/langgraph_self_correcting_graph.py @@ -2,7 +2,7 @@ LangGraph reference example: Sentience observe β†’ act β†’ verify β†’ branch (self-correcting). Install: - pip install sentienceapi[langchain] + pip install predicatelabs[langchain] Run: python examples/lang-chain/langgraph_self_correcting_graph.py @@ -13,8 +13,8 @@ import asyncio from dataclasses import dataclass -from sentience import AsyncSentienceBrowser -from sentience.integrations.langchain import SentienceLangChainContext, SentienceLangChainCore +from predicate import AsyncSentienceBrowser +from predicate.integrations.langchain import SentienceLangChainContext, SentienceLangChainCore @dataclass diff --git a/examples/langgraph/sentience_self_correcting_graph.py b/examples/langgraph/sentience_self_correcting_graph.py index cb38e79..f974c5b 100644 --- a/examples/langgraph/sentience_self_correcting_graph.py +++ b/examples/langgraph/sentience_self_correcting_graph.py @@ -2,7 +2,7 @@ LangGraph reference example: Sentience observe β†’ act β†’ verify β†’ branch (self-correcting). Install: - pip install sentienceapi[langchain] + pip install predicatelabs[langchain] Run: python examples/langgraph/sentience_self_correcting_graph.py @@ -18,8 +18,8 @@ from dataclasses import dataclass from typing import Optional -from sentience import AsyncSentienceBrowser -from sentience.integrations.langchain import SentienceLangChainContext, SentienceLangChainCore +from predicate import AsyncSentienceBrowser +from predicate.integrations.langchain import SentienceLangChainContext, SentienceLangChainCore @dataclass diff --git a/examples/local_tracing_agent.py b/examples/local_tracing_agent.py index 5d30ba9..e94219f 100644 --- a/examples/local_tracing_agent.py +++ b/examples/local_tracing_agent.py @@ -14,10 +14,10 @@ import os -from sentience import SentienceAgent, SentienceBrowser -from sentience.agent_config import AgentConfig -from sentience.llm_provider import OpenAIProvider -from sentience.tracer_factory import create_tracer +from predicate import SentienceAgent, SentienceBrowser +from predicate.agent_config import AgentConfig +from predicate.llm_provider import OpenAIProvider +from predicate.tracer_factory import create_tracer def main(): diff --git a/examples/pydantic_ai/pydantic_ai_self_correcting_click.py b/examples/pydantic_ai/pydantic_ai_self_correcting_click.py index 7b1a5fc..c1afcad 100644 --- a/examples/pydantic_ai/pydantic_ai_self_correcting_click.py +++ b/examples/pydantic_ai/pydantic_ai_self_correcting_click.py @@ -2,14 +2,14 @@ Example: PydanticAI + Sentience self-correcting action loop using URL guards. Run: - pip install sentienceapi[pydanticai] + pip install predicatelabs[pydanticai] python examples/pydantic_ai/pydantic_ai_self_correcting_click.py """ from __future__ import annotations -from sentience import AsyncSentienceBrowser -from sentience.integrations.pydanticai import SentiencePydanticDeps, register_sentience_tools +from predicate import AsyncSentienceBrowser +from predicate.integrations.pydanticai import SentiencePydanticDeps, register_sentience_tools async def main() -> None: diff --git a/examples/pydantic_ai/pydantic_ai_typed_extraction.py b/examples/pydantic_ai/pydantic_ai_typed_extraction.py index 43c8398..59ff012 100644 --- a/examples/pydantic_ai/pydantic_ai_typed_extraction.py +++ b/examples/pydantic_ai/pydantic_ai_typed_extraction.py @@ -2,7 +2,7 @@ Example: PydanticAI + Sentience typed extraction (Phase 1 integration). Run: - pip install sentienceapi[pydanticai] + pip install predicatelabs[pydanticai] python examples/pydantic_ai/pydantic_ai_typed_extraction.py """ @@ -10,8 +10,8 @@ from pydantic import BaseModel, Field -from sentience import AsyncSentienceBrowser -from sentience.integrations.pydanticai import SentiencePydanticDeps, register_sentience_tools +from predicate import AsyncSentienceBrowser +from predicate.integrations.pydanticai import SentiencePydanticDeps, register_sentience_tools class ProductInfo(BaseModel): diff --git a/examples/query_demo.py b/examples/query_demo.py index 4ae4295..b5c6ca1 100644 --- a/examples/query_demo.py +++ b/examples/query_demo.py @@ -4,7 +4,7 @@ import os -from sentience import SentienceBrowser, find, query, snapshot +from predicate import SentienceBrowser, find, query, snapshot def main(): diff --git a/examples/query_demo_async.py b/examples/query_demo_async.py index c4c98ba..ee0a952 100644 --- a/examples/query_demo_async.py +++ b/examples/query_demo_async.py @@ -5,7 +5,7 @@ import asyncio import os -from sentience.async_api import AsyncSentienceBrowser, find, query, snapshot_async +from predicate.async_api import AsyncSentienceBrowser, find, query, snapshot_async async def main(): diff --git a/examples/read_markdown.py b/examples/read_markdown.py index d2cd669..2a101c7 100644 --- a/examples/read_markdown.py +++ b/examples/read_markdown.py @@ -9,7 +9,7 @@ from markdownify import markdownify -from sentience import SentienceBrowser, read +from predicate import SentienceBrowser, read def main(): diff --git a/examples/read_markdown_async.py b/examples/read_markdown_async.py index 66268ac..aa30c58 100644 --- a/examples/read_markdown_async.py +++ b/examples/read_markdown_async.py @@ -10,7 +10,7 @@ from markdownify import markdownify -from sentience.async_api import AsyncSentienceBrowser, read_async +from predicate.async_api import AsyncSentienceBrowser, read_async async def main(): diff --git a/examples/residential_proxy_agent.py b/examples/residential_proxy_agent.py index 83e1693..ff239ae 100644 --- a/examples/residential_proxy_agent.py +++ b/examples/residential_proxy_agent.py @@ -35,9 +35,9 @@ import os -from sentience import SentienceAgent, SentienceBrowser -from sentience.agent_config import AgentConfig -from sentience.llm_provider import OpenAIProvider +from predicate import SentienceAgent, SentienceBrowser +from predicate.agent_config import AgentConfig +from predicate.llm_provider import OpenAIProvider def example_proxy_direct_argument(): @@ -214,7 +214,7 @@ def example_proxy_with_cloud_tracing(): print("❌ Error: OPENAI_API_KEY not set") return - from sentience.tracer_factory import create_tracer + from predicate.tracer_factory import create_tracer # Create tracer for cloud upload run_id = "proxy-with-tracing-demo" diff --git a/examples/runtime_agent_minimal.py b/examples/runtime_agent_minimal.py index ab2da15..890b1f1 100644 --- a/examples/runtime_agent_minimal.py +++ b/examples/runtime_agent_minimal.py @@ -10,12 +10,12 @@ import asyncio -from sentience import AsyncSentienceBrowser -from sentience.agent_runtime import AgentRuntime -from sentience.llm_provider import LLMProvider, LLMResponse -from sentience.runtime_agent import RuntimeAgent, RuntimeStep, StepVerification -from sentience.tracing import JsonlTraceSink, Tracer -from sentience.verification import AssertContext, AssertOutcome, exists, url_contains +from predicate import AsyncSentienceBrowser +from predicate.agent_runtime import AgentRuntime +from predicate.llm_provider import LLMProvider, LLMResponse +from predicate.runtime_agent import RuntimeAgent, RuntimeStep, StepVerification +from predicate.tracing import JsonlTraceSink, Tracer +from predicate.verification import AssertContext, AssertOutcome, exists, url_contains class FixedActionProvider(LLMProvider): diff --git a/examples/semantic_wait_demo.py b/examples/semantic_wait_demo.py index f072117..7b71c5a 100644 --- a/examples/semantic_wait_demo.py +++ b/examples/semantic_wait_demo.py @@ -5,7 +5,7 @@ import os -from sentience import SentienceBrowser, click, wait_for +from predicate import SentienceBrowser, click, wait_for def main(): diff --git a/examples/semantic_wait_demo_async.py b/examples/semantic_wait_demo_async.py index 0125543..3893703 100644 --- a/examples/semantic_wait_demo_async.py +++ b/examples/semantic_wait_demo_async.py @@ -6,7 +6,7 @@ import asyncio import os -from sentience.async_api import AsyncSentienceBrowser, click_async, wait_for_async +from predicate.async_api import AsyncSentienceBrowser, click_async, wait_for_async async def main(): diff --git a/examples/show_grid_examples.py b/examples/show_grid_examples.py index c428bab..b3d85f3 100644 --- a/examples/show_grid_examples.py +++ b/examples/show_grid_examples.py @@ -8,8 +8,8 @@ import os import time -from sentience import SentienceBrowser, snapshot -from sentience.models import SnapshotOptions +from predicate import SentienceBrowser, snapshot +from predicate.models import SnapshotOptions def main(): @@ -18,7 +18,9 @@ def main(): # Use VPS IP directly if domain is not configured # Replace with your actual domain once DNS is set up: api_url="https://api.sentienceapi.com" - api_url = os.environ.get("SENTIENCE_API_URL", "http://15.204.243.91:9000") + api_url = os.environ.get("PREDICATE_API_URL") or os.environ.get( + "SENTIENCE_API_URL", "http://15.204.243.91:9000" + ) try: with SentienceBrowser(api_key=api_key, api_url=api_url, headless=False) as browser: diff --git a/examples/test_local_llm_agent.py b/examples/test_local_llm_agent.py index 39132a0..a53e6e8 100644 --- a/examples/test_local_llm_agent.py +++ b/examples/test_local_llm_agent.py @@ -3,7 +3,7 @@ Demonstrates using a local LLM with SentienceAgent """ -from sentience.llm_provider import LocalLLMProvider +from predicate.llm_provider import LocalLLMProvider def test_local_llm_basic(): diff --git a/examples/video_recording_advanced.py b/examples/video_recording_advanced.py index c001490..c13599e 100644 --- a/examples/video_recording_advanced.py +++ b/examples/video_recording_advanced.py @@ -10,7 +10,7 @@ from datetime import datetime from pathlib import Path -from sentience import SentienceBrowser +from predicate import SentienceBrowser def main(): diff --git a/examples/video_recording_demo.py b/examples/video_recording_demo.py index 4e78eb7..d0ebd6c 100644 --- a/examples/video_recording_demo.py +++ b/examples/video_recording_demo.py @@ -7,7 +7,7 @@ from pathlib import Path -from sentience import SentienceBrowser +from predicate import SentienceBrowser def main(): diff --git a/examples/wait_and_click.py b/examples/wait_and_click.py index 5a1cb84..c86cb4e 100644 --- a/examples/wait_and_click.py +++ b/examples/wait_and_click.py @@ -4,7 +4,7 @@ import os -from sentience import SentienceBrowser, click, expect, find, snapshot, wait_for +from predicate import SentienceBrowser, click, expect, find, snapshot, wait_for def main(): diff --git a/examples/wait_and_click_async.py b/examples/wait_and_click_async.py index 59f6b91..cbcb385 100644 --- a/examples/wait_and_click_async.py +++ b/examples/wait_and_click_async.py @@ -5,7 +5,7 @@ import asyncio import os -from sentience.async_api import ( +from predicate.async_api import ( AsyncSentienceBrowser, click_async, expect_async, diff --git a/sentience/__init__.py b/predicate/__init__.py similarity index 98% rename from sentience/__init__.py rename to predicate/__init__.py index 2693618..882f705 100644 --- a/sentience/__init__.py +++ b/predicate/__init__.py @@ -118,7 +118,8 @@ from .snapshot import snapshot from .text_search import find_text_rect from .tools import BackendCapabilities, ToolContext, ToolRegistry, ToolSpec, register_default_tools -from .tracer_factory import SENTIENCE_API_URL, create_tracer +from .constants import PREDICATE_API_URL, SENTIENCE_API_URL +from .tracer_factory import create_tracer from .tracing import JsonlTraceSink, TraceEvent, Tracer, TraceSink # Utilities (v0.12.0+) @@ -275,6 +276,7 @@ "SentienceLogger", "TraceEvent", "create_tracer", + "PREDICATE_API_URL", "SENTIENCE_API_URL", # Utilities (v0.12.0+) "canonical_snapshot_strict", diff --git a/sentience/_extension_loader.py b/predicate/_extension_loader.py similarity index 95% rename from sentience/_extension_loader.py rename to predicate/_extension_loader.py index 3c8c74a..2bda4e1 100644 --- a/sentience/_extension_loader.py +++ b/predicate/_extension_loader.py @@ -21,7 +21,7 @@ def find_extension_path() -> Path: Find Sentience extension directory (shared logic for sync and async). Checks multiple locations: - 1. sentience/extension/ (installed package) + 1. predicate/extension/ (installed package) 2. ../sentience-chrome (development/monorepo) Returns: @@ -31,11 +31,11 @@ def find_extension_path() -> Path: FileNotFoundError: If extension not found in any location """ # 1. Try relative to this file (installed package structure) - # sentience/_extension_loader.py -> sentience/extension/ + # predicate/_extension_loader.py -> predicate/extension/ package_ext_path = Path(__file__).parent / "extension" # 2. Try development root (if running from source repo) - # sentience/_extension_loader.py -> ../sentience-chrome + # predicate/_extension_loader.py -> ../sentience-chrome dev_ext_path = Path(__file__).parent.parent.parent / "sentience-chrome" if package_ext_path.exists() and (package_ext_path / "manifest.json").exists(): @@ -47,7 +47,7 @@ def find_extension_path() -> Path: f"Extension not found. Checked:\n" f"1. {package_ext_path}\n" f"2. {dev_ext_path}\n" - "Make sure the extension is built and 'sentience/extension' directory exists." + "Make sure the extension is built and 'predicate/extension' directory exists." ) @@ -57,7 +57,7 @@ def get_extension_dir() -> str: Use this to load the extension into browser-use or other Chromium-based browsers: - from sentience import get_extension_dir + from predicate import get_extension_dir from browser_use import BrowserSession, BrowserProfile profile = BrowserProfile( diff --git a/sentience/action_executor.py b/predicate/action_executor.py similarity index 100% rename from sentience/action_executor.py rename to predicate/action_executor.py diff --git a/sentience/actions.py b/predicate/actions.py similarity index 99% rename from sentience/actions.py rename to predicate/actions.py index 90f9774..1de86a7 100644 --- a/sentience/actions.py +++ b/predicate/actions.py @@ -830,7 +830,12 @@ def search( url_before = browser.page.url url = _build_search_url(query, engine) browser.goto(url) - browser.page.wait_for_load_state("networkidle") + # Some public search engines keep long-lived connections open. Treat network-idle + # as best-effort so search() remains reliable in CI and constrained networks. + try: + browser.page.wait_for_load_state("networkidle", timeout=5000) + except Exception: + pass duration_ms = int((time.time() - start_time) * 1000) url_after = browser.page.url @@ -1031,7 +1036,7 @@ def click_rect( Example: >>> click_rect(browser, {"x": 100, "y": 200, "w": 50, "h": 30}) >>> # Or using BBox object - >>> from sentience import BBox + >>> from predicate import BBox >>> bbox = BBox(x=100, y=200, width=50, height=30) >>> click_rect(browser, {"x": bbox.x, "y": bbox.y, "w": bbox.width, "h": bbox.height}) """ @@ -1068,6 +1073,7 @@ def click_rect( center_x = x + w / 2 center_y = y + h / 2 cursor_meta: dict | None = None + error_msg = "" # Show highlight before clicking (if enabled) if highlight: @@ -1906,7 +1912,12 @@ async def search_async( url_before = browser.page.url url = _build_search_url(query, engine) await browser.goto(url) - await browser.page.wait_for_load_state("networkidle") + # Some public search engines keep long-lived connections open. Treat network-idle + # as best-effort so search_async() remains reliable in CI and constrained networks. + try: + await browser.page.wait_for_load_state("networkidle", timeout=5000) + except Exception: + pass duration_ms = int((time.time() - start_time) * 1000) url_after = browser.page.url @@ -2123,6 +2134,7 @@ async def click_rect_async( center_x = x + w / 2 center_y = y + h / 2 cursor_meta: dict | None = None + error_msg = "" # Show highlight before clicking if highlight: diff --git a/sentience/agent.py b/predicate/agent.py similarity index 99% rename from sentience/agent.py rename to predicate/agent.py index 632c282..06d1468 100644 --- a/sentience/agent.py +++ b/predicate/agent.py @@ -120,8 +120,8 @@ class SentienceAgent(BaseAgent): 3. ACT: Execute action using SDK Example: - >>> from sentience import SentienceBrowser, SentienceAgent - >>> from sentience.llm_provider import OpenAIProvider + >>> from predicate import SentienceBrowser, SentienceAgent + >>> from predicate.llm_provider import OpenAIProvider >>> >>> browser = SentienceBrowser(api_key="sentience_key") >>> llm = OpenAIProvider(api_key="openai_key", model="gpt-4o") @@ -853,9 +853,9 @@ class SentienceAgentAsync(BaseAgentAsync): 3. ACT: Execute action using SDK Example: - >>> from sentience.async_api import AsyncSentienceBrowser - >>> from sentience.agent import SentienceAgentAsync - >>> from sentience.llm_provider import OpenAIProvider + >>> from predicate.async_api import AsyncSentienceBrowser + >>> from predicate.agent import SentienceAgentAsync + >>> from predicate.llm_provider import OpenAIProvider >>> >>> async with AsyncSentienceBrowser() as browser: >>> await browser.goto("https://google.com") diff --git a/sentience/agent_config.py b/predicate/agent_config.py similarity index 96% rename from sentience/agent_config.py rename to predicate/agent_config.py index 8b8a454..932d2b1 100644 --- a/sentience/agent_config.py +++ b/predicate/agent_config.py @@ -23,7 +23,7 @@ class AgentConfig: screenshot_quality: JPEG quality 1-100, ignored for PNG (default: 80) Example: - >>> from sentience import AgentConfig, SentienceAgent + >>> from predicate import AgentConfig, SentienceAgent >>> config = AgentConfig( ... snapshot_limit=100, ... max_retries=2, diff --git a/sentience/agent_runtime.py b/predicate/agent_runtime.py similarity index 99% rename from sentience/agent_runtime.py rename to predicate/agent_runtime.py index a48fc3c..a7826b1 100644 --- a/sentience/agent_runtime.py +++ b/predicate/agent_runtime.py @@ -12,11 +12,11 @@ Example usage with browser-use: from browser_use import BrowserSession, BrowserProfile - from sentience import get_extension_dir - from sentience.backends import BrowserUseAdapter - from sentience.agent_runtime import AgentRuntime - from sentience.verification import url_matches, exists - from sentience.tracing import Tracer, JsonlTraceSink + from predicate import get_extension_dir + from predicate.backends import BrowserUseAdapter + from predicate.agent_runtime import AgentRuntime + from predicate.verification import url_matches, exists + from predicate.tracing import Tracer, JsonlTraceSink # Setup browser-use with Sentience extension profile = BrowserProfile(args=[f"--load-extension={get_extension_dir()}"]) @@ -46,8 +46,8 @@ print("Task completed!") Example usage with AsyncSentienceBrowser (backward compatible): - from sentience import AsyncSentienceBrowser - from sentience.agent_runtime import AgentRuntime + from predicate import AsyncSentienceBrowser + from predicate.agent_runtime import AgentRuntime async with AsyncSentienceBrowser() as browser: page = await browser.new_page() diff --git a/sentience/asserts/__init__.py b/predicate/asserts/__init__.py similarity index 96% rename from sentience/asserts/__init__.py rename to predicate/asserts/__init__.py index 85e9351..2522703 100644 --- a/sentience/asserts/__init__.py +++ b/predicate/asserts/__init__.py @@ -10,7 +10,7 @@ - in_dominant_list: Query over dominant group elements (ordinal access) Example usage: - from sentience.asserts import E, expect, in_dominant_list + from predicate.asserts import E, expect, in_dominant_list # Basic presence assertions runtime.assert_( diff --git a/sentience/asserts/expect.py b/predicate/asserts/expect.py similarity index 99% rename from sentience/asserts/expect.py rename to predicate/asserts/expect.py index 423b598..98f9c6d 100644 --- a/sentience/asserts/expect.py +++ b/predicate/asserts/expect.py @@ -297,7 +297,7 @@ class _ExpectFactory: This is the main entry point for the assertion DSL. Usage: - from sentience.asserts import expect, E + from predicate.asserts import expect, E # Element-based assertions expect(E(role="button")).to_exist() diff --git a/sentience/asserts/query.py b/predicate/asserts/query.py similarity index 99% rename from sentience/asserts/query.py rename to predicate/asserts/query.py index 30b9cd1..56f714e 100644 --- a/sentience/asserts/query.py +++ b/predicate/asserts/query.py @@ -379,5 +379,5 @@ def in_dominant_list() -> ListQuery: # Export the factory as E for the Playwright-like API -# Users can do: from sentience.asserts import E +# Users can do: from predicate.asserts import E # And use: E(role="button"), E.submit(), E.link(text_contains="...") diff --git a/sentience/async_api.py b/predicate/async_api.py similarity index 78% rename from sentience/async_api.py rename to predicate/async_api.py index f7ed012..2030cc1 100644 --- a/sentience/async_api.py +++ b/predicate/async_api.py @@ -5,7 +5,7 @@ You can also import directly from their respective modules: # Option 1: From async_api (recommended for convenience) - from sentience.async_api import ( + from predicate.async_api import ( AsyncSentienceBrowser, snapshot_async, click_async, @@ -16,14 +16,14 @@ ) # Option 2: From respective modules (also works) - from sentience.browser import AsyncSentienceBrowser - from sentience.snapshot import snapshot_async - from sentience.actions import click_async + from predicate.browser import AsyncSentienceBrowser + from predicate.snapshot import snapshot_async + from predicate.actions import click_async """ # ========== Actions (Phase 1) ========== # Re-export async action functions from actions.py -from sentience.actions import ( +from predicate.actions import ( click_async, click_rect_async, press_async, @@ -33,45 +33,45 @@ # ========== Phase 2C: Agent Layer ========== # Re-export async agent classes from agent.py and base_agent.py -from sentience.agent import SentienceAgentAsync -from sentience.base_agent import BaseAgentAsync +from predicate.agent import SentienceAgentAsync +from predicate.base_agent import BaseAgentAsync # ========== Browser ========== # Re-export AsyncSentienceBrowser from browser.py (moved there for better organization) -from sentience.browser import AsyncSentienceBrowser +from predicate.browser import AsyncSentienceBrowser # Re-export async expect functions from expect.py -from sentience.expect import ExpectationAsync, expect_async -from sentience.inspector import InspectorAsync, inspect_async +from predicate.expect import ExpectationAsync, expect_async +from predicate.inspector import InspectorAsync, inspect_async # Re-export async overlay functions from overlay.py -from sentience.overlay import clear_overlay_async, show_overlay_async +from predicate.overlay import clear_overlay_async, show_overlay_async # ========== Query Functions (Pure Functions - No Async Needed) ========== # Re-export query functions (pure functions, no async needed) -from sentience.query import find, query +from predicate.query import find, query # ========== Phase 2B: Supporting Utilities ========== # Re-export async read functions from read.py -from sentience.read import read_async, read_best_effort_async +from predicate.read import read_async, read_best_effort_async # ========== Phase 2D: Developer Tools ========== # Re-export async recorder and inspector from their modules -from sentience.recorder import RecorderAsync, record_async +from predicate.recorder import RecorderAsync, record_async # Re-export async screenshot function from screenshot.py -from sentience.screenshot import screenshot_async +from predicate.screenshot import screenshot_async # ========== Snapshot (Phase 1) ========== # Re-export async snapshot functions from snapshot.py -from sentience.snapshot import snapshot_async +from predicate.snapshot import snapshot_async # Re-export async text search function from text_search.py -from sentience.text_search import find_text_rect_async +from predicate.text_search import find_text_rect_async # ========== Phase 2A: Core Utilities ========== # Re-export async wait function from wait.py -from sentience.wait import wait_for_async +from predicate.wait import wait_for_async __all__ = [ # Browser diff --git a/sentience/backends/__init__.py b/predicate/backends/__init__.py similarity index 94% rename from sentience/backends/__init__.py rename to predicate/backends/__init__.py index daf2495..ed6725c 100644 --- a/sentience/backends/__init__.py +++ b/predicate/backends/__init__.py @@ -26,8 +26,8 @@ .. code-block:: python from browser_use import BrowserSession, BrowserProfile - from sentience import get_extension_dir, find - from sentience.backends import BrowserUseAdapter, snapshot, click, type_text + from predicate import get_extension_dir, find + from predicate.backends import BrowserUseAdapter, snapshot, click, type_text # Setup browser-use with Sentience extension profile = BrowserProfile(args=[f"--load-extension={get_extension_dir()}"]) @@ -51,7 +51,7 @@ .. code-block:: python - from sentience.backends import CachedSnapshot + from predicate.backends import CachedSnapshot cache = CachedSnapshot(backend, max_age_ms=2000) @@ -75,7 +75,7 @@ .. code-block:: python - from sentience.backends import ExtensionNotLoadedError, snapshot + from predicate.backends import ExtensionNotLoadedError, snapshot try: snap = await snapshot(backend) diff --git a/sentience/backends/actions.py b/predicate/backends/actions.py similarity index 99% rename from sentience/backends/actions.py rename to predicate/backends/actions.py index 01fa6aa..0823a12 100644 --- a/sentience/backends/actions.py +++ b/predicate/backends/actions.py @@ -5,8 +5,8 @@ enabling Sentience grounding with browser-use or other frameworks. Usage with browser-use: - from sentience.backends import BrowserUseAdapter - from sentience.backends.actions import click, type_text, scroll + from predicate.backends import BrowserUseAdapter + from predicate.backends.actions import click, type_text, scroll adapter = BrowserUseAdapter(session) backend = await adapter.create_backend() diff --git a/sentience/backends/browser_use_adapter.py b/predicate/backends/browser_use_adapter.py similarity index 97% rename from sentience/backends/browser_use_adapter.py rename to predicate/backends/browser_use_adapter.py index c932cd3..637ce98 100644 --- a/sentience/backends/browser_use_adapter.py +++ b/predicate/backends/browser_use_adapter.py @@ -6,8 +6,8 @@ Usage: from browser_use import BrowserSession, BrowserProfile - from sentience import get_extension_dir - from sentience.backends import BrowserUseAdapter + from predicate import get_extension_dir + from predicate.backends import BrowserUseAdapter # Create browser-use session with Sentience extension profile = BrowserProfile(args=[f"--load-extension={get_extension_dir()}"]) @@ -105,8 +105,8 @@ class BrowserUseAdapter: Example: from browser_use import BrowserSession, BrowserProfile - from sentience import get_extension_dir, snapshot_async, SnapshotOptions - from sentience.backends import BrowserUseAdapter + from predicate import get_extension_dir, snapshot_async, SnapshotOptions + from predicate.backends import BrowserUseAdapter # Setup browser-use with Sentience extension profile = BrowserProfile(args=[f"--load-extension={get_extension_dir()}"]) diff --git a/sentience/backends/cdp_backend.py b/predicate/backends/cdp_backend.py similarity index 99% rename from sentience/backends/cdp_backend.py rename to predicate/backends/cdp_backend.py index 897e195..d69bfa7 100644 --- a/sentience/backends/cdp_backend.py +++ b/predicate/backends/cdp_backend.py @@ -6,8 +6,8 @@ Usage with browser-use: from browser_use import BrowserSession - from sentience.backends import CDPBackendV0 - from sentience.backends.browser_use_adapter import BrowserUseAdapter + from predicate.backends import CDPBackendV0 + from predicate.backends.browser_use_adapter import BrowserUseAdapter session = BrowserSession(...) await session.start() diff --git a/sentience/backends/exceptions.py b/predicate/backends/exceptions.py similarity index 98% rename from sentience/backends/exceptions.py rename to predicate/backends/exceptions.py index a1d176c..2676848 100644 --- a/sentience/backends/exceptions.py +++ b/predicate/backends/exceptions.py @@ -54,7 +54,7 @@ class ExtensionNotLoadedError(SentienceBackendError): 3. Extension failed to initialize Example fix for browser-use: - from sentience import get_extension_dir + from predicate import get_extension_dir from browser_use import BrowserSession, BrowserProfile profile = BrowserProfile( @@ -94,7 +94,7 @@ def from_timeout( message = ( f"Sentience extension not loaded after {timeout_ms}ms.{diag_info}\n\n" "To fix this, ensure the extension is loaded when launching the browser:\n\n" - " from sentience import get_extension_dir\n" + " from predicate import get_extension_dir\n" " from browser_use import BrowserSession, BrowserProfile\n\n" " profile = BrowserProfile(\n" f' args=[f"--load-extension={{get_extension_dir()}}"],\n' diff --git a/sentience/backends/playwright_backend.py b/predicate/backends/playwright_backend.py similarity index 99% rename from sentience/backends/playwright_backend.py rename to predicate/backends/playwright_backend.py index 847cb0f..7efb94a 100644 --- a/sentience/backends/playwright_backend.py +++ b/predicate/backends/playwright_backend.py @@ -6,8 +6,8 @@ (CDPBackendV0) and native Playwright (PlaywrightBackend). Usage: - from sentience import SentienceBrowserAsync - from sentience.backends import PlaywrightBackend, snapshot_from_backend + from predicate import SentienceBrowserAsync + from predicate.backends import PlaywrightBackend, snapshot_from_backend browser = SentienceBrowserAsync() await browser.start() diff --git a/sentience/backends/protocol.py b/predicate/backends/protocol.py similarity index 100% rename from sentience/backends/protocol.py rename to predicate/backends/protocol.py diff --git a/sentience/backends/sentience_context.py b/predicate/backends/sentience_context.py similarity index 98% rename from sentience/backends/sentience_context.py rename to predicate/backends/sentience_context.py index e53275e..d063531 100644 --- a/sentience/backends/sentience_context.py +++ b/predicate/backends/sentience_context.py @@ -6,7 +6,7 @@ Example usage: from browser_use import Agent - from sentience.backends import SentienceContext + from predicate.backends import SentienceContext ctx = SentienceContext(show_overlay=True) state = await ctx.build(agent.browser_session, goal="Click the first Show HN post") @@ -23,7 +23,7 @@ from typing import TYPE_CHECKING, Any from urllib.parse import urlparse -from ..constants import SENTIENCE_API_URL +from ..constants import PREDICATE_API_URL if TYPE_CHECKING: from ..models import Element, Snapshot @@ -72,7 +72,7 @@ class SentienceContext: Example: from browser_use import Agent - from sentience.backends import SentienceContext + from predicate.backends import SentienceContext ctx = SentienceContext(show_overlay=True) state = await ctx.build(agent.browser_session, goal="Click the first Show HN post") @@ -81,7 +81,7 @@ class SentienceContext: """ # Sentience API endpoint - API_URL = SENTIENCE_API_URL + API_URL = PREDICATE_API_URL def __init__( self, diff --git a/sentience/backends/snapshot.py b/predicate/backends/snapshot.py similarity index 98% rename from sentience/backends/snapshot.py rename to predicate/backends/snapshot.py index f81b363..e4dcbf3 100644 --- a/sentience/backends/snapshot.py +++ b/predicate/backends/snapshot.py @@ -5,7 +5,7 @@ enabling element grounding with browser-use or other frameworks. Usage with browser-use: - from sentience.backends import BrowserUseAdapter, snapshot, CachedSnapshot + from predicate.backends import BrowserUseAdapter, snapshot, CachedSnapshot adapter = BrowserUseAdapter(session) backend = await adapter.create_backend() @@ -25,7 +25,7 @@ import time from typing import TYPE_CHECKING, Any -from ..constants import SENTIENCE_API_URL +from ..constants import PREDICATE_API_URL from ..models import Element, Snapshot, SnapshotOptions from ..snapshot import ( _build_snapshot_payload, @@ -219,9 +219,9 @@ async def snapshot( Snapshot with elements, viewport, and optional screenshot Example: - from sentience.backends import BrowserUseAdapter - from sentience.backends.snapshot import snapshot - from sentience.models import SnapshotOptions + from predicate.backends import BrowserUseAdapter + from predicate.backends.snapshot import snapshot + from predicate.models import SnapshotOptions adapter = BrowserUseAdapter(session) backend = await adapter.create_backend() @@ -246,7 +246,7 @@ async def snapshot( options = SnapshotOptions() # Determine if we should use server-side API - # Same logic as main snapshot() function in sentience/snapshot.py + # Same logic as main snapshot() function in predicate/snapshot.py should_use_api = ( options.use_api if options.use_api is not None else (options.sentience_api_key is not None) ) @@ -451,7 +451,7 @@ async def _wait_for_extension( """ import logging - logger = logging.getLogger("sentience.backends.snapshot") + logger = logging.getLogger("predicate.backends.snapshot") start = time.monotonic() timeout_sec = timeout_ms / 1000.0 @@ -559,7 +559,7 @@ async def _snapshot_via_api( ) -> Snapshot: """Take snapshot using server-side API (Pro/Enterprise tier)""" # Default API URL (same as main snapshot function) - api_url = SENTIENCE_API_URL + api_url = PREDICATE_API_URL # Wait for extension injection (needed even for API mode to collect raw data) await _wait_for_extension(backend, timeout_ms=5000) diff --git a/sentience/base_agent.py b/predicate/base_agent.py similarity index 100% rename from sentience/base_agent.py rename to predicate/base_agent.py diff --git a/sentience/browser.py b/predicate/browser.py similarity index 99% rename from sentience/browser.py rename to predicate/browser.py index 1422598..9b931a0 100644 --- a/sentience/browser.py +++ b/predicate/browser.py @@ -19,10 +19,10 @@ from playwright.async_api import async_playwright from playwright.sync_api import BrowserContext, Page, Playwright, sync_playwright -from sentience._extension_loader import find_extension_path -from sentience.constants import SENTIENCE_API_URL -from sentience.models import ProxyConfig, StorageState, Viewport -from sentience.permissions import PermissionPolicy +from predicate._extension_loader import find_extension_path +from predicate.constants import PREDICATE_API_URL +from predicate.models import ProxyConfig, StorageState, Viewport +from predicate.permissions import PermissionPolicy logger = logging.getLogger(__name__) @@ -142,7 +142,7 @@ def __init__( # Only set api_url if api_key is provided, otherwise None (free tier) # Defaults to production API if key is present but url is missing if self.api_key and not api_url: - self.api_url = SENTIENCE_API_URL + self.api_url = PREDICATE_API_URL else: self.api_url = api_url @@ -644,7 +644,7 @@ def from_existing( Example: from playwright.sync_api import sync_playwright - from sentience import SentienceBrowser, snapshot + from predicate import SentienceBrowser, snapshot with sync_playwright() as p: context = p.chromium.launch_persistent_context(...) @@ -688,7 +688,7 @@ def from_page( Example: from playwright.sync_api import sync_playwright - from sentience import SentienceBrowser, snapshot + from predicate import SentienceBrowser, snapshot with sync_playwright() as p: browser_instance = p.chromium.launch() @@ -775,7 +775,7 @@ def __init__( self.api_key = api_key # Only set api_url if api_key is provided, otherwise None (free tier) if self.api_key and not api_url: - self.api_url = SENTIENCE_API_URL + self.api_url = PREDICATE_API_URL else: self.api_url = api_url diff --git a/sentience/browser_evaluator.py b/predicate/browser_evaluator.py similarity index 100% rename from sentience/browser_evaluator.py rename to predicate/browser_evaluator.py diff --git a/sentience/canonicalization.py b/predicate/canonicalization.py similarity index 100% rename from sentience/canonicalization.py rename to predicate/canonicalization.py diff --git a/sentience/captcha.py b/predicate/captcha.py similarity index 100% rename from sentience/captcha.py rename to predicate/captcha.py diff --git a/sentience/captcha_strategies.py b/predicate/captcha_strategies.py similarity index 100% rename from sentience/captcha_strategies.py rename to predicate/captcha_strategies.py diff --git a/sentience/cli.py b/predicate/cli.py similarity index 100% rename from sentience/cli.py rename to predicate/cli.py diff --git a/sentience/cloud_tracing.py b/predicate/cloud_tracing.py similarity index 99% rename from sentience/cloud_tracing.py rename to predicate/cloud_tracing.py index a707c63..20e9a56 100644 --- a/sentience/cloud_tracing.py +++ b/predicate/cloud_tracing.py @@ -16,10 +16,10 @@ import requests -from sentience.constants import SENTIENCE_API_URL -from sentience.models import TraceStats -from sentience.trace_file_manager import TraceFileManager -from sentience.tracing import TraceSink +from predicate.constants import PREDICATE_API_URL +from predicate.models import TraceStats +from predicate.trace_file_manager import TraceFileManager +from predicate.tracing import TraceSink class SentienceLogger(Protocol): @@ -60,8 +60,8 @@ class CloudTraceSink(TraceSink): - Pro/Enterprise: Uploads to cloud via pre-signed URLs Example: - >>> from sentience.cloud_tracing import CloudTraceSink - >>> from sentience.tracing import Tracer + >>> from predicate.cloud_tracing import CloudTraceSink + >>> from predicate.tracing import Tracer >>> # Get upload URL from API >>> upload_url = "https://sentience.nyc3.digitaloceanspaces.com/..." >>> sink = CloudTraceSink(upload_url, run_id="demo") @@ -94,7 +94,7 @@ def __init__( self.upload_url = upload_url self.run_id = run_id self.api_key = api_key - self.api_url = api_url or SENTIENCE_API_URL + self.api_url = api_url or PREDICATE_API_URL self.logger = logger # Use persistent cache directory instead of temp file diff --git a/predicate/constants.py b/predicate/constants.py new file mode 100644 index 0000000..38c568b --- /dev/null +++ b/predicate/constants.py @@ -0,0 +1,7 @@ +"""Predicate SDK constants.""" + +# Canonical API endpoint constant (rebranded name). +PREDICATE_API_URL = "https://api.sentienceapi.com" + +# Backward-compat alias. Keep for transition period. +SENTIENCE_API_URL = PREDICATE_API_URL diff --git a/sentience/conversational_agent.py b/predicate/conversational_agent.py similarity index 100% rename from sentience/conversational_agent.py rename to predicate/conversational_agent.py diff --git a/sentience/cursor_policy.py b/predicate/cursor_policy.py similarity index 100% rename from sentience/cursor_policy.py rename to predicate/cursor_policy.py diff --git a/sentience/debugger.py b/predicate/debugger.py similarity index 100% rename from sentience/debugger.py rename to predicate/debugger.py diff --git a/sentience/element_filter.py b/predicate/element_filter.py similarity index 100% rename from sentience/element_filter.py rename to predicate/element_filter.py diff --git a/sentience/expect.py b/predicate/expect.py similarity index 100% rename from sentience/expect.py rename to predicate/expect.py diff --git a/sentience/extension/background.js b/predicate/extension/background.js similarity index 100% rename from sentience/extension/background.js rename to predicate/extension/background.js diff --git a/sentience/extension/content.js b/predicate/extension/content.js similarity index 100% rename from sentience/extension/content.js rename to predicate/extension/content.js diff --git a/sentience/extension/injected_api.js b/predicate/extension/injected_api.js similarity index 100% rename from sentience/extension/injected_api.js rename to predicate/extension/injected_api.js diff --git a/sentience/extension/manifest.json b/predicate/extension/manifest.json similarity index 100% rename from sentience/extension/manifest.json rename to predicate/extension/manifest.json diff --git a/sentience/extension/release.json b/predicate/extension/release.json similarity index 100% rename from sentience/extension/release.json rename to predicate/extension/release.json diff --git a/sentience/failure_artifacts.py b/predicate/failure_artifacts.py similarity index 99% rename from sentience/failure_artifacts.py rename to predicate/failure_artifacts.py index dc43b05..10e6c39 100644 --- a/sentience/failure_artifacts.py +++ b/predicate/failure_artifacts.py @@ -16,7 +16,7 @@ import requests -from sentience.constants import SENTIENCE_API_URL +from predicate.constants import PREDICATE_API_URL logger = logging.getLogger(__name__) @@ -557,7 +557,7 @@ def upload_to_cloud( >>> artifact_key = buf.upload_to_cloud(api_key="sk-...") >>> # artifact_key can be passed to /v1/traces/complete """ - base_url = api_url or SENTIENCE_API_URL + base_url = api_url or PREDICATE_API_URL # Determine which directory to upload if persisted_dir is None: diff --git a/sentience/formatting.py b/predicate/formatting.py similarity index 63% rename from sentience/formatting.py rename to predicate/formatting.py index b8dd653..4604753 100644 --- a/sentience/formatting.py +++ b/predicate/formatting.py @@ -2,11 +2,11 @@ Snapshot formatting utilities for LLM prompts. DEPRECATED: This module is maintained for backward compatibility only. -New code should import from sentience.utils.formatting or sentience directly: +New code should import from predicate.utils.formatting or sentience directly: - from sentience.utils.formatting import format_snapshot_for_llm + from predicate.utils.formatting import format_snapshot_for_llm # or - from sentience import format_snapshot_for_llm + from predicate import format_snapshot_for_llm """ # Re-export from new location for backward compatibility diff --git a/sentience/generator.py b/predicate/generator.py similarity index 99% rename from sentience/generator.py rename to predicate/generator.py index ecb5a6a..22fcc5c 100644 --- a/sentience/generator.py +++ b/predicate/generator.py @@ -19,7 +19,7 @@ def generate_python(self) -> str: f"Created: {self.trace.created_at}", '"""', "", - "from sentience import SentienceBrowser, snapshot, find, click, type_text, press", + "from predicate import SentienceBrowser, snapshot, find, click, type_text, press", "", "def main():", " with SentienceBrowser(headless=False) as browser:", diff --git a/sentience/inspector.py b/predicate/inspector.py similarity index 100% rename from sentience/inspector.py rename to predicate/inspector.py diff --git a/sentience/integrations/__init__.py b/predicate/integrations/__init__.py similarity index 100% rename from sentience/integrations/__init__.py rename to predicate/integrations/__init__.py diff --git a/sentience/integrations/langchain/__init__.py b/predicate/integrations/langchain/__init__.py similarity index 100% rename from sentience/integrations/langchain/__init__.py rename to predicate/integrations/langchain/__init__.py diff --git a/sentience/integrations/langchain/context.py b/predicate/integrations/langchain/context.py similarity index 78% rename from sentience/integrations/langchain/context.py rename to predicate/integrations/langchain/context.py index bc26c05..dd18c32 100644 --- a/sentience/integrations/langchain/context.py +++ b/predicate/integrations/langchain/context.py @@ -2,8 +2,8 @@ from dataclasses import dataclass -from sentience.browser import AsyncSentienceBrowser -from sentience.tracing import Tracer +from predicate.browser import AsyncSentienceBrowser +from predicate.tracing import Tracer @dataclass diff --git a/sentience/integrations/langchain/core.py b/predicate/integrations/langchain/core.py similarity index 96% rename from sentience/integrations/langchain/core.py rename to predicate/integrations/langchain/core.py index 28280fa..940f594 100644 --- a/sentience/integrations/langchain/core.py +++ b/predicate/integrations/langchain/core.py @@ -5,19 +5,19 @@ import time from typing import Any, Literal -from sentience.actions import ( +from predicate.actions import ( click_async, click_rect_async, press_async, scroll_to_async, type_text_async, ) -from sentience.integrations.models import AssertionResult, BrowserState, ElementSummary -from sentience.models import ReadResult, SnapshotOptions, TextRectSearchResult -from sentience.read import read_async -from sentience.snapshot import snapshot_async -from sentience.text_search import find_text_rect_async -from sentience.trace_event_builder import TraceEventBuilder +from predicate.integrations.models import AssertionResult, BrowserState, ElementSummary +from predicate.models import ReadResult, SnapshotOptions, TextRectSearchResult +from predicate.read import read_async +from predicate.snapshot import snapshot_async +from predicate.text_search import find_text_rect_async +from predicate.trace_event_builder import TraceEventBuilder from .context import SentienceLangChainContext diff --git a/sentience/integrations/langchain/tools.py b/predicate/integrations/langchain/tools.py similarity index 99% rename from sentience/integrations/langchain/tools.py rename to predicate/integrations/langchain/tools.py index 57db09f..178f17c 100644 --- a/sentience/integrations/langchain/tools.py +++ b/predicate/integrations/langchain/tools.py @@ -13,7 +13,7 @@ def build_sentience_langchain_tools(ctx: SentienceLangChainContext) -> list[Any] Build LangChain tools backed by Sentience. LangChain is an optional dependency; imports are done lazily here so that - `import sentience` works without LangChain installed. + `import predicate` works without LangChain installed. """ try: diff --git a/sentience/integrations/models.py b/predicate/integrations/models.py similarity index 91% rename from sentience/integrations/models.py rename to predicate/integrations/models.py index 180c1a0..1d707c6 100644 --- a/sentience/integrations/models.py +++ b/predicate/integrations/models.py @@ -12,11 +12,11 @@ from pydantic import BaseModel -from sentience.models import BBox +from predicate.models import BBox class ElementSummary(BaseModel): - """A small, stable subset of `sentience.models.Element` suitable for tool returns.""" + """A small, stable subset of `predicate.models.Element` suitable for tool returns.""" id: int role: str diff --git a/sentience/integrations/pydanticai/__init__.py b/predicate/integrations/pydanticai/__init__.py similarity index 90% rename from sentience/integrations/pydanticai/__init__.py rename to predicate/integrations/pydanticai/__init__.py index b714042..cf8b732 100644 --- a/sentience/integrations/pydanticai/__init__.py +++ b/predicate/integrations/pydanticai/__init__.py @@ -4,7 +4,7 @@ This module does NOT import `pydantic_ai` at import time so the base SDK can be installed without the optional dependency. Users should install: - pip install sentienceapi[pydanticai] + pip install predicatelabs[pydanticai] and then use `register_sentience_tools(...)` with a PydanticAI `Agent`. """ diff --git a/sentience/integrations/pydanticai/deps.py b/predicate/integrations/pydanticai/deps.py similarity index 80% rename from sentience/integrations/pydanticai/deps.py rename to predicate/integrations/pydanticai/deps.py index f667489..ac34814 100644 --- a/sentience/integrations/pydanticai/deps.py +++ b/predicate/integrations/pydanticai/deps.py @@ -3,8 +3,8 @@ from dataclasses import dataclass from typing import Any -from sentience.browser import AsyncSentienceBrowser -from sentience.tracing import Tracer +from predicate.browser import AsyncSentienceBrowser +from predicate.tracing import Tracer @dataclass diff --git a/sentience/integrations/pydanticai/toolset.py b/predicate/integrations/pydanticai/toolset.py similarity index 97% rename from sentience/integrations/pydanticai/toolset.py rename to predicate/integrations/pydanticai/toolset.py index 1e8e82a..859a1be 100644 --- a/sentience/integrations/pydanticai/toolset.py +++ b/predicate/integrations/pydanticai/toolset.py @@ -7,19 +7,19 @@ from pydantic import Field -from sentience.actions import ( +from predicate.actions import ( click_async, click_rect_async, press_async, scroll_to_async, type_text_async, ) -from sentience.integrations.models import AssertionResult, BrowserState, ElementSummary -from sentience.models import ReadResult, SnapshotOptions, TextRectSearchResult -from sentience.read import read_async -from sentience.snapshot import snapshot_async -from sentience.text_search import find_text_rect_async -from sentience.trace_event_builder import TraceEventBuilder +from predicate.integrations.models import AssertionResult, BrowserState, ElementSummary +from predicate.models import ReadResult, SnapshotOptions, TextRectSearchResult +from predicate.read import read_async +from predicate.snapshot import snapshot_async +from predicate.text_search import find_text_rect_async +from predicate.trace_event_builder import TraceEventBuilder from .deps import SentiencePydanticDeps diff --git a/sentience/llm_interaction_handler.py b/predicate/llm_interaction_handler.py similarity index 100% rename from sentience/llm_interaction_handler.py rename to predicate/llm_interaction_handler.py diff --git a/sentience/llm_provider.py b/predicate/llm_provider.py similarity index 99% rename from sentience/llm_provider.py rename to predicate/llm_provider.py index 21db342..fe84e65 100644 --- a/sentience/llm_provider.py +++ b/predicate/llm_provider.py @@ -135,7 +135,7 @@ class OpenAIProvider(LLMProvider): OpenAI provider implementation (GPT-4, GPT-4o, GPT-3.5-turbo, etc.) Example: - >>> from sentience.llm_provider import OpenAIProvider + >>> from predicate.llm_provider import OpenAIProvider >>> llm = OpenAIProvider(api_key="sk-...", model="gpt-4o") >>> response = llm.generate("You are a helpful assistant", "Hello!") >>> print(response.content) @@ -374,7 +374,7 @@ class AnthropicProvider(LLMProvider): Anthropic provider implementation (Claude 3 Opus, Sonnet, Haiku, etc.) Example: - >>> from sentience.llm_provider import AnthropicProvider + >>> from predicate.llm_provider import AnthropicProvider >>> llm = AnthropicProvider(api_key="sk-ant-...", model="claude-3-sonnet-20240229") >>> response = llm.generate("You are a helpful assistant", "Hello!") >>> print(response.content) @@ -555,7 +555,7 @@ class GLMProvider(LLMProvider): pip install zhipuai Example: - >>> from sentience.llm_provider import GLMProvider + >>> from predicate.llm_provider import GLMProvider >>> llm = GLMProvider(api_key="your-api-key", model="glm-4-plus") >>> response = llm.generate("You are a helpful assistant", "Hello!") >>> print(response.content) @@ -654,7 +654,7 @@ class GeminiProvider(LLMProvider): pip install google-generativeai Example: - >>> from sentience.llm_provider import GeminiProvider + >>> from predicate.llm_provider import GeminiProvider >>> llm = GeminiProvider(api_key="your-api-key", model="gemini-2.0-flash-exp") >>> response = llm.generate("You are a helpful assistant", "Hello!") >>> print(response.content) @@ -762,7 +762,7 @@ class LocalLLMProvider(LLMProvider): Supports Qwen, Llama, Gemma, Phi, and other instruction-tuned models Example: - >>> from sentience.llm_provider import LocalLLMProvider + >>> from predicate.llm_provider import LocalLLMProvider >>> llm = LocalLLMProvider(model_name="Qwen/Qwen2.5-3B-Instruct") >>> response = llm.generate("You are helpful", "Hello!") """ diff --git a/sentience/llm_provider_utils.py b/predicate/llm_provider_utils.py similarity index 100% rename from sentience/llm_provider_utils.py rename to predicate/llm_provider_utils.py diff --git a/sentience/llm_response_builder.py b/predicate/llm_response_builder.py similarity index 100% rename from sentience/llm_response_builder.py rename to predicate/llm_response_builder.py diff --git a/sentience/models.py b/predicate/models.py similarity index 99% rename from sentience/models.py rename to predicate/models.py index 07c062c..f923b19 100644 --- a/sentience/models.py +++ b/predicate/models.py @@ -764,7 +764,7 @@ class SnapshotOptions(BaseModel): For browser-use integration (where you don't have a SentienceBrowser), you can pass sentience_api_key directly in options: - from sentience.models import SnapshotOptions + from predicate.models import SnapshotOptions options = SnapshotOptions( sentience_api_key="sk_pro_xxxxx", use_api=True, diff --git a/sentience/ordinal.py b/predicate/ordinal.py similarity index 98% rename from sentience/ordinal.py rename to predicate/ordinal.py index ee66a37..981ea15 100644 --- a/sentience/ordinal.py +++ b/predicate/ordinal.py @@ -10,7 +10,7 @@ - Numeric: "#1", "#2", "number 1", "item 3" Example usage: - from sentience.ordinal import detect_ordinal_intent, select_by_ordinal + from predicate.ordinal import detect_ordinal_intent, select_by_ordinal intent = detect_ordinal_intent("click the first search result") # OrdinalIntent(kind='nth', n=1, detected=True) @@ -22,7 +22,7 @@ from dataclasses import dataclass from typing import Literal -from sentience.models import Element +from predicate.models import Element @dataclass diff --git a/sentience/overlay.py b/predicate/overlay.py similarity index 100% rename from sentience/overlay.py rename to predicate/overlay.py diff --git a/sentience/permissions.py b/predicate/permissions.py similarity index 100% rename from sentience/permissions.py rename to predicate/permissions.py diff --git a/sentience/protocols.py b/predicate/protocols.py similarity index 100% rename from sentience/protocols.py rename to predicate/protocols.py diff --git a/sentience/query.py b/predicate/query.py similarity index 100% rename from sentience/query.py rename to predicate/query.py diff --git a/sentience/read.py b/predicate/read.py similarity index 99% rename from sentience/read.py rename to predicate/read.py index ab14f28..376a8d1 100644 --- a/sentience/read.py +++ b/predicate/read.py @@ -44,7 +44,7 @@ format: fmt, content: "", length: 0, - error: "sentience.read returned non-object" + error: "predicate.read returned non-object" }; } diff --git a/sentience/recorder.py b/predicate/recorder.py similarity index 100% rename from sentience/recorder.py rename to predicate/recorder.py diff --git a/sentience/runtime_agent.py b/predicate/runtime_agent.py similarity index 100% rename from sentience/runtime_agent.py rename to predicate/runtime_agent.py diff --git a/sentience/schemas/trace_v1.json b/predicate/schemas/trace_v1.json similarity index 100% rename from sentience/schemas/trace_v1.json rename to predicate/schemas/trace_v1.json diff --git a/sentience/screenshot.py b/predicate/screenshot.py similarity index 100% rename from sentience/screenshot.py rename to predicate/screenshot.py diff --git a/sentience/sentience_methods.py b/predicate/sentience_methods.py similarity index 100% rename from sentience/sentience_methods.py rename to predicate/sentience_methods.py diff --git a/sentience/snapshot.py b/predicate/snapshot.py similarity index 99% rename from sentience/snapshot.py rename to predicate/snapshot.py index 73e334c..873c7a0 100644 --- a/sentience/snapshot.py +++ b/predicate/snapshot.py @@ -12,7 +12,7 @@ from .browser import AsyncSentienceBrowser, SentienceBrowser from .browser_evaluator import BrowserEvaluator -from .constants import SENTIENCE_API_URL +from .constants import PREDICATE_API_URL from .models import Snapshot, SnapshotOptions from .sentience_methods import SentienceMethod @@ -322,7 +322,7 @@ def _validate_payload_size(payload_json: str) -> None: def _post_snapshot_to_gateway_sync( payload: dict[str, Any], api_key: str, - api_url: str = SENTIENCE_API_URL, + api_url: str = PREDICATE_API_URL, *, timeout_s: float | None = None, ) -> dict[str, Any]: @@ -359,7 +359,7 @@ def _post_snapshot_to_gateway_sync( async def _post_snapshot_to_gateway_async( payload: dict[str, Any], api_key: str, - api_url: str = SENTIENCE_API_URL, + api_url: str = PREDICATE_API_URL, *, timeout_s: float | None = None, ) -> dict[str, Any]: @@ -591,7 +591,7 @@ def _snapshot_via_api( raise RuntimeError("Browser not started. Call browser.start() first.") # Use browser.api_url if set, otherwise default - api_url = browser.api_url or SENTIENCE_API_URL + api_url = browser.api_url or PREDICATE_API_URL # CRITICAL: Wait for extension injection to complete (CSP-resistant architecture) # Even for API mode, we need the extension to collect raw data locally @@ -858,7 +858,7 @@ async def _snapshot_via_api_async( raise RuntimeError("Browser not started. Call await browser.start() first.") # Use browser.api_url if set, otherwise default - api_url = browser.api_url or SENTIENCE_API_URL + api_url = browser.api_url or PREDICATE_API_URL # Wait for extension injection try: diff --git a/sentience/snapshot_diff.py b/predicate/snapshot_diff.py similarity index 100% rename from sentience/snapshot_diff.py rename to predicate/snapshot_diff.py diff --git a/sentience/text_search.py b/predicate/text_search.py similarity index 98% rename from sentience/text_search.py rename to predicate/text_search.py index a9a67cd..4bf4cef 100644 --- a/sentience/text_search.py +++ b/predicate/text_search.py @@ -69,7 +69,7 @@ def find_text_rect( for match in result.results: if match.in_viewport: # Use click_rect from actions module - from sentience import click_rect + from predicate import click_rect click_result = click_rect(browser, { "x": match.rect.x, "y": match.rect.y, @@ -182,7 +182,7 @@ async def find_text_rect_async( for match in result.results: if match.in_viewport: # Use click_rect_async from actions module - from sentience.actions import click_rect_async + from predicate.actions import click_rect_async click_result = await click_rect_async(browser, { "x": match.rect.x, "y": match.rect.y, diff --git a/sentience/tools/__init__.py b/predicate/tools/__init__.py similarity index 100% rename from sentience/tools/__init__.py rename to predicate/tools/__init__.py diff --git a/sentience/tools/context.py b/predicate/tools/context.py similarity index 100% rename from sentience/tools/context.py rename to predicate/tools/context.py diff --git a/sentience/tools/defaults.py b/predicate/tools/defaults.py similarity index 100% rename from sentience/tools/defaults.py rename to predicate/tools/defaults.py diff --git a/sentience/tools/filesystem.py b/predicate/tools/filesystem.py similarity index 100% rename from sentience/tools/filesystem.py rename to predicate/tools/filesystem.py diff --git a/sentience/tools/registry.py b/predicate/tools/registry.py similarity index 100% rename from sentience/tools/registry.py rename to predicate/tools/registry.py diff --git a/sentience/trace_event_builder.py b/predicate/trace_event_builder.py similarity index 100% rename from sentience/trace_event_builder.py rename to predicate/trace_event_builder.py diff --git a/sentience/trace_file_manager.py b/predicate/trace_file_manager.py similarity index 100% rename from sentience/trace_file_manager.py rename to predicate/trace_file_manager.py diff --git a/sentience/trace_indexing/__init__.py b/predicate/trace_indexing/__init__.py similarity index 100% rename from sentience/trace_indexing/__init__.py rename to predicate/trace_indexing/__init__.py diff --git a/sentience/trace_indexing/index_schema.py b/predicate/trace_indexing/index_schema.py similarity index 100% rename from sentience/trace_indexing/index_schema.py rename to predicate/trace_indexing/index_schema.py diff --git a/sentience/trace_indexing/indexer.py b/predicate/trace_indexing/indexer.py similarity index 99% rename from sentience/trace_indexing/indexer.py rename to predicate/trace_indexing/indexer.py index b70cc3d..33ce5c4 100644 --- a/sentience/trace_indexing/indexer.py +++ b/predicate/trace_indexing/indexer.py @@ -402,7 +402,7 @@ def main(): import sys if len(sys.argv) < 2: - print("Usage: python -m sentience.tracing.indexer ") + print("Usage: python -m predicate.tracing.indexer ") sys.exit(1) trace_path = sys.argv[1] diff --git a/sentience/tracer_factory.py b/predicate/tracer_factory.py similarity index 97% rename from sentience/tracer_factory.py rename to predicate/tracer_factory.py index fc8dd9f..39c1b2d 100644 --- a/sentience/tracer_factory.py +++ b/predicate/tracer_factory.py @@ -13,9 +13,9 @@ import requests -from sentience.cloud_tracing import CloudTraceSink, SentienceLogger -from sentience.constants import SENTIENCE_API_URL -from sentience.tracing import JsonlTraceSink, Tracer +from predicate.cloud_tracing import CloudTraceSink, SentienceLogger +from predicate.constants import PREDICATE_API_URL +from predicate.tracing import JsonlTraceSink, Tracer def _emit_run_start( @@ -131,7 +131,7 @@ def create_tracer( run_id = str(uuid.uuid4()) if api_url is None: - api_url = SENTIENCE_API_URL + api_url = PREDICATE_API_URL # 0. Check for orphaned traces from previous crashes (if api_key provided and upload enabled) if api_key and upload_trace: @@ -256,7 +256,7 @@ def create_tracer( return tracer -def _recover_orphaned_traces(api_key: str, api_url: str = SENTIENCE_API_URL) -> None: +def _recover_orphaned_traces(api_key: str, api_url: str = PREDICATE_API_URL) -> None: """ Attempt to upload orphaned traces from previous crashed runs. @@ -265,7 +265,7 @@ def _recover_orphaned_traces(api_key: str, api_url: str = SENTIENCE_API_URL) -> Args: api_key: Sentience API key for authentication - api_url: Sentience API base URL (defaults to SENTIENCE_API_URL) + api_url: Sentience API base URL (defaults to PREDICATE_API_URL) """ pending_dir = Path.home() / ".sentience" / "traces" / "pending" diff --git a/sentience/tracing.py b/predicate/tracing.py similarity index 99% rename from sentience/tracing.py rename to predicate/tracing.py index cbfeac0..91174d7 100644 --- a/sentience/tracing.py +++ b/predicate/tracing.py @@ -173,7 +173,7 @@ class Tracer: Useful for PII redaction or custom image processing. Example: - >>> from sentience import Tracer, JsonlTraceSink + >>> from predicate import Tracer, JsonlTraceSink >>> >>> # Basic usage >>> sink = JsonlTraceSink("trace.jsonl") diff --git a/sentience/utils/__init__.py b/predicate/utils/__init__.py similarity index 88% rename from sentience/utils/__init__.py rename to predicate/utils/__init__.py index 7f8f303..7eea10b 100644 --- a/sentience/utils/__init__.py +++ b/predicate/utils/__init__.py @@ -3,8 +3,8 @@ This module re-exports all utility functions from submodules for backward compatibility. Users can continue using: - from sentience.utils import compute_snapshot_digests, canonical_snapshot_strict - from sentience import canonical_snapshot_strict, format_snapshot_for_llm + from predicate.utils import compute_snapshot_digests, canonical_snapshot_strict + from predicate import canonical_snapshot_strict, format_snapshot_for_llm """ # Re-export all functions from submodules for backward compatibility diff --git a/sentience/utils/browser.py b/predicate/utils/browser.py similarity index 95% rename from sentience/utils/browser.py rename to predicate/utils/browser.py index 20a2132..88ea3fa 100644 --- a/sentience/utils/browser.py +++ b/predicate/utils/browser.py @@ -22,7 +22,7 @@ def save_storage_state(context: BrowserContext, file_path: str | Path) -> None: Example: ```python - from sentience import SentienceBrowser, save_storage_state + from predicate import SentienceBrowser, save_storage_state browser = SentienceBrowser() browser.start() diff --git a/sentience/utils/element.py b/predicate/utils/element.py similarity index 100% rename from sentience/utils/element.py rename to predicate/utils/element.py diff --git a/sentience/utils/formatting.py b/predicate/utils/formatting.py similarity index 100% rename from sentience/utils/formatting.py rename to predicate/utils/formatting.py diff --git a/sentience/verification.py b/predicate/verification.py similarity index 99% rename from sentience/verification.py rename to predicate/verification.py index f5209da..f521214 100644 --- a/sentience/verification.py +++ b/predicate/verification.py @@ -11,7 +11,7 @@ - Predicate: Callable that takes context and returns outcome Example usage: - from sentience.verification import url_matches, exists, AssertContext + from predicate.verification import url_matches, exists, AssertContext # Create predicates on_search_page = url_matches(r"/s\\?k=") diff --git a/sentience/vision_executor.py b/predicate/vision_executor.py similarity index 100% rename from sentience/vision_executor.py rename to predicate/vision_executor.py diff --git a/sentience/visual_agent.py b/predicate/visual_agent.py similarity index 100% rename from sentience/visual_agent.py rename to predicate/visual_agent.py diff --git a/sentience/wait.py b/predicate/wait.py similarity index 100% rename from sentience/wait.py rename to predicate/wait.py diff --git a/pyproject.toml b/pyproject.toml index fb033e7..fc93489 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ requires = ["setuptools>=61.0", "wheel"] build-backend = "setuptools.build_meta" [project] -name = "sentienceapi" +name = "predicatelabs" version = "0.99.7" description = "Python SDK for Sentience AI Agent Browser Automation" readme = "README.md" @@ -37,7 +37,7 @@ Repository = "https://github.com/SentienceAPI/sentience-python" Issues = "https://github.com/SentienceAPI/sentience-python/issues" [project.scripts] -sentience = "sentience.cli:main" +predicate = "predicate.cli:main" [project.optional-dependencies] browser-use = [ @@ -68,10 +68,10 @@ dev = [ [tool.setuptools.packages.find] where = ["."] -include = ["sentience*"] +include = ["predicate*"] [tool.setuptools.package-data] -sentience = ["schemas/*.json", "extension/**/*"] +predicate = ["schemas/*.json", "extension/**/*"] [tool.pytest.ini_options] testpaths = ["tests"] @@ -105,7 +105,7 @@ extend-exclude = ''' profile = "black" line_length = 100 skip_gitignore = true -known_first_party = ["sentience"] +known_first_party = ["predicate"] multi_line_output = 3 include_trailing_comma = true force_grid_wrap = 0 diff --git a/scripts/sync_extension.sh b/scripts/sync_extension.sh index 5da29ab..07bb0b9 100644 --- a/scripts/sync_extension.sh +++ b/scripts/sync_extension.sh @@ -4,7 +4,7 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" CHROME_DIR="${REPO_ROOT}/sentience-chrome" -SDK_EXT_DIR="${REPO_ROOT}/sdk-python/sentience/extension" +SDK_EXT_DIR="${REPO_ROOT}/sdk-python/predicate/extension" if [[ ! -d "${CHROME_DIR}" ]]; then echo "[sync_extension] sentience-chrome not found at ${CHROME_DIR}" diff --git a/sentience/constants.py b/sentience/constants.py deleted file mode 100644 index 2f2f701..0000000 --- a/sentience/constants.py +++ /dev/null @@ -1,6 +0,0 @@ -""" -Sentience SDK constants. -""" - -# Sentience API endpoint -SENTIENCE_API_URL = "https://api.sentienceapi.com" diff --git a/sentience/extension/pkg/sentience_core.d.ts b/sentience/extension/pkg/sentience_core.d.ts deleted file mode 100644 index 39ef420..0000000 --- a/sentience/extension/pkg/sentience_core.d.ts +++ /dev/null @@ -1,51 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ - -export function analyze_page(val: any): any; - -export function analyze_page_with_options(val: any, options: any): any; - -export function decide_and_act(_raw_elements: any): void; - -/** - * Prune raw elements before sending to API - * This is a "dumb" filter that reduces payload size without leaking proprietary IP - * Filters out: tiny elements, invisible elements, non-interactive wrapper divs - * Amazon: 5000-6000 elements -> ~200-400 elements (~95% reduction) - */ -export function prune_for_api(val: any): any; - -export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module; - -export interface InitOutput { - readonly memory: WebAssembly.Memory; - readonly analyze_page: (a: number) => number; - readonly analyze_page_with_options: (a: number, b: number) => number; - readonly decide_and_act: (a: number) => void; - readonly prune_for_api: (a: number) => number; - readonly __wbindgen_export: (a: number, b: number) => number; - readonly __wbindgen_export2: (a: number, b: number, c: number, d: number) => number; - readonly __wbindgen_export3: (a: number) => void; -} - -export type SyncInitInput = BufferSource | WebAssembly.Module; - -/** - * Instantiates the given `module`, which can either be bytes or - * a precompiled `WebAssembly.Module`. - * - * @param {{ module: SyncInitInput }} module - Passing `SyncInitInput` directly is deprecated. - * - * @returns {InitOutput} - */ -export function initSync(module: { module: SyncInitInput } | SyncInitInput): InitOutput; - -/** - * If `module_or_path` is {RequestInfo} or {URL}, makes a request and - * for everything else, calls `WebAssembly.instantiate` directly. - * - * @param {{ module_or_path: InitInput | Promise }} module_or_path - Passing `InitInput` directly is deprecated. - * - * @returns {Promise} - */ -export default function __wbg_init (module_or_path?: { module_or_path: InitInput | Promise } | InitInput | Promise): Promise; diff --git a/sentience/extension/pkg/sentience_core.js b/sentience/extension/pkg/sentience_core.js deleted file mode 100644 index c50ad61..0000000 --- a/sentience/extension/pkg/sentience_core.js +++ /dev/null @@ -1,371 +0,0 @@ -export function analyze_page(val) { - return takeObject(wasm.analyze_page(addHeapObject(val))); -} - -export function analyze_page_with_options(val, options) { - return takeObject(wasm.analyze_page_with_options(addHeapObject(val), addHeapObject(options))); -} - -export function decide_and_act(_raw_elements) { - wasm.decide_and_act(addHeapObject(_raw_elements)); -} - -export function prune_for_api(val) { - return takeObject(wasm.prune_for_api(addHeapObject(val))); -} - -function __wbg_get_imports() { - const import0 = { - __proto__: null, - __wbg_Error_8c4e43fe74559d73: function(arg0, arg1) { - return addHeapObject(Error(getStringFromWasm0(arg0, arg1))); - }, - __wbg_Number_04624de7d0e8332d: function(arg0) { - return Number(getObject(arg0)); - }, - __wbg___wbindgen_bigint_get_as_i64_8fcf4ce7f1ca72a2: function(arg0, arg1) { - const v = getObject(arg1), ret = "bigint" == typeof v ? v : void 0; - getDataViewMemory0().setBigInt64(arg0 + 8, isLikeNone(ret) ? BigInt(0) : ret, !0), - getDataViewMemory0().setInt32(arg0 + 0, !isLikeNone(ret), !0); - }, - __wbg___wbindgen_boolean_get_bbbb1c18aa2f5e25: function(arg0) { - const v = getObject(arg0), ret = "boolean" == typeof v ? v : void 0; - return isLikeNone(ret) ? 16777215 : ret ? 1 : 0; - }, - __wbg___wbindgen_debug_string_0bc8482c6e3508ae: function(arg0, arg1) { - const ptr1 = passStringToWasm0(debugString(getObject(arg1)), wasm.__wbindgen_export, wasm.__wbindgen_export2), len1 = WASM_VECTOR_LEN; - getDataViewMemory0().setInt32(arg0 + 4, len1, !0), getDataViewMemory0().setInt32(arg0 + 0, ptr1, !0); - }, - __wbg___wbindgen_in_47fa6863be6f2f25: function(arg0, arg1) { - return getObject(arg0) in getObject(arg1); - }, - __wbg___wbindgen_is_bigint_31b12575b56f32fc: function(arg0) { - return "bigint" == typeof getObject(arg0); - }, - __wbg___wbindgen_is_function_0095a73b8b156f76: function(arg0) { - return "function" == typeof getObject(arg0); - }, - __wbg___wbindgen_is_object_5ae8e5880f2c1fbd: function(arg0) { - const val = getObject(arg0); - return "object" == typeof val && null !== val; - }, - __wbg___wbindgen_is_undefined_9e4d92534c42d778: function(arg0) { - return void 0 === getObject(arg0); - }, - __wbg___wbindgen_jsval_eq_11888390b0186270: function(arg0, arg1) { - return getObject(arg0) === getObject(arg1); - }, - __wbg___wbindgen_jsval_loose_eq_9dd77d8cd6671811: function(arg0, arg1) { - return getObject(arg0) == getObject(arg1); - }, - __wbg___wbindgen_number_get_8ff4255516ccad3e: function(arg0, arg1) { - const obj = getObject(arg1), ret = "number" == typeof obj ? obj : void 0; - getDataViewMemory0().setFloat64(arg0 + 8, isLikeNone(ret) ? 0 : ret, !0), getDataViewMemory0().setInt32(arg0 + 0, !isLikeNone(ret), !0); - }, - __wbg___wbindgen_string_get_72fb696202c56729: function(arg0, arg1) { - const obj = getObject(arg1), ret = "string" == typeof obj ? obj : void 0; - var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_export, wasm.__wbindgen_export2), len1 = WASM_VECTOR_LEN; - getDataViewMemory0().setInt32(arg0 + 4, len1, !0), getDataViewMemory0().setInt32(arg0 + 0, ptr1, !0); - }, - __wbg___wbindgen_throw_be289d5034ed271b: function(arg0, arg1) { - throw new Error(getStringFromWasm0(arg0, arg1)); - }, - __wbg_call_389efe28435a9388: function() { - return handleError(function(arg0, arg1) { - return addHeapObject(getObject(arg0).call(getObject(arg1))); - }, arguments); - }, - __wbg_done_57b39ecd9addfe81: function(arg0) { - return getObject(arg0).done; - }, - __wbg_error_9a7fe3f932034cde: function(arg0) {}, - __wbg_get_9b94d73e6221f75c: function(arg0, arg1) { - return addHeapObject(getObject(arg0)[arg1 >>> 0]); - }, - __wbg_get_b3ed3ad4be2bc8ac: function() { - return handleError(function(arg0, arg1) { - return addHeapObject(Reflect.get(getObject(arg0), getObject(arg1))); - }, arguments); - }, - __wbg_get_with_ref_key_1dc361bd10053bfe: function(arg0, arg1) { - return addHeapObject(getObject(arg0)[getObject(arg1)]); - }, - __wbg_instanceof_ArrayBuffer_c367199e2fa2aa04: function(arg0) { - let result; - try { - result = getObject(arg0) instanceof ArrayBuffer; - } catch (_) { - result = !1; - } - return result; - }, - __wbg_instanceof_Uint8Array_9b9075935c74707c: function(arg0) { - let result; - try { - result = getObject(arg0) instanceof Uint8Array; - } catch (_) { - result = !1; - } - return result; - }, - __wbg_isArray_d314bb98fcf08331: function(arg0) { - return Array.isArray(getObject(arg0)); - }, - __wbg_isSafeInteger_bfbc7332a9768d2a: function(arg0) { - return Number.isSafeInteger(getObject(arg0)); - }, - __wbg_iterator_6ff6560ca1568e55: function() { - return addHeapObject(Symbol.iterator); - }, - __wbg_js_click_element_2fe1e774f3d232c7: function(arg0) { - js_click_element(arg0); - }, - __wbg_length_32ed9a279acd054c: function(arg0) { - return getObject(arg0).length; - }, - __wbg_length_35a7bace40f36eac: function(arg0) { - return getObject(arg0).length; - }, - __wbg_new_361308b2356cecd0: function() { - return addHeapObject(new Object); - }, - __wbg_new_3eb36ae241fe6f44: function() { - return addHeapObject(new Array); - }, - __wbg_new_dd2b680c8bf6ae29: function(arg0) { - return addHeapObject(new Uint8Array(getObject(arg0))); - }, - __wbg_next_3482f54c49e8af19: function() { - return handleError(function(arg0) { - return addHeapObject(getObject(arg0).next()); - }, arguments); - }, - __wbg_next_418f80d8f5303233: function(arg0) { - return addHeapObject(getObject(arg0).next); - }, - __wbg_prototypesetcall_bdcdcc5842e4d77d: function(arg0, arg1, arg2) { - Uint8Array.prototype.set.call(getArrayU8FromWasm0(arg0, arg1), getObject(arg2)); - }, - __wbg_set_3f1d0b984ed272ed: function(arg0, arg1, arg2) { - getObject(arg0)[takeObject(arg1)] = takeObject(arg2); - }, - __wbg_set_f43e577aea94465b: function(arg0, arg1, arg2) { - getObject(arg0)[arg1 >>> 0] = takeObject(arg2); - }, - __wbg_value_0546255b415e96c1: function(arg0) { - return addHeapObject(getObject(arg0).value); - }, - __wbindgen_cast_0000000000000001: function(arg0) { - return addHeapObject(arg0); - }, - __wbindgen_cast_0000000000000002: function(arg0, arg1) { - return addHeapObject(getStringFromWasm0(arg0, arg1)); - }, - __wbindgen_cast_0000000000000003: function(arg0) { - return addHeapObject(BigInt.asUintN(64, arg0)); - }, - __wbindgen_object_clone_ref: function(arg0) { - return addHeapObject(getObject(arg0)); - }, - __wbindgen_object_drop_ref: function(arg0) { - takeObject(arg0); - } - }; - return { - __proto__: null, - "./sentience_core_bg.js": import0 - }; -} - -function addHeapObject(obj) { - heap_next === heap.length && heap.push(heap.length + 1); - const idx = heap_next; - return heap_next = heap[idx], heap[idx] = obj, idx; -} - -function debugString(val) { - const type = typeof val; - if ("number" == type || "boolean" == type || null == val) return `${val}`; - if ("string" == type) return `"${val}"`; - if ("symbol" == type) { - const description = val.description; - return null == description ? "Symbol" : `Symbol(${description})`; - } - if ("function" == type) { - const name = val.name; - return "string" == typeof name && name.length > 0 ? `Function(${name})` : "Function"; - } - if (Array.isArray(val)) { - const length = val.length; - let debug = "["; - length > 0 && (debug += debugString(val[0])); - for (let i = 1; i < length; i++) debug += ", " + debugString(val[i]); - return debug += "]", debug; - } - const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)); - let className; - if (!(builtInMatches && builtInMatches.length > 1)) return toString.call(val); - if (className = builtInMatches[1], "Object" == className) try { - return "Object(" + JSON.stringify(val) + ")"; - } catch (_) { - return "Object"; - } - return val instanceof Error ? `${val.name}: ${val.message}\n${val.stack}` : className; -} - -function dropObject(idx) { - idx < 132 || (heap[idx] = heap_next, heap_next = idx); -} - -function getArrayU8FromWasm0(ptr, len) { - return ptr >>>= 0, getUint8ArrayMemory0().subarray(ptr / 1, ptr / 1 + len); -} - -let cachedDataViewMemory0 = null; - -function getDataViewMemory0() { - return (null === cachedDataViewMemory0 || !0 === cachedDataViewMemory0.buffer.detached || void 0 === cachedDataViewMemory0.buffer.detached && cachedDataViewMemory0.buffer !== wasm.memory.buffer) && (cachedDataViewMemory0 = new DataView(wasm.memory.buffer)), - cachedDataViewMemory0; -} - -function getStringFromWasm0(ptr, len) { - return decodeText(ptr >>>= 0, len); -} - -let cachedUint8ArrayMemory0 = null; - -function getUint8ArrayMemory0() { - return null !== cachedUint8ArrayMemory0 && 0 !== cachedUint8ArrayMemory0.byteLength || (cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer)), - cachedUint8ArrayMemory0; -} - -function getObject(idx) { - return heap[idx]; -} - -function handleError(f, args) { - try { - return f.apply(this, args); - } catch (e) { - wasm.__wbindgen_export3(addHeapObject(e)); - } -} - -let heap = new Array(128).fill(void 0); - -heap.push(void 0, null, !0, !1); - -let heap_next = heap.length; - -function isLikeNone(x) { - return null == x; -} - -function passStringToWasm0(arg, malloc, realloc) { - if (void 0 === realloc) { - const buf = cachedTextEncoder.encode(arg), ptr = malloc(buf.length, 1) >>> 0; - return getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf), WASM_VECTOR_LEN = buf.length, - ptr; - } - let len = arg.length, ptr = malloc(len, 1) >>> 0; - const mem = getUint8ArrayMemory0(); - let offset = 0; - for (;offset < len; offset++) { - const code = arg.charCodeAt(offset); - if (code > 127) break; - mem[ptr + offset] = code; - } - if (offset !== len) { - 0 !== offset && (arg = arg.slice(offset)), ptr = realloc(ptr, len, len = offset + 3 * arg.length, 1) >>> 0; - const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); - offset += cachedTextEncoder.encodeInto(arg, view).written, ptr = realloc(ptr, len, offset, 1) >>> 0; - } - return WASM_VECTOR_LEN = offset, ptr; -} - -function takeObject(idx) { - const ret = getObject(idx); - return dropObject(idx), ret; -} - -let cachedTextDecoder = new TextDecoder("utf-8", { - ignoreBOM: !0, - fatal: !0 -}); - -cachedTextDecoder.decode(); - -const MAX_SAFARI_DECODE_BYTES = 2146435072; - -let numBytesDecoded = 0; - -function decodeText(ptr, len) { - return numBytesDecoded += len, numBytesDecoded >= MAX_SAFARI_DECODE_BYTES && (cachedTextDecoder = new TextDecoder("utf-8", { - ignoreBOM: !0, - fatal: !0 - }), cachedTextDecoder.decode(), numBytesDecoded = len), cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len)); -} - -const cachedTextEncoder = new TextEncoder; - -"encodeInto" in cachedTextEncoder || (cachedTextEncoder.encodeInto = function(arg, view) { - const buf = cachedTextEncoder.encode(arg); - return view.set(buf), { - read: arg.length, - written: buf.length - }; -}); - -let wasmModule, wasm, WASM_VECTOR_LEN = 0; - -function __wbg_finalize_init(instance, module) { - return wasm = instance.exports, wasmModule = module, cachedDataViewMemory0 = null, - cachedUint8ArrayMemory0 = null, wasm; -} - -async function __wbg_load(module, imports) { - if ("function" == typeof Response && module instanceof Response) { - if ("function" == typeof WebAssembly.instantiateStreaming) try { - return await WebAssembly.instantiateStreaming(module, imports); - } catch (e) { - if (!(module.ok && function(type) { - switch (type) { - case "basic": - case "cors": - case "default": - return !0; - } - return !1; - }(module.type)) || "application/wasm" === module.headers.get("Content-Type")) throw e; - } - const bytes = await module.arrayBuffer(); - return await WebAssembly.instantiate(bytes, imports); - } - { - const instance = await WebAssembly.instantiate(module, imports); - return instance instanceof WebAssembly.Instance ? { - instance: instance, - module: module - } : instance; - } -} - -function initSync(module) { - if (void 0 !== wasm) return wasm; - void 0 !== module && Object.getPrototypeOf(module) === Object.prototype && ({module: module} = module); - const imports = __wbg_get_imports(); - module instanceof WebAssembly.Module || (module = new WebAssembly.Module(module)); - return __wbg_finalize_init(new WebAssembly.Instance(module, imports), module); -} - -async function __wbg_init(module_or_path) { - if (void 0 !== wasm) return wasm; - void 0 !== module_or_path && Object.getPrototypeOf(module_or_path) === Object.prototype && ({module_or_path: module_or_path} = module_or_path), - void 0 === module_or_path && (module_or_path = new URL("sentience_core_bg.wasm", import.meta.url)); - const imports = __wbg_get_imports(); - ("string" == typeof module_or_path || "function" == typeof Request && module_or_path instanceof Request || "function" == typeof URL && module_or_path instanceof URL) && (module_or_path = fetch(module_or_path)); - const {instance: instance, module: module} = await __wbg_load(await module_or_path, imports); - return __wbg_finalize_init(instance, module); -} - -export { initSync, __wbg_init as default }; diff --git a/sentience/extension/pkg/sentience_core_bg.wasm b/sentience/extension/pkg/sentience_core_bg.wasm deleted file mode 100644 index 4ef5500..0000000 Binary files a/sentience/extension/pkg/sentience_core_bg.wasm and /dev/null differ diff --git a/sentience/extension/pkg/sentience_core_bg.wasm.d.ts b/sentience/extension/pkg/sentience_core_bg.wasm.d.ts deleted file mode 100644 index dccf049..0000000 --- a/sentience/extension/pkg/sentience_core_bg.wasm.d.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ -export const memory: WebAssembly.Memory; -export const analyze_page: (a: number) => number; -export const analyze_page_with_options: (a: number, b: number) => number; -export const decide_and_act: (a: number) => void; -export const prune_for_api: (a: number) => number; -export const __wbindgen_export: (a: number, b: number) => number; -export const __wbindgen_export2: (a: number, b: number, c: number, d: number) => number; -export const __wbindgen_export3: (a: number) => void; diff --git a/tests/integration/test_agent_workflows.py b/tests/integration/test_agent_workflows.py index f3bd0fc..300b605 100644 --- a/tests/integration/test_agent_workflows.py +++ b/tests/integration/test_agent_workflows.py @@ -9,10 +9,10 @@ import pytest -from sentience.agent import SentienceAgent -from sentience.llm_provider import LLMProvider, LLMResponse -from sentience.models import BBox, Element, Snapshot, Viewport, VisualCues -from sentience.protocols import BrowserProtocol, PageProtocol +from predicate.agent import SentienceAgent +from predicate.llm_provider import LLMProvider, LLMResponse +from predicate.models import BBox, Element, Snapshot, Viewport, VisualCues +from predicate.protocols import BrowserProtocol, PageProtocol class MockLLMProvider(LLMProvider): @@ -146,11 +146,11 @@ def test_agent_multi_step_click_then_type(self): agent = SentienceAgent(browser, llm, verbose=False) with ( - patch("sentience.agent.snapshot") as mock_snapshot, - patch("sentience.action_executor.click") as mock_click, - patch("sentience.action_executor.type_text") as mock_type, + patch("predicate.agent.snapshot") as mock_snapshot, + patch("predicate.action_executor.click") as mock_click, + patch("predicate.action_executor.type_text") as mock_type, ): - from sentience.models import ActionResult + from predicate.models import ActionResult mock_snapshot.return_value = create_mock_snapshot() mock_click.return_value = ActionResult( @@ -183,10 +183,10 @@ def test_agent_workflow_with_retry(self): agent = SentienceAgent(browser, llm, verbose=False) with ( - patch("sentience.agent.snapshot") as mock_snapshot, - patch("sentience.action_executor.click") as mock_click, + patch("predicate.agent.snapshot") as mock_snapshot, + patch("predicate.action_executor.click") as mock_click, ): - from sentience.models import ActionResult + from predicate.models import ActionResult mock_snapshot.return_value = create_mock_snapshot() # First call raises exception (triggers retry), second succeeds @@ -209,10 +209,10 @@ def test_agent_workflow_url_change(self): agent = SentienceAgent(browser, llm, verbose=False) with ( - patch("sentience.agent.snapshot") as mock_snapshot, - patch("sentience.action_executor.click") as mock_click, + patch("predicate.agent.snapshot") as mock_snapshot, + patch("predicate.action_executor.click") as mock_click, ): - from sentience.models import ActionResult + from predicate.models import ActionResult mock_snapshot.return_value = create_mock_snapshot() mock_click.return_value = ActionResult( @@ -232,7 +232,7 @@ def test_agent_workflow_finish_action(self): llm = MockLLMProvider(responses=["FINISH()"]) agent = SentienceAgent(browser, llm, verbose=False) - with patch("sentience.agent.snapshot") as mock_snapshot: + with patch("predicate.agent.snapshot") as mock_snapshot: mock_snapshot.return_value = create_mock_snapshot() result = agent.act("Task is complete", max_retries=0) @@ -249,10 +249,10 @@ def test_agent_workflow_token_tracking(self): agent = SentienceAgent(browser, llm, verbose=False) with ( - patch("sentience.agent.snapshot") as mock_snapshot, - patch("sentience.action_executor.click") as mock_click, + patch("predicate.agent.snapshot") as mock_snapshot, + patch("predicate.action_executor.click") as mock_click, ): - from sentience.models import ActionResult + from predicate.models import ActionResult mock_snapshot.return_value = create_mock_snapshot() mock_click.return_value = ActionResult( @@ -282,10 +282,10 @@ def test_agent_recovery_after_snapshot_failure(self): agent = SentienceAgent(browser, llm, verbose=False) with ( - patch("sentience.agent.snapshot") as mock_snapshot, - patch("sentience.action_executor.click") as mock_click, + patch("predicate.agent.snapshot") as mock_snapshot, + patch("predicate.action_executor.click") as mock_click, ): - from sentience.models import ActionResult, Snapshot + from predicate.models import ActionResult, Snapshot # First snapshot fails, second succeeds failed_snapshot = Snapshot( @@ -319,10 +319,10 @@ def test_agent_recovery_after_action_failure(self): agent = SentienceAgent(browser, llm, verbose=False) with ( - patch("sentience.agent.snapshot") as mock_snapshot, - patch("sentience.action_executor.click") as mock_click, + patch("predicate.agent.snapshot") as mock_snapshot, + patch("predicate.action_executor.click") as mock_click, ): - from sentience.models import ActionResult + from predicate.models import ActionResult mock_snapshot.return_value = create_mock_snapshot() # First action fails, second succeeds @@ -345,10 +345,10 @@ def test_agent_handles_max_retries_exceeded(self): agent = SentienceAgent(browser, llm, verbose=False) with ( - patch("sentience.agent.snapshot") as mock_snapshot, - patch("sentience.action_executor.click") as mock_click, + patch("predicate.agent.snapshot") as mock_snapshot, + patch("predicate.action_executor.click") as mock_click, ): - from sentience.models import ActionResult + from predicate.models import ActionResult mock_snapshot.return_value = create_mock_snapshot() # Raise exception to trigger retry logic (agent only retries on exceptions, not failed results) @@ -375,10 +375,10 @@ def test_agent_history_preservation(self): agent = SentienceAgent(browser, llm, verbose=False) with ( - patch("sentience.agent.snapshot") as mock_snapshot, - patch("sentience.action_executor.click") as mock_click, + patch("predicate.agent.snapshot") as mock_snapshot, + patch("predicate.action_executor.click") as mock_click, ): - from sentience.models import ActionResult + from predicate.models import ActionResult mock_snapshot.return_value = create_mock_snapshot() mock_click.return_value = ActionResult( @@ -404,10 +404,10 @@ def test_agent_step_count_increments(self): agent = SentienceAgent(browser, llm, verbose=False) with ( - patch("sentience.agent.snapshot") as mock_snapshot, - patch("sentience.action_executor.click") as mock_click, + patch("predicate.agent.snapshot") as mock_snapshot, + patch("predicate.action_executor.click") as mock_click, ): - from sentience.models import ActionResult + from predicate.models import ActionResult mock_snapshot.return_value = create_mock_snapshot() mock_click.return_value = ActionResult( diff --git a/tests/test_actions.py b/tests/test_actions.py index 847d8c2..62a93a9 100644 --- a/tests/test_actions.py +++ b/tests/test_actions.py @@ -4,7 +4,7 @@ import pytest -from sentience import ( +from predicate import ( AsyncSentienceBrowser, SentienceBrowser, back, diff --git a/tests/test_agent.py b/tests/test_agent.py index 72cd5f2..565d589 100644 --- a/tests/test_agent.py +++ b/tests/test_agent.py @@ -7,9 +7,9 @@ import pytest -from sentience.agent import SentienceAgent -from sentience.llm_provider import AnthropicProvider, LLMProvider, LLMResponse, OpenAIProvider -from sentience.models import BBox, Element, Snapshot, Viewport, VisualCues +from predicate.agent import SentienceAgent +from predicate.llm_provider import AnthropicProvider, LLMProvider, LLMResponse, OpenAIProvider +from predicate.models import BBox, Element, Snapshot, Viewport, VisualCues class MockLLMProvider(LLMProvider): @@ -197,8 +197,8 @@ def test_agent_execute_click_action(): snap = create_mock_snapshot() # Mock click function via ActionExecutor - with patch("sentience.action_executor.click") as mock_click: - from sentience.models import ActionResult + with patch("predicate.action_executor.click") as mock_click: + from predicate.models import ActionResult mock_click.return_value = ActionResult( success=True, duration_ms=150, outcome="dom_updated", url_changed=False @@ -221,8 +221,8 @@ def test_agent_execute_type_action(): snap = create_mock_snapshot() # Mock type_text function via ActionExecutor - with patch("sentience.action_executor.type_text") as mock_type: - from sentience.models import ActionResult + with patch("predicate.action_executor.type_text") as mock_type: + from predicate.models import ActionResult mock_type.return_value = ActionResult(success=True, duration_ms=200, outcome="dom_updated") @@ -244,8 +244,8 @@ def test_agent_execute_press_action(): snap = create_mock_snapshot() # Mock press function via ActionExecutor - with patch("sentience.action_executor.press") as mock_press: - from sentience.models import ActionResult + with patch("predicate.action_executor.press") as mock_press: + from predicate.models import ActionResult mock_press.return_value = ActionResult(success=True, duration_ms=50, outcome="dom_updated") @@ -290,10 +290,10 @@ def test_agent_act_full_cycle(): # Mock snapshot and click with ( - patch("sentience.agent.snapshot") as mock_snapshot, - patch("sentience.action_executor.click") as mock_click, + patch("predicate.agent.snapshot") as mock_snapshot, + patch("predicate.action_executor.click") as mock_click, ): - from sentience.models import ActionResult + from predicate.models import ActionResult mock_snapshot.return_value = create_mock_snapshot() mock_click.return_value = ActionResult(success=True, duration_ms=150, outcome="dom_updated") @@ -388,8 +388,8 @@ def test_agent_retry_on_failure(): # Mock snapshot and click (click will fail) with ( - patch("sentience.agent.snapshot") as mock_snapshot, - patch("sentience.action_executor.click") as mock_click, + patch("predicate.agent.snapshot") as mock_snapshot, + patch("predicate.action_executor.click") as mock_click, ): mock_snapshot.return_value = create_mock_snapshot() # Simulate click failure @@ -411,11 +411,11 @@ def test_agent_action_parsing_variations(): snap = create_mock_snapshot() with ( - patch("sentience.action_executor.click") as mock_click, - patch("sentience.action_executor.type_text") as mock_type, - patch("sentience.action_executor.press") as mock_press, + patch("predicate.action_executor.click") as mock_click, + patch("predicate.action_executor.type_text") as mock_type, + patch("predicate.action_executor.press") as mock_press, ): - from sentience.models import ActionResult + from predicate.models import ActionResult mock_result = ActionResult(success=True, duration_ms=100, outcome="dom_updated") mock_click.return_value = mock_result diff --git a/tests/test_agent_config.py b/tests/test_agent_config.py index 281592c..4e14a5e 100644 --- a/tests/test_agent_config.py +++ b/tests/test_agent_config.py @@ -1,6 +1,6 @@ -"""Tests for sentience.agent_config module""" +"""Tests for predicate.agent_config module""" -from sentience.agent_config import AgentConfig +from predicate.agent_config import AgentConfig def test_agent_config_defaults(): diff --git a/tests/test_agent_runtime.py b/tests/test_agent_runtime.py index c0d2ef8..98162fa 100644 --- a/tests/test_agent_runtime.py +++ b/tests/test_agent_runtime.py @@ -9,9 +9,9 @@ import pytest -from sentience.agent_runtime import AgentRuntime -from sentience.models import EvaluateJsRequest, SnapshotOptions, TabInfo -from sentience.verification import AssertContext, AssertOutcome +from predicate.agent_runtime import AgentRuntime +from predicate.models import EvaluateJsRequest, SnapshotOptions, TabInfo +from predicate.verification import AssertContext, AssertOutcome class MockBackend: @@ -706,7 +706,7 @@ async def test_from_sentience_browser_creates_runtime(self) -> None: mock_page.url = "https://example.com" tracer = MockTracer() - with patch("sentience.backends.playwright_backend.PlaywrightBackend") as MockPWBackend: + with patch("predicate.backends.playwright_backend.PlaywrightBackend") as MockPWBackend: mock_backend_instance = MagicMock() MockPWBackend.return_value = mock_backend_instance @@ -728,7 +728,7 @@ async def test_from_sentience_browser_with_api_key(self) -> None: mock_page = MagicMock() tracer = MockTracer() - with patch("sentience.backends.playwright_backend.PlaywrightBackend"): + with patch("predicate.backends.playwright_backend.PlaywrightBackend"): runtime = await AgentRuntime.from_sentience_browser( browser=mock_browser, page=mock_page, @@ -748,7 +748,7 @@ def test_from_playwright_page_creates_runtime(self) -> None: mock_page = MagicMock() tracer = MockTracer() - with patch("sentience.backends.playwright_backend.PlaywrightBackend") as MockPWBackend: + with patch("predicate.backends.playwright_backend.PlaywrightBackend") as MockPWBackend: mock_backend_instance = MagicMock() MockPWBackend.return_value = mock_backend_instance @@ -764,7 +764,7 @@ def test_from_playwright_page_with_api_key(self) -> None: mock_page = MagicMock() tracer = MockTracer() - with patch("sentience.backends.playwright_backend.PlaywrightBackend"): + with patch("predicate.backends.playwright_backend.PlaywrightBackend"): runtime = AgentRuntime.from_playwright_page( page=mock_page, tracer=tracer, @@ -824,7 +824,7 @@ async def test_snapshot_with_backend(self) -> None: mock_snapshot = MagicMock() - with patch("sentience.backends.snapshot.snapshot", new_callable=AsyncMock) as mock_snap_fn: + with patch("predicate.backends.snapshot.snapshot", new_callable=AsyncMock) as mock_snap_fn: mock_snap_fn.return_value = mock_snapshot result = await runtime.snapshot(goal="test goal") @@ -848,7 +848,7 @@ async def test_snapshot_merges_options(self) -> None: snapshot_options=default_options, ) - with patch("sentience.backends.snapshot.snapshot", new_callable=AsyncMock) as mock_snap_fn: + with patch("predicate.backends.snapshot.snapshot", new_callable=AsyncMock) as mock_snap_fn: mock_snap_fn.return_value = MagicMock() await runtime.snapshot(goal="override goal") diff --git a/tests/test_asserts.py b/tests/test_asserts.py index e909001..c71177c 100644 --- a/tests/test_asserts.py +++ b/tests/test_asserts.py @@ -1,12 +1,12 @@ """ -Tests for assertion DSL module (sentience.asserts). +Tests for assertion DSL module (predicate.asserts). Tests the E() query builder, expect() fluent API, and in_dominant_list() operations. """ import pytest -from sentience.asserts import ( +from predicate.asserts import ( E, ElementQuery, EventuallyConfig, @@ -17,8 +17,8 @@ in_dominant_list, with_eventually, ) -from sentience.models import BBox, Element, Snapshot, Viewport, VisualCues -from sentience.verification import AssertContext +from predicate.models import BBox, Element, Snapshot, Viewport, VisualCues +from predicate.verification import AssertContext def make_element( diff --git a/tests/test_async_api.py b/tests/test_async_api.py index fdff935..97d8828 100644 --- a/tests/test_async_api.py +++ b/tests/test_async_api.py @@ -5,7 +5,7 @@ import pytest from playwright.async_api import async_playwright -from sentience.async_api import ( +from predicate.async_api import ( AsyncSentienceBrowser, BaseAgentAsync, ExpectationAsync, @@ -29,7 +29,7 @@ type_text_async, wait_for_async, ) -from sentience.models import BBox, SnapshotOptions +from predicate.models import BBox, SnapshotOptions @pytest.mark.asyncio @@ -510,7 +510,7 @@ async def test_base_agent_async_interface(): @pytest.mark.requires_extension async def test_sentience_agent_async_initialization(): """Test SentienceAgentAsync can be initialized""" - from sentience.llm_provider import LLMProvider, LLMResponse + from predicate.llm_provider import LLMProvider, LLMResponse # Create a simple mock LLM provider class MockLLMProvider(LLMProvider): diff --git a/tests/test_backends.py b/tests/test_backends.py index 43c153f..aefb86a 100644 --- a/tests/test_backends.py +++ b/tests/test_backends.py @@ -12,8 +12,8 @@ import pytest -from sentience import CursorPolicy -from sentience.backends import ( +from predicate import CursorPolicy +from predicate.backends import ( BrowserBackend, BrowserUseAdapter, BrowserUseCDPTransport, @@ -28,7 +28,7 @@ type_text, wait_for_stable, ) -from sentience.models import ActionResult, BBox +from predicate.models import ActionResult, BBox class MockCDPTransport: @@ -854,7 +854,7 @@ class TestCoordinateResolution: @pytest.mark.asyncio async def test_bbox_center_calculation(self) -> None: """Test BBox center calculation.""" - from sentience.backends.actions import _resolve_coordinates + from predicate.backends.actions import _resolve_coordinates bbox = BBox(x=100, y=200, width=50, height=30) x, y = _resolve_coordinates(bbox) @@ -865,7 +865,7 @@ async def test_bbox_center_calculation(self) -> None: @pytest.mark.asyncio async def test_dict_with_dimensions(self) -> None: """Test dict with width/height computes center.""" - from sentience.backends.actions import _resolve_coordinates + from predicate.backends.actions import _resolve_coordinates target = {"x": 100, "y": 200, "width": 50, "height": 30} x, y = _resolve_coordinates(target) @@ -876,7 +876,7 @@ async def test_dict_with_dimensions(self) -> None: @pytest.mark.asyncio async def test_dict_without_dimensions(self) -> None: """Test dict without width/height uses x/y directly.""" - from sentience.backends.actions import _resolve_coordinates + from predicate.backends.actions import _resolve_coordinates target = {"x": 150, "y": 250} x, y = _resolve_coordinates(target) @@ -887,7 +887,7 @@ async def test_dict_without_dimensions(self) -> None: @pytest.mark.asyncio async def test_tuple_passthrough(self) -> None: """Test tuple passes through unchanged.""" - from sentience.backends.actions import _resolve_coordinates + from predicate.backends.actions import _resolve_coordinates x, y = _resolve_coordinates((300, 400)) @@ -900,7 +900,7 @@ class TestBackendExceptions: def test_extension_diagnostics_from_dict(self) -> None: """Test ExtensionDiagnostics.from_dict.""" - from sentience.backends.exceptions import ExtensionDiagnostics + from predicate.backends.exceptions import ExtensionDiagnostics data = { "sentience_defined": True, @@ -916,7 +916,7 @@ def test_extension_diagnostics_from_dict(self) -> None: def test_extension_diagnostics_to_dict(self) -> None: """Test ExtensionDiagnostics.to_dict.""" - from sentience.backends.exceptions import ExtensionDiagnostics + from predicate.backends.exceptions import ExtensionDiagnostics diag = ExtensionDiagnostics( sentience_defined=True, @@ -932,7 +932,7 @@ def test_extension_diagnostics_to_dict(self) -> None: def test_extension_not_loaded_error_from_timeout(self) -> None: """Test ExtensionNotLoadedError.from_timeout creates helpful message.""" - from sentience.backends.exceptions import ExtensionDiagnostics, ExtensionNotLoadedError + from predicate.backends.exceptions import ExtensionDiagnostics, ExtensionNotLoadedError diag = ExtensionDiagnostics( sentience_defined=False, @@ -949,7 +949,7 @@ def test_extension_not_loaded_error_from_timeout(self) -> None: def test_extension_not_loaded_error_with_eval_error(self) -> None: """Test ExtensionNotLoadedError when diagnostics collection failed.""" - from sentience.backends.exceptions import ExtensionDiagnostics, ExtensionNotLoadedError + from predicate.backends.exceptions import ExtensionDiagnostics, ExtensionNotLoadedError diag = ExtensionDiagnostics(error="Could not evaluate JavaScript") error = ExtensionNotLoadedError.from_timeout(timeout_ms=3000, diagnostics=diag) @@ -958,7 +958,7 @@ def test_extension_not_loaded_error_with_eval_error(self) -> None: def test_snapshot_error_from_null_result(self) -> None: """Test SnapshotError.from_null_result creates helpful message.""" - from sentience.backends.exceptions import SnapshotError + from predicate.backends.exceptions import SnapshotError error = SnapshotError.from_null_result(url="https://example.com/page") @@ -968,7 +968,7 @@ def test_snapshot_error_from_null_result(self) -> None: def test_snapshot_error_from_null_result_no_url(self) -> None: """Test SnapshotError.from_null_result without URL.""" - from sentience.backends.exceptions import SnapshotError + from predicate.backends.exceptions import SnapshotError error = SnapshotError.from_null_result(url=None) @@ -977,7 +977,7 @@ def test_snapshot_error_from_null_result_no_url(self) -> None: def test_action_error_message_format(self) -> None: """Test ActionError formats message correctly.""" - from sentience.backends.exceptions import ActionError + from predicate.backends.exceptions import ActionError error = ActionError( action="click", @@ -992,7 +992,7 @@ def test_action_error_message_format(self) -> None: def test_sentience_backend_error_inheritance(self) -> None: """Test all exceptions inherit from SentienceBackendError.""" - from sentience.backends.exceptions import ( + from predicate.backends.exceptions import ( ActionError, BackendEvalError, ExtensionInjectionError, @@ -1009,7 +1009,7 @@ def test_sentience_backend_error_inheritance(self) -> None: def test_extension_injection_error_from_page(self) -> None: """Test ExtensionInjectionError.from_page.""" - from sentience.backends.exceptions import ExtensionInjectionError + from predicate.backends.exceptions import ExtensionInjectionError error = ExtensionInjectionError.from_page("https://secure-site.com") diff --git a/tests/test_bot.py b/tests/test_bot.py index 647b281..56706c3 100644 --- a/tests/test_bot.py +++ b/tests/test_bot.py @@ -1,4 +1,4 @@ -from sentience.browser import SentienceBrowser +from predicate.browser import SentienceBrowser def test_bot(): diff --git a/tests/test_browser.py b/tests/test_browser.py index da4afe3..740e3b5 100644 --- a/tests/test_browser.py +++ b/tests/test_browser.py @@ -5,7 +5,7 @@ import pytest from playwright.sync_api import sync_playwright -from sentience import SentienceBrowser +from predicate import SentienceBrowser @pytest.mark.requires_extension diff --git a/tests/test_cloud_tracing.py b/tests/test_cloud_tracing.py index 8685499..f8191a0 100644 --- a/tests/test_cloud_tracing.py +++ b/tests/test_cloud_tracing.py @@ -1,4 +1,4 @@ -"""Tests for sentience.cloud_tracing module""" +"""Tests for predicate.cloud_tracing module""" import base64 import gzip @@ -12,9 +12,9 @@ import pytest -from sentience.cloud_tracing import CloudTraceSink -from sentience.tracer_factory import create_tracer -from sentience.tracing import JsonlTraceSink, Tracer +from predicate.cloud_tracing import CloudTraceSink +from predicate.tracer_factory import create_tracer +from predicate.tracing import JsonlTraceSink, Tracer class TestCloudTraceSink: @@ -30,7 +30,7 @@ def mock_home_dir(self): mock_home = Path(tmp_dir) # Patch Path.home in the cloud_tracing module - with patch("sentience.cloud_tracing.Path.home", return_value=mock_home): + with patch("predicate.cloud_tracing.Path.home", return_value=mock_home): # Also patch it in the current test module if used directly with patch("pathlib.Path.home", return_value=mock_home): yield mock_home @@ -40,7 +40,7 @@ def test_cloud_trace_sink_upload_success(self): upload_url = "https://sentience.nyc3.digitaloceanspaces.com/user123/run456/trace.jsonl.gz" run_id = f"test-run-{uuid.uuid4().hex[:8]}" - with patch("sentience.cloud_tracing.requests.put") as mock_put: + with patch("predicate.cloud_tracing.requests.put") as mock_put: # Mock successful response mock_response = Mock() mock_response.status_code = 200 @@ -87,7 +87,7 @@ def test_cloud_trace_sink_upload_failure_preserves_trace(self, capsys): upload_url = "https://sentience.nyc3.digitaloceanspaces.com/user123/run456/trace.jsonl.gz" run_id = f"test-run-{uuid.uuid4().hex[:8]}" - with patch("sentience.cloud_tracing.requests.put") as mock_put: + with patch("predicate.cloud_tracing.requests.put") as mock_put: # Mock failed response mock_response = Mock() mock_response.status_code = 500 @@ -130,7 +130,7 @@ def test_cloud_trace_sink_emit_after_close_raises(self): def test_cloud_trace_sink_context_manager(self): """Test CloudTraceSink works as context manager.""" - with patch("sentience.cloud_tracing.requests.put") as mock_put: + with patch("predicate.cloud_tracing.requests.put") as mock_put: mock_put.return_value = Mock(status_code=200) upload_url = "https://test.com/upload" @@ -146,7 +146,7 @@ def test_cloud_trace_sink_network_error_graceful_degradation(self, capsys): upload_url = "https://sentience.nyc3.digitaloceanspaces.com/user123/run456/trace.jsonl.gz" run_id = f"test-run-{uuid.uuid4().hex[:8]}" - with patch("sentience.cloud_tracing.requests.put") as mock_put: + with patch("predicate.cloud_tracing.requests.put") as mock_put: # Simulate network error mock_put.side_effect = Exception("Network error") @@ -167,7 +167,7 @@ def test_cloud_trace_sink_network_error_graceful_degradation(self, capsys): def test_cloud_trace_sink_multiple_close_safe(self): """Test CloudTraceSink.close() is idempotent.""" - with patch("sentience.cloud_tracing.requests.put") as mock_put: + with patch("predicate.cloud_tracing.requests.put") as mock_put: mock_put.return_value = Mock(status_code=200) upload_url = "https://test.com/upload" @@ -207,7 +207,7 @@ def test_cloud_trace_sink_non_blocking_close(self): upload_url = "https://test.com/upload" run_id = f"test-run-{uuid.uuid4().hex[:8]}" - with patch("sentience.cloud_tracing.requests.put") as mock_put: + with patch("predicate.cloud_tracing.requests.put") as mock_put: mock_put.return_value = Mock(status_code=200) sink = CloudTraceSink(upload_url, run_id=run_id) @@ -236,7 +236,7 @@ def test_cloud_trace_sink_progress_callback(self): def progress_callback(uploaded: int, total: int): progress_calls.append((uploaded, total)) - with patch("sentience.cloud_tracing.requests.put") as mock_put: + with patch("predicate.cloud_tracing.requests.put") as mock_put: mock_put.return_value = Mock(status_code=200) sink = CloudTraceSink(upload_url, run_id=run_id) @@ -284,8 +284,8 @@ def test_cloud_trace_sink_uploads_screenshots_after_trace(self): } with ( - patch("sentience.cloud_tracing.requests.put") as mock_put, - patch("sentience.cloud_tracing.requests.post") as mock_post, + patch("predicate.cloud_tracing.requests.put") as mock_put, + patch("predicate.cloud_tracing.requests.post") as mock_post, ): # Mock trace upload (first PUT) mock_trace_response = Mock() @@ -485,9 +485,9 @@ class TestTracerFactory: def test_create_tracer_pro_tier_success(self, capsys): """Test create_tracer returns CloudTraceSink for Pro tier.""" # Patch orphaned trace recovery to avoid extra API calls - with patch("sentience.tracer_factory._recover_orphaned_traces"): - with patch("sentience.tracer_factory.requests.post") as mock_post: - with patch("sentience.cloud_tracing.requests.put") as mock_put: + with patch("predicate.tracer_factory._recover_orphaned_traces"): + with patch("predicate.tracer_factory.requests.post") as mock_post: + with patch("predicate.cloud_tracing.requests.put") as mock_put: # Mock API response mock_response = Mock() mock_response.status_code = 200 @@ -547,7 +547,7 @@ def test_create_tracer_free_tier_fallback(self, capsys): def test_create_tracer_api_forbidden_fallback(self, capsys): """Test create_tracer falls back when API returns 403 Forbidden.""" - with patch("sentience.tracer_factory.requests.post") as mock_post: + with patch("predicate.tracer_factory.requests.post") as mock_post: # Mock API response with 403 mock_response = Mock() mock_response.status_code = 403 @@ -572,7 +572,7 @@ def test_create_tracer_api_timeout_fallback(self, capsys): """Test create_tracer falls back on timeout.""" import requests - with patch("sentience.tracer_factory.requests.post") as mock_post: + with patch("predicate.tracer_factory.requests.post") as mock_post: # Mock timeout mock_post.side_effect = requests.exceptions.Timeout("Connection timeout") @@ -593,7 +593,7 @@ def test_create_tracer_api_connection_error_fallback(self, capsys): """Test create_tracer falls back on connection error.""" import requests - with patch("sentience.tracer_factory.requests.post") as mock_post: + with patch("predicate.tracer_factory.requests.post") as mock_post: # Mock connection error mock_post.side_effect = requests.exceptions.ConnectionError("Connection refused") @@ -621,11 +621,11 @@ def test_create_tracer_generates_run_id_if_not_provided(self): tracer.close() def test_create_tracer_uses_constant_api_url(self): - """Test create_tracer uses constant SENTIENCE_API_URL.""" - from sentience.tracer_factory import SENTIENCE_API_URL + """Test create_tracer uses canonical PREDICATE_API_URL.""" + from predicate.constants import PREDICATE_API_URL - with patch("sentience.tracer_factory.requests.post") as mock_post: - with patch("sentience.cloud_tracing.requests.put") as mock_put: + with patch("predicate.tracer_factory.requests.post") as mock_post: + with patch("predicate.cloud_tracing.requests.put") as mock_put: # Mock API response mock_response = Mock() mock_response.status_code = 200 @@ -638,8 +638,8 @@ def test_create_tracer_uses_constant_api_url(self): # Verify correct API URL was used (constant) assert mock_post.called call_args = mock_post.call_args - assert call_args[0][0] == f"{SENTIENCE_API_URL}/v1/traces/init" - assert SENTIENCE_API_URL == "https://api.sentienceapi.com" + assert call_args[0][0] == f"{PREDICATE_API_URL}/v1/traces/init" + assert PREDICATE_API_URL == "https://api.sentienceapi.com" tracer.close() @@ -647,8 +647,8 @@ def test_create_tracer_custom_api_url(self): """Test create_tracer accepts custom api_url parameter.""" custom_api_url = "https://custom.api.example.com" - with patch("sentience.tracer_factory.requests.post") as mock_post: - with patch("sentience.cloud_tracing.requests.put") as mock_put: + with patch("predicate.tracer_factory.requests.post") as mock_post: + with patch("predicate.cloud_tracing.requests.put") as mock_put: # Mock API response mock_response = Mock() mock_response.status_code = 200 @@ -672,7 +672,7 @@ def test_create_tracer_custom_api_url(self): def test_create_tracer_missing_upload_url_in_response(self, capsys): """Test create_tracer handles missing upload_url gracefully.""" - with patch("sentience.tracer_factory.requests.post") as mock_post: + with patch("predicate.tracer_factory.requests.post") as mock_post: # Mock API response without upload_url mock_response = Mock() mock_response.status_code = 200 @@ -707,8 +707,8 @@ def test_create_tracer_orphaned_trace_recovery(self, capsys): f.write('{"v": 1, "type": "run_start", "seq": 1}\n') try: - with patch("sentience.tracer_factory.requests.post") as mock_post: - with patch("sentience.tracer_factory.requests.put") as mock_put: + with patch("predicate.tracer_factory.requests.post") as mock_post: + with patch("predicate.tracer_factory.requests.put") as mock_put: # Mock API response for orphaned trace recovery mock_recovery_response = Mock() mock_recovery_response.status_code = 200 @@ -761,7 +761,7 @@ def test_create_tracer_auto_emits_run_start_by_default(self): """Test create_tracer automatically emits run_start event.""" with tempfile.TemporaryDirectory() as tmpdir: # Use local tracing (no API key) for simplicity - with patch("sentience.tracer_factory.Path") as mock_path_cls: + with patch("predicate.tracer_factory.Path") as mock_path_cls: # Make traces dir point to temp dir mock_path_cls.return_value.mkdir = Mock() mock_path_cls.return_value.__truediv__ = lambda self, x: Path(tmpdir) / x @@ -811,7 +811,7 @@ def test_create_tracer_auto_emit_with_metadata(self): tracer = Tracer(run_id="test-run", sink=sink) # Import and call the helper directly - from sentience.tracer_factory import _emit_run_start + from predicate.tracer_factory import _emit_run_start _emit_run_start( tracer, @@ -884,8 +884,8 @@ def test_cloud_trace_sink_index_upload_success(self): run_id = "test-index-upload" with ( - patch("sentience.cloud_tracing.requests.put") as mock_put, - patch("sentience.cloud_tracing.requests.post") as mock_post, + patch("predicate.cloud_tracing.requests.put") as mock_put, + patch("predicate.cloud_tracing.requests.post") as mock_post, ): # Mock successful trace upload trace_response = Mock() @@ -952,8 +952,8 @@ def test_cloud_trace_sink_index_upload_no_api_key(self): run_id = "test-no-api-key" with ( - patch("sentience.cloud_tracing.requests.put") as mock_put, - patch("sentience.cloud_tracing.requests.post") as mock_post, + patch("predicate.cloud_tracing.requests.put") as mock_put, + patch("predicate.cloud_tracing.requests.post") as mock_post, ): # Mock successful trace upload mock_put.return_value = Mock(status_code=200) @@ -985,8 +985,8 @@ def test_cloud_trace_sink_index_upload_failure_non_fatal(self, capsys): run_id = "test-index-fail" with ( - patch("sentience.cloud_tracing.requests.put") as mock_put, - patch("sentience.cloud_tracing.requests.post") as mock_post, + patch("predicate.cloud_tracing.requests.put") as mock_put, + patch("predicate.cloud_tracing.requests.post") as mock_post, ): # Mock successful trace upload trace_response = Mock() @@ -1029,9 +1029,9 @@ def test_cloud_trace_sink_index_file_missing(self, capsys): run_id = "test-missing-index" with ( - patch("sentience.cloud_tracing.requests.put") as mock_put, - patch("sentience.cloud_tracing.requests.post") as mock_post, - patch("sentience.trace_indexing.write_trace_index") as mock_write_index, + patch("predicate.cloud_tracing.requests.put") as mock_put, + patch("predicate.cloud_tracing.requests.post") as mock_post, + patch("predicate.trace_indexing.write_trace_index") as mock_write_index, ): # Mock index generation to fail (simulating missing index) mock_write_index.side_effect = Exception("Index generation failed") @@ -1119,8 +1119,8 @@ def test_cloud_trace_sink_completion_includes_all_stats(self): ) with ( - patch("sentience.cloud_tracing.requests.put") as mock_put, - patch("sentience.cloud_tracing.requests.post") as mock_post, + patch("predicate.cloud_tracing.requests.put") as mock_put, + patch("predicate.cloud_tracing.requests.post") as mock_post, ): # Mock successful trace upload mock_put.return_value = Mock(status_code=200) diff --git a/tests/test_conversational_agent.py b/tests/test_conversational_agent.py index 43af436..fa6fc43 100644 --- a/tests/test_conversational_agent.py +++ b/tests/test_conversational_agent.py @@ -8,9 +8,9 @@ import pytest -from sentience.conversational_agent import ConversationalAgent -from sentience.llm_provider import LLMProvider, LLMResponse -from sentience.models import BBox, Element, Snapshot, Viewport, VisualCues +from predicate.conversational_agent import ConversationalAgent +from predicate.llm_provider import LLMProvider, LLMResponse +from predicate.models import BBox, Element, Snapshot, Viewport, VisualCues class MockLLMProvider(LLMProvider): @@ -214,10 +214,10 @@ def test_execute_find_and_click_step(): # Patch at the action_executor level where click is actually called with ( - patch("sentience.agent.snapshot") as mock_snapshot, - patch("sentience.action_executor.click") as mock_click, + patch("predicate.agent.snapshot") as mock_snapshot, + patch("predicate.action_executor.click") as mock_click, ): - from sentience.models import ActionResult + from predicate.models import ActionResult mock_snapshot.return_value = create_mock_snapshot() mock_click.return_value = ActionResult(success=True, duration_ms=150, outcome="dom_updated") @@ -243,10 +243,10 @@ def test_execute_find_and_type_step(): # Patch at the action_executor level where type_text is actually called with ( - patch("sentience.agent.snapshot") as mock_snapshot, - patch("sentience.action_executor.type_text") as mock_type, + patch("predicate.agent.snapshot") as mock_snapshot, + patch("predicate.action_executor.type_text") as mock_type, ): - from sentience.models import ActionResult + from predicate.models import ActionResult mock_snapshot.return_value = create_mock_snapshot() mock_type.return_value = ActionResult(success=True, duration_ms=200, outcome="dom_updated") @@ -293,7 +293,7 @@ def test_execute_extract_info_step(): "parameters": {"info_type": "product price"}, } - with patch("sentience.conversational_agent.snapshot") as mock_snapshot: + with patch("predicate.conversational_agent.snapshot") as mock_snapshot: mock_snapshot.return_value = create_mock_snapshot() result = agent._execute_step(step) @@ -322,7 +322,7 @@ def test_execute_verify_step(): "parameters": {"condition": "page contains search results"}, } - with patch("sentience.conversational_agent.snapshot") as mock_snapshot: + with patch("predicate.conversational_agent.snapshot") as mock_snapshot: mock_snapshot.return_value = create_mock_snapshot() result = agent._execute_step(step) diff --git a/tests/test_debugger.py b/tests/test_debugger.py index 8dbe615..7fa201d 100644 --- a/tests/test_debugger.py +++ b/tests/test_debugger.py @@ -25,9 +25,9 @@ async def test_attach_uses_runtime_factory() -> None: runtime = MockRuntime() with patch( - "sentience.debugger.AgentRuntime.from_playwright_page", return_value=runtime + "predicate.debugger.AgentRuntime.from_playwright_page", return_value=runtime ) as mock_factory: - from sentience.debugger import SentienceDebugger + from predicate.debugger import SentienceDebugger debugger = SentienceDebugger.attach(page=mock_page, tracer=tracer) @@ -45,7 +45,7 @@ async def test_attach_uses_runtime_factory() -> None: async def test_step_context_calls_begin_and_emit() -> None: runtime = MockRuntime() - from sentience.debugger import SentienceDebugger + from predicate.debugger import SentienceDebugger debugger = SentienceDebugger(runtime=runtime) @@ -59,7 +59,7 @@ async def test_step_context_calls_begin_and_emit() -> None: def test_check_auto_opens_step_when_missing() -> None: runtime = MockRuntime() - from sentience.debugger import SentienceDebugger + from predicate.debugger import SentienceDebugger debugger = SentienceDebugger(runtime=runtime) predicate = MagicMock() @@ -75,7 +75,7 @@ def test_check_auto_opens_step_when_missing() -> None: def test_check_strict_mode_requires_explicit_step() -> None: runtime = MockRuntime() - from sentience.debugger import SentienceDebugger + from predicate.debugger import SentienceDebugger debugger = SentienceDebugger(runtime=runtime, auto_step=False) diff --git a/tests/test_file_size_tracking.py b/tests/test_file_size_tracking.py index 0d97aae..d415761 100644 --- a/tests/test_file_size_tracking.py +++ b/tests/test_file_size_tracking.py @@ -13,9 +13,9 @@ import pytest -from sentience.cloud_tracing import CloudTraceSink, SentienceLogger -from sentience.tracer_factory import create_tracer -from sentience.tracing import Tracer +from predicate.cloud_tracing import CloudTraceSink, SentienceLogger +from predicate.tracer_factory import create_tracer +from predicate.tracing import Tracer class TestFileSizeTracking: @@ -61,7 +61,7 @@ def test_cloud_sink_without_logger(self): assert sink.trace_file_size_bytes == 0 assert sink.screenshot_total_size_bytes == 0 - @patch("sentience.cloud_tracing.requests") + @patch("predicate.cloud_tracing.requests") def test_cloud_sink_logs_file_sizes(self, mock_requests): """Test that CloudTraceSink logs file sizes when logger is provided.""" # Create mock logger @@ -94,7 +94,7 @@ def test_cloud_sink_logs_file_sizes(self, mock_requests): assert any("Trace file size:" in call for call in info_calls) assert any("Screenshot total:" in call for call in info_calls) - @patch("sentience.cloud_tracing.requests") + @patch("predicate.cloud_tracing.requests") def test_complete_trace_called_after_upload(self, mock_requests): """Test that /v1/traces/complete is called after successful upload.""" # Mock successful upload and complete @@ -145,7 +145,7 @@ def test_complete_trace_called_after_upload(self, mock_requests): assert "trace_file_size_bytes" in payload["stats"] assert "screenshot_total_size_bytes" in payload["stats"] - @patch("sentience.cloud_tracing.requests") + @patch("predicate.cloud_tracing.requests") def test_complete_trace_not_called_without_api_key(self, mock_requests): """Test that /v1/traces/complete is not called without API key.""" # Mock successful upload @@ -168,7 +168,7 @@ def test_complete_trace_not_called_without_api_key(self, mock_requests): # Verify POST was NOT called assert mock_requests.post.call_count == 0 - @patch("sentience.tracer_factory.requests") + @patch("predicate.tracer_factory.requests") def test_create_tracer_passes_logger_to_cloud_sink(self, mock_requests): """Test that create_tracer passes logger to CloudTraceSink.""" # Mock successful API response @@ -181,7 +181,7 @@ def test_create_tracer_passes_logger_to_cloud_sink(self, mock_requests): mock_logger = Mock(spec=SentienceLogger) # Create tracer with logger - with patch("sentience.tracer_factory._recover_orphaned_traces"): + with patch("predicate.tracer_factory._recover_orphaned_traces"): tracer = create_tracer( api_key="sk_test_key", run_id="test-logger", @@ -200,7 +200,7 @@ def test_create_tracer_passes_logger_to_cloud_sink(self, mock_requests): class TestBackwardCompatibility: """Test that existing code continues to work.""" - @patch("sentience.tracer_factory.requests") + @patch("predicate.tracer_factory.requests") def test_create_tracer_without_logger_still_works(self, mock_requests): """Test that create_tracer works without logger parameter (backward compat).""" # Mock successful API response @@ -210,7 +210,7 @@ def test_create_tracer_without_logger_still_works(self, mock_requests): mock_requests.post.return_value = mock_response # Create tracer WITHOUT logger (old API) - with patch("sentience.tracer_factory._recover_orphaned_traces"): + with patch("predicate.tracer_factory._recover_orphaned_traces"): tracer = create_tracer( api_key="sk_test_key", run_id="test-compat", diff --git a/tests/test_formatting.py b/tests/test_formatting.py index c4406b2..6ce05e3 100644 --- a/tests/test_formatting.py +++ b/tests/test_formatting.py @@ -1,7 +1,7 @@ -"""Tests for sentience.formatting module""" +"""Tests for predicate.formatting module""" -from sentience.formatting import format_snapshot_for_llm -from sentience.models import BBox, Element, Snapshot, VisualCues +from predicate.formatting import format_snapshot_for_llm +from predicate.models import BBox, Element, Snapshot, VisualCues def test_format_snapshot_basic(): diff --git a/tests/test_generator.py b/tests/test_generator.py index 2b1bf72..ff2e3e0 100644 --- a/tests/test_generator.py +++ b/tests/test_generator.py @@ -7,9 +7,9 @@ import pytest -from sentience import SentienceBrowser, record -from sentience.generator import ScriptGenerator, generate -from sentience.recorder import Trace, TraceStep +from predicate import SentienceBrowser, record +from predicate.generator import ScriptGenerator, generate +from predicate.recorder import Trace, TraceStep def test_generator_python(): @@ -28,7 +28,7 @@ def test_generator_python(): code = generator.generate_python() # Verify code contains expected elements - assert "from sentience import" in code + assert "from predicate import" in code assert "def main():" in code assert "SentienceBrowser" in code assert "role=button text~'Click'" in code @@ -77,7 +77,7 @@ def test_generator_save_python(): with open(temp_path) as f: code = f.read() - assert "from sentience import" in code + assert "from predicate import" in code finally: os.unlink(temp_path) @@ -138,7 +138,7 @@ def test_generate_helper(): # Test Python generation py_code = generate(rec.trace, "py") - assert "from sentience import" in py_code + assert "from predicate import" in py_code # Test TypeScript generation ts_code = generate(rec.trace, "ts") diff --git a/tests/test_grid_bounds.py b/tests/test_grid_bounds.py index e9c8ca1..a892a61 100644 --- a/tests/test_grid_bounds.py +++ b/tests/test_grid_bounds.py @@ -4,7 +4,7 @@ import pytest -from sentience.models import ( +from predicate.models import ( BBox, Element, GridInfo, diff --git a/tests/test_importance_score.py b/tests/test_importance_score.py index 05d8ab7..2998ae5 100644 --- a/tests/test_importance_score.py +++ b/tests/test_importance_score.py @@ -4,8 +4,8 @@ import pytest -from sentience.models import BBox, Element, Snapshot, Viewport, VisualCues -from sentience.trace_event_builder import TraceEventBuilder +from predicate.models import BBox, Element, Snapshot, Viewport, VisualCues +from predicate.trace_event_builder import TraceEventBuilder def create_element(element_id: int, importance: int) -> Element: diff --git a/tests/test_inspector.py b/tests/test_inspector.py index ad0b9a4..1bc9005 100644 --- a/tests/test_inspector.py +++ b/tests/test_inspector.py @@ -2,7 +2,7 @@ Tests for inspector functionality """ -from sentience import SentienceBrowser, inspect +from predicate import SentienceBrowser, inspect def test_inspector_start_stop(): diff --git a/tests/test_llm_provider_utils.py b/tests/test_llm_provider_utils.py index f5f89dc..f417cfd 100644 --- a/tests/test_llm_provider_utils.py +++ b/tests/test_llm_provider_utils.py @@ -1,11 +1,11 @@ -"""Tests for sentience.llm_provider_utils module""" +"""Tests for predicate.llm_provider_utils module""" import os from unittest.mock import patch import pytest -from sentience.llm_provider_utils import ( +from predicate.llm_provider_utils import ( get_api_key_from_env, handle_provider_error, require_package, diff --git a/tests/test_llm_provider_vision.py b/tests/test_llm_provider_vision.py index 3bc9fe9..95b16d0 100644 --- a/tests/test_llm_provider_vision.py +++ b/tests/test_llm_provider_vision.py @@ -6,7 +6,7 @@ def test_deepinfra_provider_supports_vision_for_common_multimodal_names() -> None: pytest.importorskip("openai") - from sentience.llm_provider import DeepInfraProvider + from predicate.llm_provider import DeepInfraProvider p1 = DeepInfraProvider(api_key="x", model="meta-llama/Llama-3.2-11B-Vision-Instruct") assert p1.supports_vision() is True diff --git a/tests/test_llm_response_builder.py b/tests/test_llm_response_builder.py index 9ac2f14..b06e2af 100644 --- a/tests/test_llm_response_builder.py +++ b/tests/test_llm_response_builder.py @@ -4,8 +4,8 @@ import pytest -from sentience.llm_provider import LLMResponse -from sentience.llm_response_builder import LLMResponseBuilder +from predicate.llm_provider import LLMResponse +from predicate.llm_response_builder import LLMResponseBuilder class TestLLMResponseBuilder: diff --git a/tests/test_ordinal.py b/tests/test_ordinal.py index 5474851..9d59c0a 100644 --- a/tests/test_ordinal.py +++ b/tests/test_ordinal.py @@ -6,8 +6,8 @@ import pytest -from sentience.models import BBox, Element, VisualCues -from sentience.ordinal import ( +from predicate.models import BBox, Element, VisualCues +from predicate.ordinal import ( OrdinalIntent, boost_ordinal_elements, detect_ordinal_intent, diff --git a/tests/test_proxy.py b/tests/test_proxy.py index ad3c917..7f877f2 100644 --- a/tests/test_proxy.py +++ b/tests/test_proxy.py @@ -6,8 +6,8 @@ import pytest -from sentience.browser import SentienceBrowser -from sentience.models import ProxyConfig +from predicate.browser import SentienceBrowser +from predicate.models import ProxyConfig class TestProxyConfig: @@ -175,8 +175,8 @@ def test_browser_init_without_proxy(self): class TestBrowserProxyIntegration: """Test proxy integration in browser start() method""" - @patch("sentience.browser.shutil.copytree") - @patch("sentience.browser.sync_playwright") + @patch("predicate.browser.shutil.copytree") + @patch("predicate.browser.sync_playwright") def test_start_without_proxy(self, mock_playwright, mock_copytree): """Test browser start without proxy""" # Mock Playwright @@ -189,7 +189,7 @@ def test_start_without_proxy(self, mock_playwright, mock_copytree): mock_playwright.return_value.start.return_value = mock_pw_instance # Mock extension path check - with patch("sentience.browser.Path") as mock_path: + with patch("predicate.browser.Path") as mock_path: mock_ext_path = MagicMock() mock_ext_path.exists.return_value = True (mock_ext_path / "manifest.json").exists.return_value = True @@ -203,8 +203,8 @@ def test_start_without_proxy(self, mock_playwright, mock_copytree): call_kwargs = mock_pw_instance.chromium.launch_persistent_context.call_args[1] assert "proxy" not in call_kwargs - @patch("sentience.browser.shutil.copytree") - @patch("sentience.browser.sync_playwright") + @patch("predicate.browser.shutil.copytree") + @patch("predicate.browser.sync_playwright") def test_start_with_proxy(self, mock_playwright, mock_copytree, caplog): """Test browser start with proxy""" # Mock Playwright @@ -217,7 +217,7 @@ def test_start_with_proxy(self, mock_playwright, mock_copytree, caplog): mock_playwright.return_value.start.return_value = mock_pw_instance # Mock extension path check - with patch("sentience.browser.Path") as mock_path: + with patch("predicate.browser.Path") as mock_path: mock_ext_path = MagicMock() mock_ext_path.exists.return_value = True (mock_ext_path / "manifest.json").exists.return_value = True @@ -240,8 +240,8 @@ def test_start_with_proxy(self, mock_playwright, mock_copytree, caplog): # Verify log message assert "Using proxy: http://proxy.example.com:8080" in caplog.text - @patch("sentience.browser.shutil.copytree") - @patch("sentience.browser.sync_playwright") + @patch("predicate.browser.shutil.copytree") + @patch("predicate.browser.sync_playwright") def test_start_with_webrtc_flags(self, mock_playwright, mock_copytree): """Test that WebRTC leak protection flags are always included""" # Mock Playwright @@ -254,7 +254,7 @@ def test_start_with_webrtc_flags(self, mock_playwright, mock_copytree): mock_playwright.return_value.start.return_value = mock_pw_instance # Mock extension path check - with patch("sentience.browser.Path") as mock_path: + with patch("predicate.browser.Path") as mock_path: mock_ext_path = MagicMock() mock_ext_path.exists.return_value = True (mock_ext_path / "manifest.json").exists.return_value = True @@ -270,8 +270,8 @@ def test_start_with_webrtc_flags(self, mock_playwright, mock_copytree): assert "--disable-features=WebRtcHideLocalIpsWithMdns" in args assert "--force-webrtc-ip-handling-policy=disable_non_proxied_udp" in args - @patch("sentience.browser.shutil.copytree") - @patch("sentience.browser.sync_playwright") + @patch("predicate.browser.shutil.copytree") + @patch("predicate.browser.sync_playwright") def test_start_with_proxy_ignores_https_errors(self, mock_playwright, mock_copytree): """Test that ignore_https_errors is set when using proxy (for self-signed certs)""" # Mock Playwright @@ -284,7 +284,7 @@ def test_start_with_proxy_ignores_https_errors(self, mock_playwright, mock_copyt mock_playwright.return_value.start.return_value = mock_pw_instance # Mock extension path check - with patch("sentience.browser.Path") as mock_path: + with patch("predicate.browser.Path") as mock_path: mock_ext_path = MagicMock() mock_ext_path.exists.return_value = True (mock_ext_path / "manifest.json").exists.return_value = True @@ -298,8 +298,8 @@ def test_start_with_proxy_ignores_https_errors(self, mock_playwright, mock_copyt call_kwargs = mock_pw_instance.chromium.launch_persistent_context.call_args[1] assert call_kwargs["ignore_https_errors"] is True - @patch("sentience.browser.shutil.copytree") - @patch("sentience.browser.sync_playwright") + @patch("predicate.browser.shutil.copytree") + @patch("predicate.browser.sync_playwright") def test_start_without_proxy_does_not_ignore_https_errors(self, mock_playwright, mock_copytree): """Test that ignore_https_errors is NOT set when not using proxy""" # Mock Playwright @@ -312,7 +312,7 @@ def test_start_without_proxy_does_not_ignore_https_errors(self, mock_playwright, mock_playwright.return_value.start.return_value = mock_pw_instance # Mock extension path check - with patch("sentience.browser.Path") as mock_path: + with patch("predicate.browser.Path") as mock_path: mock_ext_path = MagicMock() mock_ext_path.exists.return_value = True (mock_ext_path / "manifest.json").exists.return_value = True diff --git a/tests/test_query.py b/tests/test_query.py index 8d5a6a1..64991c5 100644 --- a/tests/test_query.py +++ b/tests/test_query.py @@ -2,9 +2,9 @@ Tests for query engine """ -from sentience import SentienceBrowser, find, query, snapshot -from sentience.models import BBox, Element, VisualCues -from sentience.query import match_element, parse_selector +from predicate import SentienceBrowser, find, query, snapshot +from predicate.models import BBox, Element, VisualCues +from predicate.query import match_element, parse_selector def test_parse_selector(): @@ -263,7 +263,7 @@ def test_query_advanced_operators(): ), ] - from sentience.models import Snapshot + from predicate.models import Snapshot snap = Snapshot( status="success", diff --git a/tests/test_read.py b/tests/test_read.py index 578cb80..5b50440 100644 --- a/tests/test_read.py +++ b/tests/test_read.py @@ -4,7 +4,7 @@ from typing import Any -from sentience import SentienceBrowser, read +from predicate import SentienceBrowser, read def test_read_text(): diff --git a/tests/test_recorder.py b/tests/test_recorder.py index ca69d93..6207a29 100644 --- a/tests/test_recorder.py +++ b/tests/test_recorder.py @@ -5,7 +5,7 @@ import os import tempfile -from sentience import SentienceBrowser, Trace, record +from predicate import SentienceBrowser, Trace, record def test_recorder_start_stop(): diff --git a/tests/test_screenshot.py b/tests/test_screenshot.py index 8d6740b..a58c67d 100644 --- a/tests/test_screenshot.py +++ b/tests/test_screenshot.py @@ -4,7 +4,7 @@ import base64 -from sentience import SentienceBrowser, screenshot +from predicate import SentienceBrowser, screenshot def test_screenshot_png(): diff --git a/tests/test_screenshot_storage.py b/tests/test_screenshot_storage.py index a5fa129..bb4521e 100644 --- a/tests/test_screenshot_storage.py +++ b/tests/test_screenshot_storage.py @@ -9,7 +9,7 @@ import pytest -from sentience.cloud_tracing import CloudTraceSink +from predicate.cloud_tracing import CloudTraceSink class TestScreenshotExtraction: @@ -260,7 +260,7 @@ def test_request_screenshot_urls_success(self): "2": "https://sentience.nyc3.digitaloceanspaces.com/user123/run456/screenshots/step_0002.png?signature=...", } - with patch("sentience.cloud_tracing.requests.post") as mock_post: + with patch("predicate.cloud_tracing.requests.post") as mock_post: mock_response = Mock() mock_response.status_code = 200 mock_response.json.return_value = {"upload_urls": mock_urls} @@ -290,7 +290,7 @@ def test_request_screenshot_urls_handles_failure(self): sink = CloudTraceSink(upload_url, run_id=run_id, api_key=api_key) - with patch("sentience.cloud_tracing.requests.post") as mock_post: + with patch("predicate.cloud_tracing.requests.post") as mock_post: mock_response = Mock() mock_response.status_code = 500 mock_post.return_value = mock_response @@ -323,8 +323,8 @@ def test_upload_screenshots_uploads_in_parallel(self): } with ( - patch("sentience.cloud_tracing.requests.post") as mock_post, - patch("sentience.cloud_tracing.requests.put") as mock_put, + patch("predicate.cloud_tracing.requests.post") as mock_post, + patch("predicate.cloud_tracing.requests.put") as mock_put, ): # Mock gateway response mock_gateway_response = Mock() @@ -384,7 +384,7 @@ def test_complete_trace_includes_screenshot_count(self): # Set screenshot count (normally set during extraction) sink.screenshot_count = 2 - with patch("sentience.cloud_tracing.requests.post") as mock_post: + with patch("predicate.cloud_tracing.requests.post") as mock_post: mock_response = Mock() mock_response.status_code = 200 mock_post.return_value = mock_response @@ -438,8 +438,8 @@ def test_upload_removes_screenshot_base64_from_trace(self): } with ( - patch("sentience.cloud_tracing.requests.post") as mock_post, - patch("sentience.cloud_tracing.requests.put") as mock_put, + patch("predicate.cloud_tracing.requests.post") as mock_post, + patch("predicate.cloud_tracing.requests.put") as mock_put, ): # Mock gateway response for screenshot URLs mock_gateway_response = Mock() diff --git a/tests/test_sentience_context.py b/tests/test_sentience_context.py index b88c65a..fedf505 100644 --- a/tests/test_sentience_context.py +++ b/tests/test_sentience_context.py @@ -9,9 +9,9 @@ import pytest -from sentience.backends import SentienceContext, SentienceContextState, TopElementSelector -from sentience.constants import SENTIENCE_API_URL -from sentience.models import BBox, Element, Snapshot, Viewport, VisualCues +from predicate.backends import SentienceContext, SentienceContextState, TopElementSelector +from predicate.constants import PREDICATE_API_URL, SENTIENCE_API_URL +from predicate.models import BBox, Element, Snapshot, Viewport, VisualCues def make_element( @@ -93,7 +93,8 @@ def test_custom_values(self) -> None: def test_api_url_constant(self) -> None: """Test API URL is a class constant.""" - assert SentienceContext.API_URL == SENTIENCE_API_URL + assert SentienceContext.API_URL == PREDICATE_API_URL + assert SENTIENCE_API_URL == PREDICATE_API_URL def test_top_element_selector_defaults(self) -> None: """Test TopElementSelector has correct defaults.""" @@ -511,7 +512,7 @@ async def test_build_returns_context_state(self) -> None: ctx, "_format_snapshot_for_llm", return_value="1|button|Click|80|0|0|-|0|" ): # Patch the imports that happen inside build() - import sentience.backends.sentience_context as ctx_module + import predicate.backends.sentience_context as ctx_module original_build = ctx.build diff --git a/tests/test_smart_selector.py b/tests/test_smart_selector.py index bf72316..d4476c8 100644 --- a/tests/test_smart_selector.py +++ b/tests/test_smart_selector.py @@ -2,7 +2,7 @@ Tests for smart selector inference """ -from sentience import SentienceBrowser, record, snapshot +from predicate import SentienceBrowser, record, snapshot def test_smart_selector_inference(): @@ -68,7 +68,7 @@ def test_smart_selector_validation(): # If selector was inferred and validated, it should match the element if step.selector: # Verify selector would match the element - from sentience.query import query + from predicate.query import query matches = query(snap, step.selector) # Should match at least the original element diff --git a/tests/test_snapshot.py b/tests/test_snapshot.py index 109c9a3..ec397c5 100644 --- a/tests/test_snapshot.py +++ b/tests/test_snapshot.py @@ -4,8 +4,8 @@ import pytest -from sentience import SentienceBrowser, snapshot -from sentience.models import SnapshotOptions +from predicate import SentienceBrowser, snapshot +from predicate.models import SnapshotOptions @pytest.mark.requires_extension @@ -138,7 +138,7 @@ def test_snapshot_with_goal(): def test_element_ml_fields_optional(): """Test that Element model accepts optional ML reranking fields""" - from sentience.models import BBox, Element, VisualCues + from predicate.models import BBox, Element, VisualCues # Test element without ML fields element_without_ml = Element( @@ -200,7 +200,7 @@ def test_element_ml_fields_optional(): def test_snapshot_ml_rerank_metadata_optional(): """Test snapshot ML rerank metadata model""" - from sentience.models import MlRerankInfo, MlRerankTags, Snapshot + from predicate.models import MlRerankInfo, MlRerankTags, Snapshot snap = Snapshot( status="success", diff --git a/tests/test_snapshot_diff.py b/tests/test_snapshot_diff.py index d0e9954..736466c 100644 --- a/tests/test_snapshot_diff.py +++ b/tests/test_snapshot_diff.py @@ -4,8 +4,8 @@ import pytest -from sentience.models import BBox, Element, Snapshot, Viewport, VisualCues -from sentience.snapshot_diff import SnapshotDiff +from predicate.models import BBox, Element, Snapshot, Viewport, VisualCues +from predicate.snapshot_diff import SnapshotDiff def create_element( diff --git a/tests/test_snapshot_gateway_timeout.py b/tests/test_snapshot_gateway_timeout.py index d115878..05edf91 100644 --- a/tests/test_snapshot_gateway_timeout.py +++ b/tests/test_snapshot_gateway_timeout.py @@ -2,8 +2,8 @@ import importlib import sys -snapshot_module = importlib.import_module("sentience.snapshot") -from sentience.snapshot import _post_snapshot_to_gateway_async, _post_snapshot_to_gateway_sync +snapshot_module = importlib.import_module("predicate.snapshot") +from predicate.snapshot import _post_snapshot_to_gateway_async, _post_snapshot_to_gateway_sync class _DummyResponse: diff --git a/tests/test_spec_validation.py b/tests/test_spec_validation.py index 74129d8..bd36c57 100644 --- a/tests/test_spec_validation.py +++ b/tests/test_spec_validation.py @@ -7,8 +7,8 @@ import pytest -from sentience import SentienceBrowser, snapshot -from sentience.models import Snapshot as SnapshotModel +from predicate import SentienceBrowser, snapshot +from predicate.models import Snapshot as SnapshotModel def load_schema(): diff --git a/tests/test_stealth.py b/tests/test_stealth.py index 8860db5..b26f60b 100644 --- a/tests/test_stealth.py +++ b/tests/test_stealth.py @@ -16,7 +16,7 @@ # Add parent directory to path sys.path.insert(0, str(Path(__file__).parent.parent)) -from sentience.browser import SentienceBrowser # noqa: E402 +from predicate.browser import SentienceBrowser # noqa: E402 def test_stealth_features(): # noqa: C901 diff --git a/tests/test_trace_event_builder.py b/tests/test_trace_event_builder.py index e8923b8..7602052 100644 --- a/tests/test_trace_event_builder.py +++ b/tests/test_trace_event_builder.py @@ -4,9 +4,9 @@ import pytest -from sentience.models import BBox, Element, Snapshot, Viewport, VisualCues -from sentience.snapshot_diff import SnapshotDiff -from sentience.trace_event_builder import TraceEventBuilder +from predicate.models import BBox, Element, Snapshot, Viewport, VisualCues +from predicate.snapshot_diff import SnapshotDiff +from predicate.trace_event_builder import TraceEventBuilder def create_element( diff --git a/tests/test_trace_file_manager.py b/tests/test_trace_file_manager.py index 3774299..867931a 100644 --- a/tests/test_trace_file_manager.py +++ b/tests/test_trace_file_manager.py @@ -8,7 +8,7 @@ import pytest -from sentience.trace_file_manager import TraceFileManager +from predicate.trace_file_manager import TraceFileManager class TestTraceFileManager: diff --git a/tests/test_trace_file_manager_extract_stats.py b/tests/test_trace_file_manager_extract_stats.py index cb15431..c8502e2 100644 --- a/tests/test_trace_file_manager_extract_stats.py +++ b/tests/test_trace_file_manager_extract_stats.py @@ -4,8 +4,8 @@ import pytest -from sentience.models import TraceStats -from sentience.trace_file_manager import TraceFileManager +from predicate.models import TraceStats +from predicate.trace_file_manager import TraceFileManager def test_extract_stats_empty_events(): diff --git a/tests/test_trace_indexing.py b/tests/test_trace_indexing.py index e27f513..0c220f3 100644 --- a/tests/test_trace_indexing.py +++ b/tests/test_trace_indexing.py @@ -9,7 +9,7 @@ import pytest -from sentience.trace_indexing import ( +from predicate.trace_indexing import ( StepIndex, TraceIndex, build_trace_index, diff --git a/tests/test_tracing.py b/tests/test_tracing.py index 0f2f8d6..7b0eecb 100644 --- a/tests/test_tracing.py +++ b/tests/test_tracing.py @@ -1,10 +1,10 @@ -"""Tests for sentience.tracing module""" +"""Tests for predicate.tracing module""" import json import tempfile from pathlib import Path -from sentience.tracing import JsonlTraceSink, TraceEvent, Tracer +from predicate.tracing import JsonlTraceSink, TraceEvent, Tracer def test_trace_event_to_dict(): @@ -472,7 +472,7 @@ def test_tracer_close_with_cloud_sink_includes_final_status_in_completion(): """Test that CloudTraceSink includes auto-inferred final_status in completion request.""" from unittest.mock import Mock, patch - from sentience.cloud_tracing import CloudTraceSink + from predicate.cloud_tracing import CloudTraceSink upload_url = "https://sentience.nyc3.digitaloceanspaces.com/user123/run456/trace.jsonl.gz" run_id = "test-close-status" @@ -491,8 +491,8 @@ def test_tracer_close_with_cloud_sink_includes_final_status_in_completion(): assert tracer.final_status == "unknown" with ( - patch("sentience.cloud_tracing.requests.put") as mock_put, - patch("sentience.cloud_tracing.requests.post") as mock_post, + patch("predicate.cloud_tracing.requests.put") as mock_put, + patch("predicate.cloud_tracing.requests.post") as mock_post, ): # Mock successful trace upload mock_put.return_value = Mock(status_code=200) diff --git a/tests/test_utils.py b/tests/test_utils.py index 3f447c2..3db2b6e 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,10 +1,10 @@ """ -Unit tests for sentience.utils module. +Unit tests for predicate.utils module. Tests canonicalization and hashing functions for snapshot digests. """ -from sentience.utils import ( +from predicate.utils import ( BBox, canonical_snapshot_loose, canonical_snapshot_strict, diff --git a/tests/test_utils_browser.py b/tests/test_utils_browser.py index 145c888..fabed9f 100644 --- a/tests/test_utils_browser.py +++ b/tests/test_utils_browser.py @@ -1,5 +1,5 @@ """ -Unit tests for sentience.utils.browser module. +Unit tests for predicate.utils.browser module. Tests browser storage state saving functionality. """ @@ -11,7 +11,7 @@ import pytest -from sentience.utils.browser import save_storage_state +from predicate.utils.browser import save_storage_state class TestSaveStorageState: diff --git a/tests/test_verification.py b/tests/test_verification.py index b4af6ed..c581c74 100644 --- a/tests/test_verification.py +++ b/tests/test_verification.py @@ -4,8 +4,8 @@ import pytest -from sentience.models import BBox, Element, Snapshot, Viewport, VisualCues -from sentience.verification import ( +from predicate.models import BBox, Element, Snapshot, Viewport, VisualCues +from predicate.verification import ( AssertContext, AssertOutcome, all_of, @@ -26,7 +26,7 @@ value_contains, value_equals, ) -from sentience.vision_executor import parse_vision_executor_action +from predicate.vision_executor import parse_vision_executor_action def make_element( diff --git a/tests/test_video_recording.py b/tests/test_video_recording.py index b164dbf..10e0613 100644 --- a/tests/test_video_recording.py +++ b/tests/test_video_recording.py @@ -8,7 +8,7 @@ import pytest -from sentience import SentienceBrowser +from predicate import SentienceBrowser # Use a data URL to avoid network dependency (DNS resolution can fail in CI) # This is a minimal valid HTML page diff --git a/tests/test_wait.py b/tests/test_wait.py index 2a5e462..2619e98 100644 --- a/tests/test_wait.py +++ b/tests/test_wait.py @@ -2,7 +2,7 @@ Tests for wait functionality """ -from sentience import SentienceBrowser, expect, wait_for +from predicate import SentienceBrowser, expect, wait_for def test_wait_for(): diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py index 7047367..adfac9f 100644 --- a/tests/unit/conftest.py +++ b/tests/unit/conftest.py @@ -1,7 +1,7 @@ """ Unit-test-only stubs. -The core Sentience SDK imports Playwright at module import time (`sentience.browser`), +The core Sentience SDK imports Playwright at module import time (`predicate.browser`), but many unit tests don't actually need a real browser. In CI and developer envs, Playwright is usually installed; however in constrained environments it may not be. @@ -45,7 +45,7 @@ def _ensure_module(name: str) -> types.ModuleType: class _Dummy: """Placeholder type used for Playwright classes in unit tests.""" - # Minimal symbols imported by `sentience.browser` + # Minimal symbols imported by `predicate.browser` async_api_mod.BrowserContext = _Dummy async_api_mod.Browser = _Dummy async_api_mod.Page = _Dummy diff --git a/tests/unit/test_agent_errors.py b/tests/unit/test_agent_errors.py index 4287683..410886f 100644 --- a/tests/unit/test_agent_errors.py +++ b/tests/unit/test_agent_errors.py @@ -10,10 +10,10 @@ import pytest -from sentience.agent import SentienceAgent -from sentience.llm_provider import LLMProvider, LLMResponse -from sentience.models import BBox, Element, Snapshot, Viewport, VisualCues -from sentience.protocols import BrowserProtocol, PageProtocol +from predicate.agent import SentienceAgent +from predicate.llm_provider import LLMProvider, LLMResponse +from predicate.models import BBox, Element, Snapshot, Viewport, VisualCues +from predicate.protocols import BrowserProtocol, PageProtocol class MockLLMProvider(LLMProvider): @@ -178,7 +178,7 @@ def test_agent_handles_snapshot_timeout(self): agent = SentienceAgent(browser, llm, verbose=False) # Mock snapshot to raise timeout - with patch("sentience.agent.snapshot") as mock_snapshot: + with patch("predicate.agent.snapshot") as mock_snapshot: from playwright._impl._errors import TimeoutError mock_snapshot.side_effect = TimeoutError("Snapshot timeout") @@ -195,7 +195,7 @@ def test_agent_handles_network_failure(self): # Mock snapshot to raise network error # Patch at the agent module level since that's where it's imported - with patch("sentience.agent.snapshot") as mock_snapshot: + with patch("predicate.agent.snapshot") as mock_snapshot: mock_snapshot.side_effect = ConnectionError("Network failure") with pytest.raises(RuntimeError, match="Failed after"): @@ -218,10 +218,10 @@ def test_agent_handles_empty_snapshot(self): ) with ( - patch("sentience.snapshot.snapshot") as mock_snapshot, - patch("sentience.action_executor.click") as mock_click, + patch("predicate.snapshot.snapshot") as mock_snapshot, + patch("predicate.action_executor.click") as mock_click, ): - from sentience.models import ActionResult + from predicate.models import ActionResult mock_snapshot.return_value = empty_snap mock_click.return_value = ActionResult(success=False, duration_ms=100, outcome="error") @@ -237,7 +237,7 @@ def test_agent_handles_malformed_llm_response(self): llm = MockLLMProvider(responses=["INVALID_RESPONSE_FORMAT"]) agent = SentienceAgent(browser, llm, verbose=False) - with (patch("sentience.snapshot.snapshot") as mock_snapshot,): + with (patch("predicate.snapshot.snapshot") as mock_snapshot,): mock_snapshot.return_value = create_mock_snapshot() # Action executor should raise ValueError for invalid format @@ -251,7 +251,7 @@ def test_agent_handles_browser_not_started(self): agent = SentienceAgent(browser, llm, verbose=False) # Snapshot should fail because browser.page is None - with patch("sentience.snapshot.snapshot") as mock_snapshot: + with patch("predicate.snapshot.snapshot") as mock_snapshot: mock_snapshot.side_effect = RuntimeError("Browser not started") with pytest.raises(RuntimeError, match="Failed after"): @@ -265,8 +265,8 @@ def test_agent_handles_action_timeout(self): agent = SentienceAgent(browser, llm, verbose=False) with ( - patch("sentience.snapshot.snapshot") as mock_snapshot, - patch("sentience.action_executor.click") as mock_click, + patch("predicate.snapshot.snapshot") as mock_snapshot, + patch("predicate.action_executor.click") as mock_click, ): from playwright._impl._errors import TimeoutError @@ -284,10 +284,10 @@ def test_agent_handles_url_change_during_action(self): agent = SentienceAgent(browser, llm, verbose=False) with ( - patch("sentience.snapshot.snapshot") as mock_snapshot, - patch("sentience.action_executor.click") as mock_click, + patch("predicate.snapshot.snapshot") as mock_snapshot, + patch("predicate.action_executor.click") as mock_click, ): - from sentience.models import ActionResult + from predicate.models import ActionResult mock_snapshot.return_value = create_mock_snapshot() # Simulate URL change after click @@ -307,10 +307,10 @@ def test_agent_retry_on_transient_error(self): agent = SentienceAgent(browser, llm, verbose=False) with ( - patch("sentience.snapshot.snapshot") as mock_snapshot, - patch("sentience.action_executor.click") as mock_click, + patch("predicate.snapshot.snapshot") as mock_snapshot, + patch("predicate.action_executor.click") as mock_click, ): - from sentience.models import ActionResult + from predicate.models import ActionResult mock_snapshot.return_value = create_mock_snapshot() # First call fails, second succeeds @@ -342,7 +342,7 @@ def test_agent_handles_zero_elements_in_snapshot(self): elements=[], ) - with patch("sentience.snapshot.snapshot") as mock_snapshot: + with patch("predicate.snapshot.snapshot") as mock_snapshot: mock_snapshot.return_value = empty_snap # Agent should handle empty snapshot and finish @@ -358,10 +358,10 @@ def test_agent_handles_unicode_in_actions(self): agent = SentienceAgent(browser, llm, verbose=False) with ( - patch("sentience.snapshot.snapshot") as mock_snapshot, - patch("sentience.action_executor.type_text") as mock_type, + patch("predicate.snapshot.snapshot") as mock_snapshot, + patch("predicate.action_executor.type_text") as mock_type, ): - from sentience.models import ActionResult + from predicate.models import ActionResult mock_snapshot.return_value = create_mock_snapshot() mock_type.return_value = ActionResult( @@ -380,10 +380,10 @@ def test_agent_handles_special_characters_in_goal(self): agent = SentienceAgent(browser, llm, verbose=False) with ( - patch("sentience.snapshot.snapshot") as mock_snapshot, - patch("sentience.action_executor.click") as mock_click, + patch("predicate.snapshot.snapshot") as mock_snapshot, + patch("predicate.action_executor.click") as mock_click, ): - from sentience.models import ActionResult + from predicate.models import ActionResult mock_snapshot.return_value = create_mock_snapshot() mock_click.return_value = ActionResult( @@ -402,10 +402,10 @@ def test_agent_preserves_state_on_retry(self): agent = SentienceAgent(browser, llm, verbose=False) with ( - patch("sentience.snapshot.snapshot") as mock_snapshot, - patch("sentience.action_executor.click") as mock_click, + patch("predicate.snapshot.snapshot") as mock_snapshot, + patch("predicate.action_executor.click") as mock_click, ): - from sentience.models import ActionResult + from predicate.models import ActionResult mock_snapshot.return_value = create_mock_snapshot() # First attempt fails, second succeeds @@ -432,10 +432,10 @@ def test_agent_handles_tracer_errors_gracefully(self): agent = SentienceAgent(browser, llm, verbose=False, tracer=mock_tracer) with ( - patch("sentience.snapshot.snapshot") as mock_snapshot, - patch("sentience.action_executor.click") as mock_click, + patch("predicate.snapshot.snapshot") as mock_snapshot, + patch("predicate.action_executor.click") as mock_click, ): - from sentience.models import ActionResult + from predicate.models import ActionResult mock_snapshot.return_value = create_mock_snapshot() mock_click.return_value = ActionResult( diff --git a/tests/unit/test_agent_runtime_phase0.py b/tests/unit/test_agent_runtime_phase0.py index 06a5ae4..817bfa0 100644 --- a/tests/unit/test_agent_runtime_phase0.py +++ b/tests/unit/test_agent_runtime_phase0.py @@ -2,9 +2,9 @@ from unittest.mock import MagicMock -from sentience.agent_runtime import AgentRuntime -from sentience.models import BBox, Element, VisualCues -from sentience.verification import is_disabled, is_enabled, value_equals +from predicate.agent_runtime import AgentRuntime +from predicate.models import BBox, Element, VisualCues +from predicate.verification import is_disabled, is_enabled, value_equals class MockBackend: diff --git a/tests/unit/test_agent_runtime_regression_safety.py b/tests/unit/test_agent_runtime_regression_safety.py index c82a3fe..ed3a60c 100644 --- a/tests/unit/test_agent_runtime_regression_safety.py +++ b/tests/unit/test_agent_runtime_regression_safety.py @@ -4,9 +4,9 @@ import pytest -from sentience.agent_runtime import AgentRuntime -from sentience.models import BBox, Element, Snapshot, Viewport, VisualCues -from sentience.verification import ( +from predicate.agent_runtime import AgentRuntime +from predicate.models import BBox, Element, Snapshot, Viewport, VisualCues +from predicate.verification import ( AssertContext, AssertOutcome, is_checked, diff --git a/tests/unit/test_captcha_context_page_control.py b/tests/unit/test_captcha_context_page_control.py index 5ba8af2..c180147 100644 --- a/tests/unit/test_captcha_context_page_control.py +++ b/tests/unit/test_captcha_context_page_control.py @@ -2,9 +2,9 @@ import pytest -from sentience.agent_runtime import AgentRuntime -from sentience.captcha import PageControlHook -from sentience.models import CaptchaDiagnostics, CaptchaEvidence, Snapshot, SnapshotDiagnostics +from predicate.agent_runtime import AgentRuntime +from predicate.captcha import PageControlHook +from predicate.models import CaptchaDiagnostics, CaptchaEvidence, Snapshot, SnapshotDiagnostics class EvalBackend: diff --git a/tests/unit/test_domain_policies.py b/tests/unit/test_domain_policies.py index 3116230..ee3e1e3 100644 --- a/tests/unit/test_domain_policies.py +++ b/tests/unit/test_domain_policies.py @@ -1,6 +1,6 @@ from __future__ import annotations -from sentience.browser import _domain_matches, _extract_host, _is_domain_allowed +from predicate.browser import _domain_matches, _extract_host, _is_domain_allowed def test_domain_matches_suffix() -> None: @@ -26,7 +26,7 @@ def test_extract_host_handles_ports() -> None: def test_keep_alive_skips_close() -> None: - from sentience.browser import SentienceBrowser + from predicate.browser import SentienceBrowser class Dummy: def __init__(self) -> None: diff --git a/tests/unit/test_extract.py b/tests/unit/test_extract.py index 42ad1ed..4393c51 100644 --- a/tests/unit/test_extract.py +++ b/tests/unit/test_extract.py @@ -3,8 +3,8 @@ import pytest from pydantic import BaseModel -from sentience.llm_provider import LLMProvider, LLMResponse -from sentience.read import extract +from predicate.llm_provider import LLMProvider, LLMResponse +from predicate.read import extract class LLMStub(LLMProvider): @@ -88,7 +88,7 @@ def test_extract_schema_invalid_json() -> None: async def test_extract_async_schema_success() -> None: browser = AsyncBrowserStub("Product: Widget") llm = LLMStub('{"name":"Widget","price":"$10"}') - from sentience.read import extract_async + from predicate.read import extract_async result = await extract_async(browser, llm, query="Extract item", schema=ItemSchema) assert result.ok is True diff --git a/tests/unit/test_failure_artifacts.py b/tests/unit/test_failure_artifacts.py index cd3b6f0..1d615de 100644 --- a/tests/unit/test_failure_artifacts.py +++ b/tests/unit/test_failure_artifacts.py @@ -3,7 +3,7 @@ import json from unittest.mock import patch -from sentience.failure_artifacts import ( +from predicate.failure_artifacts import ( ClipOptions, FailureArtifactBuffer, FailureArtifactsOptions, @@ -116,7 +116,7 @@ def test_clip_mode_off_skips_generation(tmp_path) -> None: def test_clip_mode_auto_skips_when_ffmpeg_missing(tmp_path) -> None: """When clip.mode='auto' and ffmpeg is not available, skip silently.""" - with patch("sentience.failure_artifacts._is_ffmpeg_available", return_value=False): + with patch("predicate.failure_artifacts._is_ffmpeg_available", return_value=False): opts = FailureArtifactsOptions( output_dir=str(tmp_path), clip=ClipOptions(mode="auto", fps=10), @@ -133,7 +133,7 @@ def test_clip_mode_auto_skips_when_ffmpeg_missing(tmp_path) -> None: def test_clip_mode_on_warns_when_ffmpeg_missing(tmp_path) -> None: """When clip.mode='on' and ffmpeg is not available, log warning but don't fail.""" - with patch("sentience.failure_artifacts._is_ffmpeg_available", return_value=False): + with patch("predicate.failure_artifacts._is_ffmpeg_available", return_value=False): opts = FailureArtifactsOptions( output_dir=str(tmp_path), clip=ClipOptions(mode="on"), @@ -150,8 +150,8 @@ def test_clip_mode_on_warns_when_ffmpeg_missing(tmp_path) -> None: def test_clip_generation_with_mock_ffmpeg(tmp_path) -> None: """Test clip generation logic with mocked ffmpeg subprocess.""" - with patch("sentience.failure_artifacts._is_ffmpeg_available", return_value=True): - with patch("sentience.failure_artifacts._generate_clip_from_frames") as mock_gen: + with patch("predicate.failure_artifacts._is_ffmpeg_available", return_value=True): + with patch("predicate.failure_artifacts._generate_clip_from_frames") as mock_gen: mock_gen.return_value = True # Simulate successful clip generation opts = FailureArtifactsOptions( @@ -177,8 +177,8 @@ def test_clip_generation_with_mock_ffmpeg(tmp_path) -> None: def test_clip_not_generated_when_frames_dropped(tmp_path) -> None: """Clip should not be generated when frames are dropped by redaction.""" - with patch("sentience.failure_artifacts._is_ffmpeg_available", return_value=True): - with patch("sentience.failure_artifacts._generate_clip_from_frames") as mock_gen: + with patch("predicate.failure_artifacts._is_ffmpeg_available", return_value=True): + with patch("predicate.failure_artifacts._generate_clip_from_frames") as mock_gen: opts = FailureArtifactsOptions( output_dir=str(tmp_path), clip=ClipOptions(mode="on"), @@ -199,7 +199,7 @@ def test_clip_not_generated_when_frames_dropped(tmp_path) -> None: def test_is_ffmpeg_available_with_missing_binary() -> None: """Test _is_ffmpeg_available returns False when ffmpeg is not found.""" - with patch("sentience.failure_artifacts.subprocess.run") as mock_run: + with patch("predicate.failure_artifacts.subprocess.run") as mock_run: mock_run.side_effect = FileNotFoundError("ffmpeg not found") assert _is_ffmpeg_available() is False @@ -208,7 +208,7 @@ def test_is_ffmpeg_available_with_timeout() -> None: """Test _is_ffmpeg_available returns False on timeout.""" import subprocess - with patch("sentience.failure_artifacts.subprocess.run") as mock_run: + with patch("predicate.failure_artifacts.subprocess.run") as mock_run: mock_run.side_effect = subprocess.TimeoutExpired(cmd="ffmpeg", timeout=5) assert _is_ffmpeg_available() is False @@ -321,8 +321,8 @@ def test_upload_to_cloud_with_mocked_gateway(tmp_path) -> None: mock_response_complete = MagicMock() mock_response_complete.status_code = 200 - with patch("sentience.failure_artifacts.requests.post") as mock_post: - with patch("sentience.failure_artifacts.requests.put") as mock_put: + with patch("predicate.failure_artifacts.requests.post") as mock_post: + with patch("predicate.failure_artifacts.requests.put") as mock_put: mock_post.side_effect = [mock_response_init, mock_response_complete] mock_put.return_value = mock_response_upload @@ -353,7 +353,7 @@ def test_upload_to_cloud_handles_gateway_error(tmp_path) -> None: mock_response = MagicMock() mock_response.status_code = 500 - with patch("sentience.failure_artifacts.requests.post") as mock_post: + with patch("predicate.failure_artifacts.requests.post") as mock_post: mock_post.return_value = mock_response result = buf.upload_to_cloud(api_key="test-key", persisted_dir=run_dir) @@ -371,7 +371,7 @@ def test_upload_to_cloud_handles_network_error(tmp_path) -> None: run_dir = buf.persist(reason="fail", status="failure") assert run_dir is not None - with patch("sentience.failure_artifacts.requests.post") as mock_post: + with patch("predicate.failure_artifacts.requests.post") as mock_post: mock_post.side_effect = requests.exceptions.ConnectionError("Network error") result = buf.upload_to_cloud(api_key="test-key", persisted_dir=run_dir) diff --git a/tests/unit/test_filesystem_tools.py b/tests/unit/test_filesystem_tools.py index 452a9a8..a004df4 100644 --- a/tests/unit/test_filesystem_tools.py +++ b/tests/unit/test_filesystem_tools.py @@ -4,7 +4,7 @@ import pytest -from sentience.tools import FileSandbox, ToolContext, ToolRegistry, register_filesystem_tools +from predicate.tools import FileSandbox, ToolContext, ToolRegistry, register_filesystem_tools class RuntimeStub: diff --git a/tests/unit/test_integration_phase0_contracts.py b/tests/unit/test_integration_phase0_contracts.py index cd201a2..dd09359 100644 --- a/tests/unit/test_integration_phase0_contracts.py +++ b/tests/unit/test_integration_phase0_contracts.py @@ -1,9 +1,9 @@ import pytest -from sentience.models import SnapshotOptions -from sentience.read import read -from sentience.snapshot import snapshot -from sentience.text_search import find_text_rect +from predicate.models import SnapshotOptions +from predicate.read import read +from predicate.snapshot import snapshot +from predicate.text_search import find_text_rect class _FakePage: @@ -54,7 +54,7 @@ def test_snapshot_default_limit_not_sent_to_extension(monkeypatch): 'limit' to the extension unless it differs from default. """ # Avoid any real extension waiting logic - from sentience.browser_evaluator import BrowserEvaluator + from predicate.browser_evaluator import BrowserEvaluator monkeypatch.setattr(BrowserEvaluator, "wait_for_extension", lambda *args, **kwargs: None) @@ -73,7 +73,7 @@ def test_snapshot_default_limit_not_sent_to_extension(monkeypatch): def test_snapshot_non_default_limit_is_sent_to_extension(monkeypatch): - from sentience.browser_evaluator import BrowserEvaluator + from predicate.browser_evaluator import BrowserEvaluator monkeypatch.setattr(BrowserEvaluator, "wait_for_extension", lambda *args, **kwargs: None) @@ -108,7 +108,7 @@ def test_find_text_rect_unavailable_raises(monkeypatch): Contract: if the extension doesn't expose findTextRect, the SDK surfaces a clear error. """ # Avoid any real extension waiting logic (and avoid sync/async page detection details) - from sentience.browser_evaluator import BrowserEvaluator + from predicate.browser_evaluator import BrowserEvaluator monkeypatch.setattr(BrowserEvaluator, "wait_for_extension", lambda *args, **kwargs: None) diff --git a/tests/unit/test_langchain_integration_core.py b/tests/unit/test_langchain_integration_core.py index 55d64b0..d17e71c 100644 --- a/tests/unit/test_langchain_integration_core.py +++ b/tests/unit/test_langchain_integration_core.py @@ -1,8 +1,8 @@ import pytest -from sentience.integrations.langchain.context import SentienceLangChainContext -from sentience.integrations.langchain.core import SentienceLangChainCore -from sentience.models import BBox, Element, Snapshot +from predicate.integrations.langchain.context import SentienceLangChainContext +from predicate.integrations.langchain.core import SentienceLangChainCore +from predicate.models import BBox, Element, Snapshot class _FakeAsyncPage: @@ -87,7 +87,7 @@ async def _fake_snapshot_async(browser, options): ) monkeypatch.setattr( - "sentience.integrations.langchain.core.snapshot_async", _fake_snapshot_async + "predicate.integrations.langchain.core.snapshot_async", _fake_snapshot_async ) ctx = SentienceLangChainContext(browser=_FakeAsyncBrowser()) # type: ignore[arg-type] diff --git a/tests/unit/test_permission_policy.py b/tests/unit/test_permission_policy.py index 0b068fc..3e6805b 100644 --- a/tests/unit/test_permission_policy.py +++ b/tests/unit/test_permission_policy.py @@ -1,7 +1,7 @@ import pytest -from sentience import AsyncSentienceBrowser, SentienceBrowser -from sentience.permissions import PermissionPolicy +from predicate import AsyncSentienceBrowser, SentienceBrowser +from predicate.permissions import PermissionPolicy class SyncContextStub: diff --git a/tests/unit/test_pydanticai_toolset.py b/tests/unit/test_pydanticai_toolset.py index cf6d20e..c690df0 100644 --- a/tests/unit/test_pydanticai_toolset.py +++ b/tests/unit/test_pydanticai_toolset.py @@ -2,9 +2,9 @@ import pytest -from sentience.integrations.pydanticai.deps import SentiencePydanticDeps -from sentience.integrations.pydanticai.toolset import register_sentience_tools -from sentience.models import BBox, Element, Snapshot +from predicate.integrations.pydanticai.deps import SentiencePydanticDeps +from predicate.integrations.pydanticai.toolset import register_sentience_tools +from predicate.models import BBox, Element, Snapshot class _FakeAgent: @@ -110,7 +110,7 @@ async def _fake_snapshot_async(browser, options): ) monkeypatch.setattr( - "sentience.integrations.pydanticai.toolset.snapshot_async", _fake_snapshot_async + "predicate.integrations.pydanticai.toolset.snapshot_async", _fake_snapshot_async ) deps = SentiencePydanticDeps(browser=_FakeAsyncBrowser()) # type: ignore[arg-type] @@ -185,7 +185,7 @@ async def _fake_type_text_async(browser, element_id, text, take_snapshot=False, return {"success": True} monkeypatch.setattr( - "sentience.integrations.pydanticai.toolset.type_text_async", + "predicate.integrations.pydanticai.toolset.type_text_async", _fake_type_text_async, ) @@ -212,7 +212,7 @@ async def _fake_click_rect_async(browser, rect, button="left", click_count=1, ** return {"success": True} monkeypatch.setattr( - "sentience.integrations.pydanticai.toolset.click_rect_async", _fake_click_rect_async + "predicate.integrations.pydanticai.toolset.click_rect_async", _fake_click_rect_async ) deps = SentiencePydanticDeps(browser=_FakeAsyncBrowser()) # type: ignore[arg-type] @@ -240,7 +240,7 @@ async def _boom(): raise RuntimeError("boom") monkeypatch.setattr( - "sentience.integrations.pydanticai.toolset.read_async", + "predicate.integrations.pydanticai.toolset.read_async", lambda *args, **kwargs: _boom(), ) diff --git a/tests/unit/test_runtime_agent.py b/tests/unit/test_runtime_agent.py index 2565319..81dca3e 100644 --- a/tests/unit/test_runtime_agent.py +++ b/tests/unit/test_runtime_agent.py @@ -4,9 +4,9 @@ import pytest -from sentience.agent_runtime import AgentRuntime -from sentience.llm_provider import LLMProvider, LLMResponse -from sentience.models import ( +from predicate.agent_runtime import AgentRuntime +from predicate.llm_provider import LLMProvider, LLMResponse +from predicate.models import ( BBox, Element, Snapshot, @@ -15,8 +15,8 @@ Viewport, VisualCues, ) -from sentience.runtime_agent import RuntimeAgent, RuntimeStep, StepVerification -from sentience.verification import AssertContext, AssertOutcome +from predicate.runtime_agent import RuntimeAgent, RuntimeStep, StepVerification +from predicate.verification import AssertContext, AssertOutcome class MockTracer: diff --git a/tests/unit/test_snapshot_sampling_merge.py b/tests/unit/test_snapshot_sampling_merge.py index 63c14ee..85594ca 100644 --- a/tests/unit/test_snapshot_sampling_merge.py +++ b/tests/unit/test_snapshot_sampling_merge.py @@ -1,7 +1,7 @@ import pytest -from sentience.backends.snapshot import merge_snapshots -from sentience.models import BBox, Element, Snapshot, VisualCues +from predicate.backends.snapshot import merge_snapshots +from predicate.models import BBox, Element, Snapshot, VisualCues def _el( diff --git a/tests/unit/test_tool_registry.py b/tests/unit/test_tool_registry.py index 31588dd..28dbac9 100644 --- a/tests/unit/test_tool_registry.py +++ b/tests/unit/test_tool_registry.py @@ -3,7 +3,7 @@ import pytest from pydantic import BaseModel, ValidationError -from sentience.tools import ( +from predicate.tools import ( ToolContext, ToolRegistry, ToolSpec, diff --git a/traces/test-run.jsonl b/traces/test-run.jsonl index e1982f7..85084c5 100644 --- a/traces/test-run.jsonl +++ b/traces/test-run.jsonl @@ -3,3 +3,8 @@ {"v": 1, "type": "run_start", "ts": "2026-02-05T06:20:59.000Z", "run_id": "test-run", "seq": 1, "data": {"agent": "SentienceAgent"}, "ts_ms": 1770272459846} {"v": 1, "type": "run_start", "ts": "2026-02-05T06:20:59.000Z", "run_id": "test-run", "seq": 1, "data": {"agent": "SentienceAgent"}, "ts_ms": 1770272459848} {"v": 1, "type": "run_start", "ts": "2026-02-05T06:20:59.000Z", "run_id": "test-run", "seq": 1, "data": {"agent": "SentienceAgent"}, "ts_ms": 1770272459855} +{"v": 1, "type": "run_start", "ts": "2026-02-11T03:10:06.000Z", "run_id": "test-run", "seq": 1, "data": {"agent": "SentienceAgent"}, "ts_ms": 1770779406551} +{"v": 1, "type": "run_start", "ts": "2026-02-11T03:10:06.000Z", "run_id": "test-run", "seq": 1, "data": {"agent": "SentienceAgent"}, "ts_ms": 1770779406554} +{"v": 1, "type": "run_start", "ts": "2026-02-11T03:10:06.000Z", "run_id": "test-run", "seq": 1, "data": {"agent": "SentienceAgent"}, "ts_ms": 1770779406555} +{"v": 1, "type": "run_start", "ts": "2026-02-11T03:10:06.000Z", "run_id": "test-run", "seq": 1, "data": {"agent": "SentienceAgent"}, "ts_ms": 1770779406557} +{"v": 1, "type": "run_start", "ts": "2026-02-11T03:10:06.000Z", "run_id": "test-run", "seq": 1, "data": {"agent": "SentienceAgent"}, "ts_ms": 1770779406568}