Skip to content

Commit 7c3f232

Browse files
authored
Merge pull request #2 from PredicateSystems/p2
adapters for agents
2 parents 22d05e0 + 3fd2780 commit 7c3f232

6 files changed

Lines changed: 1140 additions & 15 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ Thumbs.db
5252

5353
# Runtime traces and artifacts
5454
traces/
55+
*.jsonl
5556
artifacts/
5657
tmp/
5758
temp/

src/predicate_secure/__init__.py

Lines changed: 213 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,18 @@
2424
from pathlib import Path
2525
from typing import Any
2626

27-
from .config import SecureAgentConfig, WrappedAgent
28-
from .detection import (
29-
DetectionResult,
30-
Framework,
31-
FrameworkDetector,
32-
UnsupportedFrameworkError,
27+
from .adapters import (
28+
AdapterError,
29+
AdapterResult,
30+
create_adapter,
31+
create_browser_use_adapter,
32+
create_browser_use_runtime,
33+
create_langchain_adapter,
34+
create_playwright_adapter,
35+
create_pydantic_ai_adapter,
3336
)
37+
from .config import SecureAgentConfig, WrappedAgent
38+
from .detection import DetectionResult, Framework, FrameworkDetector, UnsupportedFrameworkError
3439

3540
__version__ = "0.1.0"
3641

@@ -43,6 +48,15 @@
4348
"Framework",
4449
"FrameworkDetector",
4550
"DetectionResult",
51+
# Adapters
52+
"AdapterResult",
53+
"AdapterError",
54+
"create_adapter",
55+
"create_browser_use_adapter",
56+
"create_browser_use_runtime",
57+
"create_playwright_adapter",
58+
"create_langchain_adapter",
59+
"create_pydantic_ai_adapter",
4660
# Modes
4761
"MODE_STRICT",
4862
"MODE_PERMISSIVE",
@@ -318,9 +332,7 @@ def run(self, task: str | None = None) -> Any:
318332
if self._wrapped.framework == Framework.PYDANTIC_AI.value:
319333
return self._run_pydantic_ai(task)
320334

321-
raise NotImplementedError(
322-
f"run() not implemented for framework: {self._wrapped.framework}"
323-
)
335+
raise NotImplementedError(f"run() not implemented for framework: {self._wrapped.framework}")
324336

325337
def _run_browser_use(self, task: str | None) -> Any:
326338
"""Run browser-use agent with authorization."""
@@ -331,8 +343,8 @@ def _run_browser_use(self, task: str | None) -> Any:
331343
agent = self._wrapped.original
332344

333345
# Override task if provided
334-
if task is not None:
335-
agent.task = task
346+
if task is not None and hasattr(agent, "task"):
347+
setattr(agent, "task", task)
336348

337349
# Check if agent has a run method
338350
if hasattr(agent, "run"):
@@ -374,9 +386,7 @@ def _run_langchain(self, task: str | None) -> Any:
374386

375387
def _run_pydantic_ai(self, task: str | None) -> Any:
376388
"""Run PydanticAI agent with authorization."""
377-
raise NotImplementedError(
378-
"PydanticAI integration not yet implemented."
379-
)
389+
raise NotImplementedError("PydanticAI integration not yet implemented.")
380390

381391
@classmethod
382392
def attach(cls, agent: Any, **kwargs: Any) -> SecureAgent:
@@ -414,6 +424,195 @@ def get_pre_action_authorizer(self) -> Any:
414424
"""
415425
return self._create_pre_action_authorizer()
416426

427+
def get_adapter(
428+
self,
429+
tracer: Any | None = None,
430+
snapshot_options: Any | None = None,
431+
predicate_api_key: str | None = None,
432+
**kwargs: Any,
433+
) -> AdapterResult:
434+
"""
435+
Get an adapter for the wrapped agent.
436+
437+
This creates the appropriate adapter based on the detected framework,
438+
wiring together BrowserUseAdapter, PredicateBrowserUsePlugin,
439+
SentienceLangChainCore, or AgentRuntime.from_playwright_page().
440+
441+
Args:
442+
tracer: Optional Tracer for event emission
443+
snapshot_options: Optional SnapshotOptions
444+
predicate_api_key: Optional API key for Predicate API
445+
**kwargs: Additional framework-specific options
446+
447+
Returns:
448+
AdapterResult with initialized components
449+
450+
Raises:
451+
AdapterError: If adapter initialization fails
452+
453+
Example:
454+
secure = SecureAgent(agent=my_browser_use_agent, policy="policy.yaml")
455+
adapter = secure.get_adapter()
456+
457+
# Use plugin lifecycle hooks
458+
result = await agent.run(
459+
on_step_start=adapter.plugin.on_step_start,
460+
on_step_end=adapter.plugin.on_step_end,
461+
)
462+
"""
463+
return create_adapter(
464+
agent=self._wrapped.original,
465+
framework=self.framework,
466+
tracer=tracer,
467+
snapshot_options=snapshot_options,
468+
predicate_api_key=predicate_api_key,
469+
**kwargs,
470+
)
471+
472+
async def get_runtime_async(
473+
self,
474+
tracer: Any | None = None,
475+
snapshot_options: Any | None = None,
476+
predicate_api_key: str | None = None,
477+
) -> Any:
478+
"""
479+
Get an initialized AgentRuntime for the wrapped agent (async).
480+
481+
This is useful for browser-use agents where runtime initialization
482+
requires async operations.
483+
484+
Args:
485+
tracer: Optional Tracer for event emission
486+
snapshot_options: Optional SnapshotOptions
487+
predicate_api_key: Optional API key for Predicate API
488+
489+
Returns:
490+
AgentRuntime instance
491+
492+
Raises:
493+
AdapterError: If runtime initialization fails
494+
495+
Example:
496+
secure = SecureAgent(agent=my_browser_use_agent, policy="policy.yaml")
497+
runtime = await secure.get_runtime_async()
498+
499+
# Use with RuntimeAgent
500+
from predicate.runtime_agent import RuntimeAgent
501+
runtime_agent = RuntimeAgent(
502+
runtime=runtime,
503+
executor=my_llm,
504+
pre_action_authorizer=secure.get_pre_action_authorizer(),
505+
)
506+
"""
507+
if self.framework == Framework.BROWSER_USE:
508+
result = await create_browser_use_runtime(
509+
agent=self._wrapped.original,
510+
tracer=tracer,
511+
snapshot_options=snapshot_options,
512+
predicate_api_key=predicate_api_key,
513+
)
514+
# Cache the runtime
515+
self._wrapped.agent_runtime = result.agent_runtime
516+
return result.agent_runtime
517+
518+
if self.framework == Framework.PLAYWRIGHT:
519+
adapter = create_playwright_adapter(
520+
page=self._wrapped.original,
521+
tracer=tracer,
522+
snapshot_options=snapshot_options,
523+
predicate_api_key=predicate_api_key,
524+
)
525+
self._wrapped.agent_runtime = adapter.agent_runtime
526+
return adapter.agent_runtime
527+
528+
raise AdapterError(
529+
f"get_runtime_async() not supported for framework: {self.framework.value}",
530+
self.framework,
531+
)
532+
533+
def get_browser_use_plugin(
534+
self,
535+
tracer: Any | None = None,
536+
snapshot_options: Any | None = None,
537+
predicate_api_key: str | None = None,
538+
) -> Any:
539+
"""
540+
Get a PredicateBrowserUsePlugin for browser-use lifecycle hooks.
541+
542+
This is the recommended way to integrate with browser-use agents.
543+
544+
Args:
545+
tracer: Optional Tracer for event emission
546+
snapshot_options: Optional SnapshotOptions
547+
predicate_api_key: Optional API key for Predicate API
548+
549+
Returns:
550+
PredicateBrowserUsePlugin instance
551+
552+
Raises:
553+
AdapterError: If framework is not browser-use
554+
555+
Example:
556+
secure = SecureAgent(agent=my_agent, policy="policy.yaml")
557+
plugin = secure.get_browser_use_plugin()
558+
559+
# Run with lifecycle hooks
560+
result = await agent.run(
561+
on_step_start=plugin.on_step_start,
562+
on_step_end=plugin.on_step_end,
563+
)
564+
"""
565+
if self.framework != Framework.BROWSER_USE:
566+
raise AdapterError(
567+
"get_browser_use_plugin() only available for browser-use agents",
568+
self.framework,
569+
)
570+
571+
adapter = create_browser_use_adapter(
572+
agent=self._wrapped.original,
573+
tracer=tracer,
574+
snapshot_options=snapshot_options,
575+
predicate_api_key=predicate_api_key,
576+
)
577+
return adapter.plugin
578+
579+
def get_langchain_core(
580+
self,
581+
browser: Any | None = None,
582+
tracer: Any | None = None,
583+
snapshot_options: Any | None = None,
584+
predicate_api_key: str | None = None,
585+
) -> Any:
586+
"""
587+
Get a SentienceLangChainCore for LangChain tool interception.
588+
589+
Args:
590+
browser: Optional browser instance for browser tools
591+
tracer: Optional Tracer for event emission
592+
snapshot_options: Optional SnapshotOptions
593+
predicate_api_key: Optional API key for Predicate API
594+
595+
Returns:
596+
SentienceLangChainCore instance
597+
598+
Raises:
599+
AdapterError: If framework is not LangChain
600+
"""
601+
if self.framework != Framework.LANGCHAIN:
602+
raise AdapterError(
603+
"get_langchain_core() only available for LangChain agents",
604+
self.framework,
605+
)
606+
607+
adapter = create_langchain_adapter(
608+
agent=self._wrapped.original,
609+
browser=browser,
610+
tracer=tracer,
611+
snapshot_options=snapshot_options,
612+
predicate_api_key=predicate_api_key,
613+
)
614+
return adapter.plugin
615+
417616
def __repr__(self) -> str:
418617
return (
419618
f"SecureAgent("

0 commit comments

Comments
 (0)