v0.9.2 49bea57

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

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