v0.29.0 73d6864

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	// turn_end
60	Stop string `json:"stop,omitempty"`
61
62	// error
63	Error string `json:"error,omitempty"`
64}
65
66// PlanStep is one model-maintained task in a plan update.
67type PlanStep struct {
68	Step   string `json:"step"`
69	Status string `json:"status"`
70}
71
72// Message is one transcript entry — a user prompt, assistant reply,
73// or tool result.
74type Message struct {
75	Role    string         `json:"role"`
76	Content []ContentBlock `json:"content"`
77	Time    string         `json:"time,omitempty"`
78}
79
80// ContentBlock is one piece of message content. Discriminate on Type:
81//   - "text"        → Text
82//   - "image"       → MimeType + Data (or Bytes for size-only)
83//   - "tool_call"   → ID + Name + Args
84//   - "tool_result" → CallID + IsError + Content (recursive)
85type ContentBlock struct {
86	Type string `json:"type"`
87
88	Text     string          `json:"text,omitempty"`
89	MimeType string          `json:"mime_type,omitempty"`
90	Data     []byte          `json:"data,omitempty"`
91	Bytes    int             `json:"bytes,omitempty"`
92	ID       string          `json:"id,omitempty"`
93	Name     string          `json:"name,omitempty"`
94	Args     json.RawMessage `json:"args,omitempty"`
95	CallID   string          `json:"call_id,omitempty"`
96	IsError  bool            `json:"is_error,omitempty"`
97	Content  []ContentBlock  `json:"content,omitempty"`
98}
99
100// Image is a single user-attached image for Prompt.
101type Image struct {
102	MimeType string
103	Data     []byte
104}
105
106// CacheDiagnostics reports sanitized provider cache-routing state.
107type CacheDiagnostics struct {
108	Eligible     bool   `json:"eligible"`
109	Mode         string `json:"mode"`
110	Transport    string `json:"transport"`
111	Continuation string `json:"continuation"`
112}
113
114// Usage is per-turn or cumulative token / cost counts.
115type Usage struct {
116	Input               int     `json:"input"`
117	Output              int     `json:"output"`
118	Reasoning           *int    `json:"reasoning"`
119	CacheRead           int     `json:"cache_read"`
120	CacheWrite          int     `json:"cache_write"`
121	CacheMeasuredPrompt int     `json:"cache_measured_prompt,omitempty"`
122	CacheMeasuredRead   int     `json:"cache_measured_read,omitempty"`
123	CostUSD             float64 `json:"cost_usd"`
124}
125
126// State is a snapshot of the runtime's current state.
127type State struct {
128	Provider     string `json:"provider"`
129	Model        string `json:"model"`
130	Reasoning    string `json:"reasoning"`
131	CWD          string `json:"cwd"`
132	Busy         bool   `json:"busy"`
133	MessageCount int    `json:"message_count"`
134}
135
136// CompactResult describes the outcome of Compact.
137type CompactResult struct {
138	Summary  string    `json:"summary"`
139	Messages []Message `json:"messages"`
140}
141
142// ModelInfo describes one model known to the runtime.
143type ModelInfo struct {
144	ID            string `json:"id"`
145	Provider      string `json:"provider"`
146	ContextWindow int    `json:"context_window"`
147	MaxOutput     int    `json:"max_output"`
148	Reasoning     bool   `json:"reasoning"`
149}
150
151// ---- internal converters ----
152
153func sdkReasoningTokens(usage provider.Usage) *int {
154	if !usage.ReasoningTokensKnown {
155		return nil
156	}
157	count := usage.ReasoningTokens
158	return &count
159}
160
161func toEvent(ev core.AgentEvent) Event {
162	out := Event{Type: ev.Type()}
163	switch e := ev.(type) {
164	case core.EvTurnStart:
165		out.Step = e.Step
166	case core.EvRequestStarted:
167		out.Provider = e.Provider
168		out.Model = e.Model
169		out.Scope = string(e.Scope)
170		out.Attempt = e.Attempt
171		out.MaxAttempts = e.MaxAttempts
172	case core.EvRetryScheduled:
173		out.Scope = string(e.Scope)
174		out.Attempt = e.Attempt
175		out.MaxAttempts = e.MaxAttempts
176		delayMS := e.Delay.Milliseconds()
177		out.DelayMS = &delayMS
178	case core.EvUserMessage:
179		out.Message = &Message{Role: string(e.Message.Role), Content: convertContent(e.Message.Content)}
180	case core.EvAssistantMessage:
181		out.Message = &Message{Role: string(e.Message.Role), Content: convertContent(e.Message.Content)}
182	case core.EvTextDelta:
183		out.Delta = e.Delta
184	case core.EvToolUseStart:
185		out.ID = e.ID
186		out.Name = e.Name
187	case core.EvToolUseArgs:
188		out.ID = e.ID
189		out.Delta = e.Delta
190	case core.EvToolUseEnd:
191		out.ID = e.ID
192	case core.EvToolCall:
193		out.ID = e.ID
194		out.Name = e.Name
195		out.Args = e.Args
196	case core.EvToolExecutionStarted:
197		out.ID = e.ID
198		out.Name = e.Name
199	case core.EvToolProgress:
200		out.ID = e.ID
201		out.Text = e.Text
202	case core.EvPlanUpdate:
203		out.Explanation = e.Update.Explanation
204		plan := make([]PlanStep, len(e.Update.Plan))
205		for idx, step := range e.Update.Plan {
206			plan[idx] = PlanStep{Step: step.Step, Status: string(step.Status)}
207		}
208		out.Plan = &plan
209	case core.EvToolResult:
210		out.ID = e.ID
211		out.IsError = e.Result.IsError
212		out.Result = convertContent(e.Result.Content)
213	case core.EvCacheDiagnostics:
214		out.CacheDiagnostics = &CacheDiagnostics{
215			Eligible:     e.Eligible,
216			Mode:         e.Mode,
217			Transport:    e.Transport,
218			Continuation: e.Continuation,
219		}
220	case core.EvUsage:
221		out.Usage = &Usage{
222			Input:               e.Usage.InputTokens,
223			Output:              e.Usage.OutputTokens,
224			Reasoning:           sdkReasoningTokens(e.Usage),
225			CacheRead:           e.Usage.CacheReadTokens,
226			CacheWrite:          e.Usage.CacheWriteTokens,
227			CacheMeasuredPrompt: e.Usage.CacheMeasuredPromptTokens,
228			CacheMeasuredRead:   e.Usage.CacheMeasuredReadTokens,
229			CostUSD:             e.Usage.CostUSD,
230		}
231		out.Cumulative = &Usage{
232			Input:               e.Cumulative.InputTokens,
233			Output:              e.Cumulative.OutputTokens,
234			Reasoning:           sdkReasoningTokens(e.Cumulative),
235			CacheRead:           e.Cumulative.CacheReadTokens,
236			CacheWrite:          e.Cumulative.CacheWriteTokens,
237			CacheMeasuredPrompt: e.Cumulative.CacheMeasuredPromptTokens,
238			CacheMeasuredRead:   e.Cumulative.CacheMeasuredReadTokens,
239			CostUSD:             e.Cumulative.CostUSD,
240		}
241	case core.EvTurnEnd:
242		out.Stop = string(e.Stop)
243		if e.Err != nil {
244			out.Error = e.Err.Error()
245		}
246	case core.EvError:
247		if e.Err != nil {
248			out.Error = e.Err.Error()
249		}
250	}
251	return out
252}
253
254func convertContent(blocks []provider.Content) []ContentBlock {
255	out := make([]ContentBlock, 0, len(blocks))
256	for _, b := range blocks {
257		switch v := b.(type) {
258		case provider.TextBlock:
259			out = append(out, ContentBlock{Type: "text", Text: v.Text})
260		case provider.ImageBlock:
261			out = append(out, ContentBlock{Type: "image", MimeType: v.MimeType, Bytes: len(v.Data)})
262		case provider.ToolCallBlock:
263			out = append(out, ContentBlock{Type: "tool_call", ID: v.ID, Name: v.Name, Args: v.Arguments})
264		case provider.ToolResultBlock:
265			out = append(out, ContentBlock{
266				Type:    "tool_result",
267				CallID:  v.CallID,
268				IsError: v.IsError,
269				Content: convertContent(v.Content),
270			})
271		}
272	}
273	return out
274}
275
276func rebuildContent(blocks []ContentBlock) []provider.Content {
277	out := make([]provider.Content, 0, len(blocks))
278	for _, b := range blocks {
279		switch b.Type {
280		case "text":
281			out = append(out, provider.TextBlock{Text: b.Text})
282		case "image":
283			out = append(out, provider.ImageBlock{MimeType: b.MimeType, Data: b.Data})
284		case "tool_call":
285			args := b.Args
286			if len(args) == 0 {
287				args = json.RawMessage("{}")
288			}
289			out = append(out, provider.ToolCallBlock{ID: b.ID, Name: b.Name, Arguments: args})
290		case "tool_result":
291			out = append(out, provider.ToolResultBlock{
292				CallID:  b.CallID,
293				IsError: b.IsError,
294				Content: rebuildContent(b.Content),
295			})
296		}
297	}
298	return out
299}

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