opencode-drive 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,17 +1,25 @@
1
- import {
2
- Backend,
3
- type JsonRpc,
4
- } from "./protocol.js"
1
+ import { Backend, type JsonRpc } from "./protocol.js"
5
2
 
6
3
  const defaultBackendPort = 40950
7
4
 
8
5
  type BackendMethods = {
9
- readonly "llm.attach": { readonly params: undefined; readonly result: { readonly attached: true } }
10
- readonly "llm.chunk": { readonly params: Backend.ChunkParams; readonly result: { readonly ok: true } }
11
- readonly "llm.finish": { readonly params: Partial<Backend.FinishParams> & Pick<Backend.FinishParams, "id">; readonly result: { readonly ok: true } }
12
- readonly "llm.disconnect": { readonly params: Backend.DisconnectParams; readonly result: { readonly ok: true } }
13
- readonly "llm.pending": { readonly params: undefined; readonly result: { readonly exchanges: ReadonlyArray<Backend.OpenedExchange> } }
14
- readonly "network.log": { readonly params: undefined; readonly result: { readonly entries: ReadonlyArray<Backend.NetworkLogEntry> } }
6
+ readonly "llm.attach": {
7
+ readonly params: undefined
8
+ readonly result: { readonly attached: true }
9
+ }
10
+ readonly "llm.chunk": {
11
+ readonly params: Backend.ChunkParams
12
+ readonly result: { readonly ok: true }
13
+ }
14
+ readonly "llm.finish": {
15
+ readonly params: Partial<Backend.FinishParams> &
16
+ Pick<Backend.FinishParams, "id">
17
+ readonly result: { readonly ok: true }
18
+ }
19
+ readonly "llm.disconnect": {
20
+ readonly params: Backend.DisconnectParams
21
+ readonly result: { readonly ok: true }
22
+ }
15
23
  }
16
24
 
17
25
  type BackendMethodName = keyof BackendMethods
