feat(codex): add support for mcpToolCall item type (#159)
This commit is contained in:
@@ -779,25 +779,25 @@ func (a *Adapter) handleNotification(method string, params json.RawMessage) {
|
||||
return
|
||||
}
|
||||
// Map Codex item types to tool call updates
|
||||
// Item types: "userMessage", "agentMessage", "commandExecution", "fileChange", "reasoning"
|
||||
// Item types: "userMessage", "agentMessage", "commandExecution", "fileChange", "reasoning", "mcpToolCall"
|
||||
switch p.Item.Type {
|
||||
case "commandExecution":
|
||||
case CodexItemCommandExecution:
|
||||
args := map[string]any{
|
||||
"command": p.Item.Command,
|
||||
"cwd": p.Item.Cwd,
|
||||
}
|
||||
normalizedPayload := a.normalizer.NormalizeToolCall("commandExecution", args)
|
||||
normalizedPayload := a.normalizer.NormalizeToolCall(CodexItemCommandExecution, args)
|
||||
a.sendUpdate(AgentEvent{
|
||||
Type: streams.EventTypeToolCall,
|
||||
SessionID: threadID,
|
||||
OperationID: turnID,
|
||||
ToolCallID: p.Item.ID,
|
||||
ToolName: "commandExecution",
|
||||
ToolName: CodexItemCommandExecution,
|
||||
ToolTitle: p.Item.Command,
|
||||
ToolStatus: "running",
|
||||
NormalizedPayload: normalizedPayload,
|
||||
})
|
||||
case "fileChange":
|
||||
case CodexItemFileChange:
|
||||
// Build title from file paths
|
||||
var title string
|
||||
if len(p.Item.Changes) > 0 {
|
||||
@@ -817,13 +817,41 @@ func (a *Adapter) handleNotification(method string, params json.RawMessage) {
|
||||
args := map[string]any{
|
||||
"changes": changesArgs,
|
||||
}
|
||||
normalizedPayload := a.normalizer.NormalizeToolCall("fileChange", args)
|
||||
normalizedPayload := a.normalizer.NormalizeToolCall(CodexItemFileChange, args)
|
||||
a.sendUpdate(AgentEvent{
|
||||
Type: streams.EventTypeToolCall,
|
||||
SessionID: threadID,
|
||||
OperationID: turnID,
|
||||
ToolCallID: p.Item.ID,
|
||||
ToolName: "fileChange",
|
||||
ToolName: CodexItemFileChange,
|
||||
ToolTitle: title,
|
||||
ToolStatus: "running",
|
||||
NormalizedPayload: normalizedPayload,
|
||||
})
|
||||
case CodexItemMcpToolCall:
|
||||
// Handle MCP tool calls (e.g., create_task_plan_kandev)
|
||||
// Build title from server/tool
|
||||
title := p.Item.Tool
|
||||
if p.Item.Server != "" {
|
||||
title = p.Item.Server + "/" + p.Item.Tool
|
||||
}
|
||||
// Parse arguments as map for normalization
|
||||
var argsMap map[string]any
|
||||
if len(p.Item.Arguments) > 0 {
|
||||
_ = json.Unmarshal(p.Item.Arguments, &argsMap)
|
||||
}
|
||||
args := map[string]any{
|
||||
"server": p.Item.Server,
|
||||
"tool": p.Item.Tool,
|
||||
"arguments": argsMap,
|
||||
}
|
||||
normalizedPayload := a.normalizer.NormalizeToolCall(CodexItemMcpToolCall, args)
|
||||
a.sendUpdate(AgentEvent{
|
||||
Type: streams.EventTypeToolCall,
|
||||
SessionID: threadID,
|
||||
OperationID: turnID,
|
||||
ToolCallID: p.Item.ID,
|
||||
ToolName: p.Item.Tool, // Use the actual MCP tool name for frontend display
|
||||
ToolTitle: title,
|
||||
ToolStatus: "running",
|
||||
NormalizedPayload: normalizedPayload,
|
||||
@@ -840,7 +868,7 @@ func (a *Adapter) handleNotification(method string, params json.RawMessage) {
|
||||
return
|
||||
}
|
||||
// Only send updates for tool-like items
|
||||
if p.Item.Type == "commandExecution" || p.Item.Type == "fileChange" {
|
||||
if p.Item.Type == CodexItemCommandExecution || p.Item.Type == CodexItemFileChange || p.Item.Type == CodexItemMcpToolCall {
|
||||
status := "complete"
|
||||
if p.Item.Status == "failed" {
|
||||
status = "error"
|
||||
@@ -856,13 +884,13 @@ func (a *Adapter) handleNotification(method string, params json.RawMessage) {
|
||||
// Include normalized payload for fallback message creation
|
||||
// This ensures the correct message type is used if the message doesn't exist yet
|
||||
switch p.Item.Type {
|
||||
case "commandExecution":
|
||||
case CodexItemCommandExecution:
|
||||
args := map[string]any{
|
||||
"command": p.Item.Command,
|
||||
"cwd": p.Item.Cwd,
|
||||
}
|
||||
update.NormalizedPayload = a.normalizer.NormalizeToolCall("commandExecution", args)
|
||||
case "fileChange":
|
||||
update.NormalizedPayload = a.normalizer.NormalizeToolCall(CodexItemCommandExecution, args)
|
||||
case CodexItemFileChange:
|
||||
changesArgs := make([]any, 0, len(p.Item.Changes))
|
||||
for _, c := range p.Item.Changes {
|
||||
changesArgs = append(changesArgs, map[string]any{
|
||||
@@ -873,11 +901,32 @@ func (a *Adapter) handleNotification(method string, params json.RawMessage) {
|
||||
args := map[string]any{
|
||||
"changes": changesArgs,
|
||||
}
|
||||
update.NormalizedPayload = a.normalizer.NormalizeToolCall("fileChange", args)
|
||||
update.NormalizedPayload = a.normalizer.NormalizeToolCall(CodexItemFileChange, args)
|
||||
case CodexItemMcpToolCall:
|
||||
// Parse arguments and result for normalization
|
||||
var argsMap map[string]any
|
||||
if len(p.Item.Arguments) > 0 {
|
||||
_ = json.Unmarshal(p.Item.Arguments, &argsMap)
|
||||
}
|
||||
var resultMap any
|
||||
if len(p.Item.Result) > 0 {
|
||||
_ = json.Unmarshal(p.Item.Result, &resultMap)
|
||||
}
|
||||
args := map[string]any{
|
||||
"server": p.Item.Server,
|
||||
"tool": p.Item.Tool,
|
||||
"arguments": argsMap,
|
||||
"result": resultMap,
|
||||
}
|
||||
if p.Item.ToolError != "" {
|
||||
args["error"] = p.Item.ToolError
|
||||
}
|
||||
update.NormalizedPayload = a.normalizer.NormalizeToolCall(CodexItemMcpToolCall, args)
|
||||
update.ToolName = p.Item.Tool // Use the actual MCP tool name
|
||||
}
|
||||
|
||||
// Include diff for file changes
|
||||
if p.Item.Type == "fileChange" && len(p.Item.Changes) > 0 {
|
||||
if p.Item.Type == CodexItemFileChange && len(p.Item.Changes) > 0 {
|
||||
var diffs []string
|
||||
for _, c := range p.Item.Changes {
|
||||
if c.Diff != "" {
|
||||
|
||||
@@ -12,6 +12,7 @@ const (
|
||||
CodexItemReasoning = "reasoning"
|
||||
CodexItemUserMessage = "userMessage"
|
||||
CodexItemAgentMessage = "agentMessage"
|
||||
CodexItemMcpToolCall = "mcpToolCall"
|
||||
)
|
||||
|
||||
// Normalizer converts Codex protocol tool data to NormalizedPayload.
|
||||
@@ -31,6 +32,8 @@ func (n *Normalizer) NormalizeToolCall(toolName string, args map[string]any) *st
|
||||
return n.normalizeCommand(args)
|
||||
case CodexItemFileChange:
|
||||
return n.normalizeFileChange(args)
|
||||
case CodexItemMcpToolCall:
|
||||
return n.normalizeMcpToolCall(args)
|
||||
default:
|
||||
return n.normalizeGeneric(toolName, args)
|
||||
}
|
||||
@@ -109,6 +112,37 @@ func (n *Normalizer) normalizeFileChange(args map[string]any) *streams.Normalize
|
||||
return streams.NewModifyFile(filePath, mutations)
|
||||
}
|
||||
|
||||
// normalizeMcpToolCall converts Codex mcpToolCall item data.
|
||||
// MCP tool calls have server, tool, arguments, result, and error fields.
|
||||
func (n *Normalizer) normalizeMcpToolCall(args map[string]any) *streams.NormalizedPayload {
|
||||
server := shared.GetString(args, "server")
|
||||
tool := shared.GetString(args, "tool")
|
||||
|
||||
// Create a generic payload with the MCP tool info
|
||||
// The tool name is formatted as "server/tool" for display
|
||||
toolName := tool
|
||||
if server != "" {
|
||||
toolName = server + "/" + tool
|
||||
}
|
||||
|
||||
// Include the arguments in the payload for display
|
||||
displayArgs := map[string]any{
|
||||
"server": server,
|
||||
"tool": tool,
|
||||
}
|
||||
if arguments, ok := args["arguments"]; ok {
|
||||
displayArgs["arguments"] = arguments
|
||||
}
|
||||
if result, ok := args["result"]; ok {
|
||||
displayArgs["result"] = result
|
||||
}
|
||||
if toolError, ok := args["error"]; ok {
|
||||
displayArgs["error"] = toolError
|
||||
}
|
||||
|
||||
return streams.NewGeneric(toolName, displayArgs)
|
||||
}
|
||||
|
||||
// normalizeGeneric wraps unknown items as generic.
|
||||
func (n *Normalizer) normalizeGeneric(toolName string, args map[string]any) *streams.NormalizedPayload {
|
||||
// Use factory function
|
||||
|
||||
@@ -177,7 +177,7 @@ type TurnStartResult struct {
|
||||
// Item represents a Codex item (message, command, file change, etc.)
|
||||
type Item struct {
|
||||
ID string `json:"id"`
|
||||
Type string `json:"type"` // "userMessage", "agentMessage", "commandExecution", "fileChange", "reasoning"
|
||||
Type string `json:"type"` // "userMessage", "agentMessage", "commandExecution", "fileChange", "reasoning", "mcpToolCall"
|
||||
Status string `json:"status"` // "inProgress", "completed", "failed"
|
||||
|
||||
// For commandExecution type
|
||||
@@ -194,6 +194,13 @@ type Item struct {
|
||||
// or plain strings. FlexibleContent handles both formats.
|
||||
Summary FlexibleContent `json:"summary,omitempty"`
|
||||
Content FlexibleContent `json:"content,omitempty"`
|
||||
|
||||
// For mcpToolCall type
|
||||
Server string `json:"server,omitempty"`
|
||||
Tool string `json:"tool,omitempty"`
|
||||
Arguments json.RawMessage `json:"arguments,omitempty"`
|
||||
Result json.RawMessage `json:"result,omitempty"`
|
||||
ToolError string `json:"error,omitempty"` // Named ToolError to avoid conflict with Error type
|
||||
}
|
||||
|
||||
// ContentPart represents a content part in a Codex item.
|
||||
|
||||
Reference in New Issue
Block a user