Add a Captures tab showing screen captures from the task's agent
This commit is contained in:
+40
-10
@@ -169,8 +169,11 @@ func (m *Manager) codexItemCompleted(ctx context.Context, l *live, s *codex.Sess
|
||||
return m.emit(ctx, l, agentActor, "agent.vendor", payload)
|
||||
}
|
||||
|
||||
content, isError := codexToolResult(item)
|
||||
err := m.emit(ctx, l, agentActor, "agent.tool_result", map[string]any{"tool_use_id": item.ID, "is_error": isError, "content": content})
|
||||
content, isError, err := codexToolResult(item)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = m.emit(ctx, l, agentActor, "agent.tool_result", map[string]any{"tool_use_id": item.ID, "is_error": isError, "content": content})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -210,24 +213,51 @@ func imageAlt(prompt string) string {
|
||||
return alt
|
||||
}
|
||||
|
||||
func codexToolResult(item codex.ThreadItem) (string, bool) {
|
||||
func codexToolResult(item codex.ThreadItem) (any, bool, error) {
|
||||
status := codexStatus(item)
|
||||
switch item.Type {
|
||||
case codex.ThreadItemTypeCommandExecution:
|
||||
return item.AggregatedOutput, status != "completed" || item.ExitCode != 0
|
||||
return item.AggregatedOutput, status != "completed" || item.ExitCode != 0, nil
|
||||
case codex.ThreadItemTypeDynamicToolCall:
|
||||
texts := []string{}
|
||||
blocks := []map[string]any{}
|
||||
for _, c := range item.ContentItems {
|
||||
texts = append(texts, c.Text)
|
||||
if c.Type != codex.DynamicToolCallOutputContentItemTypeInputImage {
|
||||
blocks = append(blocks, map[string]any{"type": "text", "text": c.Text})
|
||||
continue
|
||||
}
|
||||
mime, data, ok := strings.Cut(strings.TrimPrefix(c.ImageURL, "data:"), ";base64,")
|
||||
if !ok {
|
||||
return nil, false, fmt.Errorf("dynamic tool image is not a base64 data url")
|
||||
}
|
||||
blocks = append(blocks, imageBlock(mime, data))
|
||||
}
|
||||
return strings.Join(texts, "\n"), !item.Success
|
||||
return blocks, !item.Success, nil
|
||||
case codex.ThreadItemTypeMcpToolCall:
|
||||
if item.Error != nil {
|
||||
return item.Error.Message, true
|
||||
return item.Error.Message, true, nil
|
||||
}
|
||||
return string(item.Result), false
|
||||
res := struct {
|
||||
Content []map[string]any `json:"content"`
|
||||
IsError bool `json:"isError"`
|
||||
}{}
|
||||
err := json.Unmarshal(item.Result, &res)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("decode mcp tool result: %w", err)
|
||||
}
|
||||
for i, b := range res.Content {
|
||||
if b["type"] == "image" {
|
||||
mime, _ := b["mimeType"].(string)
|
||||
data, _ := b["data"].(string)
|
||||
res.Content[i] = imageBlock(mime, data)
|
||||
}
|
||||
}
|
||||
return res.Content, res.IsError, nil
|
||||
}
|
||||
return status, status != "completed"
|
||||
return status, status != "completed", nil
|
||||
}
|
||||
|
||||
func imageBlock(mime, data string) map[string]any {
|
||||
return map[string]any{"type": "image", "source": map[string]any{"type": "base64", "media_type": mime, "data": data}}
|
||||
}
|
||||
|
||||
func (m *Manager) readCodexRateLimit() {
|
||||
|
||||
Reference in New Issue
Block a user