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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 17 additions & 5 deletions predicate/agent_runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ def __init__(
backend: BrowserBackend,
tracer: Tracer,
snapshot_options: SnapshotOptions | None = None,
predicate_api_key: str | None = None,
sentience_api_key: str | None = None,
tool_registry: ToolRegistry | None = None,
):
Expand All @@ -138,7 +139,8 @@ def __init__(
- PlaywrightBackend (future, for direct Playwright)
tracer: Tracer for emitting verification events
snapshot_options: Default options for snapshots
sentience_api_key: API key for Pro/Enterprise tier (enables Gateway refinement)
predicate_api_key: Canonical API key parameter for Pro/Enterprise tier.
sentience_api_key: Backward-compatible API key alias (legacy name).
tool_registry: Optional ToolRegistry for LLM-callable tools
"""
self.backend = backend
Expand All @@ -147,8 +149,10 @@ def __init__(

# Build default snapshot options with API key if provided
default_opts = snapshot_options or SnapshotOptions()
if sentience_api_key:
default_opts.sentience_api_key = sentience_api_key
effective_api_key = predicate_api_key or sentience_api_key
if effective_api_key:
default_opts.predicate_api_key = effective_api_key
default_opts.sentience_api_key = effective_api_key
if default_opts.use_api is None:
default_opts.use_api = True
self._snapshot_options = default_opts
Expand Down Expand Up @@ -193,6 +197,7 @@ def from_playwright_page(
page: Page,
tracer: Tracer,
snapshot_options: SnapshotOptions | None = None,
predicate_api_key: str | None = None,
sentience_api_key: str | None = None,
tool_registry: ToolRegistry | None = None,
) -> AgentRuntime:
Expand All @@ -203,7 +208,8 @@ def from_playwright_page(
page: Playwright Page for browser interaction
tracer: Tracer for emitting verification events
snapshot_options: Default options for snapshots
sentience_api_key: API key for Pro/Enterprise tier
predicate_api_key: Canonical API key parameter for Pro/Enterprise tier.
sentience_api_key: Backward-compatible API key alias (legacy name).
tool_registry: Optional ToolRegistry for LLM-callable tools

Returns:
Expand All @@ -216,6 +222,7 @@ def from_playwright_page(
backend=backend,
tracer=tracer,
snapshot_options=snapshot_options,
predicate_api_key=predicate_api_key,
sentience_api_key=sentience_api_key,
tool_registry=tool_registry,
)
Expand All @@ -226,6 +233,7 @@ def attach(
page: Page,
tracer: Tracer,
snapshot_options: SnapshotOptions | None = None,
predicate_api_key: str | None = None,
sentience_api_key: str | None = None,
tool_registry: ToolRegistry | None = None,
) -> AgentRuntime:
Expand All @@ -236,6 +244,7 @@ def attach(
page=page,
tracer=tracer,
snapshot_options=snapshot_options,
predicate_api_key=predicate_api_key,
sentience_api_key=sentience_api_key,
tool_registry=tool_registry,
)
Expand All @@ -247,6 +256,7 @@ async def from_sentience_browser(
page: Page,
tracer: Tracer,
snapshot_options: SnapshotOptions | None = None,
predicate_api_key: str | None = None,
sentience_api_key: str | None = None,
) -> AgentRuntime:
"""
Expand All @@ -260,7 +270,8 @@ async def from_sentience_browser(
page: Playwright Page for browser interaction
tracer: Tracer for emitting verification events
snapshot_options: Default options for snapshots
sentience_api_key: API key for Pro/Enterprise tier
predicate_api_key: Canonical API key parameter for Pro/Enterprise tier.
sentience_api_key: Backward-compatible API key alias (legacy name).

Returns:
AgentRuntime instance
Expand All @@ -272,6 +283,7 @@ async def from_sentience_browser(
backend=backend,
tracer=tracer,
snapshot_options=snapshot_options,
predicate_api_key=predicate_api_key,
sentience_api_key=sentience_api_key,
)
# Store browser reference for snapshot() to use
Expand Down
8 changes: 5 additions & 3 deletions predicate/backends/sentience_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ class SentienceContext:
def __init__(
self,
*,
predicate_api_key: str | None = None,
sentience_api_key: str | None = None,
use_api: bool | None = None,
max_elements: int = 60,
Expand All @@ -96,13 +97,14 @@ def __init__(
Initialize SentienceContext.

Args:
sentience_api_key: Sentience API key for gateway mode
predicate_api_key: Canonical API key parameter for gateway mode.
sentience_api_key: Backward-compatible API key alias (legacy name).
use_api: Force API vs extension mode (auto-detected if None)
max_elements: Maximum elements to fetch from snapshot
show_overlay: Show visual overlay highlighting elements in browser
top_element_selector: Configuration for element selection strategy
"""
self._api_key = sentience_api_key
self._api_key = predicate_api_key or sentience_api_key
self._use_api = use_api
self._max_elements = max_elements
self._show_overlay = show_overlay
Expand Down Expand Up @@ -155,7 +157,7 @@ async def build(

# Set API options
if self._api_key:
options.sentience_api_key = self._api_key
options.predicate_api_key = self._api_key
if self._use_api is not None:
options.use_api = self._use_api
elif self._api_key:
Expand Down
9 changes: 4 additions & 5 deletions predicate/backends/snapshot.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,11 +247,10 @@ async def snapshot(

# Determine if we should use server-side API
# 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)
)
effective_api_key = options.predicate_api_key or options.sentience_api_key
should_use_api = options.use_api if options.use_api is not None else (effective_api_key is not None)

if should_use_api and options.sentience_api_key:
if should_use_api and effective_api_key:
# Use server-side API (Pro/Enterprise tier)
return await _snapshot_via_api(backend, options)
else:
Expand Down Expand Up @@ -596,7 +595,7 @@ async def _snapshot_via_api(
try:
api_result = await _post_snapshot_to_gateway_async(
payload,
options.sentience_api_key,
options.predicate_api_key or options.sentience_api_key,
api_url,
timeout_s=options.gateway_timeout_s,
)
Expand Down
20 changes: 13 additions & 7 deletions predicate/debugger.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,16 +48,22 @@ def attach(
page: Page,
tracer: Tracer,
snapshot_options: SnapshotOptions | None = None,
predicate_api_key: str | None = None,
sentience_api_key: str | None = None,
tool_registry: ToolRegistry | None = None,
) -> SentienceDebugger:
runtime = AgentRuntime.from_playwright_page(
page=page,
tracer=tracer,
snapshot_options=snapshot_options,
sentience_api_key=sentience_api_key,
tool_registry=tool_registry,
)
factory_kwargs: dict[str, Any] = {
"page": page,
"tracer": tracer,
"snapshot_options": snapshot_options,
"sentience_api_key": sentience_api_key,
"tool_registry": tool_registry,
}
# Preserve old call shape unless new parameter is explicitly used.
if predicate_api_key is not None:
factory_kwargs["predicate_api_key"] = predicate_api_key

runtime = AgentRuntime.from_playwright_page(**factory_kwargs)
return cls(runtime=runtime)

def begin_step(self, goal: str, step_index: int | None = None) -> str:
Expand Down
18 changes: 16 additions & 2 deletions predicate/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from dataclasses import dataclass
from typing import Any, Literal

from pydantic import BaseModel, ConfigDict, Field
from pydantic import BaseModel, ConfigDict, Field, model_validator


class BBox(BaseModel):
Expand Down Expand Up @@ -787,10 +787,24 @@ class SnapshotOptions(BaseModel):
)

# API credentials (for browser-use integration without SentienceBrowser)
sentience_api_key: str | None = None # Sentience API key for Pro/Enterprise features
# Keep both names during migration; Predicate name is canonical.
predicate_api_key: str | None = None
sentience_api_key: str | None = None

model_config = ConfigDict(arbitrary_types_allowed=True)

@model_validator(mode="after")
def _sync_api_key_aliases(self) -> "SnapshotOptions":
"""
Keep predicate_api_key and sentience_api_key in sync during migration.
Predicate naming wins when both are set.
"""
if self.predicate_api_key:
self.sentience_api_key = self.predicate_api_key
elif self.sentience_api_key:
self.predicate_api_key = self.sentience_api_key
return self


class AgentActionResult(BaseModel):
"""Result of a single agent action (from agent.act())"""
Expand Down
12 changes: 6 additions & 6 deletions predicate/snapshot.py
Original file line number Diff line number Diff line change
Expand Up @@ -476,9 +476,9 @@ def snapshot(
if options is None:
options = SnapshotOptions()

# Resolve API key: options.sentience_api_key takes precedence, then browser.api_key
# This allows browser-use users to pass api_key via options without SentienceBrowser
effective_api_key = options.sentience_api_key or browser.api_key
# Resolve API key: predicate_api_key is canonical, sentience_api_key kept for compatibility.
# This allows browser-use users to pass api_key via options without SentienceBrowser.
effective_api_key = options.predicate_api_key or options.sentience_api_key or browser.api_key

# Determine if we should use server-side API
should_use_api = (
Expand Down Expand Up @@ -710,9 +710,9 @@ async def snapshot_async(
if options is None:
options = SnapshotOptions()

# Resolve API key: options.sentience_api_key takes precedence, then browser.api_key
# This allows browser-use users to pass api_key via options without SentienceBrowser
effective_api_key = options.sentience_api_key or browser.api_key
# Resolve API key: predicate_api_key is canonical, sentience_api_key kept for compatibility.
# This allows browser-use users to pass api_key via options without SentienceBrowser.
effective_api_key = options.predicate_api_key or options.sentience_api_key or browser.api_key

# Determine if we should use server-side API
should_use_api = (
Expand Down
38 changes: 38 additions & 0 deletions tests/test_predicate_api_key_aliases.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# pylint: disable=protected-access
from unittest.mock import MagicMock

from predicate.agent_runtime import AgentRuntime
from predicate.models import SnapshotOptions


def test_snapshot_options_accepts_predicate_api_key() -> None:
opts = SnapshotOptions(predicate_api_key="pk_test")
assert opts.predicate_api_key == "pk_test"
assert opts.sentience_api_key == "pk_test"


def test_snapshot_options_keeps_backward_compatible_sentience_api_key() -> None:
opts = SnapshotOptions(sentience_api_key="sk_test")
assert opts.sentience_api_key == "sk_test"
assert opts.predicate_api_key == "sk_test"


def test_agent_runtime_accepts_predicate_api_key() -> None:
runtime = AgentRuntime(
backend=MagicMock(),
tracer=MagicMock(),
predicate_api_key="pk_runtime",
)
assert runtime._snapshot_options.predicate_api_key == "pk_runtime"
assert runtime._snapshot_options.sentience_api_key == "pk_runtime"


def test_agent_runtime_prefers_predicate_api_key_when_both_provided() -> None:
runtime = AgentRuntime(
backend=MagicMock(),
tracer=MagicMock(),
predicate_api_key="pk_new",
sentience_api_key="sk_old",
)
assert runtime._snapshot_options.predicate_api_key == "pk_new"
assert runtime._snapshot_options.sentience_api_key == "pk_new"
Loading