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.
- package/README.md +57 -38
- package/package.json +1 -1
- package/src/cli/commands.ts +101 -68
- package/src/cli/control.ts +43 -0
- package/src/cli/describe.ts +4 -5
- package/src/cli/index.ts +83 -110
- package/src/cli/instance.ts +193 -148
- package/src/cli/mock-backend.ts +25 -0
- package/src/cli/registry.ts +41 -83
- package/src/cli/restart.ts +3 -66
- package/src/cli/script.ts +66 -0
- package/src/cli/send.ts +19 -39
- package/src/cli/start.ts +93 -68
- package/src/cli/stop.ts +4 -28
- package/src/cli/types.ts +7 -30
- package/src/client/backend.ts +94 -32
- package/src/client/protocol.ts +115 -29
- package/src/client/protocol.types.ts +17 -23
- package/src/experimental/driver.ts +29 -18
- package/src/experimental/flow-driver.ts +130 -43
- package/src/experimental/flows/properties.ts +19 -13
- package/src/experimental/hello-driver.ts +11 -4
- package/src/experimental/stale-running-driver.ts +45 -13
- package/src/index.ts +2 -0
- package/src/cli/driver-runner.ts +0 -39
- package/src/cli/driver.ts +0 -28
- package/src/experimental/cli-campaign.ts +0 -179
package/src/client/backend.ts
CHANGED
|
@@ -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": {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
readonly "llm.
|
|
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<
|
|
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) =>
|
|
56
|
-
|
|
57
|
-
|
|
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(
|
|
78
|
+
static async connect(
|
|
79
|
+
options?: BackendSimulationClientOptions,
|
|
80
|
+
): Promise<BackendSimulationClient> {
|
|
61
81
|
const timeout = options?.timeout ?? 30_000
|
|
62
|
-
if (options?.url !== undefined)
|
|
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(
|
|
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)
|
|
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(
|
|
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(
|
|
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(
|
|
92
|
-
|
|
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", {
|
|
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)
|
|
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)
|
|
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(
|
|
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 {
|
|
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 = (
|
|
179
|
-
|
|
239
|
+
export const connectBackendSimulation = (
|
|
240
|
+
options?: BackendSimulationClientOptions,
|
|
241
|
+
): Promise<BackendSimulationClient> => BackendSimulationClient.connect(options)
|
package/src/client/protocol.ts
CHANGED
|
@@ -31,7 +31,10 @@ export namespace JsonRpc {
|
|
|
31
31
|
|
|
32
32
|
export const decodeRequest = Schema.decodeUnknownSync(Request)
|
|
33
33
|
|
|
34
|
-
export function success(
|
|
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<
|
|
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({
|
|
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({
|
|
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({
|
|
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({
|
|
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({
|
|
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({
|
|
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({
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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({
|
|
144
|
-
|
|
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([
|
|
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({
|
|
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(
|
|
218
|
+
reason: FinishReason.pipe(
|
|
219
|
+
Schema.withDecodingDefault(Effect.succeed("stop" as const)),
|
|
220
|
+
),
|
|
158
221
|
})
|
|
159
|
-
export interface FinishParams extends Schema.Schema.Type<
|
|
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<
|
|
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.
|
|
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({
|
|
177
|
-
|
|
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<
|
|
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 =
|
|
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
|
-
| {
|
|
37
|
+
| {
|
|
38
|
+
name: "ui.press"
|
|
39
|
+
params: { key: string; modifiers?: KeyModifiers }
|
|
40
|
+
result: State
|
|
41
|
+
}
|
|
51
42
|
| { name: "ui.enter"; result: State }
|
|
52
|
-
| {
|
|
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
|
-
| {
|
|
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 {
|
|
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(
|
|
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(
|
|
14
|
-
|
|
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(
|
|
45
|
+
await ui.typeText(
|
|
46
|
+
process.argv.slice(2).join(" ") || "Hello from opencode-probe",
|
|
47
|
+
)
|
|
26
48
|
await ui.pressEnter()
|
|
27
|
-
|
|
28
|
-
|
|
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")
|