aury-agent 0.0.12__py3-none-any.whl → 0.0.14__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.
- aury/agents/backends/__init__.py +8 -0
- aury/agents/backends/hitl/__init__.py +8 -0
- aury/agents/backends/hitl/memory.py +100 -0
- aury/agents/backends/hitl/types.py +132 -0
- aury/agents/core/base.py +5 -0
- aury/agents/core/context.py +1 -0
- aury/agents/core/signals.py +37 -17
- aury/agents/core/types/__init__.py +0 -2
- aury/agents/core/types/block.py +6 -23
- aury/agents/core/types/session.py +10 -3
- aury/agents/core/types/tool.py +194 -18
- aury/agents/hitl/__init__.py +2 -0
- aury/agents/hitl/ask_user.py +59 -47
- aury/agents/hitl/exceptions.py +214 -13
- aury/agents/react/agent.py +47 -0
- aury/agents/react/context.py +51 -25
- aury/agents/react/factory.py +2 -0
- aury/agents/react/pause.py +13 -2
- aury/agents/react/step.py +39 -12
- aury/agents/react/tools.py +277 -147
- aury/agents/tool/builtin/ask_user.py +1 -5
- aury/agents/tool/builtin/delegate.py +3 -15
- aury/agents/tool/builtin/plan.py +1 -5
- aury/agents/tool/builtin/thinking.py +1 -6
- aury/agents/tool/builtin/yield_result.py +1 -6
- {aury_agent-0.0.12.dist-info → aury_agent-0.0.14.dist-info}/METADATA +1 -1
- {aury_agent-0.0.12.dist-info → aury_agent-0.0.14.dist-info}/RECORD +29 -26
- {aury_agent-0.0.12.dist-info → aury_agent-0.0.14.dist-info}/WHEEL +0 -0
- {aury_agent-0.0.12.dist-info → aury_agent-0.0.14.dist-info}/entry_points.txt +0 -0
aury/agents/core/types/tool.py
CHANGED
|
@@ -65,8 +65,8 @@ class ToolContext:
|
|
|
65
65
|
|
|
66
66
|
await global_emit(block)
|
|
67
67
|
|
|
68
|
-
async def emit_hitl(self,
|
|
69
|
-
"""Emit a HITL
|
|
68
|
+
async def emit_hitl(self, hitl_id: str, data: dict[str, Any]) -> None:
|
|
69
|
+
"""Emit a HITL block.
|
|
70
70
|
|
|
71
71
|
Convenience method for tools that need user interaction.
|
|
72
72
|
The data format is flexible - can be anything the frontend understands:
|
|
@@ -76,15 +76,15 @@ class ToolContext:
|
|
|
76
76
|
- Rich content (product cards, file selection, etc.)
|
|
77
77
|
|
|
78
78
|
Args:
|
|
79
|
-
|
|
79
|
+
hitl_id: Unique ID for this HITL request
|
|
80
80
|
data: Arbitrary data dict for frontend to render.
|
|
81
81
|
Common fields: type, question, choices, default, context
|
|
82
82
|
"""
|
|
83
83
|
from .block import BlockEvent, BlockKind
|
|
84
84
|
|
|
85
85
|
await self.emit(BlockEvent(
|
|
86
|
-
kind=BlockKind.
|
|
87
|
-
data={"
|
|
86
|
+
kind=BlockKind.HITL,
|
|
87
|
+
data={"hitl_id": hitl_id, **data},
|
|
88
88
|
))
|
|
89
89
|
|
|
90
90
|
|
|
@@ -92,15 +92,29 @@ class ToolContext:
|
|
|
92
92
|
class ToolResult:
|
|
93
93
|
"""Tool execution result for LLM.
|
|
94
94
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
95
|
+
This is the text result returned to LLM. For frontend rendering,
|
|
96
|
+
tools should use ctx.emit(BlockEvent(...)) to PATCH the TOOL_USE block
|
|
97
|
+
with structured data during execution.
|
|
98
98
|
|
|
99
|
-
|
|
99
|
+
Fields:
|
|
100
|
+
- output: Complete text output for LLM
|
|
101
|
+
- truncated_output: Shortened output for context window management
|
|
102
|
+
|
|
103
|
+
Example (image generation tool):
|
|
104
|
+
# During execution, PATCH block with structured data for frontend
|
|
105
|
+
await ctx.emit(BlockEvent(
|
|
106
|
+
block_id=ctx.block_id,
|
|
107
|
+
kind=BlockKind.TOOL_USE,
|
|
108
|
+
op=BlockOp.PATCH,
|
|
109
|
+
data={"images": [{"url": "..."}], "progress": 100},
|
|
110
|
+
))
|
|
111
|
+
|
|
112
|
+
# Return text for LLM
|
|
113
|
+
return ToolResult.success("已生成4张图片")
|
|
100
114
|
"""
|
|
101
|
-
output: str #
|
|
115
|
+
output: str # Text output for LLM
|
|
102
116
|
is_error: bool = False
|
|
103
|
-
truncated_output: str | None = None # Shortened output
|
|
117
|
+
truncated_output: str | None = None # Shortened output for LLM context window
|
|
104
118
|
|
|
105
119
|
def __post_init__(self):
|
|
106
120
|
# Default truncated to output if not provided
|
|
@@ -117,8 +131,8 @@ class ToolResult:
|
|
|
117
131
|
"""Create a successful result.
|
|
118
132
|
|
|
119
133
|
Args:
|
|
120
|
-
output:
|
|
121
|
-
truncated_output: Shortened output for context (defaults to output)
|
|
134
|
+
output: Text output for LLM
|
|
135
|
+
truncated_output: Shortened output for LLM context (defaults to output)
|
|
122
136
|
"""
|
|
123
137
|
return cls(
|
|
124
138
|
output=output,
|
|
@@ -136,7 +150,11 @@ class ToolInvocationState(Enum):
|
|
|
136
150
|
"""Tool invocation state machine."""
|
|
137
151
|
PARTIAL_CALL = "partial-call" # Arguments streaming
|
|
138
152
|
CALL = "call" # Arguments complete, ready to execute
|
|
139
|
-
RESULT = "result" # Execution complete
|
|
153
|
+
RESULT = "result" # Execution complete (deprecated, use SUCCESS/FAILED/ABORTED)
|
|
154
|
+
# Execution result states
|
|
155
|
+
SUCCESS = "success" # Execution successful
|
|
156
|
+
FAILED = "failed" # Execution failed (including timeout)
|
|
157
|
+
ABORTED = "aborted" # User aborted
|
|
140
158
|
|
|
141
159
|
|
|
142
160
|
@dataclass
|
|
@@ -144,6 +162,7 @@ class ToolInvocation:
|
|
|
144
162
|
"""Tool invocation tracking (state machine)."""
|
|
145
163
|
tool_call_id: str
|
|
146
164
|
tool_name: str
|
|
165
|
+
block_id: str = "" # Associated TOOL_USE block ID
|
|
147
166
|
state: ToolInvocationState = ToolInvocationState.PARTIAL_CALL
|
|
148
167
|
args: dict[str, Any] = field(default_factory=dict)
|
|
149
168
|
args_raw: str = "" # Raw JSON string for streaming
|
|
@@ -167,6 +186,7 @@ class ToolInvocation:
|
|
|
167
186
|
result: str,
|
|
168
187
|
is_error: bool = False,
|
|
169
188
|
truncated_result: str | None = None,
|
|
189
|
+
status: ToolInvocationState | None = None,
|
|
170
190
|
) -> None:
|
|
171
191
|
"""Mark execution complete.
|
|
172
192
|
|
|
@@ -174,8 +194,17 @@ class ToolInvocation:
|
|
|
174
194
|
result: Complete result (raw)
|
|
175
195
|
is_error: Whether this is an error result
|
|
176
196
|
truncated_result: Shortened result for context window (defaults to result)
|
|
197
|
+
status: Explicit status (SUCCESS/FAILED/ABORTED). If not provided,
|
|
198
|
+
automatically inferred from is_error flag.
|
|
177
199
|
"""
|
|
178
|
-
|
|
200
|
+
# Set state based on explicit status or infer from is_error
|
|
201
|
+
if status:
|
|
202
|
+
self.state = status
|
|
203
|
+
elif is_error:
|
|
204
|
+
self.state = ToolInvocationState.FAILED
|
|
205
|
+
else:
|
|
206
|
+
self.state = ToolInvocationState.SUCCESS
|
|
207
|
+
|
|
179
208
|
self.result = result
|
|
180
209
|
self.truncated_result = truncated_result if truncated_result is not None else result
|
|
181
210
|
self.is_error = is_error
|
|
@@ -190,9 +219,61 @@ class ToolInvocation:
|
|
|
190
219
|
|
|
191
220
|
|
|
192
221
|
class BaseTool:
|
|
193
|
-
"""Base class for tools with common functionality.
|
|
222
|
+
"""Base class for tools with common functionality.
|
|
223
|
+
|
|
224
|
+
Tools can operate in two modes:
|
|
225
|
+
|
|
226
|
+
1. Standard mode (default):
|
|
227
|
+
- Implement execute() method
|
|
228
|
+
- Tool runs to completion or raises HITLSuspend
|
|
229
|
+
- If HITLSuspend raised, user response becomes tool result
|
|
230
|
+
|
|
231
|
+
2. Continuation mode:
|
|
232
|
+
- Set supports_continuation = True
|
|
233
|
+
- Implement execute_resumable() method
|
|
234
|
+
- Tool can pause mid-execution with HITLSuspend(resume_mode="continuation")
|
|
235
|
+
- When user responds, tool resumes from checkpoint
|
|
236
|
+
- Useful for OAuth, payment, multi-step wizards
|
|
237
|
+
|
|
238
|
+
Emit support:
|
|
239
|
+
Tools can emit BlockEvents using self.emit(). The emitter is automatically
|
|
240
|
+
set when the tool is executed via the agent framework. If no emitter is
|
|
241
|
+
available (e.g., standalone testing), emit calls are silently skipped.
|
|
242
|
+
|
|
243
|
+
Example:
|
|
244
|
+
async def execute(self, params, ctx):
|
|
245
|
+
await self.emit(BlockEvent(...))
|
|
246
|
+
return ToolResult.success("done")
|
|
247
|
+
|
|
248
|
+
Example (continuation mode):
|
|
249
|
+
class OAuthTool(BaseTool):
|
|
250
|
+
_name = "oauth_connect"
|
|
251
|
+
supports_continuation = True
|
|
252
|
+
|
|
253
|
+
async def execute_resumable(
|
|
254
|
+
self,
|
|
255
|
+
params: dict[str, Any],
|
|
256
|
+
ctx: ToolContext,
|
|
257
|
+
checkpoint: "ToolCheckpoint | None" = None,
|
|
258
|
+
) -> ToolResult:
|
|
259
|
+
if checkpoint:
|
|
260
|
+
# Resume from checkpoint
|
|
261
|
+
token = checkpoint.user_response["access_token"]
|
|
262
|
+
return await self._complete_oauth(token, params)
|
|
263
|
+
|
|
264
|
+
# First execution - generate auth URL
|
|
265
|
+
auth_url = self._build_auth_url(params)
|
|
266
|
+
raise HITLSuspend(
|
|
267
|
+
request_id=generate_id("hitl"),
|
|
268
|
+
request_type="external_auth",
|
|
269
|
+
resume_mode="continuation",
|
|
270
|
+
tool_state={"step": "awaiting_callback"},
|
|
271
|
+
metadata={"auth_url": auth_url, "callback_id": "..."},
|
|
272
|
+
)
|
|
273
|
+
"""
|
|
194
274
|
|
|
195
275
|
_name: str = "base_tool"
|
|
276
|
+
_display_name: str | None = None # Optional display name for UI
|
|
196
277
|
_description: str = "Base tool"
|
|
197
278
|
_parameters: dict[str, Any] = {
|
|
198
279
|
"type": "object",
|
|
@@ -201,10 +282,21 @@ class BaseTool:
|
|
|
201
282
|
}
|
|
202
283
|
_config: ToolConfig | None = None
|
|
203
284
|
|
|
285
|
+
# Continuation mode support
|
|
286
|
+
supports_continuation: bool = False
|
|
287
|
+
|
|
288
|
+
# Runtime context (set during execution)
|
|
289
|
+
_ctx: ToolContext | None = None
|
|
290
|
+
|
|
204
291
|
@property
|
|
205
292
|
def name(self) -> str:
|
|
206
293
|
return self._name
|
|
207
294
|
|
|
295
|
+
@property
|
|
296
|
+
def display_name(self) -> str:
|
|
297
|
+
"""Get display name for UI. Falls back to name if not set."""
|
|
298
|
+
return self._display_name or self._name
|
|
299
|
+
|
|
208
300
|
@property
|
|
209
301
|
def description(self) -> str:
|
|
210
302
|
return self._description
|
|
@@ -218,10 +310,92 @@ class BaseTool:
|
|
|
218
310
|
"""Get tool config. Returns default config if not set."""
|
|
219
311
|
return self._config or ToolConfig()
|
|
220
312
|
|
|
313
|
+
@property
|
|
314
|
+
def ctx(self) -> ToolContext | None:
|
|
315
|
+
"""Get current execution context."""
|
|
316
|
+
return self._ctx
|
|
317
|
+
|
|
318
|
+
def _set_ctx(self, ctx: ToolContext | None) -> None:
|
|
319
|
+
"""Set execution context. Called by framework before execute()."""
|
|
320
|
+
self._ctx = ctx
|
|
321
|
+
|
|
322
|
+
async def emit(self, block: Any) -> None:
|
|
323
|
+
"""Emit a BlockEvent.
|
|
324
|
+
|
|
325
|
+
Uses the current ToolContext's emit function if available.
|
|
326
|
+
Silently skips if no context is set (e.g., standalone testing).
|
|
327
|
+
|
|
328
|
+
Args:
|
|
329
|
+
block: BlockEvent to emit
|
|
330
|
+
"""
|
|
331
|
+
if self._ctx is not None:
|
|
332
|
+
await self._ctx.emit(block)
|
|
333
|
+
# If no ctx, silently skip - allows standalone testing
|
|
334
|
+
|
|
335
|
+
async def emit_hitl(self, hitl_id: str, data: dict[str, Any]) -> None:
|
|
336
|
+
"""Emit a HITL block.
|
|
337
|
+
|
|
338
|
+
Convenience method for tools that need user interaction.
|
|
339
|
+
Silently skips if no context is set.
|
|
340
|
+
|
|
341
|
+
Args:
|
|
342
|
+
hitl_id: Unique ID for this HITL request
|
|
343
|
+
data: Arbitrary data dict for frontend to render.
|
|
344
|
+
"""
|
|
345
|
+
if self._ctx is not None:
|
|
346
|
+
await self._ctx.emit_hitl(hitl_id, data)
|
|
347
|
+
|
|
221
348
|
async def execute(self, params: dict[str, Any], ctx: ToolContext) -> ToolResult:
|
|
222
|
-
"""
|
|
349
|
+
"""Execute tool (standard mode).
|
|
350
|
+
|
|
351
|
+
Override this method for standard tools.
|
|
352
|
+
For continuation-capable tools, override execute_resumable() instead.
|
|
353
|
+
"""
|
|
223
354
|
raise NotImplementedError("Subclass must implement execute()")
|
|
224
355
|
|
|
356
|
+
async def execute_resumable(
|
|
357
|
+
self,
|
|
358
|
+
params: dict[str, Any],
|
|
359
|
+
ctx: ToolContext,
|
|
360
|
+
checkpoint: Any | None = None, # ToolCheckpoint, use Any to avoid circular import
|
|
361
|
+
) -> ToolResult:
|
|
362
|
+
"""Execute tool with continuation support.
|
|
363
|
+
|
|
364
|
+
Override this method for tools that need to pause mid-execution
|
|
365
|
+
and resume later (e.g., OAuth, payment, external callbacks).
|
|
366
|
+
|
|
367
|
+
Args:
|
|
368
|
+
params: Tool parameters from LLM
|
|
369
|
+
ctx: Tool execution context
|
|
370
|
+
checkpoint: If resuming, contains saved state and user response.
|
|
371
|
+
None on first execution.
|
|
372
|
+
|
|
373
|
+
Returns:
|
|
374
|
+
ToolResult on completion
|
|
375
|
+
|
|
376
|
+
Raises:
|
|
377
|
+
HITLSuspend: To pause and wait for user/callback.
|
|
378
|
+
Set resume_mode="continuation" and provide tool_state.
|
|
379
|
+
|
|
380
|
+
Example:
|
|
381
|
+
async def execute_resumable(self, params, ctx, checkpoint=None):
|
|
382
|
+
if checkpoint:
|
|
383
|
+
# Resuming - use checkpoint.user_response
|
|
384
|
+
return await self._continue(checkpoint)
|
|
385
|
+
|
|
386
|
+
# First run - do initial work, then suspend
|
|
387
|
+
partial_result = await self._step_one(params)
|
|
388
|
+
raise HITLSuspend(
|
|
389
|
+
request_id=generate_id("hitl"),
|
|
390
|
+
resume_mode="continuation",
|
|
391
|
+
tool_state={"partial": partial_result},
|
|
392
|
+
...
|
|
393
|
+
)
|
|
394
|
+
"""
|
|
395
|
+
# Default: delegate to standard execute()
|
|
396
|
+
# Tools that support continuation should override this
|
|
397
|
+
return await self.execute(params, ctx)
|
|
398
|
+
|
|
225
399
|
def get_info(self) -> ToolInfo:
|
|
226
400
|
"""Get tool info."""
|
|
227
401
|
return ToolInfo(
|
|
@@ -239,8 +413,10 @@ class ToolConfig:
|
|
|
239
413
|
requires_permission: bool = False # Needs HITL approval
|
|
240
414
|
permission_message: str | None = None
|
|
241
415
|
stream_arguments: bool = False # Stream tool arguments to client
|
|
416
|
+
require_purpose: bool = False # Generate purpose via middleware (async LLM call)
|
|
417
|
+
|
|
242
418
|
|
|
243
419
|
# Retry configuration
|
|
244
420
|
max_retries: int = 0 # 0 = no retry
|
|
245
421
|
retry_delay: float = 1.0 # Base delay between retries (seconds)
|
|
246
|
-
retry_backoff: float = 2.0 # Exponential backoff multiplier
|
|
422
|
+
retry_backoff: float = 2.0 # Exponential backoff multiplier
|
aury/agents/hitl/__init__.py
CHANGED
|
@@ -14,6 +14,7 @@ from .exceptions import (
|
|
|
14
14
|
HITLTimeoutError,
|
|
15
15
|
HITLCancelledError,
|
|
16
16
|
HITLRequest,
|
|
17
|
+
ToolCheckpoint,
|
|
17
18
|
)
|
|
18
19
|
from .ask_user import (
|
|
19
20
|
AskUserTool,
|
|
@@ -44,6 +45,7 @@ __all__ = [
|
|
|
44
45
|
"HITLCancelledError",
|
|
45
46
|
# Types
|
|
46
47
|
"HITLRequest",
|
|
48
|
+
"ToolCheckpoint",
|
|
47
49
|
# Tools
|
|
48
50
|
"AskUserTool",
|
|
49
51
|
"ConfirmTool",
|
aury/agents/hitl/ask_user.py
CHANGED
|
@@ -87,13 +87,13 @@ class AskUserTool(BaseTool):
|
|
|
87
87
|
|
|
88
88
|
from ..core.logging import tool_logger as logger
|
|
89
89
|
|
|
90
|
-
# Generate
|
|
91
|
-
|
|
90
|
+
# Generate HITL ID
|
|
91
|
+
hitl_id = generate_id("hitl")
|
|
92
92
|
logger.info(
|
|
93
93
|
"ask_user HITL request",
|
|
94
94
|
extra={
|
|
95
95
|
"invocation_id": ctx.invocation_id,
|
|
96
|
-
"
|
|
96
|
+
"hitl_id": hitl_id,
|
|
97
97
|
"question": question[:100],
|
|
98
98
|
"has_options": options is not None,
|
|
99
99
|
},
|
|
@@ -101,10 +101,9 @@ class AskUserTool(BaseTool):
|
|
|
101
101
|
|
|
102
102
|
# Create HITL request data
|
|
103
103
|
request = HITLRequest(
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
message
|
|
107
|
-
options=options,
|
|
104
|
+
hitl_id=hitl_id,
|
|
105
|
+
hitl_type="ask_user",
|
|
106
|
+
data={"message": question, "options": options},
|
|
108
107
|
tool_name=self._name,
|
|
109
108
|
metadata={"context": context} if context else {},
|
|
110
109
|
)
|
|
@@ -121,18 +120,27 @@ class AskUserTool(BaseTool):
|
|
|
121
120
|
if ctx.backends and ctx.backends.invocation:
|
|
122
121
|
await ctx.backends.invocation.update(ctx.invocation_id, {
|
|
123
122
|
"status": "suspended",
|
|
124
|
-
"pending_request_id": request_id,
|
|
125
|
-
"pending_request_type": "ask_user",
|
|
126
|
-
"pending_request_data": request.to_dict(),
|
|
127
123
|
})
|
|
128
124
|
|
|
129
|
-
#
|
|
125
|
+
# Store HITL record
|
|
126
|
+
if ctx.backends and ctx.backends.hitl:
|
|
127
|
+
await ctx.backends.hitl.create(
|
|
128
|
+
hitl_id=hitl_id,
|
|
129
|
+
hitl_type="ask_user",
|
|
130
|
+
session_id=ctx.session_id,
|
|
131
|
+
invocation_id=ctx.invocation_id,
|
|
132
|
+
data={"message": question, "options": options},
|
|
133
|
+
metadata={"context": context} if context else None,
|
|
134
|
+
tool_name=self._name,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
# Emit HITL block to frontend
|
|
130
138
|
await ctx.emit(BlockEvent(
|
|
131
|
-
kind="
|
|
139
|
+
kind="hitl",
|
|
132
140
|
data={
|
|
133
|
-
"
|
|
134
|
-
"
|
|
135
|
-
"
|
|
141
|
+
"hitl_id": hitl_id,
|
|
142
|
+
"hitl_type": "ask_user",
|
|
143
|
+
"message": question,
|
|
136
144
|
"options": options,
|
|
137
145
|
"context": context,
|
|
138
146
|
},
|
|
@@ -143,14 +151,13 @@ class AskUserTool(BaseTool):
|
|
|
143
151
|
"Suspending execution for HITL ask_user",
|
|
144
152
|
extra={
|
|
145
153
|
"invocation_id": ctx.invocation_id,
|
|
146
|
-
"
|
|
154
|
+
"hitl_id": hitl_id,
|
|
147
155
|
},
|
|
148
156
|
)
|
|
149
157
|
raise HITLSuspend(
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
message
|
|
153
|
-
options=options,
|
|
158
|
+
hitl_id=hitl_id,
|
|
159
|
+
hitl_type="ask_user",
|
|
160
|
+
data={"message": question, "options": options},
|
|
154
161
|
tool_name=self._name,
|
|
155
162
|
metadata={"context": context} if context else {},
|
|
156
163
|
)
|
|
@@ -208,12 +215,12 @@ class ConfirmTool(BaseTool):
|
|
|
208
215
|
|
|
209
216
|
from ..core.logging import tool_logger as logger
|
|
210
217
|
|
|
211
|
-
|
|
218
|
+
hitl_id = generate_id("hitl")
|
|
212
219
|
logger.info(
|
|
213
220
|
"confirm HITL request",
|
|
214
221
|
extra={
|
|
215
222
|
"invocation_id": ctx.invocation_id,
|
|
216
|
-
"
|
|
223
|
+
"hitl_id": hitl_id,
|
|
217
224
|
"action": action[:100],
|
|
218
225
|
"risk_level": risk_level,
|
|
219
226
|
},
|
|
@@ -223,17 +230,19 @@ class ConfirmTool(BaseTool):
|
|
|
223
230
|
if details:
|
|
224
231
|
message += f"\n\nDetails: {details}"
|
|
225
232
|
|
|
233
|
+
hitl_data = {
|
|
234
|
+
"message": message,
|
|
235
|
+
"options": ["Yes, proceed", "No, cancel"],
|
|
236
|
+
"action": action,
|
|
237
|
+
"details": details,
|
|
238
|
+
"risk_level": risk_level,
|
|
239
|
+
}
|
|
240
|
+
|
|
226
241
|
request = HITLRequest(
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
options=["Yes, proceed", "No, cancel"],
|
|
242
|
+
hitl_id=hitl_id,
|
|
243
|
+
hitl_type="confirm",
|
|
244
|
+
data=hitl_data,
|
|
231
245
|
tool_name=self._name,
|
|
232
|
-
metadata={
|
|
233
|
-
"action": action,
|
|
234
|
-
"details": details,
|
|
235
|
-
"risk_level": risk_level,
|
|
236
|
-
},
|
|
237
246
|
)
|
|
238
247
|
|
|
239
248
|
# Checkpoint
|
|
@@ -248,21 +257,26 @@ class ConfirmTool(BaseTool):
|
|
|
248
257
|
if ctx.backends and ctx.backends.invocation:
|
|
249
258
|
await ctx.backends.invocation.update(ctx.invocation_id, {
|
|
250
259
|
"status": "suspended",
|
|
251
|
-
"pending_request_id": request_id,
|
|
252
|
-
"pending_request_type": "confirm",
|
|
253
|
-
"pending_request_data": request.to_dict(),
|
|
254
260
|
})
|
|
255
261
|
|
|
262
|
+
# Store HITL record
|
|
263
|
+
if ctx.backends and ctx.backends.hitl:
|
|
264
|
+
await ctx.backends.hitl.create(
|
|
265
|
+
hitl_id=hitl_id,
|
|
266
|
+
hitl_type="confirm",
|
|
267
|
+
session_id=ctx.session_id,
|
|
268
|
+
invocation_id=ctx.invocation_id,
|
|
269
|
+
data=hitl_data,
|
|
270
|
+
tool_name=self._name,
|
|
271
|
+
)
|
|
272
|
+
|
|
256
273
|
# Emit block
|
|
257
274
|
await ctx.emit(BlockEvent(
|
|
258
|
-
kind="
|
|
275
|
+
kind="hitl",
|
|
259
276
|
data={
|
|
260
|
-
"
|
|
261
|
-
"
|
|
262
|
-
|
|
263
|
-
"details": details,
|
|
264
|
-
"risk_level": risk_level,
|
|
265
|
-
"options": ["Yes, proceed", "No, cancel"],
|
|
277
|
+
"hitl_id": hitl_id,
|
|
278
|
+
"hitl_type": "confirm",
|
|
279
|
+
**hitl_data,
|
|
266
280
|
},
|
|
267
281
|
))
|
|
268
282
|
|
|
@@ -270,16 +284,14 @@ class ConfirmTool(BaseTool):
|
|
|
270
284
|
"Suspending execution for confirm",
|
|
271
285
|
extra={
|
|
272
286
|
"invocation_id": ctx.invocation_id,
|
|
273
|
-
"
|
|
287
|
+
"hitl_id": hitl_id,
|
|
274
288
|
},
|
|
275
289
|
)
|
|
276
290
|
raise HITLSuspend(
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
options=["Yes, proceed", "No, cancel"],
|
|
291
|
+
hitl_id=hitl_id,
|
|
292
|
+
hitl_type="confirm",
|
|
293
|
+
data=hitl_data,
|
|
281
294
|
tool_name=self._name,
|
|
282
|
-
metadata=request.metadata,
|
|
283
295
|
)
|
|
284
296
|
|
|
285
297
|
|