Skip to content

Commit fe13c25

Browse files
committed
temporal fix tool call ui
Signed-off-by: Dmytro Rashko <dmitriy.rashko@amdocs.com>
1 parent e28ef5e commit fe13c25

File tree

2 files changed

+85
-4
lines changed

2 files changed

+85
-4
lines changed

go/adk/pkg/a2a/temporal_executor.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,11 @@ func (e *TemporalExecutor) forwardStreamEvent(
166166
switch event.Type {
167167
case streaming.EventTypeToken:
168168
msg := a2atype.NewMessage(a2atype.MessageRoleAgent, a2atype.TextPart{Text: event.Data})
169+
// Mark as partial so the task store filters out individual token messages.
170+
partialMeta := map[string]any{"adk_partial": true}
171+
msg.Metadata = partialMeta
169172
status := a2atype.NewStatusUpdateEvent(reqCtx, a2atype.TaskStateWorking, msg)
173+
status.Metadata = partialMeta
170174
if err := queue.Write(ctx, status); err != nil {
171175
e.log.V(1).Info("Failed to forward token event", "error", err)
172176
}
@@ -253,7 +257,15 @@ func (e *TemporalExecutor) writeFinalStatus(
253257
state = a2atype.TaskStateCompleted
254258
text := "Task completed"
255259
if len(result.Response) > 0 {
256-
text = string(result.Response)
260+
// Response is a serialized LLMResponse; extract the content field.
261+
var llmResp struct {
262+
Content string `json:"content"`
263+
}
264+
if err := json.Unmarshal(result.Response, &llmResp); err == nil && llmResp.Content != "" {
265+
text = llmResp.Content
266+
} else {
267+
text = string(result.Response)
268+
}
257269
}
258270
msg = a2atype.NewMessage(a2atype.MessageRoleAgent, a2atype.TextPart{Text: text})
259271

go/adk/pkg/temporal/workflows.go

Lines changed: 72 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -189,17 +189,17 @@ func processMessage(
189189
Content: llmResp.Content,
190190
})
191191

192-
// Build A2A task with proper history containing both user and agent messages.
192+
// Build A2A task with full history including tool calls/results.
193193
responseBytes, _ := json.Marshal(llmResp)
194194
now := workflow.Now(ctx)
195195

196-
userMsg := a2atype.NewMessage(a2atype.MessageRoleUser, a2atype.TextPart{Text: userText})
196+
taskHistory := buildA2AHistory(*history)
197197
agentMsg := a2atype.NewMessage(a2atype.MessageRoleAgent, a2atype.TextPart{Text: llmResp.Content})
198198

199199
task := &a2atype.Task{
200200
ID: a2atype.TaskID(req.SessionID),
201201
ContextID: req.SessionID,
202-
History: []*a2atype.Message{userMsg, agentMsg},
202+
History: taskHistory,
203203
Status: a2atype.TaskStatus{
204204
State: a2atype.TaskStateCompleted,
205205
Message: agentMsg,
@@ -477,6 +477,75 @@ func taskActivityOptions() workflow.ActivityOptions {
477477
}
478478
}
479479

480+
// buildA2AHistory converts the internal conversation history into A2A Messages
481+
// suitable for task persistence. Each entry becomes a properly typed message:
482+
// user text, assistant text, function_call DataParts, and function_response DataParts.
483+
func buildA2AHistory(history []conversationEntry) []*a2atype.Message {
484+
// Build a mapping from tool call ID to tool name for result entries.
485+
toolNameByID := make(map[string]string)
486+
for _, entry := range history {
487+
for _, tc := range entry.ToolCalls {
488+
toolNameByID[tc.ID] = tc.Name
489+
}
490+
}
491+
492+
var msgs []*a2atype.Message
493+
for _, entry := range history {
494+
switch entry.Role {
495+
case "user":
496+
msgs = append(msgs, a2atype.NewMessage(a2atype.MessageRoleUser,
497+
a2atype.TextPart{Text: entry.Content}))
498+
499+
case "assistant":
500+
if len(entry.ToolCalls) > 0 {
501+
// Emit each tool call as a separate message with function_call metadata.
502+
for _, tc := range entry.ToolCalls {
503+
var args map[string]any
504+
if len(tc.Args) > 0 {
505+
_ = json.Unmarshal(tc.Args, &args)
506+
}
507+
msgs = append(msgs, a2atype.NewMessage(a2atype.MessageRoleAgent,
508+
a2atype.DataPart{
509+
Data: map[string]any{
510+
"id": tc.ID,
511+
"name": tc.Name,
512+
"args": args,
513+
},
514+
Metadata: map[string]any{"adk_type": "function_call"},
515+
}))
516+
}
517+
}
518+
if entry.Content != "" && len(entry.ToolCalls) == 0 {
519+
msgs = append(msgs, a2atype.NewMessage(a2atype.MessageRoleAgent,
520+
a2atype.TextPart{Text: entry.Content}))
521+
}
522+
523+
case "tool":
524+
var result any
525+
if len(entry.ToolResult) > 0 {
526+
_ = json.Unmarshal(entry.ToolResult, &result)
527+
}
528+
toolName := toolNameByID[entry.ToolCallID]
529+
if toolName == "" {
530+
toolName = entry.ToolCallID
531+
}
532+
msgs = append(msgs, a2atype.NewMessage(a2atype.MessageRoleAgent,
533+
a2atype.DataPart{
534+
Data: map[string]any{
535+
"id": entry.ToolCallID,
536+
"name": toolName,
537+
"response": map[string]any{
538+
"isError": false,
539+
"result": result,
540+
},
541+
},
542+
Metadata: map[string]any{"adk_type": "function_response"},
543+
}))
544+
}
545+
}
546+
return msgs
547+
}
548+
480549
// extractTextFromA2AMessage extracts the text content from a JSON-encoded A2A Message.
481550
// Falls back to treating the bytes as plain text if parsing fails.
482551
func extractTextFromA2AMessage(msgBytes []byte) string {

0 commit comments

Comments
 (0)