From 54131073a273a13e5e7a71acaeba7efb0cb3e2e5 Mon Sep 17 00:00:00 2001 From: SentienceDEV Date: Tue, 10 Feb 2026 19:16:31 -0800 Subject: [PATCH 1/2] Rebranding to predicatelabs package name --- .github/workflows/release.yml | 34 +- .github/workflows/sync-extension.yml | 12 +- .github/workflows/test.yml | 46 +-- .gitignore | 6 +- CHANGELOG.md | 22 +- MANIFEST.in | 4 +- README.md | 20 +- docs/QUERY_DSL.md | 2 +- examples/agent_layers_demo.py | 14 +- examples/agent_runtime_captcha_strategies.py | 4 +- examples/agent_runtime_verification.py | 8 +- examples/asserts/eventually_min_confidence.py | 6 +- examples/asserts/state_assertions.py | 6 +- examples/asserts/vision_fallback.py | 8 +- examples/async_api_demo.py | 4 +- examples/auth_injection_agent.py | 6 +- examples/basic_agent.py | 2 +- examples/basic_agent_async.py | 4 +- examples/browser-use/README.md | 12 +- .../browser-use/agent_runtime_browser_use.py | 10 +- examples/browser-use/integration.py | 10 +- examples/click_rect_demo.py | 2 +- examples/find_text_demo.py | 2 +- examples/hello.py | 2 +- examples/hello_async.py | 2 +- examples/human_cursor_click_demo.py | 2 +- examples/lang-chain/README.md | 2 +- examples/lang-chain/langchain_tools_demo.py | 6 +- .../langgraph_self_correcting_graph.py | 6 +- .../sentience_self_correcting_graph.py | 6 +- examples/local_tracing_agent.py | 8 +- .../pydantic_ai_self_correcting_click.py | 6 +- .../pydantic_ai_typed_extraction.py | 6 +- examples/query_demo.py | 2 +- examples/query_demo_async.py | 2 +- examples/read_markdown.py | 2 +- examples/read_markdown_async.py | 2 +- examples/residential_proxy_agent.py | 8 +- examples/runtime_agent_minimal.py | 12 +- examples/semantic_wait_demo.py | 2 +- examples/semantic_wait_demo_async.py | 2 +- examples/show_grid_examples.py | 8 +- examples/test_local_llm_agent.py | 2 +- examples/video_recording_advanced.py | 2 +- examples/video_recording_demo.py | 2 +- examples/wait_and_click.py | 2 +- examples/wait_and_click_async.py | 2 +- {sentience => predicate}/__init__.py | 4 +- {sentience => predicate}/_extension_loader.py | 10 +- {sentience => predicate}/action_executor.py | 0 {sentience => predicate}/actions.py | 2 +- {sentience => predicate}/agent.py | 10 +- {sentience => predicate}/agent_config.py | 2 +- {sentience => predicate}/agent_runtime.py | 14 +- {sentience => predicate}/asserts/__init__.py | 2 +- {sentience => predicate}/asserts/expect.py | 2 +- {sentience => predicate}/asserts/query.py | 2 +- {sentience => predicate}/async_api.py | 36 +- {sentience => predicate}/backends/__init__.py | 8 +- {sentience => predicate}/backends/actions.py | 4 +- .../backends/browser_use_adapter.py | 8 +- .../backends/cdp_backend.py | 4 +- .../backends/exceptions.py | 4 +- .../backends/playwright_backend.py | 4 +- {sentience => predicate}/backends/protocol.py | 0 .../backends/sentience_context.py | 8 +- {sentience => predicate}/backends/snapshot.py | 16 +- {sentience => predicate}/base_agent.py | 0 {sentience => predicate}/browser.py | 16 +- {sentience => predicate}/browser_evaluator.py | 0 {sentience => predicate}/canonicalization.py | 0 {sentience => predicate}/captcha.py | 0 .../captcha_strategies.py | 0 {sentience => predicate}/cli.py | 0 {sentience => predicate}/cloud_tracing.py | 14 +- predicate/constants.py | 7 + .../conversational_agent.py | 0 {sentience => predicate}/cursor_policy.py | 0 {sentience => predicate}/debugger.py | 0 {sentience => predicate}/element_filter.py | 0 {sentience => predicate}/expect.py | 0 .../extension/background.js | 0 {sentience => predicate}/extension/content.js | 0 .../extension/injected_api.js | 0 .../extension/manifest.json | 0 .../extension/release.json | 0 {sentience => predicate}/failure_artifacts.py | 4 +- {sentience => predicate}/formatting.py | 6 +- {sentience => predicate}/generator.py | 2 +- {sentience => predicate}/inspector.py | 0 .../integrations/__init__.py | 0 .../integrations/langchain/__init__.py | 0 .../integrations/langchain/context.py | 4 +- .../integrations/langchain/core.py | 14 +- .../integrations/langchain/tools.py | 2 +- .../integrations/models.py | 4 +- .../integrations/pydanticai/__init__.py | 2 +- .../integrations/pydanticai/deps.py | 4 +- .../integrations/pydanticai/toolset.py | 14 +- .../llm_interaction_handler.py | 0 {sentience => predicate}/llm_provider.py | 10 +- .../llm_provider_utils.py | 0 .../llm_response_builder.py | 0 {sentience => predicate}/models.py | 2 +- {sentience => predicate}/ordinal.py | 4 +- {sentience => predicate}/overlay.py | 0 {sentience => predicate}/permissions.py | 0 {sentience => predicate}/protocols.py | 0 {sentience => predicate}/query.py | 0 {sentience => predicate}/read.py | 2 +- {sentience => predicate}/recorder.py | 0 {sentience => predicate}/runtime_agent.py | 0 .../schemas/trace_v1.json | 0 {sentience => predicate}/screenshot.py | 0 {sentience => predicate}/sentience_methods.py | 0 {sentience => predicate}/snapshot.py | 10 +- {sentience => predicate}/snapshot_diff.py | 0 {sentience => predicate}/text_search.py | 4 +- {sentience => predicate}/tools/__init__.py | 0 {sentience => predicate}/tools/context.py | 0 {sentience => predicate}/tools/defaults.py | 0 {sentience => predicate}/tools/filesystem.py | 0 {sentience => predicate}/tools/registry.py | 0 .../trace_event_builder.py | 0 .../trace_file_manager.py | 0 .../trace_indexing/__init__.py | 0 .../trace_indexing/index_schema.py | 0 .../trace_indexing/indexer.py | 2 +- {sentience => predicate}/tracer_factory.py | 12 +- {sentience => predicate}/tracing.py | 2 +- {sentience => predicate}/utils/__init__.py | 4 +- {sentience => predicate}/utils/browser.py | 2 +- {sentience => predicate}/utils/element.py | 0 {sentience => predicate}/utils/formatting.py | 0 {sentience => predicate}/verification.py | 2 +- {sentience => predicate}/vision_executor.py | 0 {sentience => predicate}/visual_agent.py | 0 {sentience => predicate}/wait.py | 0 pyproject.toml | 10 +- scripts/sync_extension.sh | 2 +- sentience/constants.py | 6 - sentience/extension/pkg/sentience_core.d.ts | 51 --- sentience/extension/pkg/sentience_core.js | 371 ------------------ .../extension/pkg/sentience_core_bg.wasm | Bin 110696 -> 0 bytes .../extension/pkg/sentience_core_bg.wasm.d.ts | 10 - tests/integration/test_agent_workflows.py | 66 ++-- tests/test_actions.py | 2 +- tests/test_agent.py | 36 +- tests/test_agent_config.py | 4 +- tests/test_agent_runtime.py | 18 +- tests/test_asserts.py | 8 +- tests/test_async_api.py | 6 +- tests/test_backends.py | 32 +- tests/test_bot.py | 2 +- tests/test_browser.py | 2 +- tests/test_cloud_tracing.py | 88 ++--- tests/test_conversational_agent.py | 22 +- tests/test_debugger.py | 10 +- tests/test_file_size_tracking.py | 20 +- tests/test_formatting.py | 6 +- tests/test_generator.py | 12 +- tests/test_grid_bounds.py | 2 +- tests/test_importance_score.py | 4 +- tests/test_inspector.py | 2 +- tests/test_llm_provider_utils.py | 4 +- tests/test_llm_provider_vision.py | 2 +- tests/test_llm_response_builder.py | 4 +- tests/test_ordinal.py | 4 +- tests/test_proxy.py | 34 +- tests/test_query.py | 8 +- tests/test_read.py | 2 +- tests/test_recorder.py | 2 +- tests/test_screenshot.py | 2 +- tests/test_screenshot_storage.py | 16 +- tests/test_sentience_context.py | 11 +- tests/test_smart_selector.py | 4 +- tests/test_snapshot.py | 8 +- tests/test_snapshot_diff.py | 4 +- tests/test_snapshot_gateway_timeout.py | 4 +- tests/test_spec_validation.py | 4 +- tests/test_stealth.py | 2 +- tests/test_trace_event_builder.py | 6 +- tests/test_trace_file_manager.py | 2 +- .../test_trace_file_manager_extract_stats.py | 4 +- tests/test_trace_indexing.py | 2 +- tests/test_tracing.py | 10 +- tests/test_utils.py | 4 +- tests/test_utils_browser.py | 4 +- tests/test_verification.py | 6 +- tests/test_video_recording.py | 2 +- tests/test_wait.py | 2 +- tests/unit/conftest.py | 4 +- tests/unit/test_agent_errors.py | 64 +-- tests/unit/test_agent_runtime_phase0.py | 6 +- .../test_agent_runtime_regression_safety.py | 6 +- .../unit/test_captcha_context_page_control.py | 6 +- tests/unit/test_domain_policies.py | 4 +- tests/unit/test_extract.py | 6 +- tests/unit/test_failure_artifacts.py | 26 +- tests/unit/test_filesystem_tools.py | 2 +- .../unit/test_integration_phase0_contracts.py | 14 +- tests/unit/test_langchain_integration_core.py | 8 +- tests/unit/test_permission_policy.py | 4 +- tests/unit/test_pydanticai_toolset.py | 14 +- tests/unit/test_runtime_agent.py | 10 +- tests/unit/test_snapshot_sampling_merge.py | 4 +- tests/unit/test_tool_registry.py | 2 +- traces/test-run.jsonl | 5 + 208 files changed, 671 insertions(+), 1092 deletions(-) rename {sentience => predicate}/__init__.py (98%) rename {sentience => predicate}/_extension_loader.py (95%) rename {sentience => predicate}/action_executor.py (100%) rename {sentience => predicate}/actions.py (99%) rename {sentience => predicate}/agent.py (99%) rename {sentience => predicate}/agent_config.py (96%) rename {sentience => predicate}/agent_runtime.py (99%) rename {sentience => predicate}/asserts/__init__.py (96%) rename {sentience => predicate}/asserts/expect.py (99%) rename {sentience => predicate}/asserts/query.py (99%) rename {sentience => predicate}/async_api.py (78%) rename {sentience => predicate}/backends/__init__.py (94%) rename {sentience => predicate}/backends/actions.py (99%) rename {sentience => predicate}/backends/browser_use_adapter.py (97%) rename {sentience => predicate}/backends/cdp_backend.py (99%) rename {sentience => predicate}/backends/exceptions.py (98%) rename {sentience => predicate}/backends/playwright_backend.py (99%) rename {sentience => predicate}/backends/protocol.py (100%) rename {sentience => predicate}/backends/sentience_context.py (98%) rename {sentience => predicate}/backends/snapshot.py (98%) rename {sentience => predicate}/base_agent.py (100%) rename {sentience => predicate}/browser.py (99%) rename {sentience => predicate}/browser_evaluator.py (100%) rename {sentience => predicate}/canonicalization.py (100%) rename {sentience => predicate}/captcha.py (100%) rename {sentience => predicate}/captcha_strategies.py (100%) rename {sentience => predicate}/cli.py (100%) rename {sentience => predicate}/cloud_tracing.py (99%) create mode 100644 predicate/constants.py rename {sentience => predicate}/conversational_agent.py (100%) rename {sentience => predicate}/cursor_policy.py (100%) rename {sentience => predicate}/debugger.py (100%) rename {sentience => predicate}/element_filter.py (100%) rename {sentience => predicate}/expect.py (100%) rename {sentience => predicate}/extension/background.js (100%) rename {sentience => predicate}/extension/content.js (100%) rename {sentience => predicate}/extension/injected_api.js (100%) rename {sentience => predicate}/extension/manifest.json (100%) rename {sentience => predicate}/extension/release.json (100%) rename {sentience => predicate}/failure_artifacts.py (99%) rename {sentience => predicate}/formatting.py (63%) rename {sentience => predicate}/generator.py (99%) rename {sentience => predicate}/inspector.py (100%) rename {sentience => predicate}/integrations/__init__.py (100%) rename {sentience => predicate}/integrations/langchain/__init__.py (100%) rename {sentience => predicate}/integrations/langchain/context.py (78%) rename {sentience => predicate}/integrations/langchain/core.py (96%) rename {sentience => predicate}/integrations/langchain/tools.py (99%) rename {sentience => predicate}/integrations/models.py (91%) rename {sentience => predicate}/integrations/pydanticai/__init__.py (90%) rename {sentience => predicate}/integrations/pydanticai/deps.py (80%) rename {sentience => predicate}/integrations/pydanticai/toolset.py (97%) rename {sentience => predicate}/llm_interaction_handler.py (100%) rename {sentience => predicate}/llm_provider.py (99%) rename {sentience => predicate}/llm_provider_utils.py (100%) rename {sentience => predicate}/llm_response_builder.py (100%) rename {sentience => predicate}/models.py (99%) rename {sentience => predicate}/ordinal.py (98%) rename {sentience => predicate}/overlay.py (100%) rename {sentience => predicate}/permissions.py (100%) rename {sentience => predicate}/protocols.py (100%) rename {sentience => predicate}/query.py (100%) rename {sentience => predicate}/read.py (99%) rename {sentience => predicate}/recorder.py (100%) rename {sentience => predicate}/runtime_agent.py (100%) rename {sentience => predicate}/schemas/trace_v1.json (100%) rename {sentience => predicate}/screenshot.py (100%) rename {sentience => predicate}/sentience_methods.py (100%) rename {sentience => predicate}/snapshot.py (99%) rename {sentience => predicate}/snapshot_diff.py (100%) rename {sentience => predicate}/text_search.py (98%) rename {sentience => predicate}/tools/__init__.py (100%) rename {sentience => predicate}/tools/context.py (100%) rename {sentience => predicate}/tools/defaults.py (100%) rename {sentience => predicate}/tools/filesystem.py (100%) rename {sentience => predicate}/tools/registry.py (100%) rename {sentience => predicate}/trace_event_builder.py (100%) rename {sentience => predicate}/trace_file_manager.py (100%) rename {sentience => predicate}/trace_indexing/__init__.py (100%) rename {sentience => predicate}/trace_indexing/index_schema.py (100%) rename {sentience => predicate}/trace_indexing/indexer.py (99%) rename {sentience => predicate}/tracer_factory.py (97%) rename {sentience => predicate}/tracing.py (99%) rename {sentience => predicate}/utils/__init__.py (88%) rename {sentience => predicate}/utils/browser.py (95%) rename {sentience => predicate}/utils/element.py (100%) rename {sentience => predicate}/utils/formatting.py (100%) rename {sentience => predicate}/verification.py (99%) rename {sentience => predicate}/vision_executor.py (100%) rename {sentience => predicate}/visual_agent.py (100%) rename {sentience => predicate}/wait.py (100%) delete mode 100644 sentience/constants.py delete mode 100644 sentience/extension/pkg/sentience_core.d.ts delete mode 100644 sentience/extension/pkg/sentience_core.js delete mode 100644 sentience/extension/pkg/sentience_core_bg.wasm delete mode 100644 sentience/extension/pkg/sentience_core_bg.wasm.d.ts 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..7792422 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ The core loop is: ## Install ```bash -pip install sentienceapi +pip install predicatelabs playwright install chromium ``` @@ -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: @@ -85,8 +85,8 @@ If you already have an agent loop (LangGraph, browser-use, custom planner/execut Key idea: your agent still decides and executes actions β€” Sentience **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: @@ -111,7 +111,7 @@ async def run_existing_agent(page) -> None: 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. ```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: @@ -171,7 +171,7 @@ def login_example() -> None: Sentience 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) ``` 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..4cfe1e0 100644 --- a/sentience/actions.py +++ b/predicate/actions.py @@ -1031,7 +1031,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}) """ 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 4ef5500c2a54fba6f5089f11d40b49dec3ffed95..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 110696 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 Lm|nkN*0}!%S~AAO 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} From b8737e6844658ecd4b1d09fb4716d5e0fc6ebb89 Mon Sep 17 00:00:00 2001 From: SentienceDEV Date: Tue, 10 Feb 2026 19:47:02 -0800 Subject: [PATCH 2/2] fix tests --- README.md | 24 ++++++++++++------------ predicate/actions.py | 16 ++++++++++++++-- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 7792422..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 @@ -34,7 +34,7 @@ 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) @@ -80,9 +80,9 @@ 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 predicate import SentienceDebugger, create_tracer @@ -108,7 +108,7 @@ 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 predicate import SentienceBrowser, snapshot, find, click, type_text, wait_for @@ -168,7 +168,7 @@ 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 predicate.tools import ToolRegistry, register_default_tools @@ -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/predicate/actions.py b/predicate/actions.py index 4cfe1e0..1de86a7 100644 --- a/predicate/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 @@ -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: