forked from anomalyco/opencode
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtypes.ts
More file actions
317 lines (276 loc) · 8.19 KB
/
types.ts
File metadata and controls
317 lines (276 loc) · 8.19 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
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
// Shared type vocabulary for the direct interactive mode (`run --interactive`).
//
// Direct mode uses a split-footer terminal layout: immutable scrollback for the
// session transcript, and a mutable footer for prompt input, status, and
// permission/question UI. Every module in run/* shares these types to stay
// aligned on that two-lane model.
//
// Data flow through the system:
//
// SDK events → session-data reducer → StreamCommit[] + FooterOutput
// → stream.ts bridges to footer API
// → footer.ts queues commits and patches the footer view
// → OpenTUI split-footer renderer writes to terminal
import type { KeyEvent, Renderable } from "@opentui/core"
import type { Binding } from "@opentui/keymap"
import type { OpencodeClient, PermissionRequest, QuestionRequest, ToolPart } from "@opencode-ai/sdk/v2"
export type RunFilePart = {
type: "file"
url: string
filename: string
mime: string
}
type PromptModel = Parameters<OpencodeClient["session"]["prompt"]>[0]["model"]
type PromptInput = Parameters<OpencodeClient["session"]["prompt"]>[0]
export type RunPromptPart = NonNullable<PromptInput["parts"]>[number]
export type RunCommand = NonNullable<Awaited<ReturnType<OpencodeClient["command"]["list"]>>["data"]>[number]
export type RunProvider = NonNullable<Awaited<ReturnType<OpencodeClient["provider"]["list"]>>["data"]>["all"][number]
export type RunPrompt = {
text: string
parts: RunPromptPart[]
command?: {
name: string
arguments: string
}
}
export type RunAgent = NonNullable<Awaited<ReturnType<OpencodeClient["app"]["agents"]>>["data"]>[number]
type RunResourceMap = NonNullable<Awaited<ReturnType<OpencodeClient["experimental"]["resource"]["list"]>>["data"]>
export type RunResource = RunResourceMap[string]
export type RunInput = {
sdk: OpencodeClient
directory: string
sessionID: string
sessionTitle?: string
resume?: boolean
agent: string | undefined
model: PromptModel | undefined
variant: string | undefined
files: RunFilePart[]
initialInput?: string
thinking: boolean
demo?: boolean
}
// The semantic role of a scrollback entry. Maps 1:1 to theme colors.
export type EntryKind = "system" | "user" | "assistant" | "reasoning" | "tool" | "error"
// Whether the assistant is actively processing a turn.
export type FooterPhase = "idle" | "running"
// Full snapshot of footer status bar state. Every update replaces the whole
// object in the SolidJS signal so the view re-renders atomically.
export type FooterState = {
phase: FooterPhase
status: string
queue: number
model: string
duration: string
usage: string
first: boolean
interrupt: number
exit: number
}
// A partial update to FooterState. The footer merges this onto the current state.
export type FooterPatch = Partial<FooterState>
export type RunDiffStyle = "auto" | "stacked"
export type ScrollbackOptions = {
diffStyle?: RunDiffStyle
suppressBackgrounds?: boolean
}
export type ToolCodeSnapshot = {
kind: "code"
title: string
content: string
file?: string
}
export type ToolDiffSnapshot = {
kind: "diff"
items: Array<{
title: string
diff: string
file?: string
deletions?: number
}>
}
export type ToolTaskSnapshot = {
kind: "task"
title: string
rows: string[]
tail: string
}
export type ToolTodoSnapshot = {
kind: "todo"
items: Array<{
status: string
content: string
}>
tail: string
}
export type ToolQuestionSnapshot = {
kind: "question"
items: Array<{
question: string
answer: string
}>
tail: string
}
export type ToolSnapshot =
| ToolCodeSnapshot
| ToolDiffSnapshot
| ToolTaskSnapshot
| ToolTodoSnapshot
| ToolQuestionSnapshot
export type EntryLayout = "inline" | "block"
export type RunEntryBody =
| { type: "none" }
| { type: "text"; content: string }
| { type: "code"; content: string; filetype?: string }
| { type: "markdown"; content: string }
| { type: "structured"; snapshot: ToolSnapshot }
// Which interactive surface the footer is showing. Only one view is active at
// a time. The reducer drives transitions: when a permission arrives the view
// switches to "permission", and when the permission resolves it falls back to
// "prompt".
export type FooterView =
| { type: "prompt" }
| { type: "permission"; request: PermissionRequest }
| { type: "question"; request: QuestionRequest }
export type FooterPromptRoute =
| { type: "composer" }
| { type: "subagent"; sessionID: string }
| { type: "command" }
| { type: "model" }
| { type: "variant" }
export type FooterSubagentTab = {
sessionID: string
partID: string
callID: string
label: string
description: string
status: "running" | "completed" | "error"
title?: string
toolCalls?: number
lastUpdatedAt: number
}
export type FooterSubagentDetail = {
sessionID: string
commits: StreamCommit[]
}
export type FooterSubagentState = {
tabs: FooterSubagentTab[]
details: Record<string, FooterSubagentDetail>
permissions: PermissionRequest[]
questions: QuestionRequest[]
}
// The reducer emits this alongside scrollback commits so the footer can update in the same frame.
export type FooterOutput = {
patch?: FooterPatch
view?: FooterView
subagent?: FooterSubagentState
}
// Typed messages sent to RunFooter.event(). The prompt queue and stream
// transport both emit these to update footer state without reaching into
// internal signals directly.
export type FooterEvent =
| {
type: "catalog"
agents: RunAgent[]
resources: RunResource[]
commands?: RunCommand[]
}
| {
type: "models"
providers: RunProvider[]
}
| {
type: "variants"
variants: string[]
current: string | undefined
}
| {
type: "queue"
queue: number
}
| {
type: "first"
first: boolean
}
| {
type: "model"
model: string
}
| {
type: "turn.send"
queue: number
}
| {
type: "turn.wait"
}
| {
type: "turn.idle"
queue: number
}
| {
type: "turn.duration"
duration: string
}
| {
type: "stream.patch"
patch: FooterPatch
}
| {
type: "stream.view"
view: FooterView
}
| {
type: "stream.subagent"
state: FooterSubagentState
}
export type PermissionReply = Parameters<OpencodeClient["permission"]["reply"]>[0]
export type QuestionReply = Parameters<OpencodeClient["question"]["reply"]>[0]
export type QuestionReject = Parameters<OpencodeClient["question"]["reject"]>[0]
type FooterBinding = Binding<Renderable, KeyEvent>
export type FooterKeybinds = {
leader: string
leaderTimeout: number
commandList: readonly FooterBinding[]
variantCycle: readonly FooterBinding[]
interrupt: readonly FooterBinding[]
historyPrevious: readonly FooterBinding[]
historyNext: readonly FooterBinding[]
inputClear: readonly FooterBinding[]
inputSubmit: readonly FooterBinding[]
inputNewline: readonly FooterBinding[]
}
// Lifecycle phase of a scrollback entry. "start" opens the entry, "progress"
// appends content (coalesced in the footer queue), "final" closes it.
export type StreamPhase = "start" | "progress" | "final"
export type StreamSource = "assistant" | "reasoning" | "tool" | "system"
export type StreamToolState = "running" | "completed" | "error"
// A single append-only commit to scrollback. The session-data reducer produces
// these from SDK events, and RunFooter.append() queues them for the next
// microtask flush. Once flushed, they become immutable terminal scrollback
// rows -- they cannot be rewritten.
export type StreamCommit = {
kind: EntryKind
text: string
phase: StreamPhase
source: StreamSource
messageID?: string
partID?: string
tool?: string
part?: ToolPart
interrupted?: boolean
toolState?: StreamToolState
toolError?: string
}
// The public contract between the stream transport / prompt queue and
// the footer. RunFooter implements this. The transport and queue never
// touch the renderer directly -- they go through this interface.
export type FooterApi = {
readonly isClosed: boolean
onPrompt(fn: (input: RunPrompt) => void): () => void
onClose(fn: () => void): () => void
event(next: FooterEvent): void
append(commit: StreamCommit): void
idle(): Promise<void>
close(): void
destroy(): void
}