agent-api-server 2.1.10__tar.gz → 2.1.12__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.
Files changed (92) hide show
  1. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/PKG-INFO +1 -1
  2. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/acp_bridge/go/agent.go +19 -16
  3. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/acp_bridge/go/agenthub-acp +0 -0
  4. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/acp_bridge/go/main.go +40 -107
  5. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/api/v1/thread.py +0 -150
  6. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/pyproject.toml +1 -1
  7. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/README.md +0 -0
  8. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/.idea/.gitignore +0 -0
  9. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/.idea/agent_api_server.iml +0 -0
  10. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/.idea/inspectionProfiles/Project_Default.xml +0 -0
  11. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/.idea/inspectionProfiles/profiles_settings.xml +0 -0
  12. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/.idea/misc.xml +0 -0
  13. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/.idea/modules.xml +0 -0
  14. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/.idea/vcs.xml +0 -0
  15. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/.idea/workspace.xml +0 -0
  16. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/__init__.py +0 -0
  17. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/a2a_bridge/__init__.py +0 -0
  18. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/a2a_bridge/agent_card_builder.py +0 -0
  19. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/a2a_bridge/call_a2a_client.py +0 -0
  20. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/a2a_bridge/langgraph_executor.py +0 -0
  21. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/a2a_bridge/redis_task_store.py +0 -0
  22. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/a2a_bridge/server.py +0 -0
  23. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/acp_bridge/go/Makefile +0 -0
  24. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/acp_bridge/go/go.mod +0 -0
  25. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/acp_bridge/go/go.sum +0 -0
  26. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/agent_registration.py +0 -0
  27. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/api/__init__.py +0 -0
  28. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/api/v1/__init__.py +0 -0
  29. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/api/v1/api.py +0 -0
  30. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/api/v1/config.py +0 -0
  31. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/api/v1/graph.py +0 -0
  32. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/api/v1/schema.py +0 -0
  33. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/app.py +0 -0
  34. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/cache/__init__.py +0 -0
  35. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/cache/redis_cache.py +0 -0
  36. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/callback_handler.py +0 -0
  37. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/client/css/styles.css +0 -0
  38. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/client/favicon.ico +0 -0
  39. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/client/index.html +0 -0
  40. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/client/js/app.js +0 -0
  41. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/client/js/index.umd.js +0 -0
  42. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/config_center/config_center.py +0 -0
  43. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/configs/__init__.py +0 -0
  44. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/configs/config.py +0 -0
  45. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/dynamic_llm/__init__.py +0 -0
  46. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/dynamic_llm/dynamic_llm.py +0 -0
  47. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/listener/__init__.py +0 -0
  48. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/listener/base.py +0 -0
  49. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/listener/nats_listener.py +0 -0
  50. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/listener/redis_listener.py +0 -0
  51. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/log/__init__.py +0 -0
  52. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/log/formatters.py +0 -0
  53. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/log/logging.json +0 -0
  54. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/mcp_convert/__init__.py +0 -0
  55. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/mcp_convert/mcp_convert.py +0 -0
  56. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/mcp_server.py +0 -0
  57. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/memory/__init__.py +0 -0
  58. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/memory/postgres.py +0 -0
  59. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/orchestrator/__init__.py +0 -0
  60. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/orchestrator/agent_registry.py +0 -0
  61. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/orchestrator/modes/__init__.py +0 -0
  62. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/orchestrator/modes/chain.py +0 -0
  63. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/orchestrator/modes/discussion.py +0 -0
  64. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/orchestrator/orchestrator.py +0 -0
  65. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/orchestrator/types.py +0 -0
  66. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/register/__init__.py +0 -0
  67. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/register/register.py +0 -0
  68. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/service.py +0 -0
  69. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/service_hub/service_hub.py +0 -0
  70. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/service_hub/service_hub_test.py +0 -0
  71. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/services/__init__.py +0 -0
  72. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/services/config_builder.py +0 -0
  73. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/services/stream_service.py +0 -0
  74. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/shared/__init__.py +0 -0
  75. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/shared/ase.py +0 -0
  76. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/shared/base_model.py +0 -0
  77. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/shared/common.py +0 -0
  78. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/shared/decode_token.py +0 -0
  79. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/shared/detect_message.py +0 -0
  80. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/shared/exceptions.py +0 -0
  81. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/shared/get_model_info.py +0 -0
  82. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/shared/graph_loader.py +0 -0
  83. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/shared/message.py +0 -0
  84. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/shared/model_config.py +0 -0
  85. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/shared/util_func.py +0 -0
  86. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/sso_service/__init__.py +0 -0
  87. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/sso_service/sdk/__init__.py +0 -0
  88. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/sso_service/sdk/client.py +0 -0
  89. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/sso_service/sdk/credential.py +0 -0
  90. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/sso_service/sdk/encoding.py +0 -0
  91. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/sso_service/sso_service.py +0 -0
  92. {agent_api_server-2.1.10 → agent_api_server-2.1.12}/agent_api_server/startup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: agent-api-server
