openbox-deepagent-sdk-python 0.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.
File without changes
@@ -0,0 +1,130 @@
1
+ """Shared DeepAgents utilities for subagent detection and conflict guards.
2
+
3
+ Used by both the legacy OpenBoxDeepAgentHandler and the new OpenBoxMiddleware.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import os
9
+ import sys
10
+ from typing import TYPE_CHECKING, Any
11
+
12
+ if TYPE_CHECKING:
13
+ from openbox_langgraph.types import LangGraphStreamEvent
14
+
15
+
16
+ # ═══════════════════════════════════════════════════════════════════
17
+ # DeepAgents built-in tool names
18
+ # ═══════════════════════════════════════════════════════════════════
19
+
20
+ DEEPAGENT_BUILTIN_TOOLS: frozenset[str] = frozenset({
21
+ "write_todos",
22
+ "ls",
23
+ "read_file",
24
+ "write_file",
25
+ "edit_file",
26
+ "glob",
27
+ "grep",
28
+ "execute",
29
+ "task",
30
+ })
31
+
32
+ DEEPAGENT_SUBAGENT_TOOL = "task"
33
+
34
+
35
+ # ═══════════════════════════════════════════════════════════════════
36
+ # Subagent name resolver (from LangGraph stream events)
37
+ # ═══════════════════════════════════════════════════════════════════
38
+
39
+ def resolve_deepagent_subagent_name(event: LangGraphStreamEvent) -> str | None:
40
+ """Detect DeepAgents subagent invocations from the `task` tool's `on_tool_start` event.
41
+
42
+ DeepAgents subagents run synchronously inside the `task` tool body — their
43
+ events are NOT visible in the outer stream. The only observable signal is
44
+ the `task` tool's `on_tool_start` event, which carries `subagent_type` in
45
+ its input dict.
46
+
47
+ Returns the `subagent_type` string (e.g. `"weather"`, `"general-purpose"`),
48
+ or None for all other events.
49
+ """
50
+ if event.event != "on_tool_start":
51
+ return None
52
+ if event.name != DEEPAGENT_SUBAGENT_TOOL:
53
+ return None
54
+
55
+ raw_input = event.data.get("input")
56
+ if isinstance(raw_input, dict):
57
+ subagent_type = raw_input.get("subagent_type")
58
+ if isinstance(subagent_type, str):
59
+ return subagent_type
60
+
61
+ # Fallback: DeepAgents default is general-purpose
62
+ if sys.stderr and os.environ.get("OPENBOX_DEBUG"):
63
+ sys.stderr.write(
64
+ f"[OpenBox Debug] task tool input missing subagent_type, "
65
+ f"defaulting to general-purpose. raw_input={raw_input!r}\n"
66
+ )
67
+ return "general-purpose"
68
+
69
+
70
+ # ═══════════════════════════════════════════════════════════════════
71
+ # Subagent name resolver (from tool_call dict — used by middleware)
72
+ # ═══════════════════════════════════════════════════════════════════
73
+
74
+ def resolve_subagent_from_tool_call(tool_name: str, tool_args: Any) -> str | None:
75
+ """Extract subagent_type from a tool_call dict (middleware hook context).
76
+
77
+ Unlike resolve_deepagent_subagent_name which works on LangGraph stream events,
78
+ this works on the raw tool_call data available in wrap_tool_call hooks.
79
+
80
+ Returns the subagent_type string, or None if not a subagent tool.
81
+ """
82
+ if tool_name != DEEPAGENT_SUBAGENT_TOOL:
83
+ return None
84
+
85
+ if isinstance(tool_args, dict):
86
+ subagent_type = tool_args.get("subagent_type")
87
+ if isinstance(subagent_type, str):
88
+ return subagent_type
89
+
90
+ # Fallback: DeepAgents default is general-purpose
91
+ if sys.stderr and os.environ.get("OPENBOX_DEBUG"):
92
+ sys.stderr.write(
93
+ f"[OpenBox Debug] task tool_call missing subagent_type, "
94
+ f"defaulting to general-purpose. tool_args={tool_args!r}\n"
95
+ )
96
+ return "general-purpose"
97
+
98
+
99
+ # ═══════════════════════════════════════════════════════════════════
100
+ # HITL / interrupt_on conflict detection
101
+ # ═══════════════════════════════════════════════════════════════════
102
+
103
+ def hitl_enabled(hitl: Any) -> bool:
104
+ """Return True if the HITL config indicates polling is enabled."""
105
+ if hitl is None:
106
+ return False
107
+ if isinstance(hitl, dict):
108
+ return bool(hitl.get("enabled", False))
109
+ return bool(getattr(hitl, "enabled", False))
110
+
111
+
112
+ def graph_has_interrupt_on(graph: Any) -> bool:
113
+ """Check whether the compiled graph has interrupt_before or interrupt_after configured.
114
+
115
+ DeepAgents HumanInTheLoopMiddleware sets these on the compiled graph.
116
+ This is a best-effort check — false negatives are possible for custom setups.
117
+ """
118
+ interrupt_before = (
119
+ getattr(graph, "interrupt_before", None)
120
+ or getattr(graph, "interruptBefore", None)
121
+ )
122
+ interrupt_after = (
123
+ getattr(graph, "interrupt_after", None)
124
+ or getattr(graph, "interruptAfter", None)
125
+ )
126
+ if isinstance(interrupt_before, (list, tuple, set)) and len(interrupt_before) > 0:
127
+ return True
128
+ if isinstance(interrupt_after, (list, tuple, set)) and len(interrupt_after) > 0:
129
+ return True
130
+ return False