sondera-harness 0.6.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. sondera/__init__.py +111 -0
  2. sondera/__main__.py +4 -0
  3. sondera/adk/__init__.py +3 -0
  4. sondera/adk/analyze.py +222 -0
  5. sondera/adk/plugin.py +387 -0
  6. sondera/cli.py +22 -0
  7. sondera/exceptions.py +167 -0
  8. sondera/harness/__init__.py +6 -0
  9. sondera/harness/abc.py +102 -0
  10. sondera/harness/cedar/__init__.py +0 -0
  11. sondera/harness/cedar/harness.py +363 -0
  12. sondera/harness/cedar/schema.py +225 -0
  13. sondera/harness/sondera/__init__.py +0 -0
  14. sondera/harness/sondera/_grpc.py +354 -0
  15. sondera/harness/sondera/harness.py +890 -0
  16. sondera/langgraph/__init__.py +15 -0
  17. sondera/langgraph/analyze.py +543 -0
  18. sondera/langgraph/exceptions.py +19 -0
  19. sondera/langgraph/graph.py +210 -0
  20. sondera/langgraph/middleware.py +454 -0
  21. sondera/proto/google/protobuf/any_pb2.py +37 -0
  22. sondera/proto/google/protobuf/any_pb2.pyi +14 -0
  23. sondera/proto/google/protobuf/any_pb2_grpc.py +24 -0
  24. sondera/proto/google/protobuf/duration_pb2.py +37 -0
  25. sondera/proto/google/protobuf/duration_pb2.pyi +14 -0
  26. sondera/proto/google/protobuf/duration_pb2_grpc.py +24 -0
  27. sondera/proto/google/protobuf/empty_pb2.py +37 -0
  28. sondera/proto/google/protobuf/empty_pb2.pyi +9 -0
  29. sondera/proto/google/protobuf/empty_pb2_grpc.py +24 -0
  30. sondera/proto/google/protobuf/struct_pb2.py +47 -0
  31. sondera/proto/google/protobuf/struct_pb2.pyi +49 -0
  32. sondera/proto/google/protobuf/struct_pb2_grpc.py +24 -0
  33. sondera/proto/google/protobuf/timestamp_pb2.py +37 -0
  34. sondera/proto/google/protobuf/timestamp_pb2.pyi +14 -0
  35. sondera/proto/google/protobuf/timestamp_pb2_grpc.py +24 -0
  36. sondera/proto/google/protobuf/wrappers_pb2.py +53 -0
  37. sondera/proto/google/protobuf/wrappers_pb2.pyi +59 -0
  38. sondera/proto/google/protobuf/wrappers_pb2_grpc.py +24 -0
  39. sondera/proto/sondera/__init__.py +0 -0
  40. sondera/proto/sondera/core/__init__.py +0 -0
  41. sondera/proto/sondera/core/v1/__init__.py +0 -0
  42. sondera/proto/sondera/core/v1/primitives_pb2.py +88 -0
  43. sondera/proto/sondera/core/v1/primitives_pb2.pyi +259 -0
  44. sondera/proto/sondera/core/v1/primitives_pb2_grpc.py +24 -0
  45. sondera/proto/sondera/harness/__init__.py +0 -0
  46. sondera/proto/sondera/harness/v1/__init__.py +0 -0
  47. sondera/proto/sondera/harness/v1/harness_pb2.py +81 -0
  48. sondera/proto/sondera/harness/v1/harness_pb2.pyi +192 -0
  49. sondera/proto/sondera/harness/v1/harness_pb2_grpc.py +498 -0
  50. sondera/py.typed +0 -0
  51. sondera/settings.py +20 -0
  52. sondera/strands/__init__.py +5 -0
  53. sondera/strands/analyze.py +244 -0
  54. sondera/strands/harness.py +333 -0
  55. sondera/tui/__init__.py +0 -0
  56. sondera/tui/app.py +309 -0
  57. sondera/tui/screens/__init__.py +5 -0
  58. sondera/tui/screens/adjudication.py +184 -0
  59. sondera/tui/screens/agent.py +158 -0
  60. sondera/tui/screens/trajectory.py +158 -0
  61. sondera/tui/widgets/__init__.py +23 -0
  62. sondera/tui/widgets/agent_card.py +94 -0
  63. sondera/tui/widgets/agent_list.py +73 -0
  64. sondera/tui/widgets/recent_adjudications.py +52 -0
  65. sondera/tui/widgets/recent_trajectories.py +54 -0
  66. sondera/tui/widgets/summary.py +57 -0
  67. sondera/tui/widgets/tool_card.py +33 -0
  68. sondera/tui/widgets/violation_panel.py +72 -0
  69. sondera/tui/widgets/violations_list.py +78 -0
  70. sondera/tui/widgets/violations_summary.py +104 -0
  71. sondera/types.py +346 -0
  72. sondera_harness-0.6.0.dist-info/METADATA +323 -0
  73. sondera_harness-0.6.0.dist-info/RECORD +77 -0
  74. sondera_harness-0.6.0.dist-info/WHEEL +5 -0
  75. sondera_harness-0.6.0.dist-info/entry_points.txt +2 -0
  76. sondera_harness-0.6.0.dist-info/licenses/LICENSE +21 -0
  77. sondera_harness-0.6.0.dist-info/top_level.txt +1 -0