3
- Version: 2.1.10
3
+ Version: 2.1.12
4
4
  Summary: A Langgraph agent API server that implements Langgraph agent's web capabilities and can interact with chatbot
5
5
  Author: zijie.zhang@advantech.com.cn
6
6
  Requires-Python: >=3.10,<3.13
@@ -39,6 +39,12 @@ type SessionState struct {
39
39
  UpdatedAt string `json:"updated_at,omitempty"`
40
40
  }
41
41
 
42
+ // pendingToolCall holds buffered tool call info until the tool result is available.
43
+ type pendingToolCall struct {
44
+ name string
45
+ args interface{}
46
+ }
47
+
42
48
  // AgentHubACPAgent implements the ACP protocol over stdio, proxying to an HTTP backend.
43
49
  type AgentHubACPAgent struct {
44
50
  serviceURL string
@@ -54,7 +60,7 @@ type AgentHubACPAgent struct {
54
60
  mu sync.Mutex
55
61
  httpClient *http.Client
56
62
  sessions map[string]*SessionState
57
- pendingToolCalls map[string]map[string]string // sessionID -> tcID -> tcName
63
+ pendingToolCalls map[string]map[string]*pendingToolCall // sessionID -> tcID -> info
58
64
  streamedNodes map[string]map[string]bool // sessionID -> set of nodes
59
65
  }
60
66
 
@@ -77,7 +83,7 @@ func NewAgentHubACPAgent(
77
83
  extraInputs: extraInputs,
78
84
  streamMode: streamMode,
79
85
  sessions: make(map[string]*SessionState),
80
- pendingToolCalls: make(map[string]map[string]string),
86
+ pendingToolCalls: make(map[string]map[string]*pendingToolCall),
81
87
  streamedNodes: make(map[string]map[string]bool),
82
88
  httpClient: &http.Client{
83
89
  Timeout: 300 * time.Second,
@@ -348,24 +354,22 @@ func (a *AgentHubACPAgent) processSSEEvent(sessionID, raw string) {
348
354
  a.mu.Lock()
349
355
  pending := a.pendingToolCalls[sessionID]
350
356
  if pending == nil {
351
- pending = make(map[string]string)
357
+ pending = make(map[string]*pendingToolCall)
352
358
  a.pendingToolCalls[sessionID] = pending
353
359
  }
354
360
  a.mu.Unlock()
355
361
  for _, tc := range toolCalls {
356
- tcID, _ := tc["id"].(string)
357
- if tcID == "" {
358
- tcID = uuid.New().String()
359
- }
362
+ // Always use UUID for tcID to avoid invalid IDs from LangChain
363
+ tcID := uuid.New().String()
360
364
  tcName, _ := tc["name"].(string)
361
365
  if tcName == "" {
362
366
  tcName = "unknown"
363
367
  }
364
368
  tcArgs := tc["args"]
369
+ // Buffer tool call info; emit complete tool_call when tools_message arrives
365
370
  a.mu.Lock()
366
- pending[tcID] = tcName
371
+ pending[tcID] = &pendingToolCall{name: tcName, args: tcArgs}
367
372
  a.mu.Unlock()
368
- a.sendStartToolCall(sessionID, tcID, tcName, tcArgs, nil, "")
369
373
  }
370
374
  } else if text != "" {
371
375
  a.mu.Lock()
@@ -390,12 +394,13 @@ func (a *AgentHubACPAgent) processSSEEvent(sessionID, raw string) {
390
394
  a.mu.Unlock()
391
395
 
392
396
  if len(pending) > 0 {
393
- for tcID, tcName := range pending {
397
+ for tcID, info := range pending {
394
398
  output := text
395
399
  if output == "" {
396
- output = tcName + " completed"
400
+ output = info.name + " completed"
397
401
  }
398
- a.sendUpdateToolCall(sessionID, tcID, "completed", output)
402
+ // Emit single complete tool_call with rawInput + rawOutput
403
+ a.sendStartToolCall(sessionID, tcID, info.name, info.args, output, "completed")
399
404
  }
400
405
  a.mu.Lock()
401
406
  delete(a.pendingToolCalls, sessionID)
@@ -412,10 +417,8 @@ func (a *AgentHubACPAgent) processSSEEvent(sessionID, raw string) {
412
417
  }
413
418
  }
414
419
  for _, tc := range toolCalls {
415
- tcID, _ := tc["id"].(string)
416
- if tcID == "" {
417
- tcID = uuid.New().String()
418
- }
420
+ // Always use UUID for tcID to avoid invalid IDs from LangChain
421
+ tcID := uuid.New().String()
419
422
  tcName, _ := tc["name"].(string)
420
423
  if tcName == "" {
421
424
  tcName = "unknown"
@@ -301,7 +301,6 @@ func (a *AgentHubACPAgent) handleLoadSession(req jsonrpcRequest) *jsonrpcRespons
301
301
  func (a *AgentHubACPAgent) loadSessionHistory(sessionID, cwd string) {
302
302
  params := url.Values{
303
303
  "user_input_field": {a.userInputField},
304
- "include_node_history": {"true"},
305
304
  }
306
305
  resp, err := a.doRequest("GET", "/api/v1/thread/"+sessionID+"/history", nil, params)
307
306
  if err != nil {
@@ -317,129 +316,48 @@ func (a *AgentHubACPAgent) loadSessionHistory(sessionID, cwd string) {
317
316
 
318
317
  var data struct {
319
318
  Messages []map[string]interface{} `json:"messages"`
320
- NodeExecutions []map[string]interface{} `json:"node_executions"`
321
319
  }
322
320
  if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
323
321
  log.Printf("Failed to decode session history: %v", err)
324
322
  return
325
323
  }
326
324
 
327
- if len(data.NodeExecutions) > 0 {
328
- a.replayNodeExecutions(sessionID, data.NodeExecutions)
329
- } else {
330
- a.replayMessages(sessionID, data.Messages)
331
- }
325
+ a.replayMessages(sessionID, data.Messages)
332
326
 
333
327
  log.Printf("Loaded session %s with %d messages", sessionID, len(data.Messages))
334
328
  }
335
329
 
336
- // replayNodeExecutions replays session history using node_executions data.
337
- // Each nodeExecution represents one turn (user question + AI response).
338
- // We process messages in order within each turn to maintain proper conversation flow.
339
- func (a *AgentHubACPAgent) replayNodeExecutions(sessionID string, nodeExecutions []map[string]interface{}) {
340
- shownContents := make(map[string]bool)
341
-
342
- for _, execInfo := range nodeExecutions {
343
- nodesRaw, _ := execInfo["nodes"].([]interface{})
344
- execMessagesRaw, _ := execInfo["messages"].([]interface{})
345
330
 
346
- var nodes []string
347
- for _, n := range nodesRaw {
348
- if s, ok := n.(string); ok {
349
- nodes = append(nodes, s)
331
+ // replayMessages replays session history using simple messages list.
332
+ func (a *AgentHubACPAgent) replayMessages(sessionID string, messages []map[string]interface{}) {
333
+ // Pre-scan: collect tool results by name for including in tool_call events
334
+ toolResultsByName := make(map[string][]string)
335
+ for _, msg := range messages {
336
+ if msgType, _ := msg["type"].(string); msgType == "tool" {
337
+ toolName, _ := msg["name"].(string)
338
+ if toolName == "" {
339
+ toolName = "unknown"
350
340
  }
351
- }
352
-
353
- var execMessages []map[string]interface{}
354
- for _, m := range execMessagesRaw {
355
- if mm, ok := m.(map[string]interface{}); ok {
356
- execMessages = append(execMessages, mm)
341
+ if c, _ := msg["content"].(string); c != "" {
342
+ toolResultsByName[toolName] = append(toolResultsByName[toolName], c)
357
343
  }
358
344
  }
359
-
360
- // Process messages in order: first emit human message, then AI/tool responses
361
- humanEmitted := false
362
- for _, msg := range execMessages {
363
- msgType, _ := msg["type"].(string)
364
- msgContent, _ := msg["content"].(string)
365
- msgName, _ := msg["name"].(string)
366
-
367
- if msgType == "human" {
368
- // Emit user message
369
- if msgContent != "" {
370
- a.sendUserMessage(sessionID, msgContent)
371
- humanEmitted = true
372
- }
373
- continue
374
- }
375
-
376
- // For AI and tool messages, check if this is a node output
377
- if nodeSet := make(map[string]bool); len(nodes) > 0 {
378
- for _, n := range nodes {
379
- nodeSet[n] = true
380
- }
381
- if nodeSet[msgName] && msgContent != "" {
382
- // This is a node output - emit as tool call + agent message
383
- if !shownContents[msgContent] {
384
- a.sendStartToolCall(sessionID, uuid.New().String(), msgName, nil, nil, "completed")
385
- a.sendAgentMessage(sessionID, fmt.Sprintf("[%s]\n%s", msgName, msgContent))
386
- shownContents[msgContent] = true
387
- }
388
- continue
389
- }
390
- }
391
-
392
- // Handle AI messages with tool_calls
393
- if msgType == "ai" {
394
- if toolCallsRaw, ok := msg["tool_calls"].([]interface{}); ok {
395
- for _, tc := range toolCallsRaw {
396
- if tcm, ok := tc.(map[string]interface{}); ok {
397
- tcName, _ := tcm["name"].(string)
398
- if tcName == "" {
399
- tcName = "unknown"
400
- }
401
- tcArgs := tcm["args"]
402
- a.sendStartToolCall(sessionID, uuid.New().String(), tcName, tcArgs, nil, "completed")
403
- }
404
- }
405
- }
406
- if msgContent != "" && !shownContents[msgContent] {
407
- a.sendAgentMessage(sessionID, msgContent)
408
- shownContents[msgContent] = true
409
- }
410
- } else if msgType == "tool" {
411
- toolName, _ := msg["name"].(string)
412
- if toolName == "" {
413
- toolName = "unknown"
414
- }
415
- if msgContent != "" && !shownContents[msgContent] {
416
- a.sendAgentMessage(sessionID, fmt.Sprintf("[tool:%s] %s", toolName, msgContent))
417
- shownContents[msgContent] = true
418
- }
419
- }
420
- }
421
-
422
- // If no human message was emitted but we have messages, something is wrong
423
- // (this shouldn't happen with proper turn detection)
424
- if !humanEmitted && len(execMessages) > 0 {
425
- log.Printf("Warning: turn with no human message emitted, nodes=%v", nodes)
426
- }
427
345
  }
428
- }
346
+ toolResultIdx := make(map[string]int)
429
347
 
430
- // replayMessages replays session history using simple messages list.
431
- func (a *AgentHubACPAgent) replayMessages(sessionID string, messages []map[string]interface{}) {
432
348
  for _, msg := range messages {
433
349
  msgType, _ := msg["type"].(string)
434
350
  msgContent, _ := msg["content"].(string)
351
+ msgName, _ := msg["name"].(string)
435
352
 
436
353
  switch msgType {
437
354
  case "human":
438
355
  a.sendUserMessage(sessionID, msgContent)
439
356
 
440
357
  case "ai":
441
- // Handle tool_calls in AI messages
442
- if toolCallsRaw, ok := msg["tool_calls"].([]interface{}); ok {
358
+ // Check if this AI message has tool_calls (ReAct Agent pattern)
359
+ if toolCallsRaw, ok := msg["tool_calls"].([]interface{}); ok && len(toolCallsRaw) > 0 {
360
+ // ReAct Agent pattern: AI message with tool_calls
443
361
  for _, tc := range toolCallsRaw {
444
362
  if tcm, ok := tc.(map[string]interface{}); ok {
445
363
  tcName, _ := tcm["name"].(string)
@@ -447,20 +365,35 @@ func (a *AgentHubACPAgent) replayMessages(sessionID string, messages []map[strin
447
365
  tcName = "unknown"
448
366
  }
449
367
  tcArgs := tcm["args"]
450
- a.sendStartToolCall(sessionID, uuid.New().String(), tcName, tcArgs, nil, "completed")
368
+ tcID := uuid.New().String()
369
+ // Include tool output in the tool_call event
370
+ var rawOutput interface{}
371
+ if results, ok := toolResultsByName[tcName]; ok {
372
+ idx := toolResultIdx[tcName]
373
+ if idx < len(results) {
374
+ rawOutput = results[idx]
375
+ toolResultIdx[tcName] = idx + 1
376
+ }
377
+ }
378
+ a.sendStartToolCall(sessionID, tcID, tcName, tcArgs, rawOutput, "completed")
451
379
  }
452
380
  }
453
- }
454
- if msgContent != "" {
381
+ if msgContent != "" {
382
+ a.sendAgentMessage(sessionID, msgContent)
383
+ }
384
+ } else if msgName != "" && msgContent != "" {
385
+ // Node pattern: AI message without tool_calls, but has name (node name)
386
+ tcID := uuid.New().String()
387
+ a.sendStartToolCall(sessionID, tcID, msgName, nil, msgContent, "completed")
388
+ a.sendAgentMessage(sessionID, fmt.Sprintf("[%s]\n%s", msgName, msgContent))
389
+ } else if msgContent != "" {
390
+ // Regular AI message
455
391
  a.sendAgentMessage(sessionID, msgContent)
456
392
  }
457
393
 
458
394
  case "tool":
459
- toolName, _ := msg["name"].(string)
460
- if toolName == "" {
461
- toolName = "unknown"
462
- }
463
- a.sendAgentMessage(sessionID, fmt.Sprintf("[tool:%s] %s", toolName, msgContent))
395
+ // Tool results are already included in the preceding AI message's
396
+ // tool_call rawOutput, so skip standalone tool messages.
464
397
  }
465
398
  }
466
399
  }
@@ -626,7 +626,6 @@ async def get_thread_history(
626
626
  thread_id: str,
627
627
  limit: int = Query(50, ge=1, le=200, description="Max messages to return"),
628
628
  user_input_field: Optional[str] = Query(None, description="User input field name"),
629
- include_node_history: bool = Query(False, description="Include node execution history"),
630
629
  ) -> Dict[str, Any]:
631
630
  try:
632
631
  from agent_api_server.memory.postgres import AsyncPostgresCheckpointer
@@ -706,155 +705,6 @@ async def get_thread_history(
706
705
  "next_config": state.next if hasattr(state, "next") else None,
707
706
  }
708
707
 
709
- if include_node_history:
710
- node_executions = []
711
- snapshots = []
712
- async for snapshot in graph_instance.aget_state_history(config):
713
- snapshots.append(snapshot)
714
-
715
- snapshots.reverse()
716
-
717
- from langchain_core.messages import HumanMessage as _HumanMessage
718
-
719
- # Detect turns based on state history snapshots.
720
- # Each snapshot represents a checkpoint; we detect new user
721
- # inputs by comparing either string values or HumanMessage
722
- # counts (when input_field points to a list like "messages").
723
- turn_boundaries = [] # list of (msg_index_of_human_msg, user_text)
724
-
725
- prev_human_count = 0
726
- prev_user_input_str = None
727
-
728
- for i, snapshot in enumerate(snapshots):
729
- msgs = snapshot.values.get("messages", []) if snapshot.values else []
730
- if not (snapshot.values and input_field):
731
- continue
732
-
733
- val = snapshot.values.get(input_field)
734
- current_user_input = None
735
-
736
- if val and isinstance(val, str):
737
- current_user_input = val.strip()
738
- if (current_user_input is not None and
739
- current_user_input != prev_user_input_str):
740
- # Find the last HumanMessage in current msgs
741
- human_idx = None
742
- for j in range(len(msgs) - 1, -1, -1):
743
- if isinstance(msgs[j], _HumanMessage):
744
- human_idx = j
745
- break
746
- if human_idx is not None:
747
- turn_boundaries.append((human_idx, current_user_input))
748
- prev_user_input_str = current_user_input
749
-
750
- elif isinstance(val, list):
751
- # input_field points to the messages list itself.
752
- # Find new HumanMessages by comparing count with previous snapshot.
753
- def _is_human_message(m):
754
- """Check if a message is a HumanMessage (supports both object and dict formats)."""
755
- if isinstance(m, _HumanMessage):
756
- return True
757
- if isinstance(m, dict) and m.get("type") == "human":
758
- return True
759
- if hasattr(m, "type") and getattr(m, "type", None) == "human":
760
- return True
761
- return False
762
-
763
- def _get_message_content(m):
764
- """Get content from a message (supports both object and dict formats)."""
765
- if isinstance(m, dict):
766
- return m.get("content", "")
767
- return m.content if hasattr(m, "content") else str(m)
768
-
769
- human_indices_in_msgs = [
770
- j for j, m in enumerate(val)
771
- if _is_human_message(m)
772
- ]
773
- human_count = len(human_indices_in_msgs)
774
- if human_count > prev_human_count:
775
- # New HumanMessage(s) detected.
776
- # Use the ACTUAL index in the messages list,
777
- # not human_count - 1 (which is wrong when AI/tool
778
- # messages are interspersed).
779
- for k in range(prev_human_count, human_count):
780
- actual_idx = human_indices_in_msgs[k]
781
- new_human = val[actual_idx]
782
- content_str = _get_message_content(new_human)
783
- if isinstance(content_str, str):
784
- content_str = content_str.strip()
785
- else:
786
- content_str = str(content_str)
787
- turn_boundaries.append((actual_idx, content_str))
788
- prev_human_count = human_count
789
-
790
- # Deduplicate boundaries by msg index (keep first occurrence)
791
- seen_indices = set()
792
- unique_boundaries = []
793
- for idx, text in turn_boundaries:
794
- if idx not in seen_indices:
795
- seen_indices.add(idx)
796
- unique_boundaries.append((idx, text))
797
- turn_boundaries = unique_boundaries
798
-
799
- # Build node_executions from turn boundaries
800
- final_snapshot = snapshots[-1] if snapshots else None
801
- if final_snapshot and final_snapshot.values:
802
- final_msgs = final_snapshot.values.get("messages", [])
803
-
804
- # Collect all node names from all snapshots for each turn range
805
- for turn_idx, (human_msg_idx, user_text) in enumerate(turn_boundaries):
806
- # Determine message range for this turn
807
- start_idx = human_msg_idx
808
- if turn_idx + 1 < len(turn_boundaries):
809
- end_idx = turn_boundaries[turn_idx + 1][0]
810
- else:
811
- end_idx = len(final_msgs)
812
-
813
- # Collect node names that executed in this turn's snapshots
814
- turn_nodes = []
815
- for snapshot in snapshots:
816
- s_msgs = snapshot.values.get("messages", []) if snapshot.values else []
817
- s_len = len(s_msgs)
818
- # This snapshot belongs to this turn if it overlaps
819
- # with the turn's message range
820
- if s_len > start_idx and (turn_idx + 1 >= len(turn_boundaries) or s_len <= end_idx + 1):
821
- node_names = [task.name for task in snapshot.tasks if task.name != "__start__"] if snapshot.tasks else []
822
- for n in node_names:
823
- if n not in turn_nodes:
824
- turn_nodes.append(n)
825
-
826
- # Build turn messages
827
- turn_messages = [{
828
- "type": "human",
829
- "content": user_text,
830
- "name": None,
831
- "tool_calls": [],
832
- }]
833
-
834
- # Add non-human messages in range (skip the human message at start_idx
835
- # since we already added it above)
836
- for msg in final_msgs[start_idx + 1:end_idx]:
837
- turn_messages.append({
838
- "type": getattr(msg, "type", "unknown"),
839
- "content": msg.content if hasattr(msg, "content") else str(msg),
840
- "name": getattr(msg, "name", None),
841
- "tool_calls": [
842
- {"name": tc.get("name", ""), "args": tc.get("args", {})}
843
- for tc in (getattr(msg, "tool_calls", []) or [])
844
- ] if hasattr(msg, "tool_calls") else [],
845
- })
846
-
847
- node_executions.append({
848
- "step": turn_idx,
849
- "nodes": turn_nodes,
850
- "messages": turn_messages,
851
- })
852
- elif not turn_boundaries:
853
- # Fallback: no turn boundaries detected, use simple messages
854
- pass
855
-
856
- result["node_executions"] = node_executions
857
-
858
708
  return result
859
709
 
860
710
  except HTTPException:
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "agent-api-server"
3
- version = "2.1.10"
3
+ version = "2.1.12"
4
4
  description = "A Langgraph agent API server that implements Langgraph agent's web capabilities and can interact with chatbot"
5
5
  authors = [
6
6
  {name = "zijie.zhang@advantech.com.cn"}