opencode-drive 0.1.2 → 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,16 +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> } }
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
+ }
14
23
  }
15
24
 
16
25
  type BackendMethodName = keyof BackendMethods
@@ -45,21 +54,37 @@ export class BackendSimulationClient {
45
54
  private readonly socket: WebSocket
46
55
  private readonly timeout: number
47
56
  private nextId = 1
57
+ private closing = false
48
58
  private readonly pending = new Map<number, Waiter>()
49
- private readonly llmRequests = new Set<(request: Backend.OpenedExchange) => void>()
59
+ private readonly llmRequests = new Set<
60
+ (request: Backend.OpenedExchange) => void
61
+ >()
50
62
 
51
63
  private constructor(socket: WebSocket, url: string, timeout: number) {
52
64
  this.socket = socket
53
65
  this.url = url
54
66
  this.timeout = timeout
55
- socket.addEventListener("message", (event) => this.onMessage(String(event.data)))
56
- socket.addEventListener("close", () => this.rejectAll(new BackendSimulationError("connection closed")))
57
- 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
+ )
58
76
  }
59
77
 
60
- static async connect(options?: BackendSimulationClientOptions): Promise<BackendSimulationClient> {
78
+ static async connect(
79
+ options?: BackendSimulationClientOptions,
80
+ ): Promise<BackendSimulationClient> {
61
81
  const timeout = options?.timeout ?? 30_000
62
- 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
+ )
63
88
  const first = options?.port ?? defaultBackendPort
64
89
  const attempts = options?.portAttempts ?? 10
65
90
  for (let offset = 0; offset < attempts; offset++) {
@@ -68,28 +93,52 @@ export class BackendSimulationClient {
68
93
  return new BackendSimulationClient(await open(url), url, timeout)
69
94
  } catch {}
70
95
  }
71
- 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
+ )
72
99
  }
73
100
 
74
101
  async call<M extends BackendMethodName>(
75
102
  method: M,
76
103
  params?: BackendMethods[M]["params"],
77
104
  ): Promise<BackendMethods[M]["result"]> {
78
- 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)
79
107
  const id = this.nextId++
80
108
  const promise = new Promise<unknown>((resolve, reject) => {
81
109
  const timer = setTimeout(() => {
82
110
  this.pending.delete(id)
83
- 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
+ )
84
117
  }, this.timeout)
85
118
  this.pending.set(id, { method, resolve, reject, timer })
86
119
  })
87
- 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
+ )
88
128
  return (await promise) as BackendMethods[M]["result"]
89
129
  }
90
130
 
91
- async attach(onRequest: (request: Backend.OpenedExchange) => void | Promise<void>) {
92
- 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
+ })
93
142
  return await this.call("llm.attach")
94
143
  }
95
144
 
@@ -98,18 +147,18 @@ export class BackendSimulationClient {
98
147
  }
99
148
 
100
149
  finish(id: string, reason?: Backend.FinishReason) {
101
- return this.call("llm.finish", { id, ...(reason === undefined ? {} : { reason }) })
150
+ return this.call("llm.finish", {
151
+ id,
152
+ ...(reason === undefined ? {} : { reason }),
153
+ })
102
154
  }
103
155
 
104
156
  disconnect(id: string) {
105
157
  return this.call("llm.disconnect", { id })
106
158
  }
107
159
 
108
- pendingExchanges() {
109
- return this.call("llm.pending")
110
- }
111
-
112
160
  close() {
161
+ this.closing = true
113
162
  this.socket.close()
114
163
  }
115
164
 
@@ -118,7 +167,8 @@ export class BackendSimulationClient {
118
167
  if (message === undefined) return
119
168
  if ("method" in message) {
120
169
  if (message.method === "llm.request") {
121
- 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)
122
172
  }
123
173
  return
124
174
  }
@@ -127,7 +177,10 @@ export class BackendSimulationClient {
127
177
  if (waiter === undefined) return
128
178
  this.pending.delete(message.id)
129
179
  clearTimeout(waiter.timer)
130
- 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
+ )
131
184
  else waiter.resolve(message.result)
132
185
  }
133
186
 
@@ -140,13 +193,21 @@ export class BackendSimulationClient {
140
193
  }
141
194
  }
142
195
 
143
- 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 {
144
202
  try {
145
203
  const value = JSON.parse(data) as unknown
146
204
  if (typeof value !== "object" || value === null) return undefined
147
205
  if (!("jsonrpc" in value) || value.jsonrpc !== "2.0") return undefined
148
206
  if ("method" in value && typeof value.method === "string") {
149
- 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
+ }
150
211
  }
151
212
  if (!("id" in value)) return undefined
152
213
  return value as JsonRpc.Response
@@ -175,5 +236,6 @@ function open(url: string): Promise<WebSocket> {
175
236
  })
