-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathprotocol.go
More file actions
269 lines (244 loc) · 8.75 KB
/
Copy pathprotocol.go
File metadata and controls
269 lines (244 loc) · 8.75 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
package main
import (
"encoding/json"
"strconv"
"strings"
"time"
)
// ---------------------------------------------------------------------------
// Core protocol types (mirror of the Rust core's stdin/stdout JSON-RPC)
// ---------------------------------------------------------------------------
// modelInfo mirrors a model entry from the core's "ready" event.
type modelInfo struct {
ID string `json:"id"`
Name string `json:"name"`
Reasoning bool `json:"reasoning"`
ContextWindow uint32 `json:"context_window"`
MaxTokens uint32 `json:"max_tokens"`
// ThinkingLevels are the reasoning levels the model advertises
// (e.g. ["low","medium","high"]). Empty when the endpoint provides none;
// the TUI then falls back to its own low/medium/high set.
ThinkingLevels []string `json:"thinking_levels"`
// Vision is true when the model accepts image inputs (from /models/info
// capabilities.supports_vision; only boolean true counts — "via-handoff"
// is not native client-side vision). Drives the vision-handoff plugin's routing.
Vision bool `json:"vision"`
// Provider is the owning provider name (e.g. "openai", "gemini",
// "anthropic"), populated by the core's multi-provider aggregation so
// /models can mix providers and each turn routes to the right endpoint.
Provider string `json:"provider"`
}
type intercomPrompt struct {
requestID string
from string
reason string
message string
}
// providerPreset is a built-in first-party provider template (Umans, OpenCode
// Go, OpenRouter) advertised by the core via the `provider_presets` event
// (and embedded in `ready`). The /login + /logout pickers use HasKey/Configured/
// LoggedIn to show the right action and prompt for a key when none is set.
type providerPreset struct {
ID string `json:"id"`
Label string `json:"label"`
Kind string `json:"kind"`
BaseURL string `json:"base_url"`
EnvVar string `json:"envVar"`
AltEnvs []string `json:"altEnvs"`
Description string `json:"description"`
HasKey bool `json:"hasKey"`
Configured bool `json:"configured"`
LoggedIn bool `json:"loggedIn"`
SupportsOauth bool `json:"supportsOauth"`
}
type approvalPrompt struct {
requestID string
tool string
args string
diff string // unified-diff preview for write/edit/patch (empty for other tools)
expanded bool // full diff is visible; toggled by the tool-output keybind
diffScroll int // first rendered row of the expanded diff window
receivedAt time.Time // when the request arrived (banner shows elapsed waiting)
}
type subProgressEntry struct {
runID string
agent string
toolCount int
curTool string
toolStart time.Time
toolRunning bool
tokensIn uint64
tokensOut uint64
started time.Time
}
// sessionEntry mirrors one element of the core's "sessions" event array.
type sessionEntry struct {
Name string `json:"name"`
Path string `json:"path"`
Title string `json:"title"`
Messages int `json:"messages"`
Mtime uint64 `json:"mtime"`
Current bool `json:"current"`
Pinned bool `json:"pinned"`
}
// memoryEntry mirrors one element of the core's "memory_list" event array
// (id + text + tags; any extra fields the core sends are ignored).
type memoryEntry struct {
ID string `json:"id"`
Text string `json:"text"`
Tags []string `json:"tags"`
}
// contextConsumer is one row of the core's "context_breakdown" event
// top_consumers array (the biggest token consumers in the conversation).
type contextConsumer struct {
Index int `json:"index"`
Role string `json:"role"`
Tokens uint64 `json:"tokens"`
Preview string `json:"preview"`
}
// contextBreakdown mirrors the core's "context_breakdown" event payload so the
// TUI can render a /context modal showing where the context budget is spent.
type contextBreakdown struct {
Total uint64 `json:"total_tokens"`
Window uint64 `json:"context_window"`
Pct uint64 `json:"pct"`
Messages int `json:"messages"`
DigestAt uint64 `json:"digest_threshold_tokens"`
CompactAt uint64 `json:"compact_threshold_tokens"`
HardLimit uint64 `json:"hard_limit_tokens"`
ResponseReserve uint64 `json:"response_reserve_tokens"`
SafetyMargin uint64 `json:"safety_margin_tokens"`
ByRole map[string]uint64 `json:"by_role"`
TopConsumers []contextConsumer `json:"top_consumers"`
}
// usageWindow is one rate-limit / quota row in a provider usage report.
type usageWindow struct {
ID string `json:"id"`
Label string `json:"label"`
Used *float64 `json:"used"`
Limit *float64 `json:"limit"`
Unit string `json:"unit"`
ResetsAt *int64 `json:"resets_at"`
Detail string `json:"detail"`
}
// usageReport mirrors the core's "usage" event — plan/window limits for the
// provider that owns the currently selected model (/usage command).
type usageReport struct {
Provider string `json:"provider"`
ProviderKind string `json:"provider_kind"`
Model string `json:"model"`
BaseURL string `json:"base_url"`
Available bool `json:"available"`
Plan string `json:"plan"`
Message string `json:"message"`
Windows []usageWindow `json:"windows"`
}
// skillInfo mirrors one element of the core's "skills" event array.
// The TUI palette / autocomplete only needs name + description (+ location);
// the SKILL.md body stays in the core — apply_skill sends the name and the
// core inlines the body (so we deliberately omit Content to avoid pinning
// every skill body for process life; D-005).
type skillInfo struct {
Name string `json:"name"`
Description string `json:"description"`
Location string `json:"location"`
}
type marketplaceSkill struct {
ID string `json:"id"`
Name string `json:"name"`
Source string `json:"source"`
Installs int `json:"installs"`
}
type installedMarketplaceSkill struct {
Name string `json:"name"`
Source string `json:"source"`
Scope string `json:"scope"`
}
// coreEvent is one newline-delimited JSON line from the core.
// fields caches the once-parsed object map so get/rawKey do not re-Unmarshal
// Raw on every field read (D-004). Populated at ingest or lazily on first get.
type coreEvent struct {
Type string `json:"type"`
Raw json.RawMessage `json:"-"`
fields map[string]json.RawMessage `json:"-"`
}
func (e *coreEvent) ensureFields() map[string]json.RawMessage {
if e == nil {
return nil
}
if e.fields != nil {
return e.fields
}
var m map[string]json.RawMessage
if err := json.Unmarshal(e.Raw, &m); err != nil {
e.fields = map[string]json.RawMessage{}
return e.fields
}
e.fields = m
return e.fields
}
func (e *coreEvent) get(key string) string {
m := e.ensureFields()
if m == nil {
return ""
}
v, ok := m[key]
if !ok {
return ""
}
var s string
if json.Unmarshal(v, &s) == nil {
return s
}
return strings.TrimSpace(string(v))
}
// rawKey returns the raw JSON value for a key (e.g. an array/object), so
// callers can unmarshal structured fields themselves without re-parsing Raw.
func (e *coreEvent) rawKey(key string) (json.RawMessage, bool) {
m := e.ensureFields()
if m == nil {
return nil, false
}
v, ok := m[key]
return v, ok
}
// ---------------------------------------------------------------------------
// Tea messages
// ---------------------------------------------------------------------------
// Core lifecycle messages carry the subprocess generation that produced them.
// A retry may start a replacement before the old stdout reader has completely
// unwound; generation tags keep stale events/EOF from mutating the new run.
type coreEventMsg struct {
event *coreEvent
gen uint64
}
type coreEOFMsg struct{ gen uint64 }
type tickMsg struct{ time time.Time }
// ---------------------------------------------------------------------------
// Metrics helpers
// ---------------------------------------------------------------------------
// get reads a trimmed string field from a raw JSON map.
func get(m map[string]json.RawMessage, key string) string {
v, ok := m[key]
if !ok {
return ""
}
var s string
if json.Unmarshal(v, &s) == nil {
return strings.TrimSpace(s)
}
return strings.TrimSpace(string(v))
}
// nullableInt64 parses a coreEvent.get() string into a *int64. Returns nil for
// "" / "null" / unparseable — used for the Umans concurrency fields where nil
// means "absent" (hide the field for used; render ∞ for limit).
func nullableInt64(s string) *int64 {
if s == "" || s == "null" {
return nil
}
n, err := strconv.ParseInt(s, 10, 64)
if err != nil {
return nil
}
return &n
}