update cli emitters and edge config
This commit is contained in:
parent
4b1b5472af
commit
1a72d9caa6
3 changed files with 158 additions and 6 deletions
|
|
@ -211,6 +211,42 @@ func TestStreamJSONEmitter_ErrorEvent(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestStreamJSONEmitter_ToolUseBecomesVisibleDelta(t *testing.T) {
|
||||
e := streamJSONEmitter{}
|
||||
events, _ := e.Emit(`{"type":"tool_use","tool_name":"run_shell_command","tool_id":"tool-1","parameters":{"command":"go test ./..."}}`)
|
||||
if len(events) != 1 || events[0].Type != runtime.EventTypeDelta {
|
||||
t.Fatalf("unexpected events: %+v", events)
|
||||
}
|
||||
if events[0].Delta != "[tool] run_shell_command: go test ./...\n" {
|
||||
t.Fatalf("unexpected delta: %q", events[0].Delta)
|
||||
}
|
||||
if events[0].Metadata["kind"] != "tool_use" || events[0].Metadata["tool_name"] != "run_shell_command" {
|
||||
t.Fatalf("unexpected metadata: %+v", events[0].Metadata)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStreamJSONEmitter_ToolResultBecomesVisibleDelta(t *testing.T) {
|
||||
e := streamJSONEmitter{}
|
||||
events, _ := e.Emit(`{"type":"tool_result","tool_id":"tool-1","status":"success","output":"ok\n"}`)
|
||||
if len(events) != 1 || events[0].Type != runtime.EventTypeDelta {
|
||||
t.Fatalf("unexpected events: %+v", events)
|
||||
}
|
||||
if events[0].Delta != "[tool-result] success: ok\n" {
|
||||
t.Fatalf("unexpected delta: %q", events[0].Delta)
|
||||
}
|
||||
if events[0].Metadata["kind"] != "tool_result" {
|
||||
t.Fatalf("unexpected metadata: %+v", events[0].Metadata)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStreamJSONEmitter_ResultError(t *testing.T) {
|
||||
e := streamJSONEmitter{}
|
||||
events, _ := e.Emit(`{"type":"result","status":"error","error":{"message":"quota exceeded"}}`)
|
||||
if len(events) != 1 || events[0].Type != runtime.EventTypeError || events[0].Error != "quota exceeded" {
|
||||
t.Fatalf("unexpected events: %+v", events)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStreamJSONEmitter_EmptyContentSkipped(t *testing.T) {
|
||||
e := streamJSONEmitter{}
|
||||
events, _ := e.Emit(`{"type":"message","role":"assistant","content":""}`)
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"bufio"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
|
|
@ -91,10 +92,16 @@ func (streamJSONEmitter) Name() string { return "stream-json" }
|
|||
|
||||
func (e streamJSONEmitter) Emit(line string) ([]runtime.RuntimeEvent, error) {
|
||||
var ev struct {
|
||||
Type string `json:"type"`
|
||||
Role string `json:"role"`
|
||||
Content string `json:"content"`
|
||||
Error string `json:"error"`
|
||||
Type string `json:"type"`
|
||||
Role string `json:"role"`
|
||||
Content string `json:"content"`
|
||||
Error any `json:"error"`
|
||||
Message string `json:"message"`
|
||||
Status string `json:"status"`
|
||||
ToolName string `json:"tool_name"`
|
||||
ToolID string `json:"tool_id"`
|
||||
Parameters map[string]any `json:"parameters"`
|
||||
Output string `json:"output"`
|
||||
}
|
||||
if err := json.Unmarshal([]byte(line), &ev); err != nil {
|
||||
return nil, nil
|
||||
|
|
@ -108,15 +115,124 @@ func (e streamJSONEmitter) Emit(line string) ([]runtime.RuntimeEvent, error) {
|
|||
Type: runtime.EventTypeDelta,
|
||||
Delta: ev.Content,
|
||||
}}, nil
|
||||
case "tool_use":
|
||||
if delta := geminiToolUseDelta(ev.ToolName, ev.Parameters); delta != "" {
|
||||
return []runtime.RuntimeEvent{{
|
||||
Type: runtime.EventTypeDelta,
|
||||
Delta: delta,
|
||||
Metadata: map[string]string{
|
||||
"kind": "tool_use",
|
||||
"tool_name": ev.ToolName,
|
||||
"tool_id": ev.ToolID,
|
||||
},
|
||||
}}, nil
|
||||
}
|
||||
return nil, nil
|
||||
case "tool_result":
|
||||
if delta := geminiToolResultDelta(ev.Status, ev.Output); delta != "" {
|
||||
return []runtime.RuntimeEvent{{
|
||||
Type: runtime.EventTypeDelta,
|
||||
Delta: delta,
|
||||
Metadata: map[string]string{
|
||||
"kind": "tool_result",
|
||||
"tool_id": ev.ToolID,
|
||||
},
|
||||
}}, nil
|
||||
}
|
||||
return nil, nil
|
||||
case "result":
|
||||
if ev.Status != "" && ev.Status != "success" {
|
||||
msg := ev.Message
|
||||
if msg == "" {
|
||||
msg = geminiErrorString(ev.Error)
|
||||
}
|
||||
if msg == "" {
|
||||
msg = ev.Status
|
||||
}
|
||||
return []runtime.RuntimeEvent{{
|
||||
Type: runtime.EventTypeError,
|
||||
Error: msg,
|
||||
}}, nil
|
||||
}
|
||||
return nil, nil
|
||||
case "error":
|
||||
return []runtime.RuntimeEvent{{
|
||||
Type: runtime.EventTypeError,
|
||||
Error: ev.Error,
|
||||
Error: geminiErrorString(ev.Error),
|
||||
}}, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func geminiToolUseDelta(toolName string, params map[string]any) string {
|
||||
if toolName == "" {
|
||||
return ""
|
||||
}
|
||||
summary := geminiToolParamSummary(params)
|
||||
if summary == "" {
|
||||
return fmt.Sprintf("[tool] %s\n", toolName)
|
||||
}
|
||||
return fmt.Sprintf("[tool] %s: %s\n", toolName, summary)
|
||||
}
|
||||
|
||||
func geminiToolParamSummary(params map[string]any) string {
|
||||
if params == nil {
|
||||
return ""
|
||||
}
|
||||
for _, key := range []string{"command", "description", "title", "summary"} {
|
||||
if s, ok := params[key].(string); ok && strings.TrimSpace(s) != "" {
|
||||
return truncateGeminiToolText(strings.TrimSpace(s), 180)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func geminiToolResultDelta(status, output string) string {
|
||||
status = strings.TrimSpace(status)
|
||||
output = strings.TrimSpace(output)
|
||||
if status == "" && output == "" {
|
||||
return ""
|
||||
}
|
||||
if status == "" {
|
||||
status = "done"
|
||||
}
|
||||
if output == "" {
|
||||
return fmt.Sprintf("[tool-result] %s\n", status)
|
||||
}
|
||||
return fmt.Sprintf("[tool-result] %s: %s\n", status, truncateGeminiToolText(output, 180))
|
||||
}
|
||||
|
||||
func geminiErrorString(v any) string {
|
||||
switch err := v.(type) {
|
||||
case string:
|
||||
return err
|
||||
case map[string]any:
|
||||
if msg, ok := err["message"].(string); ok {
|
||||
return msg
|
||||
}
|
||||
if data, ok := err["data"].(map[string]any); ok {
|
||||
if msg, ok := data["message"].(string); ok {
|
||||
return msg
|
||||
}
|
||||
}
|
||||
if name, ok := err["name"].(string); ok {
|
||||
return name
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func truncateGeminiToolText(s string, max int) string {
|
||||
s = strings.Join(strings.Fields(s), " ")
|
||||
if len(s) <= max {
|
||||
return s
|
||||
}
|
||||
if max <= 3 {
|
||||
return s[:max]
|
||||
}
|
||||
return s[:max-3] + "..."
|
||||
}
|
||||
|
||||
// --- claude-json emitter ---
|
||||
|
||||
type claudeJSONEmitter struct{}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ metrics:
|
|||
|
||||
console:
|
||||
adapter: "cli"
|
||||
target: "claude"
|
||||
target: "gemini"
|
||||
session_id: "default"
|
||||
background: false
|
||||
timeout_sec: 300
|
||||
|
|
|
|||
Loading…
Reference in a new issue