176
237
  }
177
238
 
178
- export const connectBackendSimulation = (options?: BackendSimulationClientOptions): Promise<BackendSimulationClient> =>
179
- BackendSimulationClient.connect(options)
239
+ export const connectBackendSimulation = (
240
+ options?: BackendSimulationClientOptions,
241
+ ): Promise<BackendSimulationClient> => BackendSimulationClient.connect(options)
@@ -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
67
  Schema.Struct({ type: Schema.Literal("ui.type"), text: Schema.String }),
63
- Schema.Struct({ type: Schema.Literal("ui.press"), key: Schema.String, modifiers: Schema.optional(KeyModifiers) }),
68
+ Schema.Struct({
69
+ type: Schema.Literal("ui.press"),
70
+ key: Schema.String,
71
+ modifiers: Schema.optional(KeyModifiers),
72
+ }),
64
73
  Schema.Struct({ type: Schema.Literal("ui.enter") }),
65
- Schema.Struct({ type: Schema.Literal("ui.arrow"), direction: Schema.Literals(["up", "down", "left", "right"]) }),
74
+ Schema.Struct({
75
+ type: Schema.Literal("ui.arrow"),
76
+ direction: Schema.Literals(["up", "down", "left", "right"]),
77
+ }),
66
78
  Schema.Struct({ type: Schema.Literal("ui.focus"), target: Schema.Number }),
67
- Schema.Struct({ type: Schema.Literal("ui.click"), target: Schema.Number, x: Schema.Number, y: 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
 
@@ -103,24 +120,53 @@ export namespace Frontend {
103
120
  export const TypeParams = Schema.Struct({ text: Schema.String })
104
121
  export interface TypeParams extends Schema.Schema.Type<typeof TypeParams> {}
105
122
 
106
- export const PressParams = Schema.Struct({ key: Schema.String, modifiers: Schema.optional(KeyModifiers) })
123
+ export const PressParams = Schema.Struct({
124
+ key: Schema.String,
125
+ modifiers: Schema.optional(KeyModifiers),
126
+ })
107
127
  export interface PressParams extends Schema.Schema.Type<typeof PressParams> {}
108
128
 
109
- export const ArrowParams = Schema.Struct({ direction: Schema.Literals(["up", "down", "left", "right"]) })
129
+ export const ArrowParams = Schema.Struct({
130
+ direction: Schema.Literals(["up", "down", "left", "right"]),
131
+ })
110
132
  export interface ArrowParams extends Schema.Schema.Type<typeof ArrowParams> {}
111
133
 
112
134
  export const FocusParams = Schema.Struct({ target: Schema.Number })
113
135
  export interface FocusParams extends Schema.Schema.Type<typeof FocusParams> {}
114
136
 
115
- export const ClickParams = Schema.Struct({ target: Schema.Number, x: Schema.Number, y: Schema.Number })
137
+ export const ClickParams = Schema.Struct({
138
+ target: Schema.Number,
139
+ x: Schema.Number,
140
+ y: Schema.Number,
141
+ })
116
142
  export interface ClickParams extends Schema.Schema.Type<typeof ClickParams> {}
117
143
 
118
144
  export const Request = Schema.Union([
119
- Schema.Struct({ ...JsonRpc.RequestFields, method: Schema.Literal("ui.type"), params: TypeParams }),
120
- Schema.Struct({ ...JsonRpc.RequestFields, method: Schema.Literal("ui.press"), params: PressParams }),
121
- Schema.Struct({ ...JsonRpc.RequestFields, method: Schema.Literal("ui.arrow"), params: ArrowParams }),
122
- Schema.Struct({ ...JsonRpc.RequestFields, method: Schema.Literal("ui.focus"), params: FocusParams }),
123
- Schema.Struct({ ...JsonRpc.RequestFields, method: Schema.Literal("ui.click"), params: ClickParams }),
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
+ }),
124
170
  Schema.Struct({
125
171
  ...JsonRpc.RequestFields,
126
172
  method: Schema.Literals([
@@ -134,47 +180,86 @@ export namespace Frontend {
134
180
  ])
135
181
  export type Request = Schema.Schema.Type<typeof Request>
136
182
  export const decodeRequest = Schema.decodeUnknownSync(Request)
137
-
138
183
  }
139
184
 
140
185
  export namespace Backend {
141
186
  export const Item = Schema.Union([
142
187
  Schema.Struct({ type: Schema.Literal("textDelta"), text: Schema.String }),
143
- Schema.Struct({ type: Schema.Literal("reasoningDelta"), text: Schema.String }),
144
- 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
+ }),
145
198
  Schema.Struct({ type: Schema.Literal("raw"), chunk: Schema.Json }),
146
199
  ])
147
200
  export type Item = Schema.Schema.Type<typeof Item>
148
201
 
149
- 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
+ ])
150
208
  export type FinishReason = Schema.Schema.Type<typeof FinishReason>
151
209
 
152
- 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
+ })
153
214
  export interface ChunkParams extends Schema.Schema.Type<typeof ChunkParams> {}
