zot/packages/agent/modes/json.go
patriceckhart fa7d8d8be5 refactor: split source into packages/{provider,core,tui,agent}
Single Go module, four top-level packages under packages/. Import
paths become github.com/patriceckhart/zot/packages/<name>; downstream
consumers can depend on individual packages without pulling the rest.

Layout:
  packages/provider/     LLM clients + catalog
  packages/provider/auth/ credential store + OAuth + login server
  packages/core/         agent loop, sessions, cost
  packages/tui/          terminal toolkit + chat view
  packages/agent/        CLI wiring, system prompt
    extensions/ extproto/ modes/ tools/ skills/ swarm/
    sdk/  (was pkg/zotcore, package renamed zotcore -> sdk)
    ext/  (was pkg/zotext, package renamed zotext -> ext)

internal/ and pkg/ removed. The internal/assets logo moved into
packages/provider/auth/assets.

Public Go SDK identifiers renamed:
  pkg/zotcore (package zotcore) -> packages/agent/sdk (package sdk)
  pkg/zotext  (package zotext)  -> packages/agent/ext (package ext)

This breaks Go-based extensions and embedders; the JSON wire protocol
for extensions and RPC is unchanged, so non-Go extensions, already-
built extension binaries, and zot rpc consumers are unaffected.

Docs, examples, and the built-in write-zot-extension skill updated
for the new paths and identifiers. Shadow-bug fixes in code samples
(ext := ext.New -> e := ext.New).
2026-05-27 09:07:15 +02:00

125 lines
3.4 KiB
Go

package modes
import (
"context"
"encoding/json"
"fmt"
"io"
"github.com/patriceckhart/zot/packages/core"
"github.com/patriceckhart/zot/packages/provider"
)
// RunJSON runs the agent to completion, writing one JSON object per
// AgentEvent as newline-delimited JSON.
func RunJSON(ctx context.Context, ag *core.Agent, prompt string, images []provider.ImageBlock, out io.Writer) error {
enc := json.NewEncoder(out)
write := func(v any) {
_ = enc.Encode(v)
}
var runErr error
sink := func(ev core.AgentEvent) {
write(EventToJSON(ev))
}
if err := ag.Prompt(ctx, prompt, images, sink); err != nil {
runErr = err
}
if runErr != nil {
fmt.Fprintln(out, `{"type":"error","message":`+jsonString(runErr.Error())+`}`)
}
return runErr
}
// EventToJSON converts an AgentEvent to a JSON-friendly map. The on-wire
// schema is deliberately simple and flat. Exported so the RPC mode can
// reuse the same serialisation as `zot --json`.
func EventToJSON(ev core.AgentEvent) map[string]any {
m := map[string]any{"type": ev.Type()}
switch e := ev.(type) {
case core.EvTurnStart:
m["step"] = e.Step
case core.EvUserMessage:
m["content"] = ContentToJSON(e.Message.Content)
m["time"] = e.Message.Time
case core.EvAssistantMessage:
m["content"] = ContentToJSON(e.Message.Content)
m["time"] = e.Message.Time
case core.EvTextDelta:
m["delta"] = e.Delta
case core.EvToolUseStart:
m["id"] = e.ID
m["name"] = e.Name
case core.EvToolUseArgs:
m["id"] = e.ID
m["delta"] = e.Delta
case core.EvToolUseEnd:
m["id"] = e.ID
case core.EvToolCall:
m["id"] = e.ID
m["name"] = e.Name
var args any
_ = json.Unmarshal(e.Args, &args)
m["args"] = args
case core.EvToolProgress:
m["id"] = e.ID
m["text"] = e.Text
case core.EvToolResult:
m["id"] = e.ID
m["is_error"] = e.Result.IsError
m["content"] = ContentToJSON(e.Result.Content)
case core.EvUsage:
m["input"] = e.Usage.InputTokens
m["output"] = e.Usage.OutputTokens
m["cache_read"] = e.Usage.CacheReadTokens
m["cache_write"] = e.Usage.CacheWriteTokens
m["cost_usd"] = e.Usage.CostUSD
m["cumulative"] = map[string]any{
"input": e.Cumulative.InputTokens,
"output": e.Cumulative.OutputTokens,
"cache_read": e.Cumulative.CacheReadTokens,
"cache_write": e.Cumulative.CacheWriteTokens,
"cost_usd": e.Cumulative.CostUSD,
}
case core.EvTurnEnd:
m["stop"] = string(e.Stop)
if e.Err != nil {
m["error"] = e.Err.Error()
}
}
return m
}
// ContentToJSON serialises a transcript content slice into the same
// shape used in EventToJSON. Exported alongside EventToJSON for the
// RPC mode.
func ContentToJSON(blocks []provider.Content) []map[string]any {
out := make([]map[string]any, 0, len(blocks))
for _, b := range blocks {
switch v := b.(type) {
case provider.TextBlock:
out = append(out, map[string]any{"type": "text", "text": v.Text})
case provider.ImageBlock:
out = append(out, map[string]any{"type": "image", "mime_type": v.MimeType, "bytes": len(v.Data)})
case provider.ToolCallBlock:
var args any
_ = json.Unmarshal(v.Arguments, &args)
out = append(out, map[string]any{"type": "tool_call", "id": v.ID, "name": v.Name, "args": args})
case provider.ToolResultBlock:
out = append(out, map[string]any{
"type": "tool_result",
"call_id": v.CallID,
"is_error": v.IsError,
"content": ContentToJSON(v.Content),
})
}
}
return out
}
func jsonString(s string) string {
b, _ := json.Marshal(s)
return string(b)
}