openbox-langgraph-sdk-python 1.0.0__py3-none-any.whl → 1.1.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.
- openbox_langgraph/__init__.py +3 -1
- openbox_langgraph/client.py +105 -20
- openbox_langgraph/config.py +55 -1
- openbox_langgraph/core_adapter.py +8 -3
- openbox_langgraph/core_runtime.py +7 -1
- openbox_langgraph/errors.py +30 -1
- openbox_langgraph/identity.py +14 -0
- openbox_langgraph/langgraph_handler.py +14 -0
- openbox_langgraph/langgraph_hook_runtime.py +9 -0
- {openbox_langgraph_sdk_python-1.0.0.dist-info → openbox_langgraph_sdk_python-1.1.0.dist-info}/METADATA +50 -8
- openbox_langgraph_sdk_python-1.1.0.dist-info/RECORD +23 -0
- {openbox_langgraph_sdk_python-1.0.0.dist-info → openbox_langgraph_sdk_python-1.1.0.dist-info}/WHEEL +1 -1
- openbox_langgraph_sdk_python-1.0.0.dist-info/RECORD +0 -23
- {openbox_langgraph_sdk_python-1.0.0.dist-info → openbox_langgraph_sdk_python-1.1.0.dist-info}/licenses/LICENSE +0 -0
openbox_langgraph/__init__.py
CHANGED
|
@@ -17,7 +17,7 @@ Example:
|
|
|
17
17
|
... )
|
|
18
18
|
"""
|
|
19
19
|
|
|
20
|
-
__version__ = "1.
|
|
20
|
+
__version__ = "1.1.0"
|
|
21
21
|
|
|
22
22
|
from openbox_langgraph.client import GovernanceClient, build_auth_headers
|
|
23
23
|
from openbox_langgraph.config import (
|
|
@@ -38,6 +38,7 @@ from openbox_langgraph.errors import (
|
|
|
38
38
|
OpenBoxError,
|
|
39
39
|
OpenBoxInsecureURLError,
|
|
40
40
|
OpenBoxNetworkError,
|
|
41
|
+
OpenBoxSigningError,
|
|
41
42
|
)
|
|
42
43
|
from openbox_langgraph.hitl import poll_until_decision
|
|
43
44
|
from openbox_langgraph.identity import (
|
|
@@ -112,6 +113,7 @@ __all__ = [
|
|
|
112
113
|
"OpenBoxLangGraphHandler",
|
|
113
114
|
"OpenBoxLangGraphHandlerOptions",
|
|
114
115
|
"OpenBoxNetworkError",
|
|
116
|
+
"OpenBoxSigningError",
|
|
115
117
|
"Verdict",
|
|
116
118
|
"VerdictContext",
|
|
117
119
|
"WorkflowEventType",
|
openbox_langgraph/client.py
CHANGED
|
@@ -13,14 +13,20 @@ from openbox_core.contracts.results import EvaluationResult
|
|
|
13
13
|
from openbox_core.contracts.results import Verdict as _CoreVerdict
|
|
14
14
|
from openbox_core.errors import ContractError as _CoreContractError
|
|
15
15
|
from openbox_core.errors import GovernanceAPIError as _CoreGovernanceAPIError
|
|
16
|
-
from openbox_core.errors import
|
|
16
|
+
from openbox_core.errors import OpenBoxConfigError as _CoreOpenBoxConfigError
|
|
17
17
|
|
|
18
18
|
from openbox_langgraph.core_events import to_envelope
|
|
19
|
-
from openbox_langgraph.errors import
|
|
19
|
+
from openbox_langgraph.errors import (
|
|
20
|
+
OpenBoxConfigError,
|
|
21
|
+
OpenBoxError,
|
|
22
|
+
OpenBoxNetworkError,
|
|
23
|
+
_raise_core_error,
|
|
24
|
+
)
|
|
20
25
|
from openbox_langgraph.identity import (
|
|
21
26
|
AgentIdentityConfig,
|
|
22
27
|
create_agent_identity_headers,
|
|
23
28
|
parse_optional_agent_identity_config,
|
|
29
|
+
parse_optional_workload_private_key,
|
|
24
30
|
)
|
|
25
31
|
from openbox_langgraph.types import (
|
|
26
32
|
ApprovalResponse,
|
|
@@ -32,9 +38,10 @@ from openbox_langgraph.types import (
|
|
|
32
38
|
)
|
|
33
39
|
|
|
34
40
|
if TYPE_CHECKING:
|
|
41
|
+
from openbox_core.client import EvaluationClient
|
|
35
42
|
from openbox_core.gate import GovernanceGate
|
|
36
43
|
|
|
37
|
-
_SDK_PACKAGE_VERSION = "1.
|
|
44
|
+
_SDK_PACKAGE_VERSION = "1.1.0"
|
|
38
45
|
_SDK_IDENTIFIER = f"openbox-langgraph-python-v{_SDK_PACKAGE_VERSION}"
|
|
39
46
|
|
|
40
47
|
|
|
@@ -114,9 +121,11 @@ async def _gate_evaluate(
|
|
|
114
121
|
independent of `on_api_error`. Enforcing fail_closed here would let an
|
|
115
122
|
SDK-side mapping defect block a user's graph for a reason their OWN policy
|
|
116
123
|
never produced — strictly worse than dropping one governance event.
|
|
117
|
-
-
|
|
118
|
-
this SDK's
|
|
119
|
-
|
|
124
|
+
- Base authentication, signing, and configuration errors always propagate
|
|
125
|
+
through this SDK's public errors, including under fail_open.
|
|
126
|
+
- `GovernanceAPIError` / `OpenBoxNetworkError` -> this SDK's
|
|
127
|
+
`OpenBoxNetworkError`. Bootstrap/token-exchange failures must propagate;
|
|
128
|
+
the base client applies fail_open only to ordinary governance transport errors.
|
|
120
129
|
- Any OTHER exception (e.g. a malformed Core 200 body the base parser
|
|
121
130
|
cannot decode) is a transport-shaped fault, NOT a governance verdict:
|
|
122
131
|
routed through `_network_fallback_result` so fail_open returns `None`
|
|
@@ -127,8 +136,8 @@ async def _gate_evaluate(
|
|
|
127
136
|
result = await gate.aevaluate(to_envelope(event))
|
|
128
137
|
except _CoreContractError:
|
|
129
138
|
return None
|
|
130
|
-
except (_CoreGovernanceAPIError,
|
|
131
|
-
|
|
139
|
+
except (_CoreGovernanceAPIError, _CoreOpenBoxConfigError) as e:
|
|
140
|
+
_raise_core_error(e)
|
|
132
141
|
except Exception as e:
|
|
133
142
|
return _network_fallback_result(on_api_error, f"Governance gate error: {e}")
|
|
134
143
|
return _collapse_client_synthesized_fallback(result)
|
|
@@ -144,7 +153,8 @@ def build_auth_headers(
|
|
|
144
153
|
) -> dict[str, str]:
|
|
145
154
|
"""Build standard auth headers for governance API calls.
|
|
146
155
|
|
|
147
|
-
|
|
156
|
+
Compatibility helper for API-key/DID requests. Workload headers are composed
|
|
157
|
+
by the base SDK after bootstrap and token exchange.
|
|
148
158
|
"""
|
|
149
159
|
headers = {
|
|
150
160
|
"Authorization": f"Bearer {api_key}",
|
|
@@ -194,6 +204,8 @@ class GovernanceClient:
|
|
|
194
204
|
agent_did: str | None = None,
|
|
195
205
|
agent_private_key: str | None = None,
|
|
196
206
|
gate: GovernanceGate | None = None,
|
|
207
|
+
workload_private_key: str | None = None,
|
|
208
|
+
core_client: EvaluationClient | None = None,
|
|
197
209
|
) -> None:
|
|
198
210
|
self._api_url = api_url.rstrip("/")
|
|
199
211
|
self._api_key = api_key
|
|
@@ -205,13 +217,37 @@ class GovernanceClient:
|
|
|
205
217
|
did=agent_did,
|
|
206
218
|
private_key=agent_private_key,
|
|
207
219
|
)
|
|
220
|
+
workload_private_key = parse_optional_workload_private_key(workload_private_key)
|
|
221
|
+
self._core_client = core_client
|
|
222
|
+
self._owns_core_client = core_client is None and workload_private_key is not None
|
|
223
|
+
if self._owns_core_client:
|
|
224
|
+
from openbox_core.client import EvaluationClient
|
|
225
|
+
from openbox_core.identity import AgentIdentity
|
|
226
|
+
|
|
227
|
+
self._core_client = EvaluationClient(
|
|
228
|
+
self._api_url,
|
|
229
|
+
self._api_key,
|
|
230
|
+
timeout_seconds=timeout,
|
|
231
|
+
on_api_error=on_api_error,
|
|
232
|
+
identity=(
|
|
233
|
+
AgentIdentity.from_private_key(
|
|
234
|
+
self._agent_identity.did, self._agent_identity.private_key
|
|
235
|
+
)
|
|
236
|
+
if self._agent_identity
|
|
237
|
+
else None
|
|
238
|
+
),
|
|
239
|
+
workload_private_key=workload_private_key,
|
|
240
|
+
sdk_version=_SDK_PACKAGE_VERSION,
|
|
241
|
+
sdk_engine="langgraph",
|
|
242
|
+
)
|
|
208
243
|
# Optional base-SDK gate. When wired (by the handler, from a core
|
|
209
244
|
# runtime built off the SAME api_url/api_key/timeout/on_api_error),
|
|
210
245
|
# `evaluate_event`'s ASYNC path routes lifecycle events through it
|
|
211
246
|
# instead of this client's own httpx transport — see `evaluate_event`.
|
|
212
247
|
# `None` (the default) preserves the exact legacy transport/serialization
|
|
213
248
|
# for every existing caller that constructs a bare `GovernanceClient()`.
|
|
214
|
-
#
|
|
249
|
+
# The handler also lends its runtime client for sync/raw calls and
|
|
250
|
+
# approval polling, so every path shares the same workload token cache.
|
|
215
251
|
self._gate = gate
|
|
216
252
|
# Deduplication: prevent sending the same (activity_id, event_type) twice
|
|
217
253
|
# within the same workflow run. Keyed by (workflow_id, run_id) so it resets
|
|
@@ -234,7 +270,9 @@ class GovernanceClient:
|
|
|
234
270
|
return self._sync_client
|
|
235
271
|
|
|
236
272
|
async def close(self) -> None:
|
|
237
|
-
"""Close
|
|
273
|
+
"""Close owned HTTP clients; a borrowed runtime client belongs to its runtime."""
|
|
274
|
+
if self._owns_core_client and self._core_client is not None:
|
|
275
|
+
await self._core_client.aclose()
|
|
238
276
|
if self._client and not self._client.is_closed:
|
|
239
277
|
await self._client.aclose()
|
|
240
278
|
self._client = None
|
|
@@ -255,6 +293,13 @@ class GovernanceClient:
|
|
|
255
293
|
"""
|
|
256
294
|
from openbox_langgraph.errors import OpenBoxAuthError
|
|
257
295
|
|
|
296
|
+
if self._core_client is not None:
|
|
297
|
+
try:
|
|
298
|
+
await self._core_client.avalidate_api_key()
|
|
299
|
+
except _CoreOpenBoxConfigError as exc:
|
|
300
|
+
_raise_core_error(exc)
|
|
301
|
+
return
|
|
302
|
+
|
|
258
303
|
try:
|
|
259
304
|
client = self._get_client()
|
|
260
305
|
response = await client.get(
|
|
@@ -341,13 +386,25 @@ class GovernanceClient:
|
|
|
341
386
|
)
|
|
342
387
|
|
|
343
388
|
if self._gate is not None:
|
|
344
|
-
|
|
389
|
+
try:
|
|
390
|
+
return await _gate_evaluate(self._gate, event, self._on_api_error)
|
|
391
|
+
except OpenBoxError:
|
|
392
|
+
self._forget_failed_event(event, server_event_type)
|
|
393
|
+
raise
|
|
345
394
|
|
|
346
395
|
payload = event.to_dict()
|
|
347
396
|
payload["event_type"] = server_event_type
|
|
348
397
|
payload["task_queue"] = event.task_queue or "langgraph"
|
|
349
398
|
payload["source"] = "workflow-telemetry"
|
|
350
399
|
|
|
400
|
+
if self._core_client is not None:
|
|
401
|
+
try:
|
|
402
|
+
result = await self._core_client.aevaluate(payload)
|
|
403
|
+
except (_CoreOpenBoxConfigError, _CoreGovernanceAPIError) as exc:
|
|
404
|
+
self._forget_failed_event(event, server_event_type)
|
|
405
|
+
_raise_core_error(exc)
|
|
406
|
+
return _collapse_client_synthesized_fallback(result)
|
|
407
|
+
|
|
351
408
|
try:
|
|
352
409
|
client = self._get_client()
|
|
353
410
|
body = _json_body(payload)
|
|
@@ -372,9 +429,7 @@ class GovernanceClient:
|
|
|
372
429
|
except OpenBoxNetworkError:
|
|
373
430
|
raise
|
|
374
431
|
except Exception as e:
|
|
375
|
-
return _network_fallback_result(
|
|
376
|
-
self._on_api_error, f"Governance API unreachable: {e}"
|
|
377
|
-
)
|
|
432
|
+
return _network_fallback_result(self._on_api_error, f"Governance API unreachable: {e}")
|
|
378
433
|
|
|
379
434
|
def evaluate_event_sync(
|
|
380
435
|
self, event: LangChainGovernanceEvent
|
|
@@ -395,6 +450,14 @@ class GovernanceClient:
|
|
|
395
450
|
payload["task_queue"] = event.task_queue or "langgraph"
|
|
396
451
|
payload["source"] = "workflow-telemetry"
|
|
397
452
|
|
|
453
|
+
if self._core_client is not None:
|
|
454
|
+
try:
|
|
455
|
+
result = self._core_client.evaluate(payload)
|
|
456
|
+
except (_CoreOpenBoxConfigError, _CoreGovernanceAPIError) as exc:
|
|
457
|
+
self._forget_failed_event(event, server_event_type)
|
|
458
|
+
_raise_core_error(exc)
|
|
459
|
+
return _collapse_client_synthesized_fallback(result)
|
|
460
|
+
|
|
398
461
|
if os.environ.get("OPENBOX_DEBUG") == "1":
|
|
399
462
|
import json
|
|
400
463
|
|
|
@@ -427,18 +490,26 @@ class GovernanceClient:
|
|
|
427
490
|
except OpenBoxNetworkError:
|
|
428
491
|
raise
|
|
429
492
|
except Exception as e:
|
|
430
|
-
return _network_fallback_result(
|
|
431
|
-
self._on_api_error, f"Governance API unreachable: {e}"
|
|
432
|
-
)
|
|
493
|
+
return _network_fallback_result(self._on_api_error, f"Governance API unreachable: {e}")
|
|
433
494
|
|
|
434
495
|
async def poll_approval(self, params: ApprovalPollParams) -> ApprovalResponse | None:
|
|
435
496
|
"""Poll for HITL approval status.
|
|
436
497
|
|
|
437
|
-
Returns `None` on
|
|
498
|
+
Returns `None` on ordinary polling transport failure so the caller can retry.
|
|
499
|
+
Workload authentication and bootstrap failures always raise.
|
|
438
500
|
|
|
439
501
|
Args:
|
|
440
502
|
params: Identifiers for the pending approval.
|
|
441
503
|
"""
|
|
504
|
+
if self._core_client is not None:
|
|
505
|
+
try:
|
|
506
|
+
result = await self._core_client.apoll_approval(
|
|
507
|
+
params.workflow_id, params.run_id, params.activity_id
|
|
508
|
+
)
|
|
509
|
+
except _CoreOpenBoxConfigError as exc:
|
|
510
|
+
_raise_core_error(exc)
|
|
511
|
+
return ApprovalResponse.from_result(result) if result is not None else None
|
|
512
|
+
|
|
442
513
|
try:
|
|
443
514
|
client = self._get_client()
|
|
444
515
|
body = _json_body(
|
|
@@ -482,8 +553,17 @@ class GovernanceClient:
|
|
|
482
553
|
by the caller (no event_type translation needed).
|
|
483
554
|
|
|
484
555
|
Args:
|
|
485
|
-
payload: The raw dict to POST to
|
|
556
|
+
payload: The raw dict to POST to the identity-appropriate evaluate route.
|
|
486
557
|
"""
|
|
558
|
+
if self._core_client is not None:
|
|
559
|
+
try:
|
|
560
|
+
result = await self._core_client.aevaluate(payload)
|
|
561
|
+
except (_CoreOpenBoxConfigError, _CoreGovernanceAPIError) as exc:
|
|
562
|
+
_raise_core_error(exc)
|
|
563
|
+
if _collapse_client_synthesized_fallback(result) is None:
|
|
564
|
+
return None
|
|
565
|
+
return dict(result.raw)
|
|
566
|
+
|
|
487
567
|
if os.environ.get("OPENBOX_DEBUG") == "1":
|
|
488
568
|
import json
|
|
489
569
|
|
|
@@ -535,6 +615,11 @@ class GovernanceClient:
|
|
|
535
615
|
# Private helpers
|
|
536
616
|
# ─────────────────────────────────────────────────────────────
|
|
537
617
|
|
|
618
|
+
def _forget_failed_event(self, event: LangChainGovernanceEvent, event_type: str) -> None:
|
|
619
|
+
"""A caller retrying a rejected event must authenticate again, never dedup to ALLOW."""
|
|
620
|
+
if event.activity_id and self._dedup_run == (event.workflow_id, event.run_id):
|
|
621
|
+
self._dedup_sent.discard((event.activity_id, event_type))
|
|
622
|
+
|
|
538
623
|
def _headers(self, *, method: str, pathname: str, body: bytes | str | None) -> dict[str, str]:
|
|
539
624
|
return build_auth_headers(
|
|
540
625
|
self._api_key,
|
openbox_langgraph/config.py
CHANGED
|
@@ -14,10 +14,12 @@ from openbox_langgraph.errors import (
|
|
|
14
14
|
OpenBoxConfigError,
|
|
15
15
|
OpenBoxInsecureURLError,
|
|
16
16
|
OpenBoxNetworkError,
|
|
17
|
+
_raise_core_error,
|
|
17
18
|
)
|
|
18
19
|
from openbox_langgraph.identity import (
|
|
19
20
|
AgentIdentityConfig,
|
|
20
21
|
parse_optional_agent_identity_config,
|
|
22
|
+
parse_optional_workload_private_key,
|
|
21
23
|
)
|
|
22
24
|
from openbox_langgraph.types import DEFAULT_HITL_CONFIG, HITLConfig
|
|
23
25
|
|
|
@@ -193,6 +195,7 @@ class _GlobalConfigState:
|
|
|
193
195
|
governance_timeout: float = 30.0 # seconds
|
|
194
196
|
agent_did: str | None = None
|
|
195
197
|
agent_private_key: str | None = None
|
|
198
|
+
workload_private_key: str | None = field(default=None, repr=False)
|
|
196
199
|
|
|
197
200
|
def configure(
|
|
198
201
|
self,
|
|
@@ -200,12 +203,14 @@ class _GlobalConfigState:
|
|
|
200
203
|
api_key: str,
|
|
201
204
|
governance_timeout: float = 30.0,
|
|
202
205
|
agent_identity: AgentIdentityConfig | None = None,
|
|
206
|
+
workload_private_key: str | None = None,
|
|
203
207
|
) -> None:
|
|
204
208
|
self.api_url = api_url.rstrip("/")
|
|
205
209
|
self.api_key = api_key
|
|
206
210
|
self.governance_timeout = governance_timeout
|
|
207
211
|
self.agent_did = agent_identity.did if agent_identity else None
|
|
208
212
|
self.agent_private_key = agent_identity.private_key if agent_identity else None
|
|
213
|
+
self.workload_private_key = workload_private_key
|
|
209
214
|
|
|
210
215
|
def __repr__(self) -> str:
|
|
211
216
|
if self.api_key and len(self.api_key) > 8:
|
|
@@ -293,6 +298,41 @@ def _validate_api_key_with_server(
|
|
|
293
298
|
# ═══════════════════════════════════════════════════════════════════
|
|
294
299
|
|
|
295
300
|
|
|
301
|
+
def _validate_workload_identity_with_server(
|
|
302
|
+
api_url: str,
|
|
303
|
+
api_key: str,
|
|
304
|
+
timeout: float,
|
|
305
|
+
workload_private_key: str,
|
|
306
|
+
agent_identity: AgentIdentityConfig | None,
|
|
307
|
+
) -> None:
|
|
308
|
+
"""Let the base SDK bootstrap, exchange a token, and validate the active identity."""
|
|
309
|
+
from openbox_core.client import EvaluationClient
|
|
310
|
+
from openbox_core.errors import OpenBoxConfigError as CoreOpenBoxConfigError
|
|
311
|
+
from openbox_core.identity import AgentIdentity
|
|
312
|
+
|
|
313
|
+
from openbox_langgraph.client import _SDK_PACKAGE_VERSION
|
|
314
|
+
|
|
315
|
+
client = EvaluationClient(
|
|
316
|
+
api_url,
|
|
317
|
+
api_key,
|
|
318
|
+
timeout_seconds=timeout,
|
|
319
|
+
workload_private_key=workload_private_key,
|
|
320
|
+
identity=(
|
|
321
|
+
AgentIdentity.from_private_key(agent_identity.did, agent_identity.private_key)
|
|
322
|
+
if agent_identity
|
|
323
|
+
else None
|
|
324
|
+
),
|
|
325
|
+
sdk_version=_SDK_PACKAGE_VERSION,
|
|
326
|
+
sdk_engine="langgraph",
|
|
327
|
+
)
|
|
328
|
+
try:
|
|
329
|
+
client.validate_api_key()
|
|
330
|
+
except CoreOpenBoxConfigError as exc:
|
|
331
|
+
_raise_core_error(exc)
|
|
332
|
+
finally:
|
|
333
|
+
client.close()
|
|
334
|
+
|
|
335
|
+
|
|
296
336
|
def initialize(
|
|
297
337
|
api_url: str,
|
|
298
338
|
api_key: str,
|
|
@@ -300,6 +340,7 @@ def initialize(
|
|
|
300
340
|
validate: bool = True,
|
|
301
341
|
agent_did: str | None = None,
|
|
302
342
|
agent_private_key: str | None = None,
|
|
343
|
+
workload_private_key: str | None = None,
|
|
303
344
|
) -> None:
|
|
304
345
|
"""Initialize the OpenBox LangGraph SDK with credentials.
|
|
305
346
|
|
|
@@ -314,6 +355,8 @@ def initialize(
|
|
|
314
355
|
agent_did: Optional OpenBox agent DID. Falls back to `OPENBOX_AGENT_DID`.
|
|
315
356
|
agent_private_key: Optional raw Ed25519 private key seed. Falls back to
|
|
316
357
|
`OPENBOX_AGENT_PRIVATE_KEY`.
|
|
358
|
+
workload_private_key: PKCS8 PEM RSA key for IAM v3. Falls back to
|
|
359
|
+
`OPENBOX_LANGGRAPH_WORKLOAD_PRIVATE_KEY`, then `OPENBOX_WORKLOAD_PRIVATE_KEY`.
|
|
317
360
|
"""
|
|
318
361
|
import os
|
|
319
362
|
|
|
@@ -337,7 +380,17 @@ def initialize(
|
|
|
337
380
|
except OpenBoxConfigError:
|
|
338
381
|
raise
|
|
339
382
|
|
|
340
|
-
if
|
|
383
|
+
if workload_private_key is None:
|
|
384
|
+
workload_private_key = os.environ.get(
|
|
385
|
+
"OPENBOX_LANGGRAPH_WORKLOAD_PRIVATE_KEY", os.environ.get("OPENBOX_WORKLOAD_PRIVATE_KEY")
|
|
386
|
+
)
|
|
387
|
+
workload_private_key = parse_optional_workload_private_key(workload_private_key)
|
|
388
|
+
|
|
389
|
+
if validate and workload_private_key is not None:
|
|
390
|
+
_validate_workload_identity_with_server(
|
|
391
|
+
api_url.rstrip("/"), api_key, governance_timeout, workload_private_key, agent_identity
|
|
392
|
+
)
|
|
393
|
+
elif validate:
|
|
341
394
|
_validate_api_key_with_server(
|
|
342
395
|
api_url.rstrip("/"),
|
|
343
396
|
api_key,
|
|
@@ -350,6 +403,7 @@ def initialize(
|
|
|
350
403
|
api_key,
|
|
351
404
|
governance_timeout,
|
|
352
405
|
agent_identity,
|
|
406
|
+
workload_private_key,
|
|
353
407
|
)
|
|
354
408
|
|
|
355
409
|
_get_logger().info(f"OpenBox LangGraph SDK initialized with API URL: {api_url}")
|
|
@@ -92,7 +92,9 @@ class LangGraphFrameworkAdapter:
|
|
|
92
92
|
|
|
93
93
|
# ── Approval (RAISE-ONLY — never an inline blocking wait) ──────────────
|
|
94
94
|
|
|
95
|
-
async def handle_approval(
|
|
95
|
+
async def handle_approval(
|
|
96
|
+
self, result: EvaluationResult, context: ActivityContext | None = None
|
|
97
|
+
) -> None:
|
|
96
98
|
"""Async started-hook REQUIRE_APPROVAL -> raise, never await inline.
|
|
97
99
|
|
|
98
100
|
The base ``HookRuntime._adecide_started`` treats a normal RETURN as
|
|
@@ -100,7 +102,8 @@ class LangGraphFrameworkAdapter:
|
|
|
100
102
|
signal for a ``requires_approval()`` verdict, so the handler's outer
|
|
101
103
|
catch/poll/retry loop drives the approval flow.
|
|
102
104
|
"""
|
|
103
|
-
|
|
105
|
+
ctx = context if context is not None else self._store.current_activity_context()
|
|
106
|
+
self._raise_pending_approval(result, ctx)
|
|
104
107
|
|
|
105
108
|
def handle_approval_sync(
|
|
106
109
|
self, result: EvaluationResult, *, context: ActivityContext | None = None
|
|
@@ -138,7 +141,9 @@ class LangGraphFrameworkAdapter:
|
|
|
138
141
|
|
|
139
142
|
# ── Completed-hook telemetry (never undoes the operation) ──────────────
|
|
140
143
|
|
|
141
|
-
def on_completed_hook_result(
|
|
144
|
+
def on_completed_hook_result(
|
|
145
|
+
self, result: EvaluationResult, context: ActivityContext | None = None
|
|
146
|
+
) -> None:
|
|
142
147
|
"""Completed verdicts affect FUTURE execution only — the operation
|
|
143
148
|
already ran. ``HookRuntime._after_completed`` has already marked the
|
|
144
149
|
abort/halt flags on the base ``ContextStore`` for a stop-shaped
|
|
@@ -42,6 +42,7 @@ from openbox_core.runtime import OpenBoxRuntime
|
|
|
42
42
|
|
|
43
43
|
from openbox_langgraph.config import GovernanceConfig
|
|
44
44
|
from openbox_langgraph.errors import OpenBoxConfigError
|
|
45
|
+
from openbox_langgraph.identity import parse_optional_workload_private_key
|
|
45
46
|
from openbox_langgraph.trace_context_registry import (
|
|
46
47
|
TraceContextRegistry,
|
|
47
48
|
get_context_store,
|
|
@@ -53,7 +54,7 @@ from openbox_langgraph.trace_context_registry import (
|
|
|
53
54
|
CORE_ENV_PREFIX = "OPENBOX_LANGGRAPH"
|
|
54
55
|
SDK_ENGINE = "langgraph"
|
|
55
56
|
SDK_LANGUAGE = "python"
|
|
56
|
-
SDK_PACKAGE_VERSION = "1.
|
|
57
|
+
SDK_PACKAGE_VERSION = "1.1.0"
|
|
57
58
|
|
|
58
59
|
__all__ = [
|
|
59
60
|
"CORE_ENV_PREFIX",
|
|
@@ -72,6 +73,7 @@ def create_core_runtime(
|
|
|
72
73
|
governance_timeout: float | None = None,
|
|
73
74
|
agent_did: str | None = None,
|
|
74
75
|
agent_private_key: str | None = None,
|
|
76
|
+
workload_private_key: str | None = None,
|
|
75
77
|
extra_ignored_urls: set[str] | None = None,
|
|
76
78
|
) -> OpenBoxRuntime:
|
|
77
79
|
"""Resolve base-SDK config and build an isolated ``OpenBoxRuntime``.
|
|
@@ -124,11 +126,15 @@ def create_core_runtime(
|
|
|
124
126
|
agent_name=config.agent_name,
|
|
125
127
|
agent_did=agent_did,
|
|
126
128
|
agent_private_key=agent_private_key,
|
|
129
|
+
workload_private_key=workload_private_key,
|
|
127
130
|
sdk_version=SDK_PACKAGE_VERSION,
|
|
128
131
|
sdk_engine=SDK_ENGINE,
|
|
129
132
|
sdk_language=SDK_LANGUAGE,
|
|
130
133
|
validate=True,
|
|
131
134
|
)
|
|
135
|
+
core_config.workload_private_key = parse_optional_workload_private_key(
|
|
136
|
+
core_config.workload_private_key
|
|
137
|
+
)
|
|
132
138
|
|
|
133
139
|
from openbox_core.context import ContextStore
|
|
134
140
|
from openbox_core.instrumentation.manager import InstrumentationManager
|
openbox_langgraph/errors.py
CHANGED
|
@@ -2,13 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
from typing import NoReturn
|
|
6
|
+
|
|
5
7
|
|
|
6
8
|
class OpenBoxError(Exception):
|
|
7
9
|
"""Base class for all OpenBox SDK errors."""
|
|
8
10
|
|
|
9
11
|
|
|
10
12
|
class OpenBoxAuthError(OpenBoxError):
|
|
11
|
-
"""Raised when the API key is invalid or unauthorized."""
|
|
13
|
+
"""Raised when the API key or workload identity is invalid or unauthorized."""
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class OpenBoxSigningError(OpenBoxAuthError):
|
|
17
|
+
"""Identity proof rejected by Core, with its machine-readable reason code."""
|
|
18
|
+
|
|
19
|
+
def __init__(self, message: str, reason_code: str | None = None) -> None:
|
|
20
|
+
self.reason_code = reason_code
|
|
21
|
+
super().__init__(message)
|
|
12
22
|
|
|
13
23
|
|
|
14
24
|
class OpenBoxConfigError(OpenBoxError):
|
|
@@ -23,6 +33,25 @@ class OpenBoxInsecureURLError(OpenBoxError):
|
|
|
23
33
|
"""Raised when an insecure HTTP URL is used for a non-localhost endpoint."""
|
|
24
34
|
|
|
25
35
|
|
|
36
|
+
def _raise_core_error(exc: Exception) -> NoReturn:
|
|
37
|
+
"""Keep base-SDK auth/configuration failures in this SDK's public hierarchy."""
|
|
38
|
+
from openbox_core.errors import GovernanceAPIError as CoreGovernanceAPIError
|
|
39
|
+
from openbox_core.errors import OpenBoxAuthError as CoreOpenBoxAuthError
|
|
40
|
+
from openbox_core.errors import OpenBoxConfigError as CoreOpenBoxConfigError
|
|
41
|
+
from openbox_core.errors import OpenBoxNetworkError as CoreOpenBoxNetworkError
|
|
42
|
+
from openbox_core.errors import OpenBoxSigningError as CoreOpenBoxSigningError
|
|
43
|
+
|
|
44
|
+
if isinstance(exc, CoreOpenBoxSigningError):
|
|
45
|
+
raise OpenBoxSigningError(str(exc), exc.reason_code) from exc
|
|
46
|
+
if isinstance(exc, CoreOpenBoxAuthError):
|
|
47
|
+
raise OpenBoxAuthError(str(exc)) from exc
|
|
48
|
+
if isinstance(exc, (CoreOpenBoxNetworkError, CoreGovernanceAPIError)):
|
|
49
|
+
raise OpenBoxNetworkError(str(exc)) from exc
|
|
50
|
+
if isinstance(exc, CoreOpenBoxConfigError):
|
|
51
|
+
raise OpenBoxConfigError(str(exc)) from exc
|
|
52
|
+
raise exc
|
|
53
|
+
|
|
54
|
+
|
|
26
55
|
class GovernanceBlockedError(OpenBoxError):
|
|
27
56
|
"""Raised when governance returns a BLOCK or HALT verdict.
|
|
28
57
|
|
openbox_langgraph/identity.py
CHANGED
|
@@ -46,6 +46,20 @@ class AgentIdentityConfig:
|
|
|
46
46
|
AgentIdentityHeaders = dict[str, str]
|
|
47
47
|
|
|
48
48
|
|
|
49
|
+
def parse_optional_workload_private_key(private_key: str | None) -> str | None:
|
|
50
|
+
"""Validate a workload key locally, including when startup validation is off."""
|
|
51
|
+
if private_key is None:
|
|
52
|
+
return None
|
|
53
|
+
from openbox_core.identity_okta import load_rsa_pkcs8_private_key
|
|
54
|
+
|
|
55
|
+
normalized = private_key.strip()
|
|
56
|
+
try:
|
|
57
|
+
load_rsa_pkcs8_private_key(normalized, key_label="workload_private_key")
|
|
58
|
+
except CoreOpenBoxConfigError as exc:
|
|
59
|
+
raise OpenBoxConfigError(str(exc)) from exc
|
|
60
|
+
return normalized
|
|
61
|
+
|
|
62
|
+
|
|
49
63
|
def build_agent_identity_canonical_request(
|
|
50
64
|
*,
|
|
51
65
|
body_sha256: str,
|
|
@@ -19,6 +19,7 @@ from collections.abc import AsyncGenerator, AsyncIterator, Callable
|
|
|
19
19
|
from dataclasses import dataclass, field
|
|
20
20
|
from typing import Any, cast
|
|
21
21
|
|
|
22
|
+
from openbox_core.errors import OpenBoxConfigError as _CoreOpenBoxConfigError
|
|
22
23
|
from openbox_langchain import (
|
|
23
24
|
ActivityBridge,
|
|
24
25
|
OpenBoxLangChainCoreAsyncCallbackHandler,
|
|
@@ -45,6 +46,7 @@ from openbox_langgraph.errors import (
|
|
|
45
46
|
GovernanceBlockedError,
|
|
46
47
|
GovernanceHaltError,
|
|
47
48
|
GuardrailsValidationError,
|
|
49
|
+
_raise_core_error,
|
|
48
50
|
)
|
|
49
51
|
from openbox_langgraph.hitl import HITLPollParams, poll_until_decision
|
|
50
52
|
from openbox_langgraph.tool_activity_binding import bind_tools_activity_scope, turn_metadata
|
|
@@ -388,6 +390,7 @@ class OpenBoxLangGraphHandler:
|
|
|
388
390
|
governance_timeout=gc.governance_timeout,
|
|
389
391
|
agent_did=gc.agent_did,
|
|
390
392
|
agent_private_key=gc.agent_private_key,
|
|
393
|
+
workload_private_key=gc.workload_private_key,
|
|
391
394
|
extra_ignored_urls={gc.api_url} if gc.api_url else None,
|
|
392
395
|
)
|
|
393
396
|
self._client = GovernanceClient(
|
|
@@ -398,6 +401,7 @@ class OpenBoxLangGraphHandler:
|
|
|
398
401
|
agent_did=gc.agent_did,
|
|
399
402
|
agent_private_key=gc.agent_private_key,
|
|
400
403
|
gate=self._core_runtime.gate,
|
|
404
|
+
core_client=self._core_runtime.client,
|
|
401
405
|
)
|
|
402
406
|
# C1 — install condition == prepare condition: the pure-LangChain-Core
|
|
403
407
|
# callback (installed per-turn in `_governed_config`) is only ever
|
|
@@ -921,6 +925,8 @@ class OpenBoxLangGraphHandler:
|
|
|
921
925
|
_logger.info("[OpenBox] Approval granted, retrying ainvoke")
|
|
922
926
|
self._reset_after_approval(workflow_id)
|
|
923
927
|
final_output = await self._graph.ainvoke(input, config=cfg, **kwargs)
|
|
928
|
+
except _CoreOpenBoxConfigError as exc:
|
|
929
|
+
_raise_core_error(exc)
|
|
924
930
|
except Exception as exc:
|
|
925
931
|
hook_err = _extract_governance_blocked(exc)
|
|
926
932
|
if hook_err is None or hook_err.verdict != "require_approval":
|
|
@@ -1006,6 +1012,8 @@ class OpenBoxLangGraphHandler:
|
|
|
1006
1012
|
pre_screen_claim=pre_screen_claim,
|
|
1007
1013
|
)
|
|
1008
1014
|
yield event
|
|
1015
|
+
except _CoreOpenBoxConfigError as exc:
|
|
1016
|
+
_raise_core_error(exc)
|
|
1009
1017
|
finally:
|
|
1010
1018
|
# Outermost `finally` on an async generator fires on normal
|
|
1011
1019
|
# exhaustion, an exception raised through the loop, OR the
|
|
@@ -1094,6 +1102,8 @@ class OpenBoxLangGraphHandler:
|
|
|
1094
1102
|
workflow_started_sent=workflow_started_sent,
|
|
1095
1103
|
)
|
|
1096
1104
|
yield event
|
|
1105
|
+
except _CoreOpenBoxConfigError as exc:
|
|
1106
|
+
_raise_core_error(exc)
|
|
1097
1107
|
finally:
|
|
1098
1108
|
# See astream_governed's identical finally for why this covers
|
|
1099
1109
|
# normal exhaustion, mid-stream exceptions, AND an abandoned
|
|
@@ -1981,6 +1991,7 @@ def create_openbox_graph_handler(
|
|
|
1981
1991
|
sqlalchemy_engine: Any = None,
|
|
1982
1992
|
agent_did: str | None = None,
|
|
1983
1993
|
agent_private_key: str | None = None,
|
|
1994
|
+
workload_private_key: str | None = None,
|
|
1984
1995
|
**handler_kwargs: Any,
|
|
1985
1996
|
) -> OpenBoxLangGraphHandler:
|
|
1986
1997
|
"""Create a fully configured `OpenBoxLangGraphHandler` wrapping a compiled LangGraph graph.
|
|
@@ -2000,6 +2011,8 @@ def create_openbox_graph_handler(
|
|
|
2000
2011
|
agent_did: Optional OpenBox agent DID. Falls back to `OPENBOX_AGENT_DID`.
|
|
2001
2012
|
agent_private_key: Optional raw Ed25519 private key seed. Falls back to
|
|
2002
2013
|
`OPENBOX_AGENT_PRIVATE_KEY`.
|
|
2014
|
+
workload_private_key: PKCS8 PEM RSA key for IAM v3. Falls back to
|
|
2015
|
+
`OPENBOX_LANGGRAPH_WORKLOAD_PRIVATE_KEY`, then `OPENBOX_WORKLOAD_PRIVATE_KEY`.
|
|
2003
2016
|
**handler_kwargs: Additional keyword arguments forwarded to
|
|
2004
2017
|
`OpenBoxLangGraphHandlerOptions`.
|
|
2005
2018
|
|
|
@@ -2023,6 +2036,7 @@ def create_openbox_graph_handler(
|
|
|
2023
2036
|
validate=validate,
|
|
2024
2037
|
agent_did=agent_did,
|
|
2025
2038
|
agent_private_key=agent_private_key,
|
|
2039
|
+
workload_private_key=workload_private_key,
|
|
2026
2040
|
)
|
|
2027
2041
|
|
|
2028
2042
|
options = OpenBoxLangGraphHandlerOptions(
|
|
@@ -35,10 +35,13 @@ from typing import Any
|
|
|
35
35
|
from openbox_core.context import activity_scope
|
|
36
36
|
from openbox_core.contracts.context import ActivityContext
|
|
37
37
|
from openbox_core.contracts.otel_spans import HookType
|
|
38
|
+
from openbox_core.errors import OpenBoxConfigError as _CoreOpenBoxConfigError
|
|
38
39
|
from openbox_core.hooks.events import resolve_context
|
|
39
40
|
from openbox_core.hooks.preflight import HookRuntime
|
|
40
41
|
from openbox_core.otel.trace_context import raw_trace_id
|
|
41
42
|
|
|
43
|
+
from openbox_langgraph.errors import _raise_core_error
|
|
44
|
+
|
|
42
45
|
__all__ = ["LangGraphHookRuntime"]
|
|
43
46
|
|
|
44
47
|
# Bound on in-flight pins. A pin is normally cleared at completed (or when
|
|
@@ -139,6 +142,9 @@ class LangGraphHookRuntime(HookRuntime):
|
|
|
139
142
|
proceed = super().preflight(
|
|
140
143
|
span, hook_type=hook_type, identifier=identifier, fields=fields
|
|
141
144
|
)
|
|
145
|
+
except _CoreOpenBoxConfigError as exc:
|
|
146
|
+
self._unpin(key)
|
|
147
|
+
_raise_core_error(exc)
|
|
142
148
|
except BaseException:
|
|
143
149
|
# Blocked / halted / approval-rejected: no completed callback will
|
|
144
150
|
# follow, so drop the pin rather than leak it.
|
|
@@ -161,6 +167,9 @@ class LangGraphHookRuntime(HookRuntime):
|
|
|
161
167
|
proceed = await super().apreflight(
|
|
162
168
|
span, hook_type=hook_type, identifier=identifier, fields=fields
|
|
163
169
|
)
|
|
170
|
+
except _CoreOpenBoxConfigError as exc:
|
|
171
|
+
self._unpin(key)
|
|
172
|
+
_raise_core_error(exc)
|
|
164
173
|
except BaseException:
|
|
165
174
|
self._unpin(key)
|
|
166
175
|
raise
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: openbox-langgraph-sdk-python
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.1.0
|
|
4
4
|
Summary: OpenBox governance and observability SDK for LangGraph
|
|
5
5
|
License: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -10,7 +10,7 @@ Requires-Dist: langchain-core>=1.3.3
|
|
|
10
10
|
Requires-Dist: langgraph>=0.2.0
|
|
11
11
|
Requires-Dist: langsmith>=0.8.18
|
|
12
12
|
Requires-Dist: openbox-langchain-sdk-python>=1.0.0
|
|
13
|
-
Requires-Dist: openbox-sdk-python>=1.0
|
|
13
|
+
Requires-Dist: openbox-sdk-python>=1.4.0
|
|
14
14
|
Requires-Dist: opentelemetry-api>=1.20.0
|
|
15
15
|
Requires-Dist: opentelemetry-instrumentation-asyncpg>=0.41b0
|
|
16
16
|
Requires-Dist: opentelemetry-instrumentation-httpx>=0.41b0
|
|
@@ -136,17 +136,14 @@ uv add openbox-langgraph-sdk-python
|
|
|
136
136
|
|
|
137
137
|
### 1. Get your agent credentials
|
|
138
138
|
|
|
139
|
-
Sign in to [dashboard.openbox.ai](https://dashboard.openbox.ai), create an agent called `"MyAgent"`, and copy
|
|
140
|
-
|
|
141
|
-
New OpenBox agents have DID signing enabled by default. Keep the private key secret and load it from your environment.
|
|
139
|
+
Sign in to [dashboard.openbox.ai](https://dashboard.openbox.ai), create an agent called `"MyAgent"`, and copy its API key. For an IAM v3 workload-enabled agent, also load the PKCS8 PEM RSA private key registered for its active Keycloak service account. Keep the key secret.
|
|
142
140
|
|
|
143
141
|
### 2. Set environment variables
|
|
144
142
|
|
|
145
143
|
```bash
|
|
146
144
|
export OPENBOX_URL="https://core.openbox.ai"
|
|
147
145
|
export OPENBOX_API_KEY="obx_live_..."
|
|
148
|
-
export
|
|
149
|
-
export OPENBOX_AGENT_PRIVATE_KEY="..."
|
|
146
|
+
export OPENBOX_WORKLOAD_PRIVATE_KEY="$(cat /path/to/workload-private-key.pem)"
|
|
150
147
|
```
|
|
151
148
|
|
|
152
149
|
### 3. Wrap your graph
|
|
@@ -181,6 +178,50 @@ asyncio.run(main())
|
|
|
181
178
|
|
|
182
179
|
That's it. Your agent now sends governance events to OpenBox on every tool call, LLM prompt, HTTP request, and database query.
|
|
183
180
|
|
|
181
|
+
### IAM v3 workload identity
|
|
182
|
+
|
|
183
|
+
IAM v3 is provided by `openbox-sdk-python>=1.4.0`. The existing
|
|
184
|
+
`openbox-langchain-sdk-python` dependency remains at `>=1.0.0`; its callbacks
|
|
185
|
+
use the same base runtime as the LangGraph handler and operation hooks.
|
|
186
|
+
|
|
187
|
+
You can pass the workload key explicitly instead of setting an environment variable:
|
|
188
|
+
|
|
189
|
+
```python
|
|
190
|
+
governed = create_openbox_graph_handler(
|
|
191
|
+
graph=agent,
|
|
192
|
+
api_url=os.environ["OPENBOX_URL"],
|
|
193
|
+
api_key=os.environ["OPENBOX_API_KEY"],
|
|
194
|
+
workload_private_key=os.environ["OPENBOX_WORKLOAD_PRIVATE_KEY"],
|
|
195
|
+
)
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Key resolution is explicit argument, then `OPENBOX_LANGGRAPH_WORKLOAD_PRIVATE_KEY`,
|
|
199
|
+
then `OPENBOX_WORKLOAD_PRIVATE_KEY`. The value is the PEM contents, not a file path.
|
|
200
|
+
The Python base SDK selects workload authentication from this key; no
|
|
201
|
+
`OPENBOX_AGENT_IDENTITY_METHOD` setting is needed for IAM v3.
|
|
202
|
+
|
|
203
|
+
The base SDK fetches `/api/v3/auth/bootstrap`, exchanges a signed `private_key_jwt`
|
|
204
|
+
for a short-lived Keycloak token, and sends the API key plus
|
|
205
|
+
`X-OpenBox-Workload-Token` on v3 validation, evaluation, and approval requests.
|
|
206
|
+
It owns token caching and refresh for OpenBox-, Okta-, and Entra-managed identities.
|
|
207
|
+
Lifecycle callbacks, hooks, synchronous/raw evaluations, and approval polling share
|
|
208
|
+
the handler's runtime client. A standalone `GovernanceClient` also accepts
|
|
209
|
+
`workload_private_key`; call `await client.close()` when finished with it.
|
|
210
|
+
|
|
211
|
+
Authentication and signing failures raise `OpenBoxAuthError` or its subclass
|
|
212
|
+
`OpenBoxSigningError` even with `on_api_error="fail_open"`. The signing error retains
|
|
213
|
+
Core's `reason_code`. Invalid bootstrap metadata raises `OpenBoxConfigError`, and
|
|
214
|
+
bootstrap/token service outages raise `OpenBoxNetworkError`; these do not become
|
|
215
|
+
implicit ALLOW or pending approval results. `validate=False` skips the startup
|
|
216
|
+
network check only: the key is still validated locally, and runtime requests
|
|
217
|
+
still authenticate.
|
|
218
|
+
|
|
219
|
+
The base SDK preserves rolling-upgrade compatibility: bootstrap HTTP 404, or
|
|
220
|
+
HTTP 409 with `workload_identity_unavailable`, retains the configured legacy route.
|
|
221
|
+
Other bootstrap failures do not downgrade. Existing DID signing remains available
|
|
222
|
+
through `agent_did`/`agent_private_key` or `OPENBOX_AGENT_DID`/`OPENBOX_AGENT_PRIVATE_KEY`.
|
|
223
|
+
Without identity credentials, the existing API-key-only behavior is preserved.
|
|
224
|
+
|
|
184
225
|
### Try it locally (included test agent)
|
|
185
226
|
|
|
186
227
|
The repository includes a runnable LangGraph test agent under `test-agent/`.
|
|
@@ -207,6 +248,7 @@ See `test-agent/README.md` for setup and run instructions.
|
|
|
207
248
|
| `api_key` | `str` | **required** | API key (`obx_live_*` or `obx_test_*`) |
|
|
208
249
|
| `agent_did` | `str` | `OPENBOX_AGENT_DID` | Agent DID used to sign governance requests |
|
|
209
250
|
| `agent_private_key` | `str` | `OPENBOX_AGENT_PRIVATE_KEY` | Base64 raw Ed25519 private key seed for the agent DID |
|
|
251
|
+
| `workload_private_key` | `str` | `OPENBOX_LANGGRAPH_WORKLOAD_PRIVATE_KEY`, then `OPENBOX_WORKLOAD_PRIVATE_KEY` | PKCS8 PEM RSA key for IAM v3 workload authentication |
|
|
210
252
|
| `agent_name` | `str` | `None` | Agent name as configured in the dashboard |
|
|
211
253
|
| `validate` | `bool` | `True` | Validate API key against server on startup |
|
|
212
254
|
| `on_api_error` | `str` | `"fail_open"` | `"fail_open"` (allow on error) or `"fail_closed"` (block on error) |
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
openbox_langgraph/__init__.py,sha256=J-3oOgnRjF5ixC8r90vthDAVdotGI-fyg84byXvl4xQ,4142
|
|
2
|
+
openbox_langgraph/activity_context_binding.py,sha256=kI_Qu7R0mjM2js50sN3H-9CNWjzF8tI7Xg9afvnQeHo,4789
|
|
3
|
+
openbox_langgraph/client.py,sha256=Asq9iw6BcazzIgRpyqP4KJOFris2ferOoz0u63EAtNo,26835
|
|
4
|
+
openbox_langgraph/config.py,sha256=XSbd5RMEsf1VWFQkCx7hJceTu_EcSs_uTMYL_LIybRE,17183
|
|
5
|
+
openbox_langgraph/core_adapter.py,sha256=xv--ptTjoeAD7bo7IvJOhP_RRnKTkoAr06VY945yYBE,11418
|
|
6
|
+
openbox_langgraph/core_events.py,sha256=UXS9iWFPBLcClbfMa9MjaJDp4ZjMrm49x_oqX3TAZQg,6237
|
|
7
|
+
openbox_langgraph/core_runtime.py,sha256=NRcjbbYnE-1sOaFM_rcmNpcAkufqTpRbn1L2WB0GXWk,8600
|
|
8
|
+
openbox_langgraph/errors.py,sha256=Xry_Ir6U2wmKerluIJ18eHPzuE5YCq0Ib1uY58raXro,5137
|
|
9
|
+
openbox_langgraph/hitl.py,sha256=p8Xyxp9vvIBciumghM2RGJ53deIIwbufLRmw-PPLteU,2751
|
|
10
|
+
openbox_langgraph/identity.py,sha256=t4jGkUNGz4OwcGXrWMgy5jVriEYJwCE_509Yi-MKaHY,5965
|
|
11
|
+
openbox_langgraph/langgraph_handler.py,sha256=Ibp4wcBznlAh4qtH2nNiWGzCUX4YFZhrJF7pTSpLZaA,106043
|
|
12
|
+
openbox_langgraph/langgraph_hook_runtime.py,sha256=FVoTvay5ZJAI8iNxRCdPNEeldi7l50AMRGP_vmWU6G4,8757
|
|
13
|
+
openbox_langgraph/otel_setup.py,sha256=Qk8GiXZ1f4WlSjT1_Id48dRfoZGkP_A0VCQFVX2wysU,1584
|
|
14
|
+
openbox_langgraph/span_processor.py,sha256=MfsHG4La0P-G1mo2GCgFqU-al5iljExTAA1bSrCRtJA,14045
|
|
15
|
+
openbox_langgraph/tool_activity_binding.py,sha256=MBql-XoqRkooy6vwBhi8kmnWnmJIIuIXRyWqj1lUqmo,14918
|
|
16
|
+
openbox_langgraph/trace_context_registry.py,sha256=fcU-WXt-8VLsyd8irJ1feQQCeLiGFSK8oNN-zfbCcPM,9583
|
|
17
|
+
openbox_langgraph/tracing.py,sha256=24CIs5hgnK2nRH0vF7g96L3fuxyP66w24Oe0sJbBCc8,7299
|
|
18
|
+
openbox_langgraph/types.py,sha256=zUrk44-1PBWirW7bPPrsLcMNzJSVYCdpzPuu4iBwIXs,26368
|
|
19
|
+
openbox_langgraph/verdict_handler.py,sha256=YmZjBxzOa2RBbkF13xDCPUAGzz7m1hwdMErwOVDl2WM,8323
|
|
20
|
+
openbox_langgraph_sdk_python-1.1.0.dist-info/METADATA,sha256=sl6QbrHNXa8u8J0SRYzXb-3JReuTwVyl1BlD9hk8rHA,24140
|
|
21
|
+
openbox_langgraph_sdk_python-1.1.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
22
|
+
openbox_langgraph_sdk_python-1.1.0.dist-info/licenses/LICENSE,sha256=bPyy7QFDMnA5r6asaR3iD9rQvpjz5cUFk1ouHCuBYIY,1071
|
|
23
|
+
openbox_langgraph_sdk_python-1.1.0.dist-info/RECORD,,
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
openbox_langgraph/__init__.py,sha256=A2qUhwWwxsySPtYQ-oRp8G06nCfjXKODBmz7ZP8dkQw,4090
|
|
2
|
-
openbox_langgraph/activity_context_binding.py,sha256=kI_Qu7R0mjM2js50sN3H-9CNWjzF8tI7Xg9afvnQeHo,4789
|
|
3
|
-
openbox_langgraph/client.py,sha256=awaj-q-ImFCfdJQAxMkSWUGeI_dN6TMe6pe1J3Nhojw,22834
|
|
4
|
-
openbox_langgraph/config.py,sha256=-eZuX59ydO9XkWMKSOkZGfuYkXnUswFdefoNpJOt3SI,15120
|
|
5
|
-
openbox_langgraph/core_adapter.py,sha256=y6oVTuq6AZvAv6EREC6HublU8Pi2si8FSAyK-ky6knc,11256
|
|
6
|
-
openbox_langgraph/core_events.py,sha256=UXS9iWFPBLcClbfMa9MjaJDp4ZjMrm49x_oqX3TAZQg,6237
|
|
7
|
-
openbox_langgraph/core_runtime.py,sha256=bVsm_9o7rI6boAL5YnQ5kpjpHWj8lrZDML__dmv3fi4,8306
|
|
8
|
-
openbox_langgraph/errors.py,sha256=2FLK2_h-ObNZ-r3jPZJQR0fU5WMBoEE_0FPOxL5BLlA,3806
|
|
9
|
-
openbox_langgraph/hitl.py,sha256=p8Xyxp9vvIBciumghM2RGJ53deIIwbufLRmw-PPLteU,2751
|
|
10
|
-
openbox_langgraph/identity.py,sha256=kFP2AwUyCrmp26kE7J-_Cajksef2x4YVFf_WSCpJUTE,5436
|
|
11
|
-
openbox_langgraph/langgraph_handler.py,sha256=kMHs1j9Pn-EFIT66706cmO_E_172Ve-9iXX3YHtyQE4,105318
|
|
12
|
-
openbox_langgraph/langgraph_hook_runtime.py,sha256=m3UXOKmsAz0qCiYyAsZvJEs1EecIrbxblPivJtlTt14,8401
|
|
13
|
-
openbox_langgraph/otel_setup.py,sha256=Qk8GiXZ1f4WlSjT1_Id48dRfoZGkP_A0VCQFVX2wysU,1584
|
|
14
|
-
openbox_langgraph/span_processor.py,sha256=MfsHG4La0P-G1mo2GCgFqU-al5iljExTAA1bSrCRtJA,14045
|
|
15
|
-
openbox_langgraph/tool_activity_binding.py,sha256=MBql-XoqRkooy6vwBhi8kmnWnmJIIuIXRyWqj1lUqmo,14918
|
|
16
|
-
openbox_langgraph/trace_context_registry.py,sha256=fcU-WXt-8VLsyd8irJ1feQQCeLiGFSK8oNN-zfbCcPM,9583
|
|
17
|
-
openbox_langgraph/tracing.py,sha256=24CIs5hgnK2nRH0vF7g96L3fuxyP66w24Oe0sJbBCc8,7299
|
|
18
|
-
openbox_langgraph/types.py,sha256=zUrk44-1PBWirW7bPPrsLcMNzJSVYCdpzPuu4iBwIXs,26368
|
|
19
|
-
openbox_langgraph/verdict_handler.py,sha256=YmZjBxzOa2RBbkF13xDCPUAGzz7m1hwdMErwOVDl2WM,8323
|
|
20
|
-
openbox_langgraph_sdk_python-1.0.0.dist-info/METADATA,sha256=6cO6xSCgLUcoltXiOruip3gVuKRCakgB_1DabcbZTEM,21647
|
|
21
|
-
openbox_langgraph_sdk_python-1.0.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
22
|
-
openbox_langgraph_sdk_python-1.0.0.dist-info/licenses/LICENSE,sha256=bPyy7QFDMnA5r6asaR3iD9rQvpjz5cUFk1ouHCuBYIY,1071
|
|
23
|
-
openbox_langgraph_sdk_python-1.0.0.dist-info/RECORD,,
|
|
File without changes
|