154
215
 
155
216
  export const FinishParams = Schema.Struct({
156
217
  id: Schema.String,
157
- reason: FinishReason.pipe(Schema.withDecodingDefault(Effect.succeed("stop" as const))),
218
+ reason: FinishReason.pipe(
219
+ Schema.withDecodingDefault(Effect.succeed("stop" as const)),
220
+ ),
158
221
  })
159
- export interface FinishParams extends Schema.Schema.Type<typeof FinishParams> {}
222
+ export interface FinishParams extends Schema.Schema.Type<
223
+ typeof FinishParams
224
+ > {}
160
225
 
161
226
  export const DisconnectParams = Schema.Struct({ id: Schema.String })
162
- export interface DisconnectParams extends Schema.Schema.Type<typeof DisconnectParams> {}
227
+ export interface DisconnectParams extends Schema.Schema.Type<
228
+ typeof DisconnectParams
229
+ > {}
163
230
 
164
231
  export const Request = Schema.Union([
165
- Schema.Struct({ ...JsonRpc.RequestFields, method: Schema.Literal("llm.chunk"), params: ChunkParams }),
166
- Schema.Struct({ ...JsonRpc.RequestFields, method: Schema.Literal("llm.finish"), params: FinishParams }),
167
- Schema.Struct({ ...JsonRpc.RequestFields, method: Schema.Literal("llm.disconnect"), params: DisconnectParams }),
168
232
  Schema.Struct({
169
233
  ...JsonRpc.RequestFields,
170
- method: Schema.Literals(["llm.attach", "llm.pending"]),
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"),
171
250
  }),
172
251
  ])
173
252
  export type Request = Schema.Schema.Type<typeof Request>
174
253
  export const decodeRequest = Schema.decodeUnknownSync(Request)
175
254
 
176
- export const OpenedExchange = Schema.Struct({ id: Schema.String, url: Schema.String, body: Schema.Json })
177
- 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
+ > {}
178
263
 
179
264
  export const NetworkLogEntry = Schema.Struct({
180
265
  time: Schema.Number,
@@ -182,8 +267,9 @@ export namespace Backend {
182
267
  url: Schema.String,
183
268
  matched: Schema.Boolean,
184
269
  })
185
- export interface NetworkLogEntry extends Schema.Schema.Type<typeof NetworkLogEntry> {}
186
-
270
+ export interface NetworkLogEntry extends Schema.Schema.Type<
271
+ typeof NetworkLogEntry
272
+ > {}
187
273
  }
188
274
 
189
275
  export * as SimulationProtocol from "./index"
@@ -1,6 +1,7 @@
1
1
  // CLI command contract for `opencode-drive send`.
2
2
 
3
- export type Json = null | boolean | number | string | Json[] | { [key: string]: Json }
3
+ export type Json =
4
+ null | boolean | number | string | Json[] | { [key: string]: Json }
4
5
 
