opencode-drive 0.1.8 → 0.1.10
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/package.json +1 -1
- package/src/cli/index.ts +0 -6
- package/src/cli/instance.ts +45 -48
- package/src/cli/script.ts +31 -7
- package/src/cli/start.ts +13 -7
- package/src/cli/types.ts +0 -1
- package/src/experimental/reproduce-stale-exploring-empty.ts +1 -1
- package/src/index.ts +6 -1
package/package.json
CHANGED
package/src/cli/index.ts
CHANGED
|
@@ -45,10 +45,6 @@ const startCommand = Command.make(
|
|
|
45
45
|
Flag.optional,
|
|
46
46
|
Flag.withDescription("Path to an OpenCode development checkout"),
|
|
47
47
|
),
|
|
48
|
-
state: Flag.string("state").pipe(
|
|
49
|
-
Flag.optional,
|
|
50
|
-
Flag.withDescription("Simulation snapshot containing files/"),
|
|
51
|
-
),
|
|
52
48
|
},
|
|
53
49
|
(config) =>
|
|
54
50
|
execute(() =>
|
|
@@ -173,7 +169,6 @@ function toStartOptions(
|
|
|
173
169
|
readonly visible: boolean
|
|
174
170
|
readonly record: boolean
|
|
175
171
|
readonly dev: Option.Option<string>
|
|
176
|
-
readonly state: Option.Option<string>
|
|
177
172
|
},
|
|
178
173
|
commands: ReadonlyArray<DriveCommand>,
|
|
179
174
|
app: ReadonlyArray<string>,
|
|
@@ -188,7 +183,6 @@ function toStartOptions(
|
|
|
188
183
|
visible: config.visible,
|
|
189
184
|
record: config.record,
|
|
190
185
|
dev: Option.getOrUndefined(config.dev),
|
|
191
|
-
state: Option.getOrUndefined(config.state),
|
|
192
186
|
command: app,
|
|
193
187
|
}
|
|
194
188
|
if (options.dev !== undefined && app.length > 0)
|
package/src/cli/instance.ts
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
|
-
import { mkdir
|
|
1
|
+
import { mkdir } from "node:fs/promises"
|
|
2
2
|
import { tmpdir } from "node:os"
|
|
3
3
|
import { join, resolve } from "node:path"
|
|
4
4
|
import { ensureMediaDirectory } from "./media.js"
|
|
5
|
+
import type { DriveScriptSetup } from "./script.js"
|
|
5
6
|
|
|
6
7
|
export interface LaunchOptions {
|
|
7
8
|
readonly name: string
|
|
8
9
|
readonly command?: ReadonlyArray<string>
|
|
9
10
|
readonly dev?: string
|
|
10
|
-
readonly state?: string
|
|
11
11
|
readonly scripted?: boolean
|
|
12
12
|
readonly visible?: boolean
|
|
13
13
|
readonly record?: boolean
|
|
14
14
|
readonly env?: Readonly<Record<string, string>>
|
|
15
|
+
readonly setup?: DriveScriptSetup
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
export async function launchInstance(options: LaunchOptions) {
|
|
@@ -47,62 +48,58 @@ export async function launchInstance(options: LaunchOptions) {
|
|
|
47
48
|
)}\n`,
|
|
48
49
|
)
|
|
49
50
|
await writeDriveManifest()
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
capabilities: {
|
|
77
|
-
tools: true,
|
|
78
|
-
input: ["text"],
|
|
79
|
-
output: ["text"],
|
|
80
|
-
},
|
|
81
|
-
limit: { context: 128000, output: 16000 },
|
|
51
|
+
const files = join(artifacts, "files")
|
|
52
|
+
await Promise.all([
|
|
53
|
+
mkdir(join(files, ".git"), { recursive: true }),
|
|
54
|
+
mkdir(join(files, ".opencode"), { recursive: true }),
|
|
55
|
+
mkdir(join(files, "src"), { recursive: true }),
|
|
56
|
+
])
|
|
57
|
+
await Promise.all([
|
|
58
|
+
Bun.write(
|
|
59
|
+
join(files, ".opencode", "opencode.jsonc"),
|
|
60
|
+
`${JSON.stringify(
|
|
61
|
+
{
|
|
62
|
+
model: "simulation/gpt-sim-model",
|
|
63
|
+
permissions: [{ action: "*", resource: "*", effect: "allow" }],
|
|
64
|
+
providers: {
|
|
65
|
+
simulation: {
|
|
66
|
+
name: "Simulation",
|
|
67
|
+
package: "aisdk:@ai-sdk/openai-compatible",
|
|
68
|
+
settings: { baseURL: "https://api.openai.com/v1" },
|
|
69
|
+
request: { body: { apiKey: "sim-key" } },
|
|
70
|
+
models: {
|
|
71
|
+
"gpt-sim-model": {
|
|
72
|
+
name: "Simulated Model",
|
|
73
|
+
capabilities: {
|
|
74
|
+
tools: true,
|
|
75
|
+
input: ["text"],
|
|
76
|
+
output: ["text"],
|
|
82
77
|
},
|
|
78
|
+
limit: { context: 128000, output: 16000 },
|
|
83
79
|
},
|
|
84
80
|
},
|
|
85
81
|
},
|
|
86
82
|
},
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
)
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
),
|
|
95
|
-
|
|
96
|
-
|
|
83
|
+
},
|
|
84
|
+
undefined,
|
|
85
|
+
2,
|
|
86
|
+
)}\n`,
|
|
87
|
+
),
|
|
88
|
+
Bun.write(
|
|
89
|
+
join(files, "src", "garden.js"),
|
|
90
|
+
'export function greet(name) {\n return `Hello, ${name}.`\n}\n',
|
|
91
|
+
),
|
|
92
|
+
])
|
|
93
|
+
await options.setup?.({ directory: files })
|
|
97
94
|
const environment = cleanEnv({
|
|
98
95
|
...process.env,
|
|
99
96
|
...options.env,
|
|
100
97
|
OPENCODE_SIMULATE: "1",
|
|
101
|
-
OPENCODE_SIMULATE_STATE: state,
|
|
102
98
|
DRIVE_REGISTRY_DIR: drive,
|
|
103
99
|
OPENCODE_DRIVE: options.name,
|
|
104
100
|
OPENCODE_DRIVE_RENDERER: options.visible ? "visible" : "headless",
|
|
105
|
-
|
|
101
|
+
OPENCODE_DRIVE_MEDIA_DIR: media,
|
|
102
|
+
OPENCODE_CONFIG_DIR: join(files, ".opencode"),
|
|
106
103
|
OPENCODE_DB: ":memory:",
|
|
107
104
|
OPENCODE_LOG_LEVEL: !options.visible
|
|
108
105
|
? "INFO"
|
|
@@ -114,13 +111,13 @@ export async function launchInstance(options: LaunchOptions) {
|
|
|
114
111
|
XDG_STATE_HOME: join(artifacts, "home", ".local", "state"),
|
|
115
112
|
})
|
|
116
113
|
const command = options.dev
|
|
117
|
-
? await prepareDev(
|
|
114
|
+
? await prepareDev(files, options.dev)
|
|
118
115
|
: options.command?.length
|
|
119
116
|
? [...options.command]
|
|
120
117
|
: ["opencode2"]
|
|
121
118
|
const spawn = () =>
|
|
122
119
|
Bun.spawn(command, {
|
|
123
|
-
cwd:
|
|
120
|
+
cwd: files,
|
|
124
121
|
env: environment,
|
|
125
122
|
stdin: options.visible ? "inherit" : "ignore",
|
|
126
123
|
stdout: !options.visible
|
package/src/cli/script.ts
CHANGED
|
@@ -15,23 +15,43 @@ export interface ScriptContext {
|
|
|
15
15
|
|
|
16
16
|
export type DriveScript = (context: ScriptContext) => void | Promise<void>
|
|
17
17
|
|
|
18
|
+
export interface ScriptSetupContext {
|
|
19
|
+
readonly directory: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type DriveScriptSetup = (
|
|
23
|
+
context: ScriptSetupContext,
|
|
24
|
+
) => void | Promise<void>
|
|
25
|
+
|
|
26
|
+
export interface LoadedDriveScript {
|
|
27
|
+
readonly run: DriveScript
|
|
28
|
+
readonly setup?: DriveScriptSetup
|
|
29
|
+
}
|
|
30
|
+
|
|
18
31
|
export function defineScript(script: DriveScript) {
|
|
19
32
|
return script
|
|
20
33
|
}
|
|
21
34
|
|
|
35
|
+
export async function loadScript(file: string): Promise<LoadedDriveScript> {
|
|
36
|
+
const module: { readonly default?: unknown; readonly setup?: unknown } =
|
|
37
|
+
await import(pathToFileURL(resolve(file)).href)
|
|
38
|
+
if (!isDriveScript(module.default))
|
|
39
|
+
throw new Error("script must default-export a function")
|
|
40
|
+
if (module.setup !== undefined && !isDriveScriptSetup(module.setup))
|
|
41
|
+
throw new Error("script setup export must be a function")
|
|
42
|
+
return {
|
|
43
|
+
run: module.default,
|
|
44
|
+
...(module.setup === undefined ? {} : { setup: module.setup }),
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
22
48
|
export async function runScript(
|
|
23
|
-
|
|
49
|
+
script: DriveScript,
|
|
24
50
|
artifacts: string,
|
|
25
51
|
endpoints: { readonly ui: string; readonly backend: string },
|
|
26
52
|
signal: AbortSignal,
|
|
27
53
|
onScreenshot?: (path: string) => void,
|
|
28
54
|
) {
|
|
29
|
-
const module: { readonly default?: unknown } = await import(
|
|
30
|
-
pathToFileURL(resolve(file)).href
|
|
31
|
-
)
|
|
32
|
-
const script = module.default
|
|
33
|
-
if (!isDriveScript(script))
|
|
34
|
-
throw new Error("script must default-export a function")
|
|
35
55
|
const ui = await connectSimulation({ url: endpoints.ui, onScreenshot })
|
|
36
56
|
const backend = await connectBackendSimulation({
|
|
37
57
|
url: endpoints.backend,
|
|
@@ -76,3 +96,7 @@ async function waitForEditor(ui: SimulationClient, signal: AbortSignal) {
|
|
|
76
96
|
function isDriveScript(value: unknown): value is DriveScript {
|
|
77
97
|
return typeof value === "function"
|
|
78
98
|
}
|
|
99
|
+
|
|
100
|
+
function isDriveScriptSetup(value: unknown): value is DriveScriptSetup {
|
|
101
|
+
return typeof value === "function"
|
|
102
|
+
}
|
package/src/cli/start.ts
CHANGED
|
@@ -5,7 +5,8 @@ import { connectSimulation } from "../client/index.js"
|
|
|
5
5
|
import { exportRecording } from "../recording/index.js"
|
|
6
6
|
import { connectMockBackend } from "./mock-backend.js"
|
|
7
7
|
import { createResponseSettings } from "./response-generator.js"
|
|
8
|
-
import { runScript } from "./script.js"
|
|
8
|
+
import { loadScript, runScript } from "./script.js"
|
|
9
|
+
import type { DriveScript } from "./script.js"
|
|
9
10
|
import { listenControl } from "./control.js"
|
|
10
11
|
import {
|
|
11
12
|
controlPath,
|
|
@@ -21,15 +22,16 @@ import type { StartOptions } from "./types.js"
|
|
|
21
22
|
export async function start(options: StartOptions) {
|
|
22
23
|
if (!options.visible && !options.script && !options.daemon)
|
|
23
24
|
return startDetached(options)
|
|
25
|
+
const script = options.script ? await loadScript(options.script) : undefined
|
|
24
26
|
const responses = createResponseSettings()
|
|
25
27
|
const instance = await launchInstance({
|
|
26
28
|
name: options.name,
|
|
27
29
|
command: options.command,
|
|
28
30
|
dev: options.dev,
|
|
29
|
-
state: options.state,
|
|
30
31
|
scripted: options.script !== undefined,
|
|
31
32
|
visible: options.visible,
|
|
32
33
|
record: options.record,
|
|
34
|
+
setup: script?.setup,
|
|
33
35
|
})
|
|
34
36
|
await register({
|
|
35
37
|
version: 1,
|
|
@@ -102,7 +104,9 @@ export async function start(options: StartOptions) {
|
|
|
102
104
|
driveReady = false
|
|
103
105
|
await instance.restart()
|
|
104
106
|
recording = undefined
|
|
105
|
-
current = run(options, instance, responses, (path) =>
|
|
107
|
+
current = run(options, instance, responses, script?.run, (path) =>
|
|
108
|
+
screenshots.push(path),
|
|
109
|
+
)
|
|
106
110
|
await current.ready
|
|
107
111
|
driveReady = true
|
|
108
112
|
await markReady(options.name, process.pid)
|
|
@@ -119,7 +123,9 @@ export async function start(options: StartOptions) {
|
|
|
119
123
|
return responses.update(input)
|
|
120
124
|
},
|
|
121
125
|
})
|
|
122
|
-
current = run(options, instance, responses, (path) =>
|
|
126
|
+
current = run(options, instance, responses, script?.run, (path) =>
|
|
127
|
+
screenshots.push(path),
|
|
128
|
+
)
|
|
123
129
|
await current.ready
|
|
124
130
|
driveReady = true
|
|
125
131
|
await markReady(options.name, process.pid)
|
|
@@ -213,7 +219,6 @@ async function startDetached(options: StartOptions) {
|
|
|
213
219
|
options.name,
|
|
214
220
|
...(options.script ? ["--script", options.script] : []),
|
|
215
221
|
...(options.dev ? ["--dev", options.dev] : []),
|
|
216
|
-
...(options.state ? ["--state", options.state] : []),
|
|
217
222
|
...(options.record ? ["--record"] : []),
|
|
218
223
|
...(options.command.length ? ["--", ...options.command] : []),
|
|
219
224
|
],
|
|
@@ -261,6 +266,7 @@ function run(
|
|
|
261
266
|
options: StartOptions,
|
|
262
267
|
instance: Awaited<ReturnType<typeof launchInstance>>,
|
|
263
268
|
responses: ReturnType<typeof createResponseSettings>,
|
|
269
|
+
driveScript: DriveScript | undefined,
|
|
264
270
|
onScreenshot: (path: string) => void,
|
|
265
271
|
) {
|
|
266
272
|
const abort = new AbortController()
|
|
@@ -274,9 +280,9 @@ function run(
|
|
|
274
280
|
ready: readiness,
|
|
275
281
|
promise: (async () => {
|
|
276
282
|
await instance.waitForDrive("both")
|
|
277
|
-
if (
|
|
283
|
+
if (driveScript) {
|
|
278
284
|
const script = runScript(
|
|
279
|
-
|
|
285
|
+
driveScript,
|
|
280
286
|
instance.artifacts,
|
|
281
287
|
instance.endpoints,
|
|
282
288
|
abort.signal,
|
package/src/cli/types.ts
CHANGED
|
@@ -19,7 +19,7 @@ export default defineScript(async ({ artifacts, backend, ui }) => {
|
|
|
19
19
|
index: 0,
|
|
20
20
|
id: "call_read",
|
|
21
21
|
name: "read",
|
|
22
|
-
input: { filePath: join(artifacts, "
|
|
22
|
+
input: { filePath: join(artifacts, "files", "src", "garden.js") },
|
|
23
23
|
},
|
|
24
24
|
])
|
|
25
25
|
await backend.finish(request.id, "tool-calls")
|
package/src/index.ts
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
1
|
export * from "./client/index.js"
|
|
2
2
|
export { defineScript } from "./cli/script.js"
|
|
3
|
-
export type {
|
|
3
|
+
export type {
|
|
4
|
+
DriveScript,
|
|
5
|
+
DriveScriptSetup,
|
|
6
|
+
ScriptContext,
|
|
7
|
+
ScriptSetupContext,
|
|
8
|
+
} from "./cli/script.js"
|