v0.50.0 c4ec31f

types.go RawHistoryFolder

1package sdk
2
3import (
4	"encoding/json"
5
6	"github.com/bnema/zut/packages/core"
7	"github.com/bnema/zut/packages/provider"
8)
9
10// Event is the unit emitted by Runtime.Prompt's channel. Type
11// identifies the event kind; remaining fields are populated based on
12// kind. The wire schema is the same as the JSON-RPC stream notifications
13// emitted by `zut rpc`, so consumers can share parsing code.
14type Event struct {
15	Type string `json:"type"`
16
17	// Common fields.
18	Step int    `json:"step,omitempty"`
19	Time string `json:"time,omitempty"`
20
21	// request_started / retry_scheduled
22	Provider    string `json:"provider,omitempty"`
23	Model       string `json:"model,omitempty"`
24	Scope       string `json:"scope,omitempty"`
25	Attempt     int    `json:"attempt,omitempty"`
26	MaxAttempts int    `json:"max_attempts,omitempty"`
27	// DelayMS is set for retry_scheduled, including a valid zero delay.
28	DelayMS *int64 `json:"delay_ms,omitempty"`
29
30	// text_delta
31	Delta string `json:"delta,omitempty"`
32
33	// tool_call
34	ID   string          `json:"id,omitempty"`
35	Name string          `json:"name,omitempty"`
36	Args json.RawMessage `json:"args,omitempty"`
37
38	// tool_progress
39	Text string `json:"text,omitempty"`
40
41	// tool_result
42	IsError bool           `json:"is_error,omitempty"`
43	Result  []ContentBlock `json:"content,omitempty"`
44
45	// plan_update
46	Explanation *string     `json:"explanation,omitempty"`
47	Plan        *[]PlanStep `json:"plan,omitempty"`
48
49	// usage
50	Usage      *Usage `json:"usage,omitempty"`
51	Cumulative *Usage `json:"cumulative,omitempty"`
52
53	// cache_diagnostics
54	CacheDiagnostics *CacheDiagnostics `json:"cache_diagnostics,omitempty"`
55
56	// user_message / assistant_message
57	Message *Message `json:"message,omitempty"`
58
59	// repetition_guard
60	RepetitionKind    string `json:"kind,omitempty"`
61	RepetitionStage   string `json:"stage,omitempty"`
62	RepetitionCount   int    `json:"count,omitempty"`
63	RepetitionElapsed int64  `json:"elapsed_ms,omitempty"`
64
65	// turn_end
66	Stop string `json:"stop,omitempty"`
67
68	// error
69	Error string `json:"error,omitempty"`
70}
71
72// PlanStep is one model-maintained task in a plan update.
73type PlanStep struct {
74	Step   string `json:"step"`
75	Status string `json:"status"`
76}
77
78// Message is one transcript entry — a user prompt, assistant reply,
79// or tool result.
80type Message struct {
81	Role    string         `json:"role"`
82	Content []ContentBlock `json:"content"`
83	Time    string         `json:"time,omitempty"`
84}
85
86// ContentBlock is one piece of message content. Discriminate on Type:
87//   - "text"        → Text
88//   - "image"       → MimeType + Data (or Bytes for size-only)
89//   - "tool_call"   → ID + Name + Args
90//   - "tool_result" → CallID + IsError + Content (recursive)
91type ContentBlock struct {
92	Type string `json:"type"`
93
94	Text     string          `json:"text,omitempty"`
95	MimeType string          `json:"mime_type,omitempty"`
96	Data     []byte          `json:"data,omitempty"`
97	Bytes    int             `json:"bytes,omitempty"`
98	ID       string          `json:"id,omitempty"`
99	Name     string          `json:"name,omitempty"`
100	Args     json.RawMessage `json:"args,omitempty"`
101	CallID   string          `json:"call_id,omitempty"`
102	IsError  bool            `json:"is_error,omitempty"`
103	Content  []ContentBlock  `json:"content,omitempty"`
104}
105
106// Image is a single user-attached image for Prompt.
107type Image struct {
108	MimeType string
109	Data     []byte
110}
111
112// CacheDiagnostics reports sanitized provider cache-routing state.
113type CacheDiagnostics struct {
114	Eligible     bool   `json:"eligible"`
115	Mode         string `json:"mode"`
116	Transport    string `json:"transport"`
117	Continuation string `json:"continuation"`
118}
119
120// Usage is per-turn or cumulative token / cost counts.
121type Usage struct {
122	Input               int     `json:"input"`
123	Output              int     `json:"output"`
124	Reasoning           *int    `json:"reasoning"`
125	CacheRead           int     `json:"cache_read"`
126	CacheWrite          int     `json:"cache_write"`
127	CacheMeasuredPrompt int     `json:"cache_measured_prompt,omitempty"`
128	CacheMeasuredRead   int     `json:"cache_measured_read,omitempty"`
129	CostUSD             float64 `json:"cost_usd"`
130}
131
132// State is a snapshot of the runtime's current state.
133type State struct {
134	Provider     string `json:"provider"`
135	Model        string `json:"model"`
136	Reasoning    string `json:"reasoning"`
137	CWD          string `json:"cwd"`
138	Busy         bool   `json:"busy"`
139	MessageCount int    `json:"message_count"`
140}
141
142// CompactResult describes the outcome of Compact.
143type CompactResult struct {
144	Summary  string    `json:"summary"`
145	Messages []Message `json:"messages"`
146}
147
148// ModelInfo describes one model known to the runtime.
149type ModelInfo struct {
150	ID            string `json:"id"`
151	Provider      string `json:"provider"`
152	ContextWindow int    `json:"context_window"`
153	MaxOutput     int    `json:"max_output"`
154	Reasoning     bool   `json:"reasoning"`
155}
156
157// ---- internal converters ----
158
159func sdkReasoningTokens(usage provider.Usage) *int {
160	if !usage.ReasoningTokensKnown {
161		return nil
162	}
163	count := usage.ReasoningTokens
164	return &count
165}
166
167func toEvent(ev core.AgentEvent) Event {
168	out := Event{Type: ev.Type()}
169	switch e := ev.(type) {
170	case core.EvTurnStart:
171		out.Step = e.Step
172	case core.EvRequestStarted:
173		out.Provider = e.Provider
174		out.Model = e.Model
175		out.Scope = string(e.Scope)
176		out.Attempt = e.Attempt
177		out.MaxAttempts = e.MaxAttempts
178	case core.EvRetryScheduled:
179		out.Scope = string(e.Scope)
180		out.Attempt = e.Attempt
181		out.MaxAttempts = e.MaxAttempts
182		delayMS := e.Delay.Milliseconds()
183		out.DelayMS = &delayMS
184	case core.EvUserMessage:
185		out.Message = &Message{Role: string(e.Message.Role), Content: convertContent(e.Message.Content)}
186	case core.EvAssistantMessage:
187		out.Message = &Message{Role: string(e.Message.Role), Content: convertContent(e.Message.Content)}
188	case core.EvTextDelta:
189		out.Delta = e.Delta
190	case core.EvToolUseStart:
191		out.ID = e.ID
192		out.Name = e.Name
193	case core.EvToolUseArgs:
194		out.ID = e.ID
195		out.Delta = e.Delta
196	case core.EvToolUseEnd:
197		out.ID = e.ID
198	case core.EvToolCall:
199		out.ID = e.ID
200		out.Name = e.Name
201		out.Args = e.Args
202	case core.EvToolExecutionStarted:
203		out.ID = e.ID
204		out.Name = e.Name
205	case core.EvToolProgress:
206		out.ID = e.ID
207		out.Text = e.Text
208	case core.EvPlanUpdate:
209		out.ID = e.CallID
210		out.Explanation = e.Update.Explanation
211		plan := make([]PlanStep, len(e.Update.Plan))
212		for idx, step := range e.Update.Plan {
213			plan[idx] = PlanStep{Step: step.Step, Status: string(step.Status)}
214		}
215		out.Plan = &plan
216	case core.EvToolResult:
217		out.ID = e.ID
218		out.IsError = e.Result.IsError
219		out.Result = convertContent(e.Result.Content)
220	case core.EvCacheDiagnostics:
221		out.CacheDiagnostics = &CacheDiagnostics{
222			Eligible:     e.Eligible,
223			Mode:         e.Mode,
224			Transport:    e.Transport,
225			Continuation: e.Continuation,
226		}
227	case core.EvUsage:
228		out.Usage = &Usage{
229			Input:               e.Usage.InputTokens,
230			Output:              e.Usage.OutputTokens,
231			Reasoning:           sdkReasoningTokens(e.Usage),
232			CacheRead:           e.Usage.CacheReadTokens,
233			CacheWrite:          e.Usage.CacheWriteTokens,
234			CacheMeasuredPrompt: e.Usage.CacheMeasuredPromptTokens,
235			CacheMeasuredRead:   e.Usage.CacheMeasuredReadTokens,
236			CostUSD:             e.Usage.CostUSD,
237		}
238		out.Cumulative = &Usage{
239			Input:               e.Cumulative.InputTokens,
240			Output:              e.Cumulative.OutputTokens,
241			Reasoning:           sdkReasoningTokens(e.Cumulative),
242			CacheRead:           e.Cumulative.CacheReadTokens,
243			CacheWrite:          e.Cumulative.CacheWriteTokens,
244			CacheMeasuredPrompt: e.Cumulative.CacheMeasuredPromptTokens,
245			CacheMeasuredRead:   e.Cumulative.CacheMeasuredReadTokens,
246			CostUSD:             e.Cumulative.CostUSD,
247		}
248	case core.EvRepetitionGuard:
249		out.RepetitionKind = string(e.Kind)
250		out.RepetitionStage = string(e.Stage)
251		out.RepetitionCount = e.Count
252		out.RepetitionElapsed = e.Elapsed.Milliseconds()
253	case core.EvTurnEnd:
254		out.Stop = string(e.Stop)
255		if e.Err != nil {
256			out.Error = e.Err.Error()
257		}
258	case core.EvError:
259		if e.Err != nil {
260			out.Error = e.Err.Error()
261		}
262	}
263	return out
264}
265
266func convertContent(blocks []provider.Content) []ContentBlock {
267	out := make([]ContentBlock, 0, len(blocks))
268	for _, b := range blocks {
269		switch v := b.(type) {
270		case provider.TextBlock:
271			out = append(out, ContentBlock{Type: "text", Text: v.Text})
272		case provider.ImageBlock:
273			out = append(out, ContentBlock{Type: "image", MimeType: v.MimeType, Bytes: len(v.Data)})
274		case provider.ToolCallBlock:
275			out = append(out, ContentBlock{Type: "tool_call", ID: v.ID, Name: v.Name, Args: v.Arguments})
276		case provider.ToolResultBlock:
277			out = append(out, ContentBlock{
278				Type:    "tool_result",
279				CallID:  v.CallID,
280				IsError: v.IsError,
281				Content: convertContent(v.Content),
282			})
283		}
284	}
285	return out
286}
287
288func rebuildContent(blocks []ContentBlock) []provider.Content {
289	out := make([]provider.Content, 0, len(blocks))
290	for _, b := range blocks {
291		switch b.Type {
292		case "text":
293			out = append(out, provider.TextBlock{Text: b.Text})
294		case "image":
295			out = append(out, provider.ImageBlock{MimeType: b.MimeType, Data: b.Data})
296		case "tool_call":
297			args := b.Args
298			if len(args) == 0 {
299				args = json.RawMessage("{}")
300			}
301			out = append(out, provider.ToolCallBlock{ID: b.ID, Name: b.Name, Arguments: args})
302		case "tool_result":
303			out = append(out, provider.ToolResultBlock{
304				CallID:  b.CallID,
305				IsError: b.IsError,
306				Content: rebuildContent(b.Content),
307			})
308		}
309	}
310	return out
311}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 477

↑↓ moveenter openesc close