langgraph-agent-common 2.0.1__tar.gz → 2.0.3__tar.gz
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.
- {langgraph_agent_common-2.0.1 → langgraph_agent_common-2.0.3}/PKG-INFO +1 -1
- {langgraph_agent_common-2.0.1 → langgraph_agent_common-2.0.3}/pyproject.toml +1 -1
- {langgraph_agent_common-2.0.1 → langgraph_agent_common-2.0.3}/src/langgraph_a2a_api/adaptor/agent_executor.py +16 -16
- {langgraph_agent_common-2.0.1 → langgraph_agent_common-2.0.3}/src/langgraph_a2a_api/langgraph/validator.py +4 -1
- {langgraph_agent_common-2.0.1 → langgraph_agent_common-2.0.3}/src/langgraph_a2a_api/resources/static/css/style.css +137 -49
- {langgraph_agent_common-2.0.1 → langgraph_agent_common-2.0.3}/src/langgraph_a2a_api/resources/static/index.html +7 -24
- {langgraph_agent_common-2.0.1 → langgraph_agent_common-2.0.3}/src/langgraph_a2a_api/resources/static/js/app.js +209 -104
- {langgraph_agent_common-2.0.1 → langgraph_agent_common-2.0.3}/src/langgraph_agent_common.egg-info/PKG-INFO +1 -1
- {langgraph_agent_common-2.0.1 → langgraph_agent_common-2.0.3}/README.md +0 -0
- {langgraph_agent_common-2.0.1 → langgraph_agent_common-2.0.3}/setup.cfg +0 -0
- {langgraph_agent_common-2.0.1 → langgraph_agent_common-2.0.3}/src/langgraph_a2a_api/__init__.py +0 -0
- {langgraph_agent_common-2.0.1 → langgraph_agent_common-2.0.3}/src/langgraph_a2a_api/adaptor/__init__.py +0 -0
- {langgraph_agent_common-2.0.1 → langgraph_agent_common-2.0.3}/src/langgraph_a2a_api/adaptor/conversion.py +0 -0
- {langgraph_agent_common-2.0.1 → langgraph_agent_common-2.0.3}/src/langgraph_a2a_api/adaptor/sanitize_messages_for_llm.py +0 -0
- {langgraph_agent_common-2.0.1 → langgraph_agent_common-2.0.3}/src/langgraph_a2a_api/fastapi_application.py +0 -0
- {langgraph_agent_common-2.0.1 → langgraph_agent_common-2.0.3}/src/langgraph_a2a_api/langgraph/__init__.py +0 -0
- {langgraph_agent_common-2.0.1 → langgraph_agent_common-2.0.3}/src/langgraph_a2a_api/langgraph/checkpointer.py +0 -0
- {langgraph_agent_common-2.0.1 → langgraph_agent_common-2.0.3}/src/langgraph_a2a_api/langgraph/graph.py +0 -0
- {langgraph_agent_common-2.0.1 → langgraph_agent_common-2.0.3}/src/langgraph_a2a_api/langgraph/store.py +0 -0
- {langgraph_agent_common-2.0.1 → langgraph_agent_common-2.0.3}/src/langgraph_a2a_api/resources/openapi_schema.json +0 -0
- {langgraph_agent_common-2.0.1 → langgraph_agent_common-2.0.3}/src/langgraph_a2a_api/server.py +0 -0
- {langgraph_agent_common-2.0.1 → langgraph_agent_common-2.0.3}/src/langgraph_a2a_api/tasks/__init__.py +0 -0
- {langgraph_agent_common-2.0.1 → langgraph_agent_common-2.0.3}/src/langgraph_a2a_api/tasks/push_notification_config_store.py +0 -0
- {langgraph_agent_common-2.0.1 → langgraph_agent_common-2.0.3}/src/langgraph_a2a_api/tasks/task_store.py +0 -0
- {langgraph_agent_common-2.0.1 → langgraph_agent_common-2.0.3}/src/langgraph_a2a_api/utils/__init__.py +0 -0
- {langgraph_agent_common-2.0.1 → langgraph_agent_common-2.0.3}/src/langgraph_a2a_api/utils/lock.py +0 -0
- {langgraph_agent_common-2.0.1 → langgraph_agent_common-2.0.3}/src/langgraph_a2a_api/utils/types.py +0 -0
- {langgraph_agent_common-2.0.1 → langgraph_agent_common-2.0.3}/src/langgraph_agent_common.egg-info/SOURCES.txt +0 -0
- {langgraph_agent_common-2.0.1 → langgraph_agent_common-2.0.3}/src/langgraph_agent_common.egg-info/dependency_links.txt +0 -0
- {langgraph_agent_common-2.0.1 → langgraph_agent_common-2.0.3}/src/langgraph_agent_common.egg-info/requires.txt +0 -0
- {langgraph_agent_common-2.0.1 → langgraph_agent_common-2.0.3}/src/langgraph_agent_common.egg-info/top_level.txt +0 -0
- {langgraph_agent_common-2.0.1 → langgraph_agent_common-2.0.3}/src/langgraph_aux/__init__.py +0 -0
- {langgraph_agent_common-2.0.1 → langgraph_agent_common-2.0.3}/src/langgraph_aux/callbacks/__init__.py +0 -0
- {langgraph_agent_common-2.0.1 → langgraph_agent_common-2.0.3}/src/langgraph_aux/callbacks/talos_callback_handler.py +0 -0
- {langgraph_agent_common-2.0.1 → langgraph_agent_common-2.0.3}/src/langgraph_aux/utils/__init__.py +0 -0
- {langgraph_agent_common-2.0.1 → langgraph_agent_common-2.0.3}/src/langgraph_aux/utils/stream_output.py +0 -0
- {langgraph_agent_common-2.0.1 → langgraph_agent_common-2.0.3}/test/test_deepagents_a2a_server.py +0 -0
|
@@ -92,13 +92,8 @@ class GraphAgentExecutor(AgentExecutor):
|
|
|
92
92
|
if current_interrupts:
|
|
93
93
|
current_interrupt_ids = [c.id for c in current_interrupts]
|
|
94
94
|
if isinstance(langgraph_message.content, list) and len(current_interrupts) == len(langgraph_message.content) and all([isinstance(part, dict) and part.get("type") == "non_standard" and validate_HITLResponse(part.get("value")) for part in langgraph_message.content]):
|
|
95
|
-
|
|
96
|
-
if set(current_interrupt_ids)
|
|
97
|
-
resume_decisions = []
|
|
98
|
-
for hitl_id in current_interrupt_ids:
|
|
99
|
-
resume_decisions.append(decisions[hitl_id])
|
|
100
|
-
resume_content = {"decisions": resume_decisions}
|
|
101
|
-
else:
|
|
95
|
+
resume_content = {part.get("value", {}).get("hitl_id"): {"decisions": part.get("value", {}).get("decisions")} for part in langgraph_message.content}
|
|
96
|
+
if set(current_interrupt_ids) != set(resume_content.keys()):
|
|
102
97
|
raise ValueError(f"Interrupt resume content: {langgraph_message.content}. Must contain overall original decision ids {current_interrupt_ids}.")
|
|
103
98
|
else:
|
|
104
99
|
raise ValueError(f"Invalid interrupt resume content: {langgraph_message.content}. Must be a list with the same length as interrupt ids: {current_interrupt_ids}. Each item in the list must be a data with HITLResponse schema: {HITLResponseModel.schema()}.")
|
|
@@ -124,7 +119,9 @@ class GraphAgentExecutor(AgentExecutor):
|
|
|
124
119
|
finally:
|
|
125
120
|
self.run_controls.pop(task_id, None)
|
|
126
121
|
|
|
127
|
-
async def _get_interrupts(self, state: StateSnapshot, subgraphs: bool, seen_ids: set[str] =
|
|
122
|
+
async def _get_interrupts(self, state: StateSnapshot, subgraphs: bool, seen_ids: set[str] = None) -> list[Interrupt]:
|
|
123
|
+
if seen_ids is None:
|
|
124
|
+
seen_ids = set()
|
|
128
125
|
interrupts = []
|
|
129
126
|
for interrupt in state.interrupts:
|
|
130
127
|
if interrupt.id not in seen_ids:
|
|
@@ -235,11 +232,6 @@ class GraphAgentExecutor(AgentExecutor):
|
|
|
235
232
|
yield pre_yield
|
|
236
233
|
pre_yield = None
|
|
237
234
|
if step == "__interrupt__":
|
|
238
|
-
graph_status = TASK_STATE_INPUT_REQUIRED
|
|
239
|
-
pre_yield = {
|
|
240
|
-
"messages": [AIMessage(content=any_content_to_langgraph([{"hitl_id": interrupt.id, "content": interrupt.value} for interrupt in update]))],
|
|
241
|
-
"graph_status": graph_status,
|
|
242
|
-
}
|
|
243
235
|
break_out = True
|
|
244
236
|
else:
|
|
245
237
|
messages = self._normalize_messages(update.get("messages"))
|
|
@@ -256,7 +248,16 @@ class GraphAgentExecutor(AgentExecutor):
|
|
|
256
248
|
"graph_status": graph_status,
|
|
257
249
|
}
|
|
258
250
|
else:
|
|
259
|
-
|
|
251
|
+
current_state_after = await self.graph_with_ck.aget_state(config, subgraphs=subgraphs)
|
|
252
|
+
current_interrupts_after = await self._get_interrupts(current_state_after, subgraphs)
|
|
253
|
+
if not current_interrupts_after:
|
|
254
|
+
raise RuntimeError(f"__interrupt__ step encountered but no pending interrupts found in state.")
|
|
255
|
+
graph_status = TASK_STATE_INPUT_REQUIRED
|
|
256
|
+
yield {
|
|
257
|
+
"messages": [AIMessage(content=any_content_to_langgraph([{"hitl_id": interrupt.id, "content": interrupt.value} for interrupt in current_interrupts_after]))],
|
|
258
|
+
"graph_status": graph_status,
|
|
259
|
+
}
|
|
260
|
+
|
|
260
261
|
|
|
261
262
|
except GraphRecursionError as e:
|
|
262
263
|
if pre_yield is not None:
|
|
@@ -343,8 +344,7 @@ class GraphAgentExecutor(AgentExecutor):
|
|
|
343
344
|
if alive_task.status.state == TASK_STATE_INPUT_REQUIRED:
|
|
344
345
|
if alive_task.id == task_id:
|
|
345
346
|
await updater.start_work()
|
|
346
|
-
|
|
347
|
-
return (True, history_tasks)
|
|
347
|
+
return (True, [])
|
|
348
348
|
else:
|
|
349
349
|
return (False, [alive_task])
|
|
350
350
|
else:
|
|
@@ -27,11 +27,14 @@ class RespondDecision(BaseModel):
|
|
|
27
27
|
message: str
|
|
28
28
|
|
|
29
29
|
|
|
30
|
+
Decision = ApproveDecision | EditDecision | RejectDecision | RespondDecision
|
|
31
|
+
|
|
32
|
+
|
|
30
33
|
class HITLResponseModel(BaseModel):
|
|
31
34
|
model_config = ConfigDict(extra="forbid")
|
|
32
35
|
|
|
33
36
|
hitl_id: str
|
|
34
|
-
|
|
37
|
+
decisions: list[Decision]
|
|
35
38
|
|
|
36
39
|
|
|
37
40
|
def validate_HITLResponse(payload: Any) -> bool:
|
|
@@ -310,68 +310,172 @@ textarea:focus {
|
|
|
310
310
|
color: var(--warning);
|
|
311
311
|
}
|
|
312
312
|
|
|
313
|
-
.hitl-
|
|
313
|
+
.hitl-header-actions {
|
|
314
|
+
display: flex;
|
|
315
|
+
align-items: center;
|
|
316
|
+
gap: 8px;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
.hitl-progress-label {
|
|
320
|
+
font-size: 12px;
|
|
321
|
+
color: var(--text-secondary);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
.hitl-cards-container {
|
|
314
325
|
display: flex;
|
|
315
326
|
flex-direction: column;
|
|
316
327
|
gap: 12px;
|
|
317
328
|
}
|
|
318
329
|
|
|
319
|
-
.hitl-
|
|
320
|
-
|
|
330
|
+
.hitl-card {
|
|
331
|
+
background: var(--bg-tertiary);
|
|
332
|
+
border: 1px solid var(--border);
|
|
333
|
+
border-radius: 8px;
|
|
334
|
+
padding: 12px;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
.hitl-card-header {
|
|
338
|
+
display: flex;
|
|
339
|
+
align-items: center;
|
|
340
|
+
gap: 10px;
|
|
341
|
+
margin-bottom: 10px;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
.hitl-card-title {
|
|
345
|
+
font-size: 14px;
|
|
346
|
+
font-weight: 600;
|
|
347
|
+
color: var(--warning);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
.hitl-card-id {
|
|
351
|
+
font-size: 11px;
|
|
352
|
+
font-family: monospace;
|
|
321
353
|
color: var(--text-secondary);
|
|
322
|
-
|
|
354
|
+
background: var(--bg-primary);
|
|
355
|
+
padding: 2px 6px;
|
|
356
|
+
border-radius: 3px;
|
|
323
357
|
}
|
|
324
358
|
|
|
325
|
-
.
|
|
359
|
+
.hitl-card-status {
|
|
360
|
+
margin-left: auto;
|
|
361
|
+
font-size: 12px;
|
|
362
|
+
color: var(--text-secondary);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
.hitl-card-status.decided {
|
|
366
|
+
color: var(--success);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
.hitl-card-actions {
|
|
370
|
+
margin-bottom: 10px;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
.hitl-card-actions h6 {
|
|
374
|
+
font-size: 12px;
|
|
375
|
+
color: var(--text-secondary);
|
|
376
|
+
margin-bottom: 6px;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
.hitl-action-block {
|
|
380
|
+
padding: 10px 0;
|
|
381
|
+
border-bottom: 1px solid var(--border);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
.hitl-action-block:last-child {
|
|
385
|
+
border-bottom: none;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
.hitl-action-item {
|
|
326
389
|
background: var(--bg-primary);
|
|
327
|
-
padding: 12px;
|
|
328
390
|
border-radius: 4px;
|
|
391
|
+
padding: 8px;
|
|
392
|
+
margin-bottom: 6px;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
.hitl-action-name {
|
|
396
|
+
font-size: 13px;
|
|
397
|
+
font-weight: 500;
|
|
398
|
+
color: var(--accent);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
.hitl-action-args {
|
|
329
402
|
font-family: 'Monaco', 'Menlo', monospace;
|
|
330
|
-
font-size:
|
|
331
|
-
|
|
332
|
-
|
|
403
|
+
font-size: 11px;
|
|
404
|
+
color: var(--text-secondary);
|
|
405
|
+
margin-top: 4px;
|
|
333
406
|
white-space: pre-wrap;
|
|
334
407
|
word-break: break-word;
|
|
335
408
|
}
|
|
336
409
|
|
|
337
|
-
.hitl-decisions {
|
|
410
|
+
.hitl-card-decisions {
|
|
338
411
|
display: flex;
|
|
339
412
|
gap: 8px;
|
|
340
413
|
margin-bottom: 8px;
|
|
414
|
+
flex-wrap: wrap;
|
|
341
415
|
}
|
|
342
416
|
|
|
343
|
-
.hitl-
|
|
344
|
-
|
|
345
|
-
align-items: center;
|
|
346
|
-
justify-content: space-between;
|
|
347
|
-
margin-bottom: 12px;
|
|
348
|
-
padding: 8px;
|
|
349
|
-
background: var(--bg-primary);
|
|
350
|
-
border-radius: 4px;
|
|
417
|
+
.hitl-card-form {
|
|
418
|
+
margin-top: 8px;
|
|
351
419
|
}
|
|
352
420
|
|
|
353
|
-
.hitl-
|
|
354
|
-
|
|
355
|
-
|
|
421
|
+
.hitl-card-form.hidden {
|
|
422
|
+
display: none;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
.hitl-card-input {
|
|
426
|
+
width: 100%;
|
|
427
|
+
padding: 8px;
|
|
428
|
+
background: var(--bg-secondary);
|
|
356
429
|
border: 1px solid var(--border);
|
|
357
430
|
border-radius: 4px;
|
|
358
431
|
color: var(--text-primary);
|
|
359
|
-
|
|
432
|
+
font-family: 'Monaco', 'Menlo', monospace;
|
|
360
433
|
font-size: 12px;
|
|
434
|
+
resize: vertical;
|
|
361
435
|
}
|
|
362
436
|
|
|
363
|
-
.hitl-
|
|
364
|
-
|
|
437
|
+
.hitl-card-edit {
|
|
438
|
+
margin-top: 8px;
|
|
365
439
|
}
|
|
366
440
|
|
|
367
|
-
.hitl-
|
|
368
|
-
|
|
369
|
-
cursor: not-allowed;
|
|
441
|
+
.hitl-card-edit.hidden {
|
|
442
|
+
display: none;
|
|
370
443
|
}
|
|
371
444
|
|
|
372
|
-
.hitl-
|
|
373
|
-
font-size:
|
|
445
|
+
.hitl-card-edit h6 {
|
|
446
|
+
font-size: 12px;
|
|
374
447
|
color: var(--text-secondary);
|
|
448
|
+
margin-bottom: 4px;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
.hitl-card-edit-input {
|
|
452
|
+
width: 100%;
|
|
453
|
+
padding: 8px;
|
|
454
|
+
background: var(--bg-secondary);
|
|
455
|
+
border: 1px solid var(--border);
|
|
456
|
+
border-radius: 4px;
|
|
457
|
+
color: var(--text-primary);
|
|
458
|
+
font-family: 'Monaco', 'Menlo', monospace;
|
|
459
|
+
font-size: 12px;
|
|
460
|
+
resize: vertical;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
.json-display {
|
|
464
|
+
background: var(--bg-primary);
|
|
465
|
+
padding: 12px;
|
|
466
|
+
border-radius: 4px;
|
|
467
|
+
font-family: 'Monaco', 'Menlo', monospace;
|
|
468
|
+
font-size: 12px;
|
|
469
|
+
max-height: 200px;
|
|
470
|
+
overflow: auto;
|
|
471
|
+
white-space: pre-wrap;
|
|
472
|
+
word-break: break-word;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
.hitl-decisions {
|
|
476
|
+
display: flex;
|
|
477
|
+
gap: 8px;
|
|
478
|
+
margin-bottom: 8px;
|
|
375
479
|
}
|
|
376
480
|
|
|
377
481
|
.hitl-btn {
|
|
@@ -395,6 +499,10 @@ textarea:focus {
|
|
|
395
499
|
background: var(--accent);
|
|
396
500
|
}
|
|
397
501
|
|
|
502
|
+
.hitl-btn.edit-btn {
|
|
503
|
+
background: var(--warning);
|
|
504
|
+
}
|
|
505
|
+
|
|
398
506
|
.hitl-btn:hover {
|
|
399
507
|
opacity: 0.9;
|
|
400
508
|
}
|
|
@@ -403,27 +511,7 @@ textarea:focus {
|
|
|
403
511
|
box-shadow: 0 0 0 2px white;
|
|
404
512
|
}
|
|
405
513
|
|
|
406
|
-
#hitlResponseForm {
|
|
407
|
-
margin-top: 8px;
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
#hitlResponseForm.hidden {
|
|
411
|
-
display: none;
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
#hitlEditSection {
|
|
415
|
-
margin-top: 8px;
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
#hitlEditSection.hidden {
|
|
419
|
-
display: none;
|
|
420
|
-
}
|
|
421
514
|
|
|
422
|
-
#hitlEditSection h6 {
|
|
423
|
-
font-size: 12px;
|
|
424
|
-
color: var(--text-secondary);
|
|
425
|
-
margin-bottom: 4px;
|
|
426
|
-
}
|
|
427
515
|
|
|
428
516
|
.chat-input-container {
|
|
429
517
|
padding: 12px 16px;
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<title>A2A Chat UI</title>
|
|
7
|
-
<link rel="stylesheet" href="static/css/style.css?v=
|
|
7
|
+
<link rel="stylesheet" href="static/css/style.css?v=6">
|
|
8
8
|
</head>
|
|
9
9
|
<body>
|
|
10
10
|
<div class="app-container">
|
|
@@ -69,30 +69,13 @@
|
|
|
69
69
|
<div id="hitlPanel" class="hitl-panel hidden">
|
|
70
70
|
<div class="hitl-header">
|
|
71
71
|
<h4>Input Required (HITL)</h4>
|
|
72
|
-
<
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
<h5>Agent Response Parts:</h5>
|
|
77
|
-
<pre id="hitlPartsJson" class="json-display"></pre>
|
|
78
|
-
</div>
|
|
79
|
-
<div class="hitl-section">
|
|
80
|
-
<h5>HITL Response:</h5>
|
|
81
|
-
<div class="hitl-decisions">
|
|
82
|
-
<button class="hitl-btn approve-btn" data-type="approve">Approve</button>
|
|
83
|
-
<button class="hitl-btn reject-btn" data-type="reject">Reject</button>
|
|
84
|
-
<button class="hitl-btn respond-btn" data-type="respond">Respond</button>
|
|
85
|
-
</div>
|
|
86
|
-
<div id="hitlResponseForm" class="hidden">
|
|
87
|
-
<textarea id="hitlResponseInput" rows="3" placeholder="Enter response message..."></textarea>
|
|
88
|
-
<div id="hitlEditSection" class="hidden">
|
|
89
|
-
<h6>Edited Action (JSON):</h6>
|
|
90
|
-
<textarea id="hitlEditInput" rows="4" placeholder='{"name": "tool_name", "args": {}}'></textarea>
|
|
91
|
-
</div>
|
|
92
|
-
</div>
|
|
93
|
-
<button id="submitHitlBtn" class="btn-primary hidden">Submit Response</button>
|
|
72
|
+
<div class="hitl-header-actions">
|
|
73
|
+
<span id="hitlProgressLabel" class="hitl-progress-label"></span>
|
|
74
|
+
<button id="submitAllHitlBtn" class="btn-primary" disabled>Submit All</button>
|
|
75
|
+
<button id="closeHitlBtn" class="btn-small">Close</button>
|
|
94
76
|
</div>
|
|
95
77
|
</div>
|
|
78
|
+
<div id="hitlCardsContainer" class="hitl-cards-container"></div>
|
|
96
79
|
</div>
|
|
97
80
|
|
|
98
81
|
<div class="chat-input-container">
|
|
@@ -102,6 +85,6 @@
|
|
|
102
85
|
</main>
|
|
103
86
|
</div>
|
|
104
87
|
|
|
105
|
-
<script src="static/js/app.js?v=
|
|
88
|
+
<script src="static/js/app.js?v=6"></script>
|
|
106
89
|
</body>
|
|
107
90
|
</html>
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
currentTaskId: null,
|
|
8
8
|
isSending: false,
|
|
9
9
|
hitlState: null,
|
|
10
|
-
|
|
10
|
+
hitlDecisions: {},
|
|
11
11
|
abortController: null,
|
|
12
12
|
};
|
|
13
13
|
|
|
@@ -42,12 +42,9 @@
|
|
|
42
42
|
elements.clearChatBtn = document.getElementById('clearChatBtn');
|
|
43
43
|
elements.exportChatBtn = document.getElementById('exportChatBtn');
|
|
44
44
|
elements.hitlPanel = document.getElementById('hitlPanel');
|
|
45
|
-
elements.
|
|
46
|
-
elements.
|
|
47
|
-
elements.
|
|
48
|
-
elements.hitlEditSection = document.getElementById('hitlEditSection');
|
|
49
|
-
elements.hitlEditInput = document.getElementById('hitlEditInput');
|
|
50
|
-
elements.submitHitlBtn = document.getElementById('submitHitlBtn');
|
|
45
|
+
elements.hitlCardsContainer = document.getElementById('hitlCardsContainer');
|
|
46
|
+
elements.submitAllHitlBtn = document.getElementById('submitAllHitlBtn');
|
|
47
|
+
elements.hitlProgressLabel = document.getElementById('hitlProgressLabel');
|
|
51
48
|
elements.closeHitlBtn = document.getElementById('closeHitlBtn');
|
|
52
49
|
}
|
|
53
50
|
|
|
@@ -67,11 +64,7 @@
|
|
|
67
64
|
elements.clearChatBtn.addEventListener('click', clearChat);
|
|
68
65
|
elements.exportChatBtn.addEventListener('click', exportChat);
|
|
69
66
|
elements.closeHitlBtn.addEventListener('click', () => elements.hitlPanel.classList.add('hidden'));
|
|
70
|
-
|
|
71
|
-
document.querySelectorAll('.hitl-btn').forEach(btn => {
|
|
72
|
-
btn.addEventListener('click', () => selectHitlType(btn.dataset.type));
|
|
73
|
-
});
|
|
74
|
-
elements.submitHitlBtn.addEventListener('click', submitHitlResponse);
|
|
67
|
+
elements.submitAllHitlBtn.addEventListener('click', submitAllHitlResponses);
|
|
75
68
|
}
|
|
76
69
|
|
|
77
70
|
function setApiBaseUrl() {
|
|
@@ -116,6 +109,7 @@
|
|
|
116
109
|
renderChatHistory();
|
|
117
110
|
elements.hitlPanel.classList.add('hidden');
|
|
118
111
|
state.hitlState = null;
|
|
112
|
+
state.hitlDecisions = {};
|
|
119
113
|
}
|
|
120
114
|
}
|
|
121
115
|
|
|
@@ -232,6 +226,7 @@
|
|
|
232
226
|
removeLoadingMessage();
|
|
233
227
|
elements.hitlPanel.classList.add('hidden');
|
|
234
228
|
state.hitlState = null;
|
|
229
|
+
state.hitlDecisions = {};
|
|
235
230
|
state.isSending = false;
|
|
236
231
|
state.currentTaskId = null;
|
|
237
232
|
const context = getCurrentContext();
|
|
@@ -433,6 +428,7 @@
|
|
|
433
428
|
context.taskId = null;
|
|
434
429
|
elements.hitlPanel.classList.add('hidden');
|
|
435
430
|
state.hitlState = null;
|
|
431
|
+
state.hitlDecisions = {};
|
|
436
432
|
}
|
|
437
433
|
|
|
438
434
|
updateContextDisplay();
|
|
@@ -449,137 +445,243 @@
|
|
|
449
445
|
|
|
450
446
|
function showHitlPanel(task) {
|
|
451
447
|
state.hitlState = task;
|
|
452
|
-
state.
|
|
448
|
+
state.hitlDecisions = {}; // Map: hitl_id -> decision object
|
|
453
449
|
|
|
454
450
|
const parts = task.status?.message?.parts || task.artifacts?.flatMap(a => a.parts) || [];
|
|
455
451
|
const interruptParts = parts.filter(p => p.data && p.data.hitl_id);
|
|
456
452
|
|
|
457
453
|
state.hitlState.interruptParts = interruptParts;
|
|
458
|
-
state.hitlState.currentInterruptIndex = 0;
|
|
459
454
|
|
|
460
|
-
elements.
|
|
455
|
+
elements.hitlCardsContainer.innerHTML = '';
|
|
461
456
|
|
|
462
|
-
if (interruptParts.length
|
|
463
|
-
|
|
457
|
+
if (interruptParts.length === 0) {
|
|
458
|
+
// Fallback: show raw parts JSON
|
|
459
|
+
const fallbackCard = document.createElement('div');
|
|
460
|
+
fallbackCard.className = 'hitl-card';
|
|
461
|
+
fallbackCard.innerHTML = `
|
|
462
|
+
<div class="hitl-card-header"><span class="hitl-card-title">Raw Parts</span></div>
|
|
463
|
+
<pre class="json-display">${JSON.stringify(parts, null, 2)}</pre>
|
|
464
|
+
`;
|
|
465
|
+
elements.hitlCardsContainer.appendChild(fallbackCard);
|
|
466
|
+
} else {
|
|
467
|
+
interruptParts.forEach((part, index) => {
|
|
468
|
+
const card = createHitlCard(part, index);
|
|
469
|
+
elements.hitlCardsContainer.appendChild(card);
|
|
470
|
+
});
|
|
464
471
|
}
|
|
465
472
|
|
|
473
|
+
updateHitlProgress();
|
|
466
474
|
elements.hitlPanel.classList.remove('hidden');
|
|
467
|
-
elements.hitlResponseForm.classList.add('hidden');
|
|
468
|
-
elements.submitHitlBtn.classList.add('hidden');
|
|
469
|
-
elements.hitlEditSection.classList.add('hidden');
|
|
470
|
-
|
|
471
|
-
document.querySelectorAll('.hitl-btn').forEach(btn => btn.classList.remove('active'));
|
|
472
475
|
}
|
|
473
476
|
|
|
474
|
-
function
|
|
475
|
-
const
|
|
476
|
-
const
|
|
477
|
+
function createHitlCard(part, index) {
|
|
478
|
+
const hitlId = part.data.hitl_id;
|
|
479
|
+
const content = part.data.content || {};
|
|
480
|
+
const actionRequests = content.action_requests || [];
|
|
481
|
+
const reviewConfigs = content.review_configs || [];
|
|
477
482
|
|
|
478
|
-
|
|
483
|
+
const card = document.createElement('div');
|
|
484
|
+
card.className = 'hitl-card';
|
|
485
|
+
card.dataset.hitlId = hitlId;
|
|
486
|
+
card.dataset.index = index;
|
|
479
487
|
|
|
480
|
-
const
|
|
481
|
-
|
|
482
|
-
|
|
488
|
+
const header = document.createElement('div');
|
|
489
|
+
header.className = 'hitl-card-header';
|
|
490
|
+
header.innerHTML = `
|
|
491
|
+
<span class="hitl-card-title">HITL #${index + 1}</span>
|
|
492
|
+
<span class="hitl-card-id">${hitlId}</span>
|
|
493
|
+
<span class="hitl-card-status" id="cardStatus_${index}">⏳ 0/${actionRequests.length || 1}</span>
|
|
494
|
+
`;
|
|
495
|
+
card.appendChild(header);
|
|
483
496
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
497
|
+
if (actionRequests.length === 0) {
|
|
498
|
+
const allowedDecisions = ['approve', 'reject', 'respond'];
|
|
499
|
+
const actionBlock = createActionBlock(hitlId, index, 0, null, allowedDecisions);
|
|
500
|
+
card.appendChild(actionBlock);
|
|
501
|
+
} else {
|
|
502
|
+
actionRequests.forEach((action, actionIdx) => {
|
|
503
|
+
const config = reviewConfigs[actionIdx] || reviewConfigs[0] || {};
|
|
504
|
+
const allowedDecisions = config.allowed_decisions || ['approve', 'reject', 'respond'];
|
|
505
|
+
const actionBlock = createActionBlock(hitlId, index, actionIdx, action, allowedDecisions);
|
|
506
|
+
card.appendChild(actionBlock);
|
|
507
|
+
});
|
|
508
|
+
}
|
|
487
509
|
|
|
488
|
-
|
|
510
|
+
return card;
|
|
511
|
+
}
|
|
489
512
|
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
<button class="hitl-nav-btn" id="nextInterruptBtn" ${currentIndex === interruptParts.length - 1 ? 'disabled' : ''}>Next</button>
|
|
495
|
-
</div>
|
|
496
|
-
`;
|
|
513
|
+
function createActionBlock(hitlId, cardIndex, actionIdx, action, allowedDecisions) {
|
|
514
|
+
const blockId = `${cardIndex}_${actionIdx}`;
|
|
515
|
+
const block = document.createElement('div');
|
|
516
|
+
block.className = 'hitl-action-block';
|
|
497
517
|
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
518
|
+
if (action) {
|
|
519
|
+
block.innerHTML += `
|
|
520
|
+
<div class="hitl-action-item">
|
|
521
|
+
<span class="hitl-action-name">${action.name}</span>
|
|
522
|
+
<pre class="hitl-action-args">${JSON.stringify(action.args, null, 2)}</pre>
|
|
523
|
+
</div>
|
|
524
|
+
`;
|
|
504
525
|
}
|
|
505
526
|
|
|
506
|
-
document.
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
}
|
|
527
|
+
const decisionsDiv = document.createElement('div');
|
|
528
|
+
decisionsDiv.className = 'hitl-card-decisions';
|
|
529
|
+
allowedDecisions.forEach(type => {
|
|
530
|
+
const btn = document.createElement('button');
|
|
531
|
+
btn.className = `hitl-btn ${type}-btn`;
|
|
532
|
+
btn.dataset.type = type;
|
|
533
|
+
btn.dataset.blockId = blockId;
|
|
534
|
+
btn.textContent = type.charAt(0).toUpperCase() + type.slice(1);
|
|
535
|
+
btn.addEventListener('click', () => selectActionDecision(hitlId, cardIndex, actionIdx, type, block));
|
|
536
|
+
decisionsDiv.appendChild(btn);
|
|
511
537
|
});
|
|
538
|
+
block.appendChild(decisionsDiv);
|
|
539
|
+
|
|
540
|
+
const formDiv = document.createElement('div');
|
|
541
|
+
formDiv.className = 'hitl-card-form hidden';
|
|
542
|
+
formDiv.id = `actionForm_${blockId}`;
|
|
543
|
+
formDiv.innerHTML = `
|
|
544
|
+
<textarea class="hitl-card-input" id="actionInput_${blockId}" rows="2" placeholder="Enter message..."></textarea>
|
|
545
|
+
<div class="hitl-card-edit hidden" id="actionEdit_${blockId}">
|
|
546
|
+
<h6>Edited Action (JSON):</h6>
|
|
547
|
+
<textarea class="hitl-card-edit-input" id="actionEditInput_${blockId}" rows="3" placeholder='{"name": "tool_name", "args": {}}'></textarea>
|
|
548
|
+
</div>
|
|
549
|
+
`;
|
|
550
|
+
block.appendChild(formDiv);
|
|
512
551
|
|
|
513
|
-
|
|
514
|
-
if (state.hitlState.currentInterruptIndex < interruptParts.length - 1) {
|
|
515
|
-
state.hitlState.currentInterruptIndex++;
|
|
516
|
-
updateInterruptDisplay();
|
|
517
|
-
}
|
|
518
|
-
});
|
|
552
|
+
return block;
|
|
519
553
|
}
|
|
520
554
|
|
|
521
|
-
function
|
|
522
|
-
|
|
523
|
-
document.querySelectorAll('.hitl-btn').forEach(btn => {
|
|
555
|
+
function selectActionDecision(hitlId, cardIndex, actionIdx, type, block) {
|
|
556
|
+
block.querySelectorAll('.hitl-btn').forEach(btn => {
|
|
524
557
|
btn.classList.toggle('active', btn.dataset.type === type);
|
|
525
558
|
});
|
|
526
559
|
|
|
527
|
-
|
|
528
|
-
|
|
560
|
+
const blockId = `${cardIndex}_${actionIdx}`;
|
|
561
|
+
const form = document.getElementById(`actionForm_${blockId}`);
|
|
562
|
+
const input = document.getElementById(`actionInput_${blockId}`);
|
|
563
|
+
const editSection = document.getElementById(`actionEdit_${blockId}`);
|
|
529
564
|
|
|
530
565
|
if (type === 'approve') {
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
elements.hitlEditSection.classList.add('hidden');
|
|
566
|
+
form.classList.add('hidden');
|
|
567
|
+
editSection.classList.add('hidden');
|
|
534
568
|
} else if (type === 'reject') {
|
|
535
|
-
|
|
536
|
-
|
|
569
|
+
form.classList.remove('hidden');
|
|
570
|
+
input.placeholder = 'Rejection reason (optional)...';
|
|
571
|
+
editSection.classList.add('hidden');
|
|
537
572
|
} else if (type === 'respond') {
|
|
538
|
-
|
|
539
|
-
|
|
573
|
+
form.classList.remove('hidden');
|
|
574
|
+
input.placeholder = 'Response message...';
|
|
575
|
+
editSection.classList.add('hidden');
|
|
576
|
+
} else if (type === 'edit') {
|
|
577
|
+
form.classList.remove('hidden');
|
|
578
|
+
input.placeholder = 'Edit message (optional)...';
|
|
579
|
+
editSection.classList.remove('hidden');
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
if (!state.hitlDecisions[hitlId]) {
|
|
583
|
+
state.hitlDecisions[hitlId] = {};
|
|
584
|
+
}
|
|
585
|
+
state.hitlDecisions[hitlId][actionIdx] = { type, blockId };
|
|
586
|
+
|
|
587
|
+
const interruptPart = state.hitlState.interruptParts.find(p => p.data.hitl_id === hitlId);
|
|
588
|
+
const totalActions = (interruptPart.data.content?.action_requests || []).length || 1;
|
|
589
|
+
const decidedCount = Object.keys(state.hitlDecisions[hitlId]).length;
|
|
590
|
+
const statusEl = document.getElementById(`cardStatus_${cardIndex}`);
|
|
591
|
+
if (statusEl) {
|
|
592
|
+
if (decidedCount >= totalActions) {
|
|
593
|
+
statusEl.textContent = `✓ ${decidedCount}/${totalActions}`;
|
|
594
|
+
statusEl.className = 'hitl-card-status decided';
|
|
595
|
+
} else {
|
|
596
|
+
statusEl.textContent = `⏳ ${decidedCount}/${totalActions}`;
|
|
597
|
+
statusEl.className = 'hitl-card-status';
|
|
598
|
+
}
|
|
540
599
|
}
|
|
600
|
+
|
|
601
|
+
updateHitlProgress();
|
|
541
602
|
}
|
|
542
603
|
|
|
543
|
-
function
|
|
544
|
-
|
|
604
|
+
function updateHitlProgress() {
|
|
605
|
+
const interruptParts = state.hitlState?.interruptParts || [];
|
|
606
|
+
let totalActions = 0;
|
|
607
|
+
let decidedActions = 0;
|
|
608
|
+
interruptParts.forEach(p => {
|
|
609
|
+
const actions = (p.data.content?.action_requests || []).length || 1;
|
|
610
|
+
totalActions += actions;
|
|
611
|
+
const cardDecisions = state.hitlDecisions[p.data.hitl_id];
|
|
612
|
+
if (cardDecisions) {
|
|
613
|
+
decidedActions += Object.keys(cardDecisions).length;
|
|
614
|
+
}
|
|
615
|
+
});
|
|
545
616
|
|
|
546
|
-
|
|
547
|
-
|
|
617
|
+
elements.hitlProgressLabel.textContent = `${decidedActions}/${totalActions} decided`;
|
|
618
|
+
elements.submitAllHitlBtn.disabled = decidedActions < totalActions;
|
|
619
|
+
}
|
|
548
620
|
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
return;
|
|
552
|
-
}
|
|
621
|
+
function submitAllHitlResponses() {
|
|
622
|
+
if (!state.hitlState) return;
|
|
553
623
|
|
|
554
|
-
const
|
|
555
|
-
const currentPart = interruptParts[currentIndex];
|
|
556
|
-
const hitlId = currentPart.data.hitl_id;
|
|
624
|
+
const interruptParts = state.hitlState.interruptParts || [];
|
|
557
625
|
|
|
558
|
-
|
|
626
|
+
const responseParts = interruptParts.map((part, idx) => {
|
|
627
|
+
const hitlId = part.data.hitl_id;
|
|
628
|
+
const cardDecisions = state.hitlDecisions[hitlId] || {};
|
|
629
|
+
const actionRequests = part.data.content?.action_requests || [];
|
|
630
|
+
const totalActions = actionRequests.length || 1;
|
|
631
|
+
|
|
632
|
+
const decisions = [];
|
|
633
|
+
for (let actionIdx = 0; actionIdx < totalActions; actionIdx++) {
|
|
634
|
+
const decisionInfo = cardDecisions[actionIdx];
|
|
635
|
+
if (!decisionInfo) {
|
|
636
|
+
addSystemMessage(`Decision missing for HITL #${idx + 1}, action #${actionIdx + 1}`);
|
|
637
|
+
return null;
|
|
638
|
+
}
|
|
559
639
|
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
640
|
+
let decision = { type: decisionInfo.type };
|
|
641
|
+
const blockId = decisionInfo.blockId;
|
|
642
|
+
const inputEl = document.getElementById(`actionInput_${blockId}`);
|
|
643
|
+
const editInputEl = document.getElementById(`actionEditInput_${blockId}`);
|
|
644
|
+
const message = inputEl ? inputEl.value.trim() : '';
|
|
645
|
+
const editJson = editInputEl ? editInputEl.value.trim() : '';
|
|
646
|
+
|
|
647
|
+
if (decisionInfo.type === 'reject' && message) {
|
|
648
|
+
decision.message = message;
|
|
649
|
+
} else if (decisionInfo.type === 'respond') {
|
|
650
|
+
if (!message) {
|
|
651
|
+
addSystemMessage(`Response message is required for HITL #${idx + 1}, action #${actionIdx + 1}`);
|
|
652
|
+
return null;
|
|
653
|
+
}
|
|
654
|
+
decision.message = message;
|
|
655
|
+
} else if (decisionInfo.type === 'edit') {
|
|
656
|
+
if (editJson) {
|
|
657
|
+
try {
|
|
658
|
+
decision.edited_action = JSON.parse(editJson);
|
|
659
|
+
} catch (e) {
|
|
660
|
+
addSystemMessage(`Invalid JSON in edited action for HITL #${idx + 1}, action #${actionIdx + 1}`);
|
|
661
|
+
return null;
|
|
662
|
+
}
|
|
663
|
+
} else {
|
|
664
|
+
addSystemMessage(`Edited action JSON is required for HITL #${idx + 1}, action #${actionIdx + 1}`);
|
|
665
|
+
return null;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
decisions.push(decision);
|
|
571
670
|
}
|
|
572
|
-
decision = { type: 'respond', message: message };
|
|
573
|
-
}
|
|
574
671
|
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
672
|
+
return {
|
|
673
|
+
data: {
|
|
674
|
+
hitl_id: hitlId,
|
|
675
|
+
decisions: decisions,
|
|
676
|
+
}
|
|
677
|
+
};
|
|
678
|
+
});
|
|
679
|
+
|
|
680
|
+
// Check if any part failed validation
|
|
681
|
+
if (responseParts.some(p => p === null)) return;
|
|
579
682
|
|
|
580
683
|
const messageMeta = getJsonInput('messageMeta') || {};
|
|
581
684
|
const config = getJsonInput('config') || {};
|
|
582
|
-
|
|
583
685
|
const taskId = state.hitlState.id;
|
|
584
686
|
const messageId = generateId();
|
|
585
687
|
|
|
@@ -589,20 +691,23 @@
|
|
|
589
691
|
contextId: state.currentContextId,
|
|
590
692
|
taskId: taskId,
|
|
591
693
|
role: 'ROLE_USER',
|
|
592
|
-
parts:
|
|
593
|
-
data: hitlResponse,
|
|
594
|
-
}],
|
|
694
|
+
parts: responseParts,
|
|
595
695
|
metadata: messageMeta,
|
|
596
696
|
},
|
|
597
697
|
configuration: config,
|
|
598
698
|
metadata: getJsonInput('taskMeta') || {},
|
|
599
699
|
};
|
|
600
700
|
|
|
701
|
+
// Clean up HITL panel
|
|
601
702
|
elements.hitlPanel.classList.add('hidden');
|
|
703
|
+
const decisionSummary = interruptParts.map(p => {
|
|
704
|
+
const d = state.hitlDecisions[p.data.hitl_id];
|
|
705
|
+
return `${p.data.hitl_id}: ${d.type}`;
|
|
706
|
+
}).join(', ');
|
|
602
707
|
state.hitlState = null;
|
|
603
|
-
state.
|
|
708
|
+
state.hitlDecisions = {};
|
|
604
709
|
|
|
605
|
-
addUserMessage(`[HITL Response]
|
|
710
|
+
addUserMessage(`[HITL Response] ${decisionSummary}`);
|
|
606
711
|
addLoadingMessage();
|
|
607
712
|
|
|
608
713
|
state.isSending = true;
|
|
File without changes
|
|
File without changes
|
{langgraph_agent_common-2.0.1 → langgraph_agent_common-2.0.3}/src/langgraph_a2a_api/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{langgraph_agent_common-2.0.1 → langgraph_agent_common-2.0.3}/src/langgraph_a2a_api/server.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{langgraph_agent_common-2.0.1 → langgraph_agent_common-2.0.3}/src/langgraph_a2a_api/utils/lock.py
RENAMED
|
File without changes
|
{langgraph_agent_common-2.0.1 → langgraph_agent_common-2.0.3}/src/langgraph_a2a_api/utils/types.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{langgraph_agent_common-2.0.1 → langgraph_agent_common-2.0.3}/src/langgraph_aux/utils/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{langgraph_agent_common-2.0.1 → langgraph_agent_common-2.0.3}/test/test_deepagents_a2a_server.py
RENAMED
|
File without changes
|