@@ -46,21 +54,37 @@ export class BackendSimulationClient {
46
54
  private readonly socket: WebSocket
47
55
  private readonly timeout: number
48
56
  private nextId = 1
57
+ private closing = false
49
58
  private readonly pending = new Map<number, Waiter>()
50
- private readonly llmRequests = new Set<(request: Backend.OpenedExchange) => void>()
59
+ private readonly llmRequests = new Set<
60
+ (request: Backend.OpenedExchange) => void
61
+ >()
51
62
 
52
63
  private constructor(socket: WebSocket, url: string, timeout: number) {
53
64
  this.socket = socket
54
65
  this.url = url
55
66
  this.timeout = timeout
56
- socket.addEventListener("message", (event) => this.onMessage(String(event.data)))
57
- socket.addEventListener("close", () => this.rejectAll(new BackendSimulationError("connection closed")))
58
- socket.addEventListener("error", () => this.rejectAll(new BackendSimulationError("connection error")))
67
+ socket.addEventListener("message", (event) =>
68
+ this.onMessage(String(event.data)),
69
+ )
70
+ socket.addEventListener("close", () =>
71
+ this.rejectAll(new BackendSimulationError("connection closed")),
72
+ )
73
+ socket.addEventListener("error", () =>
74
+ this.rejectAll(new BackendSimulationError("connection error")),
75
+ )
59
76
  }
60
77
 
61
- static async connect(options?: BackendSimulationClientOptions): Promise<BackendSimulationClient> {
78
+ static async connect(
79
+ options?: BackendSimulationClientOptions,
80
+ ): Promise<BackendSimulationClient> {
62
81
  const timeout = options?.timeout ?? 30_000
63
- if (options?.url !== undefined) return new BackendSimulationClient(await open(options.url), options.url, timeout)
82
+ if (options?.url !== undefined)
83
+ return new BackendSimulationClient(
84
+ await open(options.url),
85
+ options.url,
86
+ timeout,
87
+ )
64
88
  const first = options?.port ?? defaultBackendPort
65
89
  const attempts = options?.portAttempts ?? 10
66
90
  for (let offset = 0; offset < attempts; offset++) {
@@ -69,28 +93,52 @@ export class BackendSimulationClient {
69
93
  return new BackendSimulationClient(await open(url), url, timeout)
70
94
  } catch {}
71
95
  }
72
- throw new BackendSimulationError(`no backend simulation server found on ports ${first}-${first + attempts - 1}`)
96
+ throw new BackendSimulationError(
97
+ `no backend simulation server found on ports ${first}-${first + attempts - 1}`,
98
+ )
73
99
  }
74
100
 
75
101
  async call<M extends BackendMethodName>(
76
102
  method: M,
77
103
  params?: BackendMethods[M]["params"],
78
104
  ): Promise<BackendMethods[M]["result"]> {
79
- if (this.socket.readyState !== WebSocket.OPEN) throw new BackendSimulationError("connection is not open", method)
105
+ if (this.socket.readyState !== WebSocket.OPEN)
106
+ throw new BackendSimulationError("connection is not open", method)
80
107
  const id = this.nextId++
81
108
  const promise = new Promise<unknown>((resolve, reject) => {
82
109
  const timer = setTimeout(() => {
83
110
  this.pending.delete(id)
84
- reject(new BackendSimulationError(`timed out after ${this.timeout}ms`, method))
111
+ reject(
112
+ new BackendSimulationError(
113
+ `timed out after ${this.timeout}ms`,
114
+ method,
115
+ ),
116
+ )
85
117
  }, this.timeout)
86
118
  this.pending.set(id, { method, resolve, reject, timer })
87
119
  })
88
- this.socket.send(JSON.stringify({ jsonrpc: "2.0", id, method, ...(params === undefined ? {} : { params }) }))
120
+ this.socket.send(
121
+ JSON.stringify({
122
+ jsonrpc: "2.0",
123
+ id,
124
+ method,
125
+ ...(params === undefined ? {} : { params }),
126
+ }),
127
+ )
89
128
  return (await promise) as BackendMethods[M]["result"]
90
129
  }
91
130
 
92
- async attach(onRequest: (request: Backend.OpenedExchange) => void | Promise<void>) {
93
- this.llmRequests.add((request) => void onRequest(request))
131
+ async attach(
132
+ onRequest: (request: Backend.OpenedExchange) => void | Promise<void>,
133
+ ) {
134
+ this.llmRequests.add((request) => {
135
+ void Promise.resolve(onRequest(request)).catch((error) => {
136
+ if (!this.closing)
137
+ console.error(
138
+ `error: ${error instanceof Error ? error.message : String(error)}`,
139
+ )
140
+ })
141
+ })
94
142
  return await this.call("llm.attach")
95
143
  }
96
144
 
@@ -99,22 +147,18 @@ export class BackendSimulationClient {
99
147
  }
100
148
 
101
149
  finish(id: string, reason?: Backend.FinishReason) {
102
- return this.call("llm.finish", { id, ...(reason === undefined ? {} : { reason }) })
150
+ return this.call("llm.finish", {
151
+ id,
152
+ ...(reason === undefined ? {} : { reason }),
153
+ })
103
154
  }
104
155
 
105
156
  disconnect(id: string) {
106
157
  return this.call("llm.disconnect", { id })
107
158
  }
108
159
 
109
- pendingExchanges() {
110
- return this.call("llm.pending")
111
- }
112
-
113
- networkLog(): Promise<{ readonly entries: ReadonlyArray<Backend.NetworkLogEntry> }> {
114
- return this.call("network.log")
115
- }
116
-
117
160
  close() {
161
+ this.closing = true
118
162
  this.socket.close()
119
163
  }
120
164
 
@@ -123,7 +167,8 @@ export class BackendSimulationClient {
123
167
  if (message === undefined) return
124
168
  if ("method" in message) {
125
169
  if (message.method === "llm.request") {
126
- for (const listener of this.llmRequests) listener(message.params as Backend.OpenedExchange)
170
+ for (const listener of this.llmRequests)
171
+ listener(message.params as Backend.OpenedExchange)
127
172
  }
128
173
  return
129
174
  }
@@ -132,7 +177,10 @@ export class BackendSimulationClient {
132
177
  if (waiter === undefined) return
133
178
  this.pending.delete(message.id)
134
179
  clearTimeout(waiter.timer)
135
- if (message.error) waiter.reject(new BackendSimulationError(message.error.message, waiter.method))
180
+ if (message.error)
181
+ waiter.reject(
182
+ new BackendSimulationError(message.error.message, waiter.method),
183
+ )
136
184
  else waiter.resolve(message.result)
137
185
  }
138
186
 
@@ -145,13 +193,21 @@ export class BackendSimulationClient {
145
193
  }
146
194
  }
147
195
 
148
- function parseResponse(data: string): JsonRpc.Response | { readonly method: string; readonly params: unknown } | undefined {
196
+ function parseResponse(
197
+ data: string,
198
+ ):
199
+ | JsonRpc.Response
200
+ | { readonly method: string; readonly params: unknown }
201
+ | undefined {
149
202
  try {
150
203
  const value = JSON.parse(data) as unknown
151
204
  if (typeof value !== "object" || value === null) return undefined
152
205
  if (!("jsonrpc" in value) || value.jsonrpc !== "2.0") return undefined
153
206
  if ("method" in value && typeof value.method === "string") {
154
- return { method: value.method, params: "params" in value ? value.params : undefined }
207
+ return {
208
+ method: value.method,
209
+ params: "params" in value ? value.params : undefined,
210
+ }
155
211
  }
156
212
  if (!("id" in value)) return undefined
157
213
  return value as JsonRpc.Response
@@ -180,5 +236,6 @@ function open(url: string): Promise<WebSocket> {
180
236
  })
181
237
  }
182
238
 
183
- export const connectBackendSimulation = (options?: BackendSimulationClientOptions): Promise<BackendSimulationClient> =>
184
- BackendSimulationClient.connect(options)
239
+ export const connectBackendSimulation = (
240
+ options?: BackendSimulationClientOptions,
241
+ ): Promise<BackendSimulationClient> => BackendSimulationClient.connect(options)
@@ -6,14 +6,16 @@ import {
6
6
  const defaultPort = 40900
7
7
 
8
8
  type Methods = {
9
- readonly "ui.render": { readonly params: undefined; readonly result: Frontend.Render }
9
+ readonly "ui.screenshot": { readonly params: undefined; readonly result: Frontend.Screenshot }
10
10
  readonly "ui.state": { readonly params: undefined; readonly result: Frontend.State }
11
11
  readonly "ui.start-record": { readonly params: undefined; readonly result: Frontend.StartRecord }
12
12
  readonly "ui.end-record": { readonly params: undefined; readonly result: Frontend.EndRecord }
13
- readonly "ui.action": { readonly params: Frontend.ActionParams; readonly result: Frontend.State }
14
- readonly "trace.list": { readonly params: undefined; readonly result: Frontend.TraceList }
15
- readonly "trace.clear": { readonly params: undefined; readonly result: { readonly cleared: true } }
16
- readonly "trace.export": { readonly params: undefined; readonly result: Frontend.TraceList }
13
+ readonly "ui.type": { readonly params: Frontend.TypeParams; readonly result: Frontend.State }
14
+ readonly "ui.press": { readonly params: Frontend.PressParams; readonly result: Frontend.State }
15
+ readonly "ui.enter": { readonly params: undefined; readonly result: Frontend.State }
16
+ readonly "ui.arrow": { readonly params: Frontend.ArrowParams; readonly result: Frontend.State }
17
+ readonly "ui.focus": { readonly params: Frontend.FocusParams; readonly result: Frontend.State }
18
+ readonly "ui.click": { readonly params: Frontend.ClickParams; readonly result: Frontend.State }
17
19
  }
18
20
 
19
21
  type MethodName = keyof Methods
@@ -123,8 +125,8 @@ export class SimulationClient {
123
125
  return this.call("ui.state")
124
126
  }
125
127
 
126
- render(): Promise<Frontend.Render> {
127
- return this.call("ui.render")
128
+ screenshot(): Promise<Frontend.Screenshot> {
129
+ return this.call("ui.screenshot")
128
130
  }
129
131
 
130
132
  startRecord(): Promise<Frontend.StartRecord> {
@@ -136,46 +138,28 @@ export class SimulationClient {
136
138
  }
137
139
 
138
140
  /** Executes one user-level action and returns the post-action state. */
139
- action(action: Frontend.Action): Promise<Frontend.State> {
140
- return this.call("ui.action", { action })
141
- }
142
-
143
141
  typeText(text: string): Promise<Frontend.State> {
144
- return this.action({ type: "typeText", text })
142
+ return this.call("ui.type", { text })
145
143
  }
146
144
 
147
145
  pressKey(key: string, modifiers?: Frontend.KeyModifiers): Promise<Frontend.State> {
148
- return this.action({ type: "pressKey", key: key === "escape" ? "\u001b" : key, ...(modifiers === undefined ? {} : { modifiers }) })
146
+ return this.call("ui.press", { key: key === "escape" ? "\u001b" : key, ...(modifiers === undefined ? {} : { modifiers }) })
149
147
  }
150
148
 
151
149
  pressEnter(): Promise<Frontend.State> {
152
- return this.action({ type: "pressEnter" })
150
+ return this.call("ui.enter")
153
151
  }
154
152
 
155
153
  pressArrow(direction: "up" | "down" | "left" | "right"): Promise<Frontend.State> {
156
- return this.action({ type: "pressArrow", direction })
154
+ return this.call("ui.arrow", { direction })
157
155
  }
158
156
 
159
157
  focus(target: number): Promise<Frontend.State> {
160
- return this.action({ type: "focus", target })
158
+ return this.call("ui.focus", { target })
161
159
  }
162
160
 
163
161
  click(target: number, x: number, y: number): Promise<Frontend.State> {
164
- return this.action({ type: "click", target, x, y })
165
- }
166
-
167
- // ── trace ─────────────────────────────────────────────────────────────
168
-
169
- traceList(): Promise<Frontend.TraceList> {
170
- return this.call("trace.list")
171
- }
172
-
173
- traceClear(): Promise<{ readonly cleared: true }> {
174
- return this.call("trace.clear")
175
- }
176
-
177
- traceExport(): Promise<Frontend.TraceList> {
178
- return this.call("trace.export")
162
+ return this.call("ui.click", { target, x, y })
179
163
  }
180
164
 
181
165
  // ── lifecycle ─────────────────────────────────────────────────────────
@@ -7,11 +7,8 @@ export const defaultBackendPort = 40950
7
7
  export { Backend, Frontend, JsonRpc, SimulationProtocol } from "./protocol.js"
8
8
  export type BackendFinishReason = import("./protocol.js").Backend.FinishReason
9
9
  export type BackendItem = import("./protocol.js").Backend.Item
10
- export type NetworkLogEntry = import("./protocol.js").Backend.NetworkLogEntry
11
10
  export type OpenedExchange = import("./protocol.js").Backend.OpenedExchange
12
11
  export type KeyModifiers = import("./protocol.js").Frontend.KeyModifiers
13
- export type TraceList = import("./protocol.js").Frontend.TraceList
14
- export type TraceRecord = import("./protocol.js").Frontend.TraceRecord
15
12
  export type UiAction = import("./protocol.js").Frontend.Action
16
13
  export type UiElement = import("./protocol.js").Frontend.Element
17
14
  export type UiState = import("./protocol.js").Frontend.State
@@ -31,7 +31,10 @@ export namespace JsonRpc {
31
31
 
32
32
  export const decodeRequest = Schema.decodeUnknownSync(Request)
33
33
 
34
- export function success(id: Request["id"], result: unknown): Response | undefined {
34
+ export function success(
35
+ id: Request["id"],
36
+ result: unknown,
37
+ ): Response | undefined {
35
38
  if (id === undefined) return undefined
36
39
  return { jsonrpc: "2.0", id, result: result as Json }
37
40
  }
@@ -56,15 +59,29 @@ export namespace Frontend {
56
59
  super: Schema.optional(Schema.Boolean),
57
60
  hyper: Schema.optional(Schema.Boolean),
58
61
  })
59
- export interface KeyModifiers extends Schema.Schema.Type<typeof KeyModifiers> {}
62
+ export interface KeyModifiers extends Schema.Schema.Type<
63
+ typeof KeyModifiers
64
+ > {}
60
65
 
61
66
  export const Action = Schema.Union([
62
- Schema.Struct({ type: Schema.Literal("typeText"), text: Schema.String }),
63
- Schema.Struct({ type: Schema.Literal("pressKey"), key: Schema.String, modifiers: Schema.optional(KeyModifiers) }),
64
- Schema.Struct({ type: Schema.Literal("pressEnter") }),
65
- Schema.Struct({ type: Schema.Literal("pressArrow"), direction: Schema.Literals(["up", "down", "left", "right"]) }),
66
- Schema.Struct({ type: Schema.Literal("focus"), target: Schema.Number }),
67
- Schema.Struct({ type: Schema.Literal("click"), target: Schema.Number, x: Schema.Number, y: Schema.Number }),
67
+ Schema.Struct({ type: Schema.Literal("ui.type"), text: Schema.String }),
68
+ Schema.Struct({
69
+ type: Schema.Literal("ui.press"),
70
+ key: Schema.String,
71
+ modifiers: Schema.optional(KeyModifiers),
72
+ }),
73
+ Schema.Struct({ type: Schema.Literal("ui.enter") }),
74
+ Schema.Struct({
75
+ type: Schema.Literal("ui.arrow"),
76
+ direction: Schema.Literals(["up", "down", "left", "right"]),
77
+ }),
78
+ Schema.Struct({ type: Schema.Literal("ui.focus"), target: Schema.Number }),
79
+ Schema.Struct({
80
+ type: Schema.Literal("ui.click"),
81
+ target: Schema.Number,
82
+ x: Schema.Number,
83
+ y: Schema.Number,
84
+ }),
68
85
  ])
69
86
  export type Action = Schema.Schema.Type<typeof Action>
70
87
 
@@ -88,12 +105,11 @@ export namespace Frontend {
88
105
  editor: Schema.Boolean,
89
106
  }),
90
107
  elements: Schema.Array(Element),
91
- actions: Schema.Array(Action),
92
108
  })
93
109
  export interface State extends Schema.Schema.Type<typeof State> {}
94
110
 
95
- export const Render = Schema.String
96
- export type Render = Schema.Schema.Type<typeof Render>
111
+ export const Screenshot = Schema.String
112
+ export type Screenshot = Schema.Schema.Type<typeof Screenshot>
97
113
 
98
114
  export const StartRecord = Schema.Struct({ recording: Schema.Literal(true) })
99
115
  export interface StartRecord extends Schema.Schema.Type<typeof StartRecord> {}
@@ -101,77 +117,149 @@ export namespace Frontend {
101
117
  export const EndRecord = Schema.String
102
118
  export type EndRecord = Schema.Schema.Type<typeof EndRecord>
103
119
 
104
- export const ActionParams = Schema.Struct({ action: Action })
105
- export interface ActionParams extends Schema.Schema.Type<typeof ActionParams> {}
120
+ export const TypeParams = Schema.Struct({ text: Schema.String })
121
+ export interface TypeParams extends Schema.Schema.Type<typeof TypeParams> {}
122
+
123
+ export const PressParams = Schema.Struct({
124
+ key: Schema.String,
125
+ modifiers: Schema.optional(KeyModifiers),
126
+ })
127
+ export interface PressParams extends Schema.Schema.Type<typeof PressParams> {}
128
+
129
+ export const ArrowParams = Schema.Struct({
130
+ direction: Schema.Literals(["up", "down", "left", "right"]),
131
+ })
132
+ export interface ArrowParams extends Schema.Schema.Type<typeof ArrowParams> {}
133
+
134
+ export const FocusParams = Schema.Struct({ target: Schema.Number })
135
+ export interface FocusParams extends Schema.Schema.Type<typeof FocusParams> {}
136
+
137
+ export const ClickParams = Schema.Struct({
138
+ target: Schema.Number,
139
+ x: Schema.Number,
140
+ y: Schema.Number,
141
+ })
142
+ export interface ClickParams extends Schema.Schema.Type<typeof ClickParams> {}
106
143
 
107
144
  export const Request = Schema.Union([
108
- Schema.Struct({ ...JsonRpc.RequestFields, method: Schema.Literal("ui.action"), params: ActionParams }),
145
+ Schema.Struct({
146
+ ...JsonRpc.RequestFields,
147
+ method: Schema.Literal("ui.type"),
148
+ params: TypeParams,
149
+ }),
150
+ Schema.Struct({
151
+ ...JsonRpc.RequestFields,
152
+ method: Schema.Literal("ui.press"),
153
+ params: PressParams,
154
+ }),
155
+ Schema.Struct({
156
+ ...JsonRpc.RequestFields,
157
+ method: Schema.Literal("ui.arrow"),
158
+ params: ArrowParams,
159
+ }),
160
+ Schema.Struct({
161
+ ...JsonRpc.RequestFields,
162
+ method: Schema.Literal("ui.focus"),
163
+ params: FocusParams,
164
+ }),
165
+ Schema.Struct({
166
+ ...JsonRpc.RequestFields,
167
+ method: Schema.Literal("ui.click"),
168
+ params: ClickParams,
169
+ }),
109
170
  Schema.Struct({
110
171
  ...JsonRpc.RequestFields,
111
172
  method: Schema.Literals([
112
- "ui.render",
173
+ "ui.enter",
174
+ "ui.screenshot",
113
175
  "ui.state",
114
176
  "ui.start-record",
115
177
  "ui.end-record",
116
- "trace.list",
117
- "trace.clear",
118
- "trace.export",
119
178
  ]),
120
179
  }),
121
180
  ])
122
181
  export type Request = Schema.Schema.Type<typeof Request>
123
182
  export const decodeRequest = Schema.decodeUnknownSync(Request)
124
-
125
- export const TraceRecord = Schema.Struct({
126
- id: Schema.Number,
127
- time: Schema.String,
128
- type: Schema.String,
129
- data: Schema.optional(Schema.Json),
130
- })
131
- export interface TraceRecord extends Schema.Schema.Type<typeof TraceRecord> {}
132
-
133
- export const TraceList = Schema.Struct({ records: Schema.Array(TraceRecord) })
134
- export interface TraceList extends Schema.Schema.Type<typeof TraceList> {}
135
183
  }
136
184
 
137
185
  export namespace Backend {
138
186
  export const Item = Schema.Union([
139
187
  Schema.Struct({ type: Schema.Literal("textDelta"), text: Schema.String }),
140
- Schema.Struct({ type: Schema.Literal("reasoningDelta"), text: Schema.String }),
141
- Schema.Struct({ type: Schema.Literal("toolCall"), id: Schema.String, name: Schema.String, input: Schema.Json }),
188
+ Schema.Struct({
189
+ type: Schema.Literal("reasoningDelta"),
190
+ text: Schema.String,
191
+ }),
192
+ Schema.Struct({
193
+ type: Schema.Literal("toolCall"),
194
+ id: Schema.String,
195
+ name: Schema.String,
196
+ input: Schema.Json,
197
+ }),
142
198
  Schema.Struct({ type: Schema.Literal("raw"), chunk: Schema.Json }),
143
199
  ])
144
200
  export type Item = Schema.Schema.Type<typeof Item>
145
201
 
146
- export const FinishReason = Schema.Literals(["stop", "tool-calls", "length", "content-filter"])
202
+ export const FinishReason = Schema.Literals([
203
+ "stop",
204
+ "tool-calls",
205
+ "length",
206
+ "content-filter",
207
+ ])
147
208
  export type FinishReason = Schema.Schema.Type<typeof FinishReason>
148
209
 
149
- export const ChunkParams = Schema.Struct({ id: Schema.String, items: Schema.Array(Item) })
210
+ export const ChunkParams = Schema.Struct({
211
+ id: Schema.String,
212
+ items: Schema.Array(Item),
213
+ })
150
214
  export interface ChunkParams extends Schema.Schema.Type<typeof ChunkParams> {}
151
215
 
152
216
  export const FinishParams = Schema.Struct({
153
217
  id: Schema.String,
154
- reason: FinishReason.pipe(Schema.withDecodingDefault(Effect.succeed("stop" as const))),
218
+ reason: FinishReason.pipe(
219
+ Schema.withDecodingDefault(Effect.succeed("stop" as const)),
220
+ ),
155
221
  })
156
- export interface FinishParams extends Schema.Schema.Type<typeof FinishParams> {}
222
+ export interface FinishParams extends Schema.Schema.Type<
223
+ typeof FinishParams
224
+ > {}
157
225
 
158
226
  export const DisconnectParams = Schema.Struct({ id: Schema.String })
159
- export interface DisconnectParams extends Schema.Schema.Type<typeof DisconnectParams> {}
227
+ export interface DisconnectParams extends Schema.Schema.Type<
228
+ typeof DisconnectParams
229
+ > {}
160
230
 
161
231
  export const Request = Schema.Union([
162
- Schema.Struct({ ...JsonRpc.RequestFields, method: Schema.Literal("llm.chunk"), params: ChunkParams }),
163
- Schema.Struct({ ...JsonRpc.RequestFields, method: Schema.Literal("llm.finish"), params: FinishParams }),
164
- Schema.Struct({ ...JsonRpc.RequestFields, method: Schema.Literal("llm.disconnect"), params: DisconnectParams }),
165
232
  Schema.Struct({
166
233
  ...JsonRpc.RequestFields,
167
- method: Schema.Literals(["llm.attach", "llm.pending", "network.log"]),
234
+ method: Schema.Literal("llm.chunk"),
235
+ params: ChunkParams,
236
+ }),
237
+ Schema.Struct({
238
+ ...JsonRpc.RequestFields,
239
+ method: Schema.Literal("llm.finish"),
240
+ params: FinishParams,
241
+ }),
242
+ Schema.Struct({
243
+ ...JsonRpc.RequestFields,
244
+ method: Schema.Literal("llm.disconnect"),
245
+ params: DisconnectParams,
246
+ }),
247
+ Schema.Struct({
248
+ ...JsonRpc.RequestFields,
249
+ method: Schema.Literal("llm.attach"),
168
250
  }),
169
251
  ])
170
252
  export type Request = Schema.Schema.Type<typeof Request>
171
253
  export const decodeRequest = Schema.decodeUnknownSync(Request)
172
254
 
173
- export const OpenedExchange = Schema.Struct({ id: Schema.String, url: Schema.String, body: Schema.Json })
174
- export interface OpenedExchange extends Schema.Schema.Type<typeof OpenedExchange> {}
255
+ export const OpenedExchange = Schema.Struct({
256
+ id: Schema.String,
257
+ url: Schema.String,
258
+ body: Schema.Json,
259
+ })
260
+ export interface OpenedExchange extends Schema.Schema.Type<
261
+ typeof OpenedExchange
262
+ > {}
175
263
 
176
264
  export const NetworkLogEntry = Schema.Struct({
177
265
  time: Schema.Number,
@@ -179,8 +267,9 @@ export namespace Backend {
179
267
  url: Schema.String,
180
268
  matched: Schema.Boolean,
181
269
  })
182
- export interface NetworkLogEntry extends Schema.Schema.Type<typeof NetworkLogEntry> {}
183
-
270
+ export interface NetworkLogEntry extends Schema.Schema.Type<
271
+ typeof NetworkLogEntry
272
+ > {}
184
273
  }
185
274
 
186
275
  export * as SimulationProtocol from "./index"
@@ -0,0 +1,62 @@
1
+ // CLI command contract for `opencode-drive send`.
2
+
3
+ export type Json =
4
+ null | boolean | number | string | Json[] | { [key: string]: Json }
5
+
6
+ export type KeyModifiers = {
7
+ ctrl?: boolean
8
+ shift?: boolean
9
+ meta?: boolean
10
+ super?: boolean
11
+ hyper?: boolean
12
+ }
13
+
14
+ export type Element = {
15
+ id: string
16
+ num: number
17
+ x: number
18
+ y: number
19
+ width: number
20
+ height: number
21
+ focusable: boolean
22
+ focused: boolean
23
+ clickable: boolean
24
+ editor: boolean
25
+ }
26
+
27
+ export type State = {
28
+ focused: {
29
+ renderable?: number
30
+ editor: boolean
31
+ }
32
+ elements: Element[]
33
+ }
34
+
35
+ export type Command =
36
+ | { name: "ui.type"; params: { text: string }; result: State }
37
+ | {
38
+ name: "ui.press"
39
+ params: { key: string; modifiers?: KeyModifiers }
40
+ result: State
41
+ }
42
+ | { name: "ui.enter"; result: State }
43
+ | {
44
+ name: "ui.arrow"
45
+ params: { direction: "up" | "down" | "left" | "right" }
46
+ result: State
47
+ }
48
+ | { name: "ui.focus"; params: { target: number }; result: State }
49
+ | {
50
+ name: "ui.click"
51
+ params: { target: number; x: number; y: number }
52
+ result: State
53
+ }
54
+ | { name: "ui.screenshot"; result: string }
55
+ | { name: "ui.state"; result: State }
56
+ | { name: "ui.start-record"; result: { recording: true } }
57
+ | { name: "ui.end-record"; result: string }
58
+
59
+ // Commands with `params` take one JSON argument:
60
+ // --command.ui.type '{"text":"hello"}'
61
+ // Commands without `params` are flags:
62
+ // --command.ui.enter
@@ -83,7 +83,7 @@ for (let index = 0; index < options.count; index++) {
83
83
  "--driver", `bun ${path.resolve("src/experimental/flow-driver.ts")} ${path.join(directory, "scenario.json")} ${path.join(directory, "result.json")}`,
84
84
  "--",
85
85
  "bun", "run", "--conditions=browser", "--preload=@opentui/solid/preload",
86
- "/root/projects/opencode-latest/packages/cli/src/index.ts", "--standalone",
86
+ "/root/projects/opencode-latest/packages/cli/src/index.ts",
87
87
  ], {
88
88
  cwd: path.resolve("."),
89
89
  env: {
@@ -107,7 +107,7 @@ for (let index = 0; index < options.count; index++) {
107
107
  }
108
108
  const result: FlowResult = await Bun.file(path.join(directory, "result.json")).json()
109
109
  results.push(result)
110
- console.log(` passed in ${result.durationMs}ms; trace=${result.traceRecords} title=${result.titleExchanges}`)
110
+ console.log(` passed in ${result.durationMs}ms; title=${result.titleExchanges}`)
111
111
  }
112
112
 
113
113
  const summary = {
@@ -117,7 +117,6 @@ const summary = {
117
117
  assistantExchanges: results.reduce((total, result) => total + result.assistantExchanges, 0),
118
118
  subagentExchanges: results.reduce((total, result) => total + result.subagentExchanges, 0),
119
119
  titleExchanges: results.reduce((total, result) => total + result.titleExchanges, 0),
120
- traceRecords: results.reduce((total, result) => total + result.traceRecords, 0),
121
120
  durationMs: results.reduce((total, result) => total + result.durationMs, 0),
122
121
  coverage: { ...coverage, streamChunkTypes: [...coverage.streamChunkTypes] },
123
122
  }