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.
- package/README.md +90 -61
- package/package.json +2 -2
- package/src/cli/api.ts +5 -0
- package/src/cli/commands.ts +117 -134
- package/src/cli/control.ts +43 -0
- package/src/cli/describe.ts +4 -5
- package/src/cli/index.ts +85 -101
- package/src/cli/instance.ts +220 -98
- package/src/cli/mock-backend.ts +25 -0
- package/src/cli/registry.ts +41 -83
- package/src/cli/restart.ts +7 -0
- package/src/cli/script.ts +66 -0
- package/src/cli/send.ts +19 -39
- package/src/cli/start.ts +100 -33
- package/src/cli/stop.ts +4 -28
- package/src/cli/types.ts +7 -30
- package/src/client/backend.ts +94 -37
- package/src/client/client.ts +15 -31
- package/src/client/index.ts +0 -3
- package/src/client/protocol.ts +133 -44
- package/src/client/protocol.types.ts +62 -0
- package/src/experimental/campaign.ts +2 -3
- package/src/experimental/driver.ts +29 -18
- package/src/experimental/flow-driver.ts +130 -45
- package/src/experimental/flows/properties.ts +19 -13
- package/src/experimental/flows/types.ts +0 -1
- package/src/experimental/hello-driver.ts +11 -4
- package/src/experimental/reproduce-stale-running-visible.ts +1 -1
- 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,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": {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
readonly "llm.
|
|
14
|
-
|
|
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<
|
|
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) =>
|
|
57
|
-
|
|
58
|
-
|
|
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(
|
|
78
|
+
static async connect(
|
|
79
|
+
options?: BackendSimulationClientOptions,
|
|
80
|
+
): Promise<BackendSimulationClient> {
|
|
62
81
|
const timeout = options?.timeout ?? 30_000
|
|
63
|
-
if (options?.url !== undefined)
|
|
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(
|
|
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)
|
|
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(
|
|
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(
|
|
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(
|
|
93
|
-
|
|
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", {
|
|
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)
|
|
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)
|
|
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(
|
|
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 {
|
|
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 = (
|
|
184
|
-
|
|
239
|
+
export const connectBackendSimulation = (
|
|
240
|
+
options?: BackendSimulationClientOptions,
|
|
241
|
+
): Promise<BackendSimulationClient> => BackendSimulationClient.connect(options)
|
package/src/client/client.ts
CHANGED
|
@@ -6,14 +6,16 @@ import {
|
|
|
6
6
|
const defaultPort = 40900
|
|
7
7
|
|
|
8
8
|
type Methods = {
|
|
9
|
-
readonly "ui.
|
|
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.
|
|
14
|
-
readonly "
|
|
15
|
-
readonly "
|
|
16
|
-
readonly "
|
|
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
|
-
|
|
127
|
-
return this.call("ui.
|
|
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.
|
|
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.
|
|
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.
|
|
150
|
+
return this.call("ui.enter")
|
|
153
151
|
}
|
|
154
152
|
|
|
155
153
|
pressArrow(direction: "up" | "down" | "left" | "right"): Promise<Frontend.State> {
|
|
156
|
-
return this.
|
|
154
|
+
return this.call("ui.arrow", { direction })
|
|
157
155
|
}
|
|
158
156
|
|
|
159
157
|
focus(target: number): Promise<Frontend.State> {
|
|
160
|
-
return this.
|
|
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.
|
|
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 ─────────────────────────────────────────────────────────
|
package/src/client/index.ts
CHANGED
|
@@ -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
|
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
|
-
Schema.Struct({ type: Schema.Literal("
|
|
63
|
-
Schema.Struct({
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
|
96
|
-
export type
|
|
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
|
|
105
|
-
export interface
|
|
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({
|
|
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.
|
|
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({
|
|
141
|
-
|
|
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([
|
|
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({
|
|
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(
|
|
218
|
+
reason: FinishReason.pipe(
|
|
219
|
+
Schema.withDecodingDefault(Effect.succeed("stop" as const)),
|
|
220
|
+
),
|
|
155
221
|
})
|
|
156
|
-
export interface FinishParams extends Schema.Schema.Type<
|
|
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<
|
|
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.
|
|
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({
|
|
174
|
-
|
|
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<
|
|
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",
|
|
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;
|
|
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
|
}
|