sondera/__init__.py ADDED
@@ -0,0 +1,111 @@
1
+ """Sondera SDK for Python - Agent governance and policy enforcement.
2
+
3
+ This SDK provides tools for integrating AI agents with the Sondera Platform
4
+ for policy enforcement, guardrails, and governance.
5
+
6
+ Main Components:
7
+ - Harness: Abstract base class for harness implementations
8
+ - RemoteHarness: Production harness connecting to Sondera Platform
9
+ - CedarPolicyEngine: Local policy-as-code engine using Cedar
10
+
11
+ Framework Integrations:
12
+ - sondera.langgraph: LangGraph/LangChain middleware
13
+ - sondera.adk: Google ADK plugin
14
+ - sondera.strands: Strands Agent SDK hook
15
+
16
+ Example:
17
+ >>> from sondera import SonderaRemoteHarness, Agent
18
+ >>> harness = SonderaRemoteHarness(sondera_api_key="<YOUR_SONDERA_API_KEY>")
19
+ >>> agent = Agent(
20
+ ... id="my-agent",
21
+ ... provider_id="langchain",
22
+ ... name="My Agent",
23
+ ... description="A helpful assistant",
24
+ ... instruction="Be helpful and concise",
25
+ ... tools=[],
26
+ ... )
27
+ >>> await harness.initialize(agent=agent)
28
+ """
29
+
30
+ from sondera.exceptions import (
31
+ AgentError,
32
+ AuthenticationError,
33
+ ConfigurationError,
34
+ ConnectionError,
35
+ PolicyError,
36
+ PolicyEvaluationError,
37
+ PolicyViolationError,
38
+ SerializationError,
39
+ SonderaError,
40
+ ToolBlockedError,
41
+ ToolError,
42
+ TrajectoryError,
43
+ TrajectoryNotInitializedError,
44
+ )
45
+ from sondera.harness import CedarPolicyHarness, Harness, SonderaRemoteHarness
46
+ from sondera.types import (
47
+ AdjudicatedStep,
48
+ AdjudicatedTrajectory,
49
+ Adjudication,
50
+ Agent,
51
+ Content,
52
+ Decision,
53
+ Parameter,
54
+ PolicyEngineMode,
55
+ PromptContent,
56
+ Role,
57
+ SourceCode,
58
+ Stage,
59
+ Tool,
60
+ ToolRequestContent,
61
+ ToolResponseContent,
62
+ Trajectory,
63
+ TrajectoryStatus,
64
+ TrajectoryStep,
65
+ )
66
+
67
+ __version__ = "0.6.0"
68
+
69
+ __all__ = [
70
+ # Harness implementations
71
+ "Harness",
72
+ "SonderaRemoteHarness",
73
+ "CedarPolicyHarness",
74
+ # Core types
75
+ "Agent",
76
+ "Tool",
77
+ "Parameter",
78
+ "SourceCode",
79
+ # Trajectory types
80
+ "Trajectory",
81
+ "TrajectoryStep",
82
+ "TrajectoryStatus",
83
+ "Stage",
84
+ "Role",
85
+ # Content types
86
+ "Content",
87
+ "PromptContent",
88
+ "ToolRequestContent",
89
+ "ToolResponseContent",
90
+ # Policy types
91
+ "PolicyEngineMode",
92
+ # Adjudication types
93
+ "Adjudication",
94
+ "AdjudicatedStep",
95
+ "AdjudicatedTrajectory",
96
+ "Decision",
97
+ # Exceptions
98
+ "SonderaError",
99
+ "ConfigurationError",
100
+ "AuthenticationError",
101
+ "ConnectionError",
102
+ "TrajectoryError",
103
+ "TrajectoryNotInitializedError",
104
+ "PolicyError",
105
+ "PolicyViolationError",
106
+ "PolicyEvaluationError",
107
+ "AgentError",
108
+ "ToolError",
109
+ "ToolBlockedError",
110
+ "SerializationError",
111
+ ]
sondera/__main__.py ADDED
@@ -0,0 +1,4 @@
1
+ from sondera.cli import cli
2
+
3
+ if __name__ == "__main__":
4
+ cli()
@@ -0,0 +1,3 @@
1
+ from .plugin import SonderaHarnessPlugin
2
+
3
+ __all__ = ["SonderaHarnessPlugin"]
sondera/adk/analyze.py ADDED
@@ -0,0 +1,222 @@
1
+ import inspect
2
+ import logging
3
+ from collections.abc import Callable
4
+ from typing import get_type_hints
5
+
6
+ from google.adk import Agent as AdkAgent
7
+ from google.adk.tools.base_tool import BaseTool
8
+ from google.adk.tools.function_tool import FunctionTool
9
+
10
+ from sondera.types import Agent, Parameter, SourceCode, Tool
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ def _get_function_source(func: Callable) -> tuple[str, str]:
16
+ """Extract source code and language from a function."""
17
+ try:
18
+ source = inspect.getsource(func)
19
+ return "python", source
20
+ except (OSError, TypeError):
21
+ # Source not available (e.g., built-in function)
22
+ return "python", f"# Source code not available for {func.__name__}"
23
+
24
+
25
+ def _analyze_function_parameters(func: Callable) -> list[Parameter]:
26
+ """Analyze function parameters and return Sondera format Parameters."""
27
+ parameters = []
28
+ sig = inspect.signature(func)
29
+
30
+ # Try to get type hints for better type information
31
+ try:
32
+ type_hints = get_type_hints(func)
33
+ except Exception:
34
+ type_hints = {}
35
+
36
+ for param_name, param in sig.parameters.items():
37
+ # Skip special parameters like tool_context that ADK injects
38
+ if param_name in ["tool_context", "self", "cls"]:
39
+ continue
40
+
41
+ # Get parameter type
42
+ param_type = "Any"
43
+ if param.annotation != inspect.Parameter.empty:
44
+ if isinstance(param.annotation, type):
45
+ param_type = param.annotation.__name__
46
+ else:
47
+ param_type = str(param.annotation)
48
+ elif param_name in type_hints:
49
+ hint = type_hints[param_name]
50
+ param_type = hint.__name__ if isinstance(hint, type) else str(hint)
51
+
52
+ # Extract parameter description from docstring if available
53
+ description = f"Parameter {param_name}"
54
+ if func.__doc__:
55
+ # Simple extraction - could be enhanced with proper docstring parsing
56
+ lines = func.__doc__.split("\n")
57
+ for line in lines:
58
+ if param_name in line:
59
+ description = line.strip()
60
+ break
61
+
62
+ parameters.append(
63
+ Parameter(name=param_name, description=description, type=param_type)
64
+ )
65
+
66
+ return parameters
67
+
68
+
69
+ def _get_function_return_type(func: Callable) -> str:
70
+ """Extract the return type from a function."""
71
+ sig = inspect.signature(func)
72
+ if sig.return_annotation != inspect.Signature.empty:
73
+ if isinstance(sig.return_annotation, type):
74
+ return sig.return_annotation.__name__
75
+ else:
76
+ return str(sig.return_annotation)
77
+
78
+ # Try type hints as fallback
79
+ try:
80
+ type_hints = get_type_hints(func)
81
+ if "return" in type_hints:
82
+ hint = type_hints["return"]
83
+ if isinstance(hint, type):
84
+ return hint.__name__
85
+ else:
86
+ return str(hint)
87
+ except Exception:
88
+ pass
89
+
90
+ return "Any"
91
+
92
+
93
+ def _extract_json_schemas(func_decl) -> tuple[str | None, str | None]:
94
+ """Extract parameters and response JSON schemas from a function declaration."""
95
+ parameters_json_schema = None
96
+ response_json_schema = None
97
+
98
+ if func_decl and func_decl.parameters is not None:
99
+ parameters_json_schema = func_decl.parameters.model_dump_json(
100
+ exclude_unset=True
101
+ )
102
+ if func_decl and func_decl.response is not None:
103
+ response_json_schema = func_decl.response.model_dump_json(exclude_unset=True)
104
+
105
+ return parameters_json_schema, response_json_schema
106
+
107
+
108
+ def _extract_source_code(obj: Callable | object, default_name: str) -> tuple[str, str]:
109
+ """Extract source code from a function or tool object."""
110
+ # If it's a function, get source directly
111
+ if inspect.isfunction(obj):
112
+ return _get_function_source(obj)
113
+
114
+ # For BaseTool instances, try to find source from methods
115
+ language = "python"
116
+ source_code = f"# {obj.__class__.__name__} instance: {default_name}"
117
+ for method_name in ["run_async", "run", "execute", "__call__"]:
118
+ if hasattr(obj, method_name):
119
+ try:
120
+ method = getattr(obj, method_name)
121
+ source_code = inspect.getsource(method)
122
+ break
123
+ except Exception:
124
+ pass
125
+
126
+ return language, source_code
127
+
128
+
129
+ def format(
130
+ agent: AdkAgent, agent_name: str | None = None, agent_id: str | None = None
131
+ ) -> Agent:
132
+ """Transform the ADK agent into the Sondera Format."""
133
+
134
+ agent_name = agent_name or agent.name
135
+ agent_id = agent_id or agent.name
136
+
137
+ if type(agent) is not AdkAgent:
138
+ raise ValueError("Agent must be an ADK agent")
139
+
140
+ # Extract instruction from various ADK instruction provider types
141
+ if isinstance(agent.instruction, str):
142
+ instruction = agent.instruction
143
+ elif (render := getattr(agent.instruction, "render", None)) and callable(render):
144
+ # Handle InstructionProvider with render method
145
+ try:
146
+ instruction = render()
147
+ if not isinstance(instruction, str):
148
+ raise TypeError(
149
+ f"Expected render() to return str, got {type(instruction).__name__}"
150
+ )
151
+ except TypeError:
152
+ raise
153
+ except Exception:
154
+ instruction = str(agent.instruction)
155
+ elif agent.instruction is not None:
156
+ # Fallback for other types - convert to string
157
+ instruction = str(agent.instruction)
158
+ else:
159
+ instruction = ""
160
+
161
+ tools = []
162
+ for tool in agent.tools:
163
+ logger.info(f"Analyzing tool: {tool}")
164
+ # ADK tools can be functions or BaseTool/BaseToolset instances
165
+ if inspect.isfunction(tool):
166
+ # It's a raw function - wrap it in FunctionTool to get JSON schemas
167
+ func = tool
168
+ tool_name = func.__name__
169
+ tool_description = func.__doc__ or f"Function {tool_name}"
170
+
171
+ # Wrap function in FunctionTool to get JSON schema declarations
172
+ function_tool = FunctionTool(func)
173
+ func_decl = function_tool._get_declaration()
174
+ parameters_json_schema, response_json_schema = _extract_json_schemas(
175
+ func_decl
176
+ )
177
+
178
+ # Analyze function signature for parameters (fallback if JSON schema unavailable)
179
+ parameters = _analyze_function_parameters(func)
180
+ response_type = _get_function_return_type(func)
181
+ language, source_code = _extract_source_code(func, tool_name)
182
+
183
+ elif isinstance(tool, BaseTool):
184
+ # It's a BaseTool instance or similar
185
+ tool_name = getattr(tool, "name", tool.__class__.__name__)
186
+ tool_description = getattr(tool, "description", f"Tool {tool_name}")
187
+
188
+ # Try to get JSON schema from tool declaration
189
+ func_decl = tool._get_declaration()
190
+ parameters_json_schema, response_json_schema = _extract_json_schemas(
191
+ func_decl
192
+ )
193
+
194
+ # For BaseTool instances, we may not have parameter info readily available
195
+ parameters = []
196
+ response_type = "Any"
197
+ language, source_code = _extract_source_code(tool, tool_name)
198
+
199
+ else:
200
+ raise ValueError(f"Unknown tool type: {type(tool)}")
201
+
202
+ # Create Tool object (common for both function and BaseTool paths)
203
+ tools.append(
204
+ Tool(
205
+ name=tool_name,
206
+ description=tool_description.strip(),
207
+ parameters=parameters,
208
+ response=response_type,
209
+ parameters_json_schema=parameters_json_schema,
210
+ response_json_schema=response_json_schema,
211
+ source=SourceCode(language=language, code=source_code),
212
+ )
213
+ )
214
+
215
+ return Agent(
216
+ id=agent_id,
217
+ provider_id="google",
218
+ name=agent_name,
219
+ instruction=instruction,
220
+ description=agent.description,
221
+ tools=tools,
222
+ )