v0.22.0 fae6c20

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

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 476

↑↓ moveenter openesc close