5
6
  export type KeyModifiers = {
6
7
  ctrl?: boolean
@@ -31,36 +32,29 @@ export type State = {
31
32
  elements: Element[]
32
33
  }
33
34
 
34
- export type LlmItem =
35
- | { type: "textDelta"; text: string }
36
- | { type: "reasoningDelta"; text: string }
37
- | { type: "toolCall"; id: string; name: string; input: Json }
38
- | { type: "raw"; chunk: Json }
39
-
40
- export type FinishReason = "stop" | "tool-calls" | "length" | "content-filter"
41
-
42
- export type OpenedExchange = {
43
- id: string
44
- url: string
45
- body: Json
46
- }
47
-
48
35
  export type Command =
49
36
  | { name: "ui.type"; params: { text: string }; result: State }
50
- | { name: "ui.press"; params: { key: string; modifiers?: KeyModifiers }; result: State }
37
+ | {
38
+ name: "ui.press"
39
+ params: { key: string; modifiers?: KeyModifiers }
40
+ result: State
41
+ }
51
42
  | { name: "ui.enter"; result: State }
52
- | { name: "ui.arrow"; params: { direction: "up" | "down" | "left" | "right" }; result: State }
43
+ | {
44
+ name: "ui.arrow"
45
+ params: { direction: "up" | "down" | "left" | "right" }
46
+ result: State
47
+ }
53
48
  | { name: "ui.focus"; params: { target: number }; result: State }
54
- | { name: "ui.click"; params: { target: number; x: number; y: number }; result: State }
49
+ | {
50
+ name: "ui.click"
51
+ params: { target: number; x: number; y: number }
52
+ result: State
53
+ }
55
54
  | { name: "ui.screenshot"; result: string }
56
55
  | { name: "ui.state"; result: State }
57
56
  | { name: "ui.start-record"; result: { recording: true } }
58
57
  | { name: "ui.end-record"; result: string }
59
- | { name: "llm.chunk"; params: { id: string; items: LlmItem[] }; result: { ok: true } }
60
- | { name: "llm.finish"; params: { id: string; reason?: FinishReason }; result: { ok: true } }
61
- | { name: "llm.disconnect"; params: { id: string }; result: { ok: true } }
62
- | { name: "llm.attach"; result: { attached: true } }
63
- | { name: "llm.pending"; result: { exchanges: OpenedExchange[] } }
64
58
 
65
59
  // Commands with `params` take one JSON argument:
66
60
  // --command.ui.type '{"text":"hello"}'
@@ -1,18 +1,38 @@
1
- import { connectBackendSimulation, connectSimulation, type OpenedExchange } from "../client/index.js"
1
+ import {
2
+ connectBackendSimulation,
3
+ connectSimulation,
4
+ type OpenedExchange,
5
+ } from "../client/index.js"
2
6
 
3
7
  const uiUrl = process.env.OPENCODE_SIMULATION_UI_WS
4
8
  const backendUrl = process.env.OPENCODE_SIMULATION_BACKEND_WS
5
9
 
6
10
  const ui = await connectSimulation(uiUrl ? { url: uiUrl } : undefined)
7
- const backend = await connectBackendSimulation(backendUrl ? { url: backendUrl } : undefined)
11
+ const backend = await connectBackendSimulation(
12
+ backendUrl ? { url: backendUrl } : undefined,
13
+ )
8
14
 
9
15
  console.log("ui:", ui.url)
10
16
  console.log("backend:", backend.url)
11
17
 
18
+ let completed!: () => void
19
+ const responseCompleted = new Promise<void>((resolve) => {
20
+ completed = resolve
21
+ })
22
+
12
23
  await backend.attach(async (request: OpenedExchange) => {
13
- console.log("llm.request:", JSON.stringify({ id: request.id, model: (request.body as { model?: string }).model }))
14
- await backend.chunk(request.id, [{ type: "textDelta", text: "Hello from opencode-probe." }])
24
+ console.log(
25
+ "llm.request:",
26
+ JSON.stringify({
27
+ id: request.id,
28
+ model: (request.body as { model?: string }).model,
29
+ }),
30
+ )
31
+ await backend.chunk(request.id, [
32
+ { type: "textDelta", text: "Hello from opencode-probe." },
33
+ ])
15
34
  await backend.finish(request.id, "stop")
35
+ completed()
16
36
  })
17
37
 
18
38
  for (let attempt = 0; attempt < 60; attempt++) {
@@ -22,20 +42,11 @@ for (let attempt = 0; attempt < 60; attempt++) {
22
42
  if (attempt === 59) throw new Error("prompt editor did not become ready")
23
43
  }
24
44
 
25
- await ui.typeText(process.argv.slice(2).join(" ") || "Hello from opencode-probe")
45
+ await ui.typeText(
46
+ process.argv.slice(2).join(" ") || "Hello from opencode-probe",
47
+ )
26
48
  await ui.pressEnter()
27
-
28
- for (let attempt = 0; attempt < 30; attempt++) {
29
- await new Promise((resolve) => setTimeout(resolve, 500))
30
- if ((await backend.pendingExchanges()).exchanges.length === 0) {
31
- console.log("response complete")
32
- ui.close()
33
- backend.close()
34
- process.exit(0)
35
- }
36
- }
37
-
38
- console.log(await ui.state())
49
+ await responseCompleted
50
+ console.log("response complete")
39
51
  ui.close()
40
52
  backend.close()
41
- throw new Error("assistant reply did not render")