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.
- sondera/__init__.py +111 -0
- sondera/__main__.py +4 -0
- sondera/adk/__init__.py +3 -0
- sondera/adk/analyze.py +222 -0
- sondera/adk/plugin.py +387 -0
- sondera/cli.py +22 -0
- sondera/exceptions.py +167 -0
- sondera/harness/__init__.py +6 -0
- sondera/harness/abc.py +102 -0
- sondera/harness/cedar/__init__.py +0 -0
- sondera/harness/cedar/harness.py +363 -0
- sondera/harness/cedar/schema.py +225 -0
- sondera/harness/sondera/__init__.py +0 -0
- sondera/harness/sondera/_grpc.py +354 -0
- sondera/harness/sondera/harness.py +890 -0
- sondera/langgraph/__init__.py +15 -0
- sondera/langgraph/analyze.py +543 -0
- sondera/langgraph/exceptions.py +19 -0
- sondera/langgraph/graph.py +210 -0
- sondera/langgraph/middleware.py +454 -0
- sondera/proto/google/protobuf/any_pb2.py +37 -0
- sondera/proto/google/protobuf/any_pb2.pyi +14 -0
- sondera/proto/google/protobuf/any_pb2_grpc.py +24 -0
- sondera/proto/google/protobuf/duration_pb2.py +37 -0
- sondera/proto/google/protobuf/duration_pb2.pyi +14 -0
- sondera/proto/google/protobuf/duration_pb2_grpc.py +24 -0
- sondera/proto/google/protobuf/empty_pb2.py +37 -0
- sondera/proto/google/protobuf/empty_pb2.pyi +9 -0
- sondera/proto/google/protobuf/empty_pb2_grpc.py +24 -0
- sondera/proto/google/protobuf/struct_pb2.py +47 -0
- sondera/proto/google/protobuf/struct_pb2.pyi +49 -0
- sondera/proto/google/protobuf/struct_pb2_grpc.py +24 -0
- sondera/proto/google/protobuf/timestamp_pb2.py +37 -0
- sondera/proto/google/protobuf/timestamp_pb2.pyi +14 -0
- sondera/proto/google/protobuf/timestamp_pb2_grpc.py +24 -0
- sondera/proto/google/protobuf/wrappers_pb2.py +53 -0
- sondera/proto/google/protobuf/wrappers_pb2.pyi +59 -0
- sondera/proto/google/protobuf/wrappers_pb2_grpc.py +24 -0
- sondera/proto/sondera/__init__.py +0 -0
- sondera/proto/sondera/core/__init__.py +0 -0
- sondera/proto/sondera/core/v1/__init__.py +0 -0
- sondera/proto/sondera/core/v1/primitives_pb2.py +88 -0
- sondera/proto/sondera/core/v1/primitives_pb2.pyi +259 -0
- sondera/proto/sondera/core/v1/primitives_pb2_grpc.py +24 -0
- sondera/proto/sondera/harness/__init__.py +0 -0
- sondera/proto/sondera/harness/v1/__init__.py +0 -0
- sondera/proto/sondera/harness/v1/harness_pb2.py +81 -0
- sondera/proto/sondera/harness/v1/harness_pb2.pyi +192 -0
- sondera/proto/sondera/harness/v1/harness_pb2_grpc.py +498 -0
- sondera/py.typed +0 -0
- sondera/settings.py +20 -0
- sondera/strands/__init__.py +5 -0
- sondera/strands/analyze.py +244 -0
- sondera/strands/harness.py +333 -0
- sondera/tui/__init__.py +0 -0
- sondera/tui/app.py +309 -0
- sondera/tui/screens/__init__.py +5 -0
- sondera/tui/screens/adjudication.py +184 -0
- sondera/tui/screens/agent.py +158 -0
- sondera/tui/screens/trajectory.py +158 -0
- sondera/tui/widgets/__init__.py +23 -0
- sondera/tui/widgets/agent_card.py +94 -0
- sondera/tui/widgets/agent_list.py +73 -0
- sondera/tui/widgets/recent_adjudications.py +52 -0
- sondera/tui/widgets/recent_trajectories.py +54 -0
- sondera/tui/widgets/summary.py +57 -0
- sondera/tui/widgets/tool_card.py +33 -0
- sondera/tui/widgets/violation_panel.py +72 -0
- sondera/tui/widgets/violations_list.py +78 -0
- sondera/tui/widgets/violations_summary.py +104 -0
- sondera/types.py +346 -0
- sondera_harness-0.6.0.dist-info/METADATA +323 -0
- sondera_harness-0.6.0.dist-info/RECORD +77 -0
- sondera_harness-0.6.0.dist-info/WHEEL +5 -0
- sondera_harness-0.6.0.dist-info/entry_points.txt +2 -0
- sondera_harness-0.6.0.dist-info/licenses/LICENSE +21 -0
- sondera_harness-0.6.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,890 @@
|
|
|
1
|
+
"""Async GRPC client for the Sondera Harness Service."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from datetime import UTC, datetime
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
import grpc
|
|
8
|
+
from google.protobuf.json_format import MessageToDict
|
|
9
|
+
from google.protobuf.timestamp_pb2 import Timestamp
|
|
10
|
+
|
|
11
|
+
from sondera.exceptions import (
|
|
12
|
+
AuthenticationError,
|
|
13
|
+
ConfigurationError,
|
|
14
|
+
TrajectoryError,
|
|
15
|
+
TrajectoryNotInitializedError,
|
|
16
|
+
)
|
|
17
|
+
from sondera.harness.abc import Harness as AbstractHarness
|
|
18
|
+
from sondera.harness.sondera._grpc import (
|
|
19
|
+
_convert_pb_adjudicated_trajectory_to_sdk,
|
|
20
|
+
_convert_pb_adjudication_record_to_sdk,
|
|
21
|
+
_convert_pb_adjudication_to_sdk,
|
|
22
|
+
_convert_pb_agent_to_sdk,
|
|
23
|
+
_convert_pb_trajectory_to_sdk,
|
|
24
|
+
_convert_sdk_content_to_pb,
|
|
25
|
+
_convert_sdk_role_to_pb,
|
|
26
|
+
_convert_sdk_stage_to_pb,
|
|
27
|
+
_convert_sdk_trajectory_status_to_pb,
|
|
28
|
+
)
|
|
29
|
+
from sondera.proto.sondera.core.v1 import primitives_pb2
|
|
30
|
+
from sondera.proto.sondera.harness.v1 import harness_pb2, harness_pb2_grpc
|
|
31
|
+
from sondera.settings import SETTINGS
|
|
32
|
+
from sondera.types import (
|
|
33
|
+
AdjudicatedTrajectory,
|
|
34
|
+
Adjudication,
|
|
35
|
+
AdjudicationRecord,
|
|
36
|
+
Agent,
|
|
37
|
+
Content,
|
|
38
|
+
Role,
|
|
39
|
+
Stage,
|
|
40
|
+
Trajectory,
|
|
41
|
+
TrajectoryStatus,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class SonderaRemoteHarness(AbstractHarness):
|
|
46
|
+
"""gRPC-based Harness implementation for the Sondera Platform.
|
|
47
|
+
|
|
48
|
+
Example:
|
|
49
|
+
```python
|
|
50
|
+
from sondera.harness import Harness
|
|
51
|
+
from sondera.types import Agent, Stage, Role, PromptContent
|
|
52
|
+
|
|
53
|
+
harness = Harness(
|
|
54
|
+
sondera_harness_endpoint="localhost:50051",
|
|
55
|
+
sondera_api_key="<YOUR_SONDERA_API_KEY>",
|
|
56
|
+
agent=Agent(
|
|
57
|
+
id="my-agent",
|
|
58
|
+
provider_id="my-provider",
|
|
59
|
+
name="My Agent",
|
|
60
|
+
description="An agent with Sondera governance",
|
|
61
|
+
instruction="Be helpful",
|
|
62
|
+
tools=[],
|
|
63
|
+
),
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
await harness.initialize()
|
|
67
|
+
adjudication = await harness.adjudicate(
|
|
68
|
+
Stage.PRE_MODEL,
|
|
69
|
+
Role.USER,
|
|
70
|
+
PromptContent(text="Hello, world!"),
|
|
71
|
+
)
|
|
72
|
+
await harness.finalize()
|
|
73
|
+
```
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
def __init__(
|
|
77
|
+
self,
|
|
78
|
+
*,
|
|
79
|
+
agent: Agent | None = None,
|
|
80
|
+
sondera_harness_endpoint: str = SETTINGS.sondera_harness_endpoint,
|
|
81
|
+
sondera_api_key: str | None = SETTINGS.sondera_api_token,
|
|
82
|
+
sondera_harness_client_secure: bool = SETTINGS.sondera_harness_client_secure,
|
|
83
|
+
sondera_harness_client_options: list[tuple] | None = None,
|
|
84
|
+
):
|
|
85
|
+
"""Initialize the harness.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
agent: The agent to be governed
|
|
89
|
+
sondera_harness_endpoint: The endpoint of the Sondera Harness service
|
|
90
|
+
sondera_api_key: JWT token for authentication (required, must include organization_id claim)
|
|
91
|
+
sondera_harness_client_secure: Whether to use a secure (TLS) connection
|
|
92
|
+
sondera_harness_client_options: Optional gRPC channel options
|
|
93
|
+
|
|
94
|
+
Raises:
|
|
95
|
+
ConfigurationError: If sondera_api_key is None or empty
|
|
96
|
+
|
|
97
|
+
Note:
|
|
98
|
+
The organization_id for multi-tenancy is now derived from the JWT token's organization_id claim.
|
|
99
|
+
Ensure your Clerk JWT template includes: {{org.public_metadata.organization_id}}
|
|
100
|
+
"""
|
|
101
|
+
# Validate sondera_api_key
|
|
102
|
+
if not sondera_api_key:
|
|
103
|
+
raise ConfigurationError(
|
|
104
|
+
"sondera_api_key is required and cannot be None or empty"
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
self._sondera_api_key = sondera_api_key
|
|
108
|
+
self._agent: Agent | None = agent
|
|
109
|
+
self._sondera_harness_endpoint = sondera_harness_endpoint
|
|
110
|
+
self._secure = sondera_harness_client_secure
|
|
111
|
+
self._options = sondera_harness_client_options or []
|
|
112
|
+
# Client connection state
|
|
113
|
+
self._channel: grpc.aio.Channel | None = None
|
|
114
|
+
self._stub: harness_pb2_grpc.HarnessServiceStub | None = None
|
|
115
|
+
|
|
116
|
+
# Current trajectory state
|
|
117
|
+
self._trajectory_id: str | None = None
|
|
118
|
+
|
|
119
|
+
def _get_metadata(self) -> list[tuple[str, str]]:
|
|
120
|
+
"""Build gRPC metadata with JWT auth token.
|
|
121
|
+
|
|
122
|
+
The JWT token includes the organization_id claim, which is extracted by the
|
|
123
|
+
Harness service for tenant isolation.
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
List of metadata tuples to pass to gRPC calls
|
|
127
|
+
"""
|
|
128
|
+
metadata: list[tuple[str, str]] = [
|
|
129
|
+
("authorization", f"Bearer {self._sondera_api_key}")
|
|
130
|
+
]
|
|
131
|
+
return metadata
|
|
132
|
+
|
|
133
|
+
async def initialize(
|
|
134
|
+
self,
|
|
135
|
+
*,
|
|
136
|
+
agent: Agent | None = None,
|
|
137
|
+
) -> None:
|
|
138
|
+
"""Initialize a new trajectory for the current execution."""
|
|
139
|
+
await self._ensure_connected()
|
|
140
|
+
assert self._stub is not None, "Client not connected"
|
|
141
|
+
if agent:
|
|
142
|
+
self._agent = agent
|
|
143
|
+
assert self._agent is not None, (
|
|
144
|
+
"Agent not provided on initialization or in constructor."
|
|
145
|
+
)
|
|
146
|
+
# Get or register an agent
|
|
147
|
+
try:
|
|
148
|
+
await self._get_agent(self._agent.id)
|
|
149
|
+
logging.debug(f"Agent {self._agent.id} already exists")
|
|
150
|
+
except grpc.aio.AioRpcError as e:
|
|
151
|
+
if e.code() == grpc.StatusCode.NOT_FOUND:
|
|
152
|
+
logging.debug(f"Agent {self._agent.id} not found, registering...")
|
|
153
|
+
registered_agent = await self._register_agent(self._agent)
|
|
154
|
+
# Update agent ID to match what the server assigned
|
|
155
|
+
self._agent.id = registered_agent.id
|
|
156
|
+
else:
|
|
157
|
+
raise
|
|
158
|
+
# Create trajectory
|
|
159
|
+
logging.debug(f"Creating trajectory for agent {self._agent.id}...")
|
|
160
|
+
response = await self._create_trajectory(self._agent.id)
|
|
161
|
+
logging.debug(f"Trajectory created for agent {self._agent.id}: {response.id}")
|
|
162
|
+
self._trajectory_id = response.id
|
|
163
|
+
|
|
164
|
+
async def resume(self, trajectory_id: str, *, agent: Agent | None = None) -> None:
|
|
165
|
+
"""Resume an existing trajectory for continued execution.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
trajectory_id: The ID of the trajectory to resume
|
|
169
|
+
agent: Optional agent to use for this trajectory. If provided, overrides
|
|
170
|
+
any agent set during construction.
|
|
171
|
+
|
|
172
|
+
Raises:
|
|
173
|
+
ValueError: If the trajectory doesn't exist or belongs to a different agent
|
|
174
|
+
RuntimeError: If there's already an active trajectory
|
|
175
|
+
"""
|
|
176
|
+
if self._trajectory_id:
|
|
177
|
+
raise RuntimeError(
|
|
178
|
+
f"Already have active trajectory {self._trajectory_id}. Call finalize first."
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
if agent:
|
|
182
|
+
self._agent = agent
|
|
183
|
+
assert self._agent is not None, (
|
|
184
|
+
"Agent not provided on initialization or in constructor."
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
await self._ensure_connected()
|
|
188
|
+
|
|
189
|
+
# Verify the trajectory exists and get its details
|
|
190
|
+
trajectory = await self._get_trajectory(trajectory_id)
|
|
191
|
+
if trajectory is None:
|
|
192
|
+
raise TrajectoryError(f"Trajectory {trajectory_id} not found")
|
|
193
|
+
|
|
194
|
+
self._trajectory_id = trajectory.id
|
|
195
|
+
|
|
196
|
+
# Verify the trajectory belongs to our agent (if we have one)
|
|
197
|
+
if self._agent and trajectory.agent_id != self._agent.id:
|
|
198
|
+
raise TrajectoryError(
|
|
199
|
+
f"Trajectory {trajectory_id} belongs to agent {trajectory.agent_id}, not {self._agent.id}"
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
# Set the trajectory as active
|
|
203
|
+
await self._update_trajectory_status(
|
|
204
|
+
self._trajectory_id, primitives_pb2.TRAJECTORY_STATUS_RUNNING
|
|
205
|
+
)
|
|
206
|
+
self._trajectory_id = trajectory_id
|
|
207
|
+
logging.debug(
|
|
208
|
+
f"Resumed trajectory {trajectory_id} for agent {trajectory.agent_id}"
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
async def finalize(self) -> None:
|
|
212
|
+
"""Finalize the current trajectory and save artifacts."""
|
|
213
|
+
if not self._trajectory_id:
|
|
214
|
+
raise TrajectoryNotInitializedError()
|
|
215
|
+
assert self._stub is not None, "Client not connected"
|
|
216
|
+
# Update trajectory status to completed
|
|
217
|
+
await self._update_trajectory_status(
|
|
218
|
+
self._trajectory_id, primitives_pb2.TRAJECTORY_STATUS_COMPLETED
|
|
219
|
+
)
|
|
220
|
+
# Clear trajectory ID to indicate no active trajectory
|
|
221
|
+
self._trajectory_id = None
|
|
222
|
+
|
|
223
|
+
async def adjudicate(
|
|
224
|
+
self,
|
|
225
|
+
stage: Stage,
|
|
226
|
+
role: Role,
|
|
227
|
+
content: Content,
|
|
228
|
+
) -> Adjudication:
|
|
229
|
+
"""Adjudicate a trajectory step using the policy engine.
|
|
230
|
+
|
|
231
|
+
Args:
|
|
232
|
+
stage: The stage of the step
|
|
233
|
+
role: The role of the step
|
|
234
|
+
content: The content of the step
|
|
235
|
+
|
|
236
|
+
Returns:
|
|
237
|
+
The adjudication result
|
|
238
|
+
"""
|
|
239
|
+
if not self._trajectory_id:
|
|
240
|
+
raise RuntimeError(
|
|
241
|
+
"No active trajectory. Call initialize_trajectory first."
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
await self._ensure_connected()
|
|
245
|
+
|
|
246
|
+
# Convert SDK types to protobuf
|
|
247
|
+
pb_stage = _convert_sdk_stage_to_pb(stage)
|
|
248
|
+
pb_role = _convert_sdk_role_to_pb(role)
|
|
249
|
+
pb_content = _convert_sdk_content_to_pb(content)
|
|
250
|
+
|
|
251
|
+
# Add step and get adjudication
|
|
252
|
+
logging.debug(
|
|
253
|
+
f"Adjudicating (trajectory_id: {self._trajectory_id}): {stage} {role} {content}"
|
|
254
|
+
)
|
|
255
|
+
adjudicated_step = await self._add_trajectory_step(
|
|
256
|
+
self._trajectory_id, pb_stage, pb_role, pb_content
|
|
257
|
+
)
|
|
258
|
+
# Convert protobuf adjudication to SDK type
|
|
259
|
+
adjudication = _convert_pb_adjudication_to_sdk(adjudicated_step.adjudication)
|
|
260
|
+
logging.debug(
|
|
261
|
+
f"Adjudication (trajectory_id:{self._trajectory_id}): {adjudication}"
|
|
262
|
+
)
|
|
263
|
+
return adjudication
|
|
264
|
+
|
|
265
|
+
async def _ensure_connected(self):
|
|
266
|
+
"""Ensure the client is connected."""
|
|
267
|
+
if not self._is_connected():
|
|
268
|
+
await self._connect()
|
|
269
|
+
assert self._stub is not None, "Client not connected"
|
|
270
|
+
|
|
271
|
+
def _is_connected(self) -> bool:
|
|
272
|
+
"""Check if the client is connected."""
|
|
273
|
+
return self._channel is not None and self._stub is not None
|
|
274
|
+
|
|
275
|
+
async def _connect(self):
|
|
276
|
+
"""Establish connection to the gRPC server."""
|
|
277
|
+
if self._secure:
|
|
278
|
+
self._channel = grpc.aio.secure_channel(
|
|
279
|
+
self._sondera_harness_endpoint,
|
|
280
|
+
credentials=grpc.ssl_channel_credentials(),
|
|
281
|
+
options=self._options,
|
|
282
|
+
)
|
|
283
|
+
else:
|
|
284
|
+
self._channel = grpc.aio.insecure_channel(
|
|
285
|
+
self._sondera_harness_endpoint,
|
|
286
|
+
options=self._options,
|
|
287
|
+
)
|
|
288
|
+
self._stub = harness_pb2_grpc.HarnessServiceStub(self._channel)
|
|
289
|
+
|
|
290
|
+
async def _close(self):
|
|
291
|
+
"""Close the gRPC channel."""
|
|
292
|
+
if self._channel:
|
|
293
|
+
await self._channel.close()
|
|
294
|
+
self._channel = None
|
|
295
|
+
self._stub = None
|
|
296
|
+
|
|
297
|
+
async def _get_agent(self, agent_id: str) -> primitives_pb2.Agent:
|
|
298
|
+
"""Get an agent internally.
|
|
299
|
+
|
|
300
|
+
Args:
|
|
301
|
+
agent_id: The agent ID
|
|
302
|
+
|
|
303
|
+
Returns:
|
|
304
|
+
The agent
|
|
305
|
+
|
|
306
|
+
Raises:
|
|
307
|
+
AuthenticationError: If authentication fails
|
|
308
|
+
grpc.RpcError: If other gRPC error occurs
|
|
309
|
+
"""
|
|
310
|
+
request = harness_pb2.GetAgentRequest(agent_id=agent_id)
|
|
311
|
+
await self._ensure_connected()
|
|
312
|
+
assert self._stub is not None, "Client not connected"
|
|
313
|
+
|
|
314
|
+
# Inject organization_id and auth metadata
|
|
315
|
+
metadata = self._get_metadata()
|
|
316
|
+
|
|
317
|
+
try:
|
|
318
|
+
return await self._stub.GetAgent(request, metadata=metadata)
|
|
319
|
+
except grpc.aio.AioRpcError as e:
|
|
320
|
+
# Handle authentication errors
|
|
321
|
+
if e.code() == grpc.StatusCode.UNAUTHENTICATED:
|
|
322
|
+
raise AuthenticationError(
|
|
323
|
+
f"Authentication failed: {e.details()}"
|
|
324
|
+
) from e
|
|
325
|
+
raise
|
|
326
|
+
|
|
327
|
+
async def _register_agent(self, agent: Agent) -> primitives_pb2.Agent:
|
|
328
|
+
"""Register an agent internally.
|
|
329
|
+
|
|
330
|
+
Args:
|
|
331
|
+
agent: The agent to register
|
|
332
|
+
|
|
333
|
+
Returns:
|
|
334
|
+
The registered agent
|
|
335
|
+
|
|
336
|
+
Raises:
|
|
337
|
+
AuthenticationError: If authentication fails
|
|
338
|
+
grpc.RpcError: If other gRPC error occurs
|
|
339
|
+
"""
|
|
340
|
+
# Convert SDK agent to protobuf tools
|
|
341
|
+
pb_tools = []
|
|
342
|
+
for tool in agent.tools:
|
|
343
|
+
pb_params = []
|
|
344
|
+
for param in tool.parameters:
|
|
345
|
+
pb_params.append(
|
|
346
|
+
primitives_pb2.Parameter(
|
|
347
|
+
name=param.name, description=param.description, type=param.type
|
|
348
|
+
)
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
pb_tool = primitives_pb2.Tool(
|
|
352
|
+
name=tool.name,
|
|
353
|
+
description=tool.description,
|
|
354
|
+
parameters=pb_params,
|
|
355
|
+
response=tool.response,
|
|
356
|
+
)
|
|
357
|
+
pb_tools.append(pb_tool)
|
|
358
|
+
|
|
359
|
+
request = harness_pb2.RegisterAgentRequest(
|
|
360
|
+
provider_id=agent.provider_id,
|
|
361
|
+
name=agent.name,
|
|
362
|
+
description=agent.description,
|
|
363
|
+
instruction=agent.instruction,
|
|
364
|
+
tools=pb_tools,
|
|
365
|
+
)
|
|
366
|
+
await self._ensure_connected()
|
|
367
|
+
assert self._stub is not None, "Client not connected"
|
|
368
|
+
|
|
369
|
+
# Inject organization_id and auth metadata
|
|
370
|
+
metadata = self._get_metadata()
|
|
371
|
+
|
|
372
|
+
try:
|
|
373
|
+
response = await self._stub.RegisterAgent(request, metadata=metadata)
|
|
374
|
+
return response.agent
|
|
375
|
+
except grpc.aio.AioRpcError as e:
|
|
376
|
+
# Handle authentication errors
|
|
377
|
+
if e.code() == grpc.StatusCode.UNAUTHENTICATED:
|
|
378
|
+
raise AuthenticationError(
|
|
379
|
+
f"Authentication failed: {e.details()}"
|
|
380
|
+
) from e
|
|
381
|
+
logging.error(f"Failed to register agent: {e.code()} - {e.details()}")
|
|
382
|
+
raise
|
|
383
|
+
|
|
384
|
+
async def _create_trajectory(self, agent_id: str) -> primitives_pb2.Trajectory:
|
|
385
|
+
"""Create a trajectory internally.
|
|
386
|
+
|
|
387
|
+
Args:
|
|
388
|
+
agent_id: The agent ID
|
|
389
|
+
|
|
390
|
+
Returns:
|
|
391
|
+
The created trajectory
|
|
392
|
+
|
|
393
|
+
Raises:
|
|
394
|
+
AuthenticationError: If authentication fails
|
|
395
|
+
grpc.RpcError: If other gRPC error occurs
|
|
396
|
+
"""
|
|
397
|
+
request = harness_pb2.CreateTrajectoryRequest(agent_id=agent_id)
|
|
398
|
+
await self._ensure_connected()
|
|
399
|
+
assert self._stub is not None, "Client not connected"
|
|
400
|
+
|
|
401
|
+
# Inject organization_id and auth metadata
|
|
402
|
+
metadata = self._get_metadata()
|
|
403
|
+
|
|
404
|
+
try:
|
|
405
|
+
response = await self._stub.CreateTrajectory(request, metadata=metadata)
|
|
406
|
+
return response.trajectory
|
|
407
|
+
except grpc.aio.AioRpcError as e:
|
|
408
|
+
# Handle authentication errors
|
|
409
|
+
if e.code() == grpc.StatusCode.UNAUTHENTICATED:
|
|
410
|
+
raise AuthenticationError(
|
|
411
|
+
f"Authentication failed: {e.details()}"
|
|
412
|
+
) from e
|
|
413
|
+
logging.error(
|
|
414
|
+
f"Failed to create trajectory for agent {agent_id}: {e.code()} - {e.details()}"
|
|
415
|
+
)
|
|
416
|
+
raise
|
|
417
|
+
|
|
418
|
+
async def _add_trajectory_step(
|
|
419
|
+
self,
|
|
420
|
+
trajectory_id: str,
|
|
421
|
+
stage: primitives_pb2.Stage,
|
|
422
|
+
role: primitives_pb2.Role,
|
|
423
|
+
content: primitives_pb2.Content,
|
|
424
|
+
) -> primitives_pb2.AdjudicatedStep:
|
|
425
|
+
"""Add a trajectory step internally.
|
|
426
|
+
|
|
427
|
+
Args:
|
|
428
|
+
trajectory_id: The trajectory ID
|
|
429
|
+
stage: The step stage
|
|
430
|
+
role: The step role
|
|
431
|
+
content: The step content
|
|
432
|
+
|
|
433
|
+
Returns:
|
|
434
|
+
The adjudicated step
|
|
435
|
+
|
|
436
|
+
Raises:
|
|
437
|
+
AuthenticationError: If authentication fails
|
|
438
|
+
grpc.RpcError: If other gRPC error occurs
|
|
439
|
+
"""
|
|
440
|
+
request = harness_pb2.AddTrajectoryStepRequest(
|
|
441
|
+
trajectory_id=trajectory_id, stage=stage, role=role, content=content
|
|
442
|
+
)
|
|
443
|
+
await self._ensure_connected()
|
|
444
|
+
assert self._stub is not None, "Client not connected"
|
|
445
|
+
|
|
446
|
+
# Inject organization_id and auth metadata
|
|
447
|
+
metadata = self._get_metadata()
|
|
448
|
+
|
|
449
|
+
try:
|
|
450
|
+
response = await self._stub.AddTrajectoryStep(request, metadata=metadata)
|
|
451
|
+
return response.adjudicated_step
|
|
452
|
+
except grpc.aio.AioRpcError as e:
|
|
453
|
+
# Handle authentication errors
|
|
454
|
+
if e.code() == grpc.StatusCode.UNAUTHENTICATED:
|
|
455
|
+
raise AuthenticationError(
|
|
456
|
+
f"Authentication failed: {e.details()}"
|
|
457
|
+
) from e
|
|
458
|
+
logging.error(
|
|
459
|
+
f"Failed to add step to trajectory {trajectory_id}: {e.code()} - {e.details()}"
|
|
460
|
+
)
|
|
461
|
+
raise
|
|
462
|
+
|
|
463
|
+
async def _update_trajectory_status(
|
|
464
|
+
self, trajectory_id: str, status: primitives_pb2.TrajectoryStatus
|
|
465
|
+
) -> primitives_pb2.Trajectory:
|
|
466
|
+
"""Update trajectory status internally.
|
|
467
|
+
|
|
468
|
+
Args:
|
|
469
|
+
trajectory_id: The trajectory ID
|
|
470
|
+
status: The new status
|
|
471
|
+
|
|
472
|
+
Returns:
|
|
473
|
+
The updated trajectory
|
|
474
|
+
|
|
475
|
+
Raises:
|
|
476
|
+
AuthenticationError: If authentication fails
|
|
477
|
+
grpc.RpcError: If other gRPC error occurs
|
|
478
|
+
"""
|
|
479
|
+
request = harness_pb2.UpdateTrajectoryStatusRequest(
|
|
480
|
+
trajectory_id=trajectory_id, status=status
|
|
481
|
+
)
|
|
482
|
+
await self._ensure_connected()
|
|
483
|
+
assert self._stub is not None, "Client not connected"
|
|
484
|
+
|
|
485
|
+
# Inject organization_id and auth metadata
|
|
486
|
+
metadata = self._get_metadata()
|
|
487
|
+
|
|
488
|
+
try:
|
|
489
|
+
response = await self._stub.UpdateTrajectoryStatus(
|
|
490
|
+
request, metadata=metadata
|
|
491
|
+
)
|
|
492
|
+
return response.trajectory
|
|
493
|
+
except grpc.aio.AioRpcError as e:
|
|
494
|
+
# Handle authentication errors
|
|
495
|
+
if e.code() == grpc.StatusCode.UNAUTHENTICATED:
|
|
496
|
+
raise AuthenticationError(
|
|
497
|
+
f"Authentication failed: {e.details()}"
|
|
498
|
+
) from e
|
|
499
|
+
logging.error(
|
|
500
|
+
f"Failed to update trajectory {trajectory_id} status: {e.code()} - {e.details()}"
|
|
501
|
+
)
|
|
502
|
+
raise
|
|
503
|
+
|
|
504
|
+
async def _list_trajectories(
|
|
505
|
+
self,
|
|
506
|
+
agent_id: str | None = None,
|
|
507
|
+
status: primitives_pb2.TrajectoryStatus | None = None,
|
|
508
|
+
page_size: int = 100,
|
|
509
|
+
page_token: str = "",
|
|
510
|
+
) -> list[primitives_pb2.Trajectory]:
|
|
511
|
+
"""List trajectories internally."""
|
|
512
|
+
request = harness_pb2.ListTrajectoriesRequest(
|
|
513
|
+
agent_id=agent_id or "",
|
|
514
|
+
page_size=page_size,
|
|
515
|
+
page_token=page_token,
|
|
516
|
+
)
|
|
517
|
+
if status is not None:
|
|
518
|
+
request.status = status
|
|
519
|
+
await self._ensure_connected()
|
|
520
|
+
assert self._stub is not None, "Client not connected"
|
|
521
|
+
|
|
522
|
+
# Inject organization_id and auth metadata
|
|
523
|
+
metadata = self._get_metadata()
|
|
524
|
+
|
|
525
|
+
try:
|
|
526
|
+
response = await self._stub.ListTrajectories(request, metadata=metadata)
|
|
527
|
+
return list(response.trajectories)
|
|
528
|
+
except grpc.aio.AioRpcError as e:
|
|
529
|
+
logging.error(f"Failed to list trajectories: {e.code()} - {e.details()}")
|
|
530
|
+
raise
|
|
531
|
+
|
|
532
|
+
async def _get_adjudicated_trajectory(
|
|
533
|
+
self, trajectory_id: str
|
|
534
|
+
) -> harness_pb2.GetTrajectoryResponse | None:
|
|
535
|
+
"""Get a trajectory internally. Returns None if not found (fail-closed)."""
|
|
536
|
+
request = harness_pb2.GetTrajectoryRequest(
|
|
537
|
+
trajectory_id=trajectory_id,
|
|
538
|
+
)
|
|
539
|
+
await self._ensure_connected()
|
|
540
|
+
assert self._stub is not None, "Client not connected"
|
|
541
|
+
|
|
542
|
+
# Inject organization_id and auth metadata
|
|
543
|
+
metadata = self._get_metadata()
|
|
544
|
+
|
|
545
|
+
try:
|
|
546
|
+
response = await self._stub.GetTrajectory(request, metadata=metadata)
|
|
547
|
+
return response
|
|
548
|
+
except grpc.aio.AioRpcError as e:
|
|
549
|
+
if e.code() == grpc.StatusCode.NOT_FOUND:
|
|
550
|
+
return None
|
|
551
|
+
logging.error(
|
|
552
|
+
f"Failed to get adjudicated trajectory {trajectory_id}: {e.code()} - {e.details()}"
|
|
553
|
+
)
|
|
554
|
+
raise
|
|
555
|
+
|
|
556
|
+
async def _get_trajectory(
|
|
557
|
+
self, trajectory_id: str
|
|
558
|
+
) -> primitives_pb2.Trajectory | None:
|
|
559
|
+
"""Get a trajectory internally. Returns None if not found (fail-closed)."""
|
|
560
|
+
request = harness_pb2.GetTrajectoryRequest(
|
|
561
|
+
trajectory_id=trajectory_id,
|
|
562
|
+
)
|
|
563
|
+
await self._ensure_connected()
|
|
564
|
+
assert self._stub is not None, "Client not connected"
|
|
565
|
+
|
|
566
|
+
# Inject organization_id and auth metadata
|
|
567
|
+
metadata = self._get_metadata()
|
|
568
|
+
|
|
569
|
+
try:
|
|
570
|
+
response = await self._stub.GetTrajectory(request, metadata=metadata)
|
|
571
|
+
return response.trajectory
|
|
572
|
+
except grpc.aio.AioRpcError as e:
|
|
573
|
+
if e.code() == grpc.StatusCode.NOT_FOUND:
|
|
574
|
+
return None
|
|
575
|
+
logging.error(
|
|
576
|
+
f"Failed to get trajectory {trajectory_id}: {e.code()} - {e.details()}"
|
|
577
|
+
)
|
|
578
|
+
raise
|
|
579
|
+
|
|
580
|
+
async def _list_trajectory_steps(
|
|
581
|
+
self, trajectory_id: str
|
|
582
|
+
) -> list[primitives_pb2.AdjudicatedStep]:
|
|
583
|
+
"""List trajectory steps internally."""
|
|
584
|
+
request = harness_pb2.GetTrajectoryRequest(
|
|
585
|
+
trajectory_id=trajectory_id,
|
|
586
|
+
)
|
|
587
|
+
await self._ensure_connected()
|
|
588
|
+
assert self._stub is not None, "Client not connected"
|
|
589
|
+
|
|
590
|
+
# Inject organization_id and auth metadata
|
|
591
|
+
metadata = self._get_metadata()
|
|
592
|
+
|
|
593
|
+
try:
|
|
594
|
+
response = await self._stub.GetTrajectory(request, metadata=metadata)
|
|
595
|
+
return list(response.steps)
|
|
596
|
+
except grpc.aio.AioRpcError as e:
|
|
597
|
+
if e.code() == grpc.StatusCode.NOT_FOUND:
|
|
598
|
+
return []
|
|
599
|
+
logging.error(
|
|
600
|
+
f"Failed to list trajectory steps for {trajectory_id}: {e.code()} - {e.details()}"
|
|
601
|
+
)
|
|
602
|
+
raise
|
|
603
|
+
|
|
604
|
+
async def _list_agents(
|
|
605
|
+
self,
|
|
606
|
+
provider_id: str | None = None,
|
|
607
|
+
page_size: int = 50,
|
|
608
|
+
page_token: str = "",
|
|
609
|
+
) -> tuple[list[primitives_pb2.Agent], str]:
|
|
610
|
+
"""List agents internally.
|
|
611
|
+
|
|
612
|
+
Args:
|
|
613
|
+
provider_id: Optional provider ID to filter agents
|
|
614
|
+
page_size: Maximum number of agents to return
|
|
615
|
+
page_token: Token for pagination
|
|
616
|
+
|
|
617
|
+
Returns:
|
|
618
|
+
Tuple of (list of protobuf agents, next page token)
|
|
619
|
+
|
|
620
|
+
Raises:
|
|
621
|
+
AuthenticationError: If authentication fails
|
|
622
|
+
grpc.RpcError: If other gRPC error occurs
|
|
623
|
+
"""
|
|
624
|
+
request = harness_pb2.ListAgentsRequest(
|
|
625
|
+
page_size=page_size,
|
|
626
|
+
page_token=page_token,
|
|
627
|
+
)
|
|
628
|
+
if provider_id is not None:
|
|
629
|
+
request.provider_id = provider_id
|
|
630
|
+
|
|
631
|
+
await self._ensure_connected()
|
|
632
|
+
assert self._stub is not None, "Client not connected"
|
|
633
|
+
|
|
634
|
+
# Inject organization_id and auth metadata
|
|
635
|
+
metadata = self._get_metadata()
|
|
636
|
+
|
|
637
|
+
try:
|
|
638
|
+
response = await self._stub.ListAgents(request, metadata=metadata)
|
|
639
|
+
return list(response.agents), response.next_page_token
|
|
640
|
+
except grpc.aio.AioRpcError as e:
|
|
641
|
+
# Handle authentication errors
|
|
642
|
+
if e.code() == grpc.StatusCode.UNAUTHENTICATED:
|
|
643
|
+
raise AuthenticationError(
|
|
644
|
+
f"Authentication failed: {e.details()}"
|
|
645
|
+
) from e
|
|
646
|
+
logging.error(f"Failed to list agents: {e.code()} - {e.details()}")
|
|
647
|
+
raise
|
|
648
|
+
|
|
649
|
+
async def list_agents(
|
|
650
|
+
self,
|
|
651
|
+
provider_id: str | None = None,
|
|
652
|
+
page_size: int = 50,
|
|
653
|
+
page_token: str = "",
|
|
654
|
+
) -> list[Agent]:
|
|
655
|
+
"""List registered agents.
|
|
656
|
+
|
|
657
|
+
Args:
|
|
658
|
+
provider_id: Optional provider ID to filter agents
|
|
659
|
+
page_size: Maximum number of agents to return per page
|
|
660
|
+
page_token: Token for pagination (empty string for first page)
|
|
661
|
+
|
|
662
|
+
Returns:
|
|
663
|
+
List of Agent objects
|
|
664
|
+
|
|
665
|
+
Raises:
|
|
666
|
+
AuthenticationError: If authentication fails
|
|
667
|
+
grpc.RpcError: If other gRPC error occurs
|
|
668
|
+
"""
|
|
669
|
+
pb_agents, _ = await self._list_agents(
|
|
670
|
+
provider_id=provider_id,
|
|
671
|
+
page_size=page_size,
|
|
672
|
+
page_token=page_token,
|
|
673
|
+
)
|
|
674
|
+
return [_convert_pb_agent_to_sdk(pb_agent) for pb_agent in pb_agents]
|
|
675
|
+
|
|
676
|
+
async def get_trajectory(self, trajectory_id: str) -> AdjudicatedTrajectory | None:
|
|
677
|
+
"""Get a trajectory by ID.
|
|
678
|
+
|
|
679
|
+
Args:
|
|
680
|
+
trajectory_id: The unique identifier of the trajectory
|
|
681
|
+
|
|
682
|
+
Returns:
|
|
683
|
+
Trajectory object if found, None otherwise
|
|
684
|
+
|
|
685
|
+
Raises:
|
|
686
|
+
grpc.RpcError: If gRPC error occurs (other than NOT_FOUND)
|
|
687
|
+
"""
|
|
688
|
+
pb_adjudicated_trajectory = await self._get_adjudicated_trajectory(
|
|
689
|
+
trajectory_id
|
|
690
|
+
)
|
|
691
|
+
if pb_adjudicated_trajectory is None:
|
|
692
|
+
return None
|
|
693
|
+
return _convert_pb_adjudicated_trajectory_to_sdk(pb_adjudicated_trajectory)
|
|
694
|
+
|
|
695
|
+
async def list_trajectories(
|
|
696
|
+
self,
|
|
697
|
+
agent_id: str,
|
|
698
|
+
status: TrajectoryStatus | None = None,
|
|
699
|
+
page_size: int = 50,
|
|
700
|
+
page_token: str = "",
|
|
701
|
+
) -> list[Trajectory]:
|
|
702
|
+
"""List trajectories for an agent.
|
|
703
|
+
|
|
704
|
+
Args:
|
|
705
|
+
agent_id: The agent ID to filter trajectories
|
|
706
|
+
status: Optional status to filter trajectories
|
|
707
|
+
page_size: Maximum number of trajectories to return per page
|
|
708
|
+
page_token: Token for pagination (empty string for first page)
|
|
709
|
+
|
|
710
|
+
Returns:
|
|
711
|
+
List of Trajectory objects
|
|
712
|
+
|
|
713
|
+
Raises:
|
|
714
|
+
grpc.RpcError: If gRPC error occurs
|
|
715
|
+
"""
|
|
716
|
+
pb_status = _convert_sdk_trajectory_status_to_pb(status) if status else None
|
|
717
|
+
pb_trajectories = await self._list_trajectories(
|
|
718
|
+
agent_id=agent_id,
|
|
719
|
+
status=pb_status,
|
|
720
|
+
page_size=page_size,
|
|
721
|
+
page_token=page_token,
|
|
722
|
+
)
|
|
723
|
+
return [_convert_pb_trajectory_to_sdk(pb_traj) for pb_traj in pb_trajectories]
|
|
724
|
+
|
|
725
|
+
async def analyze_trajectories(
|
|
726
|
+
self,
|
|
727
|
+
agent_id: str,
|
|
728
|
+
start_time: "datetime | None" = None,
|
|
729
|
+
end_time: "datetime | None" = None,
|
|
730
|
+
analytics: list[str] | None = None,
|
|
731
|
+
) -> dict[str, Any]:
|
|
732
|
+
"""Analyze trajectories for an agent (AIP-136 custom method).
|
|
733
|
+
|
|
734
|
+
Args:
|
|
735
|
+
agent_id: The agent ID to analyze trajectories for
|
|
736
|
+
start_time: Optional start time filter (inclusive). Only count trajectories
|
|
737
|
+
created at or after this time.
|
|
738
|
+
end_time: Optional end time filter (inclusive). Only count trajectories
|
|
739
|
+
created at or before this time.
|
|
740
|
+
analytics: List of analytics to compute. Empty or None means all available analytics.
|
|
741
|
+
Available analytics:
|
|
742
|
+
- "trajectory_count": Total number of trajectories for the agent
|
|
743
|
+
|
|
744
|
+
Returns:
|
|
745
|
+
Dictionary containing:
|
|
746
|
+
- "analytics": Dict of computed analytics (keys are analytic names)
|
|
747
|
+
- "trajectory_count": Total number of trajectories analyzed
|
|
748
|
+
- "computed_at": Timestamp when analytics were computed (datetime)
|
|
749
|
+
|
|
750
|
+
Raises:
|
|
751
|
+
grpc.RpcError: If gRPC error occurs
|
|
752
|
+
|
|
753
|
+
Example:
|
|
754
|
+
```python
|
|
755
|
+
from datetime import datetime, timedelta, timezone
|
|
756
|
+
|
|
757
|
+
# Get all trajectory analytics
|
|
758
|
+
result = await harness.analyze_trajectories(
|
|
759
|
+
agent_id="my-agent",
|
|
760
|
+
analytics=["trajectory_count"],
|
|
761
|
+
)
|
|
762
|
+
print(f"Total trajectories: {result['analytics']['trajectory_count']['total']}")
|
|
763
|
+
|
|
764
|
+
# Get trajectories from the last 24 hours
|
|
765
|
+
result = await harness.analyze_trajectories(
|
|
766
|
+
agent_id="my-agent",
|
|
767
|
+
start_time=datetime.now(timezone.utc) - timedelta(hours=24),
|
|
768
|
+
)
|
|
769
|
+
```
|
|
770
|
+
"""
|
|
771
|
+
request = harness_pb2.AnalyzeTrajectoriesRequest(
|
|
772
|
+
agent_id=agent_id,
|
|
773
|
+
analytics=analytics or [],
|
|
774
|
+
)
|
|
775
|
+
|
|
776
|
+
# Convert datetime to protobuf Timestamp if provided
|
|
777
|
+
if start_time is not None:
|
|
778
|
+
ts = Timestamp()
|
|
779
|
+
ts.FromDatetime(start_time)
|
|
780
|
+
request.start_time.CopyFrom(ts)
|
|
781
|
+
|
|
782
|
+
if end_time is not None:
|
|
783
|
+
ts = Timestamp()
|
|
784
|
+
ts.FromDatetime(end_time)
|
|
785
|
+
request.end_time.CopyFrom(ts)
|
|
786
|
+
|
|
787
|
+
await self._ensure_connected()
|
|
788
|
+
assert self._stub is not None, "Client not connected"
|
|
789
|
+
|
|
790
|
+
# Inject organization_id and auth metadata
|
|
791
|
+
metadata = self._get_metadata()
|
|
792
|
+
|
|
793
|
+
try:
|
|
794
|
+
response = await self._stub.AnalyzeTrajectories(request, metadata=metadata)
|
|
795
|
+
|
|
796
|
+
# Convert protobuf Struct to Python dict
|
|
797
|
+
analytics_dict = {}
|
|
798
|
+
if response.analytics:
|
|
799
|
+
analytics_dict = MessageToDict(
|
|
800
|
+
response.analytics, preserving_proto_field_name=True
|
|
801
|
+
)
|
|
802
|
+
|
|
803
|
+
# Convert computed_at timestamp
|
|
804
|
+
computed_at = (
|
|
805
|
+
datetime.fromtimestamp(response.computed_at.seconds, tz=UTC)
|
|
806
|
+
if response.computed_at
|
|
807
|
+
else datetime.now(tz=UTC)
|
|
808
|
+
)
|
|
809
|
+
|
|
810
|
+
return {
|
|
811
|
+
"analytics": analytics_dict,
|
|
812
|
+
"trajectory_count": response.trajectory_count,
|
|
813
|
+
"computed_at": computed_at,
|
|
814
|
+
}
|
|
815
|
+
except grpc.aio.AioRpcError as e:
|
|
816
|
+
logging.error(
|
|
817
|
+
f"Failed to analyze trajectories for agent {agent_id}: {e.code()} - {e.details()}"
|
|
818
|
+
)
|
|
819
|
+
raise
|
|
820
|
+
|
|
821
|
+
async def list_adjudications(
|
|
822
|
+
self,
|
|
823
|
+
agent_id: str | None = None,
|
|
824
|
+
page_size: int = 50,
|
|
825
|
+
page_token: str = "",
|
|
826
|
+
) -> tuple[list[AdjudicationRecord], str]:
|
|
827
|
+
"""List adjudication records with optional agent filtering.
|
|
828
|
+
|
|
829
|
+
This method retrieves adjudication records (policy decisions) that have
|
|
830
|
+
occurred during agent execution. Results can be filtered by agent and
|
|
831
|
+
are paginated.
|
|
832
|
+
|
|
833
|
+
Args:
|
|
834
|
+
agent_id: Optional agent ID to filter adjudications. If None, returns
|
|
835
|
+
adjudications for all agents.
|
|
836
|
+
page_size: Maximum number of records to return per page (default: 50)
|
|
837
|
+
page_token: Token for pagination (empty string for first page)
|
|
838
|
+
|
|
839
|
+
Returns:
|
|
840
|
+
Tuple of (list of AdjudicationRecord objects, next page token).
|
|
841
|
+
The next page token will be empty if there are no more results.
|
|
842
|
+
|
|
843
|
+
Raises:
|
|
844
|
+
AuthenticationError: If authentication fails
|
|
845
|
+
grpc.RpcError: If other gRPC error occurs
|
|
846
|
+
|
|
847
|
+
Example:
|
|
848
|
+
```python
|
|
849
|
+
# List all adjudications
|
|
850
|
+
records, next_token = await harness.list_adjudications()
|
|
851
|
+
|
|
852
|
+
# List adjudications for a specific agent
|
|
853
|
+
records, next_token = await harness.list_adjudications(agent_id="my-agent")
|
|
854
|
+
|
|
855
|
+
# Paginate through results
|
|
856
|
+
all_records = []
|
|
857
|
+
token = ""
|
|
858
|
+
while True:
|
|
859
|
+
records, token = await harness.list_adjudications(page_token=token)
|
|
860
|
+
all_records.extend(records)
|
|
861
|
+
if not token:
|
|
862
|
+
break
|
|
863
|
+
```
|
|
864
|
+
"""
|
|
865
|
+
request = harness_pb2.ListAdjudicationsRequest(
|
|
866
|
+
page_size=page_size,
|
|
867
|
+
page_token=page_token,
|
|
868
|
+
)
|
|
869
|
+
if agent_id is not None:
|
|
870
|
+
request.agent_id = agent_id
|
|
871
|
+
|
|
872
|
+
await self._ensure_connected()
|
|
873
|
+
assert self._stub is not None, "Client not connected"
|
|
874
|
+
|
|
875
|
+
metadata = self._get_metadata()
|
|
876
|
+
|
|
877
|
+
try:
|
|
878
|
+
response = await self._stub.ListAdjudications(request, metadata=metadata)
|
|
879
|
+
records = [
|
|
880
|
+
_convert_pb_adjudication_record_to_sdk(pb_record)
|
|
881
|
+
for pb_record in response.adjudications
|
|
882
|
+
]
|
|
883
|
+
return records, response.next_page_token
|
|
884
|
+
except grpc.aio.AioRpcError as e:
|
|
885
|
+
if e.code() == grpc.StatusCode.UNAUTHENTICATED:
|
|
886
|
+
raise AuthenticationError(
|
|
887
|
+
f"Authentication failed: {e.details()}"
|
|
888
|
+
) from e
|
|
889
|
+
logging.error(f"Failed to list adjudications: {e.code()} - {e.details()}")
|
|
890
|
+
raise
|