@agentos-software/opencode 0.0.0-nathan-binding-workspace.abc7ec9
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/.turbo/turbo-build.log +25 -0
- package/dist/adapter.d.ts +1 -0
- package/dist/adapter.js +13 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +23 -0
- package/dist/opencode-acp/acp.js +250773 -0
- package/dist/opencode-acp/home/runner/work/agentos/agentos/registry/agent/opencode/dist/opencode-acp/acp.js +250760 -0
- package/dist/opencode-acp/home/runner/work/agentos/agentos/registry/agent/opencode/dist/opencode-acp/tree-sitter-3jzf13jk.wasm +0 -0
- package/dist/opencode-acp/home/runner/work/agentos/agentos/registry/agent/opencode/dist/opencode-acp/tree-sitter-bash-hq5s6fxb.wasm +0 -0
- package/dist/opencode-acp/home/runner/work/agentos/agentos/registry/agent/opencode/dist/opencode-acp/tree-sitter-powershell-ryb2ffqs.wasm +0 -0
- package/dist/opencode-acp/tree-sitter-3jzf13jk.wasm +0 -0
- package/dist/opencode-acp/tree-sitter-bash-hq5s6fxb.wasm +0 -0
- package/dist/opencode-acp/tree-sitter-powershell-ryb2ffqs.wasm +0 -0
- package/dist/opencode-acp.manifest.json +13 -0
- package/package.json +30 -0
- package/scripts/build-opencode-acp.mjs +3681 -0
- package/src/adapter.ts +27 -0
- package/src/index.ts +26 -0
- package/src/opencode-acp.mjs.d.ts +14 -0
- package/tsconfig.json +10 -0
- package/upstream/opencode-v1.3.13.patch +251 -0
|
@@ -0,0 +1,3681 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
import {
|
|
5
|
+
copyFileSync,
|
|
6
|
+
existsSync,
|
|
7
|
+
mkdirSync,
|
|
8
|
+
readFileSync,
|
|
9
|
+
rmSync,
|
|
10
|
+
writeFileSync,
|
|
11
|
+
} from "node:fs";
|
|
12
|
+
import { mkdtemp, readdir, readFile, writeFile } from "node:fs/promises";
|
|
13
|
+
import { tmpdir } from "node:os";
|
|
14
|
+
import { dirname, join, resolve } from "node:path";
|
|
15
|
+
import { spawnSync } from "node:child_process";
|
|
16
|
+
import { fileURLToPath } from "node:url";
|
|
17
|
+
|
|
18
|
+
const SOURCE_REPOSITORY = "anomalyco/opencode";
|
|
19
|
+
const SOURCE_VERSION = "1.3.13";
|
|
20
|
+
const SOURCE_TARBALL_URL = `https://github.com/${SOURCE_REPOSITORY}/archive/refs/tags/v${SOURCE_VERSION}.tar.gz`;
|
|
21
|
+
|
|
22
|
+
// Upstream `packages/app/package.json` pins `ghostty-web: github:anomalyco/ghostty-web#main`,
|
|
23
|
+
// but the bundled bun.lock snapshots the SHA below. Because `main` is a moving ref, a fresh
|
|
24
|
+
// `bun install --frozen-lockfile` resolves to whatever `main` points at *now* and fails the
|
|
25
|
+
// lockfile check. Rewriting the manifest to the lockfile's SHA before install keeps frozen-
|
|
26
|
+
// lockfile guarantees intact (i.e. CI still breaks loudly if either side drifts) without
|
|
27
|
+
// trusting arbitrary new HEADs on the ghostty-web branch.
|
|
28
|
+
const GHOSTTY_WEB_PINNED_SHA = "4af877d";
|
|
29
|
+
|
|
30
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
31
|
+
const packageDir = resolve(__dirname, "..");
|
|
32
|
+
const distDir = resolve(packageDir, "dist");
|
|
33
|
+
const cacheDir = resolve(packageDir, "node_modules", ".cache", "opencode-build");
|
|
34
|
+
const patchPath = resolve(packageDir, "upstream", `opencode-v${SOURCE_VERSION}.patch`);
|
|
35
|
+
const bundleDir = resolve(distDir, "opencode-acp");
|
|
36
|
+
const manifestPath = resolve(distDir, "opencode-acp.manifest.json");
|
|
37
|
+
const SQL_JS_VERSION = "1.14.1";
|
|
38
|
+
const bunBin = resolve(
|
|
39
|
+
packageDir,
|
|
40
|
+
"node_modules",
|
|
41
|
+
".bin",
|
|
42
|
+
process.platform === "win32" ? "bun.cmd" : "bun",
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
function run(command, args, options = {}) {
|
|
46
|
+
const result = spawnSync(command, args, {
|
|
47
|
+
stdio: "inherit",
|
|
48
|
+
...options,
|
|
49
|
+
});
|
|
50
|
+
if (result.status !== 0) {
|
|
51
|
+
throw new Error(
|
|
52
|
+
`Command failed (${result.status ?? "unknown"}): ${command} ${args.join(" ")}`,
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
return result;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function pinGhosttyWebRef(sourceRoot) {
|
|
59
|
+
const manifestPath = resolve(sourceRoot, "packages", "app", "package.json");
|
|
60
|
+
const raw = readFileSync(manifestPath, "utf-8");
|
|
61
|
+
const movingRef = '"ghostty-web": "github:anomalyco/ghostty-web#main"';
|
|
62
|
+
const pinnedRef = `"ghostty-web": "github:anomalyco/ghostty-web#${GHOSTTY_WEB_PINNED_SHA}"`;
|
|
63
|
+
if (raw.includes(pinnedRef)) return;
|
|
64
|
+
if (!raw.includes(movingRef)) {
|
|
65
|
+
throw new Error(
|
|
66
|
+
`Expected ghostty-web ref ${movingRef} in ${manifestPath}; upstream layout changed — re-audit GHOSTTY_WEB_PINNED_SHA before updating.`,
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
writeFileSync(manifestPath, raw.replace(movingRef, pinnedRef));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const PATCHED_SOURCE_FILES = [
|
|
73
|
+
"packages/opencode/src/cli/cmd/acp.ts",
|
|
74
|
+
"packages/opencode/src/config/config.ts",
|
|
75
|
+
"packages/opencode/src/plugin/index.ts",
|
|
76
|
+
"packages/opencode/src/server/instance.ts",
|
|
77
|
+
"packages/opencode/src/server/server.ts",
|
|
78
|
+
];
|
|
79
|
+
|
|
80
|
+
async function ensureNodeAcpPatch(sourceRoot, tarballPath) {
|
|
81
|
+
const serverFile = resolve(
|
|
82
|
+
sourceRoot,
|
|
83
|
+
"packages",
|
|
84
|
+
"opencode",
|
|
85
|
+
"src",
|
|
86
|
+
"server",
|
|
87
|
+
"server.ts",
|
|
88
|
+
);
|
|
89
|
+
const pluginFile = resolve(
|
|
90
|
+
sourceRoot,
|
|
91
|
+
"packages",
|
|
92
|
+
"opencode",
|
|
93
|
+
"src",
|
|
94
|
+
"plugin",
|
|
95
|
+
"index.ts",
|
|
96
|
+
);
|
|
97
|
+
const acpFile = resolve(
|
|
98
|
+
sourceRoot,
|
|
99
|
+
"packages",
|
|
100
|
+
"opencode",
|
|
101
|
+
"src",
|
|
102
|
+
"cli",
|
|
103
|
+
"cmd",
|
|
104
|
+
"acp.ts",
|
|
105
|
+
);
|
|
106
|
+
const serverSource = readFileSync(serverFile, "utf-8");
|
|
107
|
+
const pluginSource = readFileSync(pluginFile, "utf-8");
|
|
108
|
+
const acpSource = readFileSync(acpFile, "utf-8");
|
|
109
|
+
const alreadyPatched =
|
|
110
|
+
serverSource.includes('from "node:http"') &&
|
|
111
|
+
!serverSource.includes('from "hono/bun"') &&
|
|
112
|
+
pluginSource.includes("Bun shell is unavailable in the Node ACP build") &&
|
|
113
|
+
acpSource.includes("const server = await Server.listen(opts)");
|
|
114
|
+
|
|
115
|
+
if (alreadyPatched) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const scratchRoot = await mkdtemp(join(tmpdir(), "agentos-opencode-patch-"));
|
|
120
|
+
try {
|
|
121
|
+
run("tar", ["-xzf", tarballPath, "--strip-components=1", "-C", scratchRoot]);
|
|
122
|
+
run("git", ["apply", "--whitespace=nowarn", patchPath], { cwd: scratchRoot });
|
|
123
|
+
for (const relativePath of PATCHED_SOURCE_FILES) {
|
|
124
|
+
copyFileSync(
|
|
125
|
+
resolve(scratchRoot, relativePath),
|
|
126
|
+
resolve(sourceRoot, relativePath),
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
} finally {
|
|
130
|
+
rmSync(scratchRoot, { recursive: true, force: true });
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const verifiedServerSource = readFileSync(serverFile, "utf-8");
|
|
134
|
+
const verifiedPluginSource = readFileSync(pluginFile, "utf-8");
|
|
135
|
+
const verifiedAcpSource = readFileSync(acpFile, "utf-8");
|
|
136
|
+
if (
|
|
137
|
+
!verifiedServerSource.includes('from "node:http"') ||
|
|
138
|
+
verifiedServerSource.includes('from "hono/bun"') ||
|
|
139
|
+
!verifiedPluginSource.includes("Bun shell is unavailable in the Node ACP build") ||
|
|
140
|
+
!verifiedAcpSource.includes("const server = await Server.listen(opts)")
|
|
141
|
+
) {
|
|
142
|
+
throw new Error("Failed to stage the Node ACP patches into the prepared OpenCode source tree");
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async function downloadFile(url, destination) {
|
|
147
|
+
const response = await fetch(url);
|
|
148
|
+
if (!response.ok) {
|
|
149
|
+
throw new Error(`Failed to download ${url}: ${response.status} ${response.statusText}`);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const buffer = Buffer.from(await response.arrayBuffer());
|
|
153
|
+
await writeFile(destination, buffer);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async function readMigrations(sourceRoot) {
|
|
157
|
+
const migrationRoot = join(sourceRoot, "packages", "opencode", "migration");
|
|
158
|
+
const entries = (await readdir(migrationRoot, { withFileTypes: true }))
|
|
159
|
+
.filter((entry) => entry.isDirectory() && /^\d{14}/.test(entry.name))
|
|
160
|
+
.map((entry) => entry.name)
|
|
161
|
+
.sort();
|
|
162
|
+
|
|
163
|
+
return Promise.all(
|
|
164
|
+
entries.map(async (name) => {
|
|
165
|
+
const sql = await readFile(
|
|
166
|
+
join(migrationRoot, name, "migration.sql"),
|
|
167
|
+
"utf8",
|
|
168
|
+
);
|
|
169
|
+
const match = /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/.exec(name);
|
|
170
|
+
const timestamp = match
|
|
171
|
+
? Date.UTC(
|
|
172
|
+
Number(match[1]),
|
|
173
|
+
Number(match[2]) - 1,
|
|
174
|
+
Number(match[3]),
|
|
175
|
+
Number(match[4]),
|
|
176
|
+
Number(match[5]),
|
|
177
|
+
Number(match[6]),
|
|
178
|
+
)
|
|
179
|
+
: 0;
|
|
180
|
+
return { name, sql, timestamp };
|
|
181
|
+
}),
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async function rewriteSourceFile(sourceRoot, relativePath, transform) {
|
|
186
|
+
const filePath = resolve(sourceRoot, relativePath);
|
|
187
|
+
const original = await readFile(filePath, "utf8");
|
|
188
|
+
const next = transform(original);
|
|
189
|
+
if (next !== original) {
|
|
190
|
+
await writeFile(filePath, next);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async function ensureSqlJsDependency(sourceRoot) {
|
|
195
|
+
const packageJsonPath = resolve(
|
|
196
|
+
sourceRoot,
|
|
197
|
+
"packages",
|
|
198
|
+
"opencode",
|
|
199
|
+
"package.json",
|
|
200
|
+
);
|
|
201
|
+
const packageJson = JSON.parse(await readFile(packageJsonPath, "utf8"));
|
|
202
|
+
const dependencies = packageJson.dependencies ?? {};
|
|
203
|
+
const bunStoreDir = resolve(sourceRoot, "node_modules", ".bun");
|
|
204
|
+
const hasInstalledSqlJs =
|
|
205
|
+
existsSync(bunStoreDir) &&
|
|
206
|
+
(await readdir(bunStoreDir)).some((entry) => entry.startsWith("sql.js@"));
|
|
207
|
+
if (dependencies["sql.js"] === SQL_JS_VERSION && hasInstalledSqlJs) {
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
packageJson.dependencies = {
|
|
212
|
+
...dependencies,
|
|
213
|
+
"sql.js": SQL_JS_VERSION,
|
|
214
|
+
};
|
|
215
|
+
await writeFile(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\n`);
|
|
216
|
+
run(bunBin, ["install"], { cwd: sourceRoot });
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
async function applyNodeAcpRuntimeTweaks(sourceRoot) {
|
|
220
|
+
await writeFile(
|
|
221
|
+
resolve(sourceRoot, "packages/opencode/src/cli/cmd/acp.ts"),
|
|
222
|
+
`import type { Argv, InferredOptionTypes } from "yargs"
|
|
223
|
+
import { cmd } from "./cmd"
|
|
224
|
+
import { AgentSideConnection, ndJsonStream } from "@agentclientprotocol/sdk"
|
|
225
|
+
import { Log } from "../../util/log"
|
|
226
|
+
|
|
227
|
+
const options = {
|
|
228
|
+
port: {
|
|
229
|
+
type: "number" as const,
|
|
230
|
+
describe: "port to listen on",
|
|
231
|
+
default: 0,
|
|
232
|
+
},
|
|
233
|
+
hostname: {
|
|
234
|
+
type: "string" as const,
|
|
235
|
+
describe: "hostname to listen on",
|
|
236
|
+
default: "127.0.0.1",
|
|
237
|
+
},
|
|
238
|
+
mdns: {
|
|
239
|
+
type: "boolean" as const,
|
|
240
|
+
describe: "enable mDNS service discovery (defaults hostname to 0.0.0.0)",
|
|
241
|
+
default: false,
|
|
242
|
+
},
|
|
243
|
+
"mdns-domain": {
|
|
244
|
+
type: "string" as const,
|
|
245
|
+
describe: "custom domain name for mDNS service (default: opencode.local)",
|
|
246
|
+
default: "opencode.local",
|
|
247
|
+
},
|
|
248
|
+
cors: {
|
|
249
|
+
type: "string" as const,
|
|
250
|
+
array: true,
|
|
251
|
+
describe: "additional domains to allow for CORS",
|
|
252
|
+
default: [] as string[],
|
|
253
|
+
},
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
type NetworkOptions = InferredOptionTypes<typeof options>
|
|
257
|
+
|
|
258
|
+
function withNetworkOptions<T>(yargs: Argv<T>) {
|
|
259
|
+
return yargs.options(options)
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
async function resolveNetworkOptions(args: NetworkOptions) {
|
|
263
|
+
const { Config } = await import("../../config/config")
|
|
264
|
+
const config = await Config.getGlobal()
|
|
265
|
+
const portExplicitlySet = process.argv.includes("--port")
|
|
266
|
+
const hostnameExplicitlySet = process.argv.includes("--hostname")
|
|
267
|
+
const mdnsExplicitlySet = process.argv.includes("--mdns")
|
|
268
|
+
const mdnsDomainExplicitlySet = process.argv.includes("--mdns-domain")
|
|
269
|
+
const corsExplicitlySet = process.argv.includes("--cors")
|
|
270
|
+
|
|
271
|
+
const mdns = mdnsExplicitlySet ? args.mdns : (config?.server?.mdns ?? args.mdns)
|
|
272
|
+
const mdnsDomain = mdnsDomainExplicitlySet ? args["mdns-domain"] : (config?.server?.mdnsDomain ?? args["mdns-domain"])
|
|
273
|
+
const port = portExplicitlySet ? args.port : (config?.server?.port ?? args.port)
|
|
274
|
+
const hostname = hostnameExplicitlySet
|
|
275
|
+
? args.hostname
|
|
276
|
+
: mdns && !config?.server?.hostname
|
|
277
|
+
? "0.0.0.0"
|
|
278
|
+
: (config?.server?.hostname ?? args.hostname)
|
|
279
|
+
const configCors = config?.server?.cors ?? []
|
|
280
|
+
const argsCors = Array.isArray(args.cors) ? args.cors : args.cors ? [args.cors] : []
|
|
281
|
+
const cors = [...configCors, ...argsCors]
|
|
282
|
+
|
|
283
|
+
return { hostname, port, mdns, mdnsDomain, cors }
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function wrapData<T>(data: T) {
|
|
287
|
+
return { data }
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
async function loadProviderCatalog(directory: string | undefined) {
|
|
291
|
+
const [{ Config }] = await Promise.all([import("../../config/config")])
|
|
292
|
+
|
|
293
|
+
return withDirectory(directory, async () => {
|
|
294
|
+
const config = await Config.get()
|
|
295
|
+
const providers: any[] = []
|
|
296
|
+
|
|
297
|
+
if (config?.provider?.anthropic || config?.model?.startsWith("anthropic/") || process.env.ANTHROPIC_API_KEY) {
|
|
298
|
+
providers.push({
|
|
299
|
+
id: "anthropic",
|
|
300
|
+
name: "Anthropic",
|
|
301
|
+
source: "config",
|
|
302
|
+
env: ["ANTHROPIC_API_KEY"],
|
|
303
|
+
options: {
|
|
304
|
+
...(config?.provider?.anthropic?.options ?? {}),
|
|
305
|
+
},
|
|
306
|
+
models: {
|
|
307
|
+
"claude-sonnet-4-20250514": {
|
|
308
|
+
id: "claude-sonnet-4-20250514",
|
|
309
|
+
providerID: "anthropic",
|
|
310
|
+
api: { id: "claude-sonnet-4-20250514", url: "", npm: "@ai-sdk/anthropic" },
|
|
311
|
+
name: "Claude Sonnet 4",
|
|
312
|
+
family: "claude-sonnet-4",
|
|
313
|
+
capabilities: {
|
|
314
|
+
temperature: true,
|
|
315
|
+
reasoning: true,
|
|
316
|
+
attachment: true,
|
|
317
|
+
toolcall: true,
|
|
318
|
+
input: { text: true, audio: false, image: true, video: false, pdf: true },
|
|
319
|
+
output: { text: true, audio: false, image: false, video: false, pdf: false },
|
|
320
|
+
interleaved: false,
|
|
321
|
+
},
|
|
322
|
+
cost: { input: 0, output: 0, cache: { read: 0, write: 0 } },
|
|
323
|
+
limit: { context: 200000, output: 32000 },
|
|
324
|
+
status: "active",
|
|
325
|
+
options: {},
|
|
326
|
+
headers: {},
|
|
327
|
+
release_date: "2025-05-14",
|
|
328
|
+
variants: {},
|
|
329
|
+
},
|
|
330
|
+
"claude-opus-4-1-20250805": {
|
|
331
|
+
id: "claude-opus-4-1-20250805",
|
|
332
|
+
providerID: "anthropic",
|
|
333
|
+
api: { id: "claude-opus-4-1-20250805", url: "", npm: "@ai-sdk/anthropic" },
|
|
334
|
+
name: "Claude Opus 4.1",
|
|
335
|
+
family: "claude-opus-4-1",
|
|
336
|
+
capabilities: {
|
|
337
|
+
temperature: true,
|
|
338
|
+
reasoning: true,
|
|
339
|
+
attachment: true,
|
|
340
|
+
toolcall: true,
|
|
341
|
+
input: { text: true, audio: false, image: true, video: false, pdf: true },
|
|
342
|
+
output: { text: true, audio: false, image: false, video: false, pdf: false },
|
|
343
|
+
interleaved: false,
|
|
344
|
+
},
|
|
345
|
+
cost: { input: 0, output: 0, cache: { read: 0, write: 0 } },
|
|
346
|
+
limit: { context: 200000, output: 32000 },
|
|
347
|
+
status: "active",
|
|
348
|
+
options: {},
|
|
349
|
+
headers: {},
|
|
350
|
+
release_date: "2025-08-05",
|
|
351
|
+
variants: {},
|
|
352
|
+
},
|
|
353
|
+
"claude-haiku-4-5-20251001": {
|
|
354
|
+
id: "claude-haiku-4-5-20251001",
|
|
355
|
+
providerID: "anthropic",
|
|
356
|
+
api: { id: "claude-haiku-4-5-20251001", url: "", npm: "@ai-sdk/anthropic" },
|
|
357
|
+
name: "Claude Haiku 4.5",
|
|
358
|
+
family: "claude-haiku-4-5",
|
|
359
|
+
capabilities: {
|
|
360
|
+
temperature: true,
|
|
361
|
+
reasoning: false,
|
|
362
|
+
attachment: true,
|
|
363
|
+
toolcall: true,
|
|
364
|
+
input: { text: true, audio: false, image: true, video: false, pdf: true },
|
|
365
|
+
output: { text: true, audio: false, image: false, video: false, pdf: false },
|
|
366
|
+
interleaved: false,
|
|
367
|
+
},
|
|
368
|
+
cost: { input: 0, output: 0, cache: { read: 0, write: 0 } },
|
|
369
|
+
limit: { context: 200000, output: 16000 },
|
|
370
|
+
status: "active",
|
|
371
|
+
options: {},
|
|
372
|
+
headers: {},
|
|
373
|
+
release_date: "2025-10-01",
|
|
374
|
+
variants: {},
|
|
375
|
+
},
|
|
376
|
+
},
|
|
377
|
+
})
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (config?.provider?.openai || config?.model?.startsWith("openai/") || process.env.OPENAI_API_KEY) {
|
|
381
|
+
providers.push({
|
|
382
|
+
id: "openai",
|
|
383
|
+
name: "OpenAI",
|
|
384
|
+
source: "config",
|
|
385
|
+
env: ["OPENAI_API_KEY"],
|
|
386
|
+
options: {
|
|
387
|
+
...(config?.provider?.openai?.options ?? {}),
|
|
388
|
+
},
|
|
389
|
+
models: {
|
|
390
|
+
"gpt-5": {
|
|
391
|
+
id: "gpt-5",
|
|
392
|
+
providerID: "openai",
|
|
393
|
+
api: { id: "gpt-5", url: "", npm: "@ai-sdk/openai" },
|
|
394
|
+
name: "GPT-5",
|
|
395
|
+
family: "gpt-5",
|
|
396
|
+
capabilities: {
|
|
397
|
+
temperature: true,
|
|
398
|
+
reasoning: true,
|
|
399
|
+
attachment: true,
|
|
400
|
+
toolcall: true,
|
|
401
|
+
input: { text: true, audio: false, image: true, video: false, pdf: true },
|
|
402
|
+
output: { text: true, audio: false, image: false, video: false, pdf: false },
|
|
403
|
+
interleaved: false,
|
|
404
|
+
},
|
|
405
|
+
cost: { input: 0, output: 0, cache: { read: 0, write: 0 } },
|
|
406
|
+
limit: { context: 200000, output: 32000 },
|
|
407
|
+
status: "active",
|
|
408
|
+
options: {},
|
|
409
|
+
headers: {},
|
|
410
|
+
release_date: "2025-01-01",
|
|
411
|
+
variants: {},
|
|
412
|
+
},
|
|
413
|
+
"gpt-5-mini": {
|
|
414
|
+
id: "gpt-5-mini",
|
|
415
|
+
providerID: "openai",
|
|
416
|
+
api: { id: "gpt-5-mini", url: "", npm: "@ai-sdk/openai" },
|
|
417
|
+
name: "GPT-5 Mini",
|
|
418
|
+
family: "gpt-5-mini",
|
|
419
|
+
capabilities: {
|
|
420
|
+
temperature: true,
|
|
421
|
+
reasoning: true,
|
|
422
|
+
attachment: true,
|
|
423
|
+
toolcall: true,
|
|
424
|
+
input: { text: true, audio: false, image: true, video: false, pdf: true },
|
|
425
|
+
output: { text: true, audio: false, image: false, video: false, pdf: false },
|
|
426
|
+
interleaved: false,
|
|
427
|
+
},
|
|
428
|
+
cost: { input: 0, output: 0, cache: { read: 0, write: 0 } },
|
|
429
|
+
limit: { context: 200000, output: 16000 },
|
|
430
|
+
status: "active",
|
|
431
|
+
options: {},
|
|
432
|
+
headers: {},
|
|
433
|
+
release_date: "2025-01-01",
|
|
434
|
+
variants: {},
|
|
435
|
+
},
|
|
436
|
+
},
|
|
437
|
+
})
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
if (
|
|
441
|
+
config?.provider?.google ||
|
|
442
|
+
config?.model?.startsWith("google/") ||
|
|
443
|
+
process.env.GOOGLE_GENERATIVE_AI_API_KEY
|
|
444
|
+
) {
|
|
445
|
+
providers.push({
|
|
446
|
+
id: "google",
|
|
447
|
+
name: "Google",
|
|
448
|
+
source: "config",
|
|
449
|
+
env: ["GOOGLE_GENERATIVE_AI_API_KEY"],
|
|
450
|
+
options: {
|
|
451
|
+
...(config?.provider?.google?.options ?? {}),
|
|
452
|
+
},
|
|
453
|
+
models: {
|
|
454
|
+
"gemini-2.5-pro": {
|
|
455
|
+
id: "gemini-2.5-pro",
|
|
456
|
+
providerID: "google",
|
|
457
|
+
api: { id: "gemini-2.5-pro", url: "", npm: "@ai-sdk/google" },
|
|
458
|
+
name: "Gemini 2.5 Pro",
|
|
459
|
+
family: "gemini-2.5-pro",
|
|
460
|
+
capabilities: {
|
|
461
|
+
temperature: true,
|
|
462
|
+
reasoning: true,
|
|
463
|
+
attachment: true,
|
|
464
|
+
toolcall: true,
|
|
465
|
+
input: { text: true, audio: false, image: true, video: false, pdf: true },
|
|
466
|
+
output: { text: true, audio: false, image: false, video: false, pdf: false },
|
|
467
|
+
interleaved: false,
|
|
468
|
+
},
|
|
469
|
+
cost: { input: 0, output: 0, cache: { read: 0, write: 0 } },
|
|
470
|
+
limit: { context: 200000, output: 32000 },
|
|
471
|
+
status: "active",
|
|
472
|
+
options: {},
|
|
473
|
+
headers: {},
|
|
474
|
+
release_date: "2025-01-01",
|
|
475
|
+
variants: {},
|
|
476
|
+
},
|
|
477
|
+
"gemini-2.5-flash": {
|
|
478
|
+
id: "gemini-2.5-flash",
|
|
479
|
+
providerID: "google",
|
|
480
|
+
api: { id: "gemini-2.5-flash", url: "", npm: "@ai-sdk/google" },
|
|
481
|
+
name: "Gemini 2.5 Flash",
|
|
482
|
+
family: "gemini-2.5-flash",
|
|
483
|
+
capabilities: {
|
|
484
|
+
temperature: true,
|
|
485
|
+
reasoning: true,
|
|
486
|
+
attachment: true,
|
|
487
|
+
toolcall: true,
|
|
488
|
+
input: { text: true, audio: false, image: true, video: false, pdf: true },
|
|
489
|
+
output: { text: true, audio: false, image: false, video: false, pdf: false },
|
|
490
|
+
interleaved: false,
|
|
491
|
+
},
|
|
492
|
+
cost: { input: 0, output: 0, cache: { read: 0, write: 0 } },
|
|
493
|
+
limit: { context: 200000, output: 16000 },
|
|
494
|
+
status: "active",
|
|
495
|
+
options: {},
|
|
496
|
+
headers: {},
|
|
497
|
+
release_date: "2025-01-01",
|
|
498
|
+
variants: {},
|
|
499
|
+
},
|
|
500
|
+
},
|
|
501
|
+
})
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
if (
|
|
505
|
+
config?.provider?.["google-vertex"] ||
|
|
506
|
+
config?.model?.startsWith("google-vertex/") ||
|
|
507
|
+
(process.env.GOOGLE_VERTEX_PROJECT && process.env.GOOGLE_VERTEX_LOCATION)
|
|
508
|
+
) {
|
|
509
|
+
providers.push({
|
|
510
|
+
id: "google-vertex",
|
|
511
|
+
name: "Google Vertex",
|
|
512
|
+
source: "config",
|
|
513
|
+
env: [],
|
|
514
|
+
options: {
|
|
515
|
+
...(config?.provider?.["google-vertex"]?.options ?? {}),
|
|
516
|
+
},
|
|
517
|
+
models: {
|
|
518
|
+
"gemini-2.5-pro": {
|
|
519
|
+
id: "gemini-2.5-pro",
|
|
520
|
+
providerID: "google-vertex",
|
|
521
|
+
api: { id: "gemini-2.5-pro", url: "", npm: "@ai-sdk/google-vertex" },
|
|
522
|
+
name: "Gemini 2.5 Pro",
|
|
523
|
+
family: "gemini-2.5-pro",
|
|
524
|
+
capabilities: {
|
|
525
|
+
temperature: true,
|
|
526
|
+
reasoning: true,
|
|
527
|
+
attachment: true,
|
|
528
|
+
toolcall: true,
|
|
529
|
+
input: { text: true, audio: false, image: true, video: false, pdf: true },
|
|
530
|
+
output: { text: true, audio: false, image: false, video: false, pdf: false },
|
|
531
|
+
interleaved: false,
|
|
532
|
+
},
|
|
533
|
+
cost: { input: 0, output: 0, cache: { read: 0, write: 0 } },
|
|
534
|
+
limit: { context: 200000, output: 32000 },
|
|
535
|
+
status: "active",
|
|
536
|
+
options: {},
|
|
537
|
+
headers: {},
|
|
538
|
+
release_date: "2025-01-01",
|
|
539
|
+
variants: {},
|
|
540
|
+
},
|
|
541
|
+
},
|
|
542
|
+
})
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
if (config?.provider?.groq || config?.model?.startsWith("groq/") || process.env.GROQ_API_KEY) {
|
|
546
|
+
providers.push({
|
|
547
|
+
id: "groq",
|
|
548
|
+
name: "Groq",
|
|
549
|
+
source: "config",
|
|
550
|
+
env: ["GROQ_API_KEY"],
|
|
551
|
+
options: {
|
|
552
|
+
...(config?.provider?.groq?.options ?? {}),
|
|
553
|
+
},
|
|
554
|
+
models: {
|
|
555
|
+
"llama-3.3-70b-versatile": {
|
|
556
|
+
id: "llama-3.3-70b-versatile",
|
|
557
|
+
providerID: "groq",
|
|
558
|
+
api: { id: "llama-3.3-70b-versatile", url: "", npm: "@ai-sdk/groq" },
|
|
559
|
+
name: "Llama 3.3 70B Versatile",
|
|
560
|
+
family: "llama-3.3-70b",
|
|
561
|
+
capabilities: {
|
|
562
|
+
temperature: true,
|
|
563
|
+
reasoning: true,
|
|
564
|
+
attachment: false,
|
|
565
|
+
toolcall: true,
|
|
566
|
+
input: { text: true, audio: false, image: false, video: false, pdf: false },
|
|
567
|
+
output: { text: true, audio: false, image: false, video: false, pdf: false },
|
|
568
|
+
interleaved: false,
|
|
569
|
+
},
|
|
570
|
+
cost: { input: 0, output: 0, cache: { read: 0, write: 0 } },
|
|
571
|
+
limit: { context: 200000, output: 32000 },
|
|
572
|
+
status: "active",
|
|
573
|
+
options: {},
|
|
574
|
+
headers: {},
|
|
575
|
+
release_date: "2025-01-01",
|
|
576
|
+
variants: {},
|
|
577
|
+
},
|
|
578
|
+
},
|
|
579
|
+
})
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
if (config?.provider?.mistral || config?.model?.startsWith("mistral/") || process.env.MISTRAL_API_KEY) {
|
|
583
|
+
providers.push({
|
|
584
|
+
id: "mistral",
|
|
585
|
+
name: "Mistral",
|
|
586
|
+
source: "config",
|
|
587
|
+
env: ["MISTRAL_API_KEY"],
|
|
588
|
+
options: {
|
|
589
|
+
...(config?.provider?.mistral?.options ?? {}),
|
|
590
|
+
},
|
|
591
|
+
models: {
|
|
592
|
+
"mistral-small-latest": {
|
|
593
|
+
id: "mistral-small-latest",
|
|
594
|
+
providerID: "mistral",
|
|
595
|
+
api: { id: "mistral-small-latest", url: "", npm: "@ai-sdk/mistral" },
|
|
596
|
+
name: "Mistral Small Latest",
|
|
597
|
+
family: "mistral-small",
|
|
598
|
+
capabilities: {
|
|
599
|
+
temperature: true,
|
|
600
|
+
reasoning: true,
|
|
601
|
+
attachment: true,
|
|
602
|
+
toolcall: true,
|
|
603
|
+
input: { text: true, audio: false, image: true, video: false, pdf: true },
|
|
604
|
+
output: { text: true, audio: false, image: false, video: false, pdf: false },
|
|
605
|
+
interleaved: false,
|
|
606
|
+
},
|
|
607
|
+
cost: { input: 0, output: 0, cache: { read: 0, write: 0 } },
|
|
608
|
+
limit: { context: 200000, output: 32000 },
|
|
609
|
+
status: "active",
|
|
610
|
+
options: {},
|
|
611
|
+
headers: {},
|
|
612
|
+
release_date: "2025-01-01",
|
|
613
|
+
variants: {},
|
|
614
|
+
},
|
|
615
|
+
},
|
|
616
|
+
})
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
if (providers.length === 0) {
|
|
620
|
+
providers.push({
|
|
621
|
+
id: "anthropic",
|
|
622
|
+
name: "Anthropic",
|
|
623
|
+
source: "custom",
|
|
624
|
+
env: ["ANTHROPIC_API_KEY"],
|
|
625
|
+
options: {},
|
|
626
|
+
models: {
|
|
627
|
+
"claude-sonnet-4-20250514": {
|
|
628
|
+
id: "claude-sonnet-4-20250514",
|
|
629
|
+
providerID: "anthropic",
|
|
630
|
+
api: { id: "claude-sonnet-4-20250514", url: "", npm: "@ai-sdk/anthropic" },
|
|
631
|
+
name: "Claude Sonnet 4",
|
|
632
|
+
family: "claude-sonnet-4",
|
|
633
|
+
capabilities: {
|
|
634
|
+
temperature: true,
|
|
635
|
+
reasoning: true,
|
|
636
|
+
attachment: true,
|
|
637
|
+
toolcall: true,
|
|
638
|
+
input: { text: true, audio: false, image: true, video: false, pdf: true },
|
|
639
|
+
output: { text: true, audio: false, image: false, video: false, pdf: false },
|
|
640
|
+
interleaved: false,
|
|
641
|
+
},
|
|
642
|
+
cost: { input: 0, output: 0, cache: { read: 0, write: 0 } },
|
|
643
|
+
limit: { context: 200000, output: 32000 },
|
|
644
|
+
status: "active",
|
|
645
|
+
options: {},
|
|
646
|
+
headers: {},
|
|
647
|
+
release_date: "2025-05-14",
|
|
648
|
+
variants: {},
|
|
649
|
+
},
|
|
650
|
+
},
|
|
651
|
+
})
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
const defaults = Object.fromEntries(
|
|
655
|
+
providers.map((provider) => {
|
|
656
|
+
const specifiedModel =
|
|
657
|
+
typeof config.model === "string" && config.model.startsWith(provider.id + "/")
|
|
658
|
+
? config.model.slice(provider.id.length + 1)
|
|
659
|
+
: undefined
|
|
660
|
+
return [
|
|
661
|
+
provider.id,
|
|
662
|
+
specifiedModel ?? Object.keys(provider.models)[0] ?? "",
|
|
663
|
+
]
|
|
664
|
+
}),
|
|
665
|
+
)
|
|
666
|
+
|
|
667
|
+
return { providers, default: defaults }
|
|
668
|
+
})
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
async function runServiceWithCurrentInstance<T>(
|
|
672
|
+
serviceModule: { Service: any; defaultLayer: any },
|
|
673
|
+
ctx: any,
|
|
674
|
+
fn: (service: any) => any,
|
|
675
|
+
): Promise<T> {
|
|
676
|
+
const [{ Effect, ManagedRuntime }, { InstanceRef }, { memoMap }, { Instance }] = await Promise.all([
|
|
677
|
+
import("effect"),
|
|
678
|
+
import("../../effect/instance-ref"),
|
|
679
|
+
import("../../effect/run-service"),
|
|
680
|
+
import("../../project/instance"),
|
|
681
|
+
])
|
|
682
|
+
console.error("[opencode-acp] serviceRuntime:ctx", ctx.directory)
|
|
683
|
+
;(globalThis as typeof globalThis & { __agentosOpencodeInstanceFallback?: unknown }).__agentosOpencodeInstanceFallback =
|
|
684
|
+
ctx
|
|
685
|
+
const runtime = ManagedRuntime.make(serviceModule.defaultLayer, { memoMap })
|
|
686
|
+
try {
|
|
687
|
+
const result = await Instance.restore(
|
|
688
|
+
ctx,
|
|
689
|
+
() =>
|
|
690
|
+
runtime.runPromise(
|
|
691
|
+
Effect.provideService(serviceModule.Service.use(fn), InstanceRef, ctx),
|
|
692
|
+
),
|
|
693
|
+
)
|
|
694
|
+
console.error("[opencode-acp] serviceRuntime:done", ctx.directory)
|
|
695
|
+
return result
|
|
696
|
+
} catch (error) {
|
|
697
|
+
console.error(
|
|
698
|
+
"[opencode-acp] serviceRuntime:error",
|
|
699
|
+
error instanceof Error ? error.stack ?? error.message : String(error),
|
|
700
|
+
)
|
|
701
|
+
throw error
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
async function loadAgentCatalog(directory: string | undefined) {
|
|
706
|
+
const { Config } = await import("../../config/config")
|
|
707
|
+
|
|
708
|
+
return withDirectory(directory, async () => {
|
|
709
|
+
const cfg = await Config.get()
|
|
710
|
+
const agents = new Map<string, any>([
|
|
711
|
+
[
|
|
712
|
+
"build",
|
|
713
|
+
{
|
|
714
|
+
name: "build",
|
|
715
|
+
description: "The default agent. Executes tools based on configured permissions.",
|
|
716
|
+
mode: "primary",
|
|
717
|
+
},
|
|
718
|
+
],
|
|
719
|
+
[
|
|
720
|
+
"plan",
|
|
721
|
+
{
|
|
722
|
+
name: "plan",
|
|
723
|
+
description: "Plan mode. Disallows all edit tools.",
|
|
724
|
+
mode: "primary",
|
|
725
|
+
},
|
|
726
|
+
],
|
|
727
|
+
[
|
|
728
|
+
"general",
|
|
729
|
+
{
|
|
730
|
+
name: "general",
|
|
731
|
+
description:
|
|
732
|
+
"General-purpose agent for researching complex questions and executing multi-step tasks. Use this agent to execute multiple units of work in parallel.",
|
|
733
|
+
mode: "subagent",
|
|
734
|
+
},
|
|
735
|
+
],
|
|
736
|
+
[
|
|
737
|
+
"explore",
|
|
738
|
+
{
|
|
739
|
+
name: "explore",
|
|
740
|
+
description:
|
|
741
|
+
"Fast agent specialized for exploring codebases. Use this when you need to quickly find files, search code, or answer questions about the codebase.",
|
|
742
|
+
mode: "subagent",
|
|
743
|
+
},
|
|
744
|
+
],
|
|
745
|
+
["compaction", { name: "compaction", mode: "primary", hidden: true }],
|
|
746
|
+
["title", { name: "title", mode: "primary", hidden: true }],
|
|
747
|
+
["summary", { name: "summary", mode: "primary", hidden: true }],
|
|
748
|
+
])
|
|
749
|
+
|
|
750
|
+
for (const [key, value] of Object.entries(cfg.agent ?? {})) {
|
|
751
|
+
if (value?.disable) {
|
|
752
|
+
agents.delete(key)
|
|
753
|
+
continue
|
|
754
|
+
}
|
|
755
|
+
const current = agents.get(key) ?? {
|
|
756
|
+
name: key,
|
|
757
|
+
mode: "all",
|
|
758
|
+
}
|
|
759
|
+
agents.set(key, {
|
|
760
|
+
...current,
|
|
761
|
+
...(value?.name ? { name: value.name } : {}),
|
|
762
|
+
...(value?.description ? { description: value.description } : {}),
|
|
763
|
+
...(value?.mode ? { mode: value.mode } : {}),
|
|
764
|
+
...(value?.hidden !== undefined ? { hidden: value.hidden } : {}),
|
|
765
|
+
})
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
const defaultAgent = cfg.default_agent ?? "build"
|
|
769
|
+
return Array.from(agents.values()).sort((a, b) => {
|
|
770
|
+
const aRank = a.name === defaultAgent ? 0 : 1
|
|
771
|
+
const bRank = b.name === defaultAgent ? 0 : 1
|
|
772
|
+
if (aRank !== bRank) return aRank - bRank
|
|
773
|
+
return a.name.localeCompare(b.name)
|
|
774
|
+
})
|
|
775
|
+
})
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
async function loadCommandCatalog(directory: string | undefined) {
|
|
779
|
+
const { Config } = await import("../../config/config")
|
|
780
|
+
|
|
781
|
+
return withDirectory(directory, async () => {
|
|
782
|
+
const cfg = await Config.get()
|
|
783
|
+
const commands = new Map<string, any>([
|
|
784
|
+
["init", { name: "init", description: "create/update AGENTS.md" }],
|
|
785
|
+
["review", { name: "review", description: "review changes [commit|branch|pr], defaults to uncommitted" }],
|
|
786
|
+
])
|
|
787
|
+
|
|
788
|
+
for (const [name, command] of Object.entries(cfg.command ?? {})) {
|
|
789
|
+
commands.set(name, {
|
|
790
|
+
name,
|
|
791
|
+
...(command?.description ? { description: command.description } : {}),
|
|
792
|
+
})
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
return Array.from(commands.values()).sort((a, b) => a.name.localeCompare(b.name))
|
|
796
|
+
})
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
async function withDirectory<T>(
|
|
800
|
+
directory: string | undefined,
|
|
801
|
+
fn: (ctx: any) => Promise<T>,
|
|
802
|
+
): Promise<T> {
|
|
803
|
+
console.error("[opencode-acp] withDirectory:start", directory ?? process.cwd())
|
|
804
|
+
const [{ Instance }, { InstanceBootstrap }] = await Promise.all([
|
|
805
|
+
import("../../project/instance"),
|
|
806
|
+
import("../../project/bootstrap"),
|
|
807
|
+
])
|
|
808
|
+
console.error("[opencode-acp] withDirectory:modules:done", directory ?? process.cwd())
|
|
809
|
+
return Instance.provide({
|
|
810
|
+
directory: directory ?? process.cwd(),
|
|
811
|
+
init: InstanceBootstrap,
|
|
812
|
+
fn: () => {
|
|
813
|
+
const ctx = Instance.current
|
|
814
|
+
;(globalThis as typeof globalThis & { __agentosOpencodeInstanceFallback?: unknown }).__agentosOpencodeInstanceFallback =
|
|
815
|
+
ctx
|
|
816
|
+
return fn(ctx)
|
|
817
|
+
},
|
|
818
|
+
})
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
async function ensureProjectors() {
|
|
822
|
+
if ((globalThis as any).__agentosOpencodeProjectorsReady) return
|
|
823
|
+
console.error("[opencode-acp] projectors:ensure:start")
|
|
824
|
+
const [{ SyncEvent }, { default: sessionProjectors }] = await Promise.all([
|
|
825
|
+
import("../../sync"),
|
|
826
|
+
import("../../session/projectors"),
|
|
827
|
+
])
|
|
828
|
+
SyncEvent.init({
|
|
829
|
+
projectors: sessionProjectors,
|
|
830
|
+
})
|
|
831
|
+
;(globalThis as any).__agentosOpencodeProjectorsReady = true
|
|
832
|
+
console.error("[opencode-acp] projectors:ensure:done")
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
async function createGlobalEventStream(signal?: AbortSignal) {
|
|
836
|
+
const { GlobalBus } = await import("../../bus/global")
|
|
837
|
+
let closed = false
|
|
838
|
+
const queue: any[] = []
|
|
839
|
+
let nextResolve: ((result: IteratorResult<any>) => void) | undefined
|
|
840
|
+
|
|
841
|
+
const close = () => {
|
|
842
|
+
if (closed) return
|
|
843
|
+
closed = true
|
|
844
|
+
GlobalBus.off("event", onEvent)
|
|
845
|
+
signal?.removeEventListener("abort", close)
|
|
846
|
+
nextResolve?.({ done: true, value: undefined })
|
|
847
|
+
nextResolve = undefined
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
const onEvent = (event: any) => {
|
|
851
|
+
if (closed) return
|
|
852
|
+
if (nextResolve) {
|
|
853
|
+
const resolve = nextResolve
|
|
854
|
+
nextResolve = undefined
|
|
855
|
+
resolve({ done: false, value: event })
|
|
856
|
+
return
|
|
857
|
+
}
|
|
858
|
+
queue.push(event)
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
GlobalBus.on("event", onEvent)
|
|
862
|
+
if (signal) {
|
|
863
|
+
if (signal.aborted) close()
|
|
864
|
+
else signal.addEventListener("abort", close, { once: true })
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
return {
|
|
868
|
+
stream: (async function* () {
|
|
869
|
+
try {
|
|
870
|
+
while (true) {
|
|
871
|
+
if (queue.length > 0) {
|
|
872
|
+
yield queue.shift()
|
|
873
|
+
continue
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
const next = await new Promise<IteratorResult<any>>((resolve) => {
|
|
877
|
+
nextResolve = resolve
|
|
878
|
+
})
|
|
879
|
+
if (next.done) return
|
|
880
|
+
yield next.value
|
|
881
|
+
}
|
|
882
|
+
} finally {
|
|
883
|
+
close()
|
|
884
|
+
}
|
|
885
|
+
})(),
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
function createLocalSdk() {
|
|
890
|
+
return {
|
|
891
|
+
global: {
|
|
892
|
+
event: async ({ signal }: { signal?: AbortSignal }) => createGlobalEventStream(signal),
|
|
893
|
+
},
|
|
894
|
+
permission: {
|
|
895
|
+
reply: async ({ requestID, reply, directory }: any) =>
|
|
896
|
+
wrapData(
|
|
897
|
+
await withDirectory(directory, async () => {
|
|
898
|
+
const { Permission } = await import("../../permission")
|
|
899
|
+
await Permission.reply({ requestID, reply })
|
|
900
|
+
return true
|
|
901
|
+
}),
|
|
902
|
+
),
|
|
903
|
+
},
|
|
904
|
+
config: {
|
|
905
|
+
get: async ({ directory }: any) =>
|
|
906
|
+
wrapData(
|
|
907
|
+
await (async () => {
|
|
908
|
+
const { Config } = await import("../../config/config")
|
|
909
|
+
return withDirectory(directory, async () => {
|
|
910
|
+
console.error("[opencode-acp] sdk.config.get:start", directory ?? process.cwd())
|
|
911
|
+
const result = await Config.get()
|
|
912
|
+
console.error("[opencode-acp] sdk.config.get:done", directory ?? process.cwd())
|
|
913
|
+
return result
|
|
914
|
+
})
|
|
915
|
+
})(),
|
|
916
|
+
),
|
|
917
|
+
providers: async ({ directory }: any) =>
|
|
918
|
+
wrapData(
|
|
919
|
+
await (async () => {
|
|
920
|
+
console.error("[opencode-acp] sdk.config.providers:start", directory ?? process.cwd())
|
|
921
|
+
return withDirectory(directory, async () => {
|
|
922
|
+
const result = await loadProviderCatalog(directory)
|
|
923
|
+
console.error(
|
|
924
|
+
"[opencode-acp] sdk.config.providers:done",
|
|
925
|
+
directory ?? process.cwd(),
|
|
926
|
+
result.providers.length,
|
|
927
|
+
)
|
|
928
|
+
return result
|
|
929
|
+
})
|
|
930
|
+
})(),
|
|
931
|
+
),
|
|
932
|
+
},
|
|
933
|
+
app: {
|
|
934
|
+
agents: async ({ directory }: any) =>
|
|
935
|
+
wrapData(
|
|
936
|
+
await loadAgentCatalog(directory),
|
|
937
|
+
),
|
|
938
|
+
},
|
|
939
|
+
command: {
|
|
940
|
+
list: async ({ directory }: any) =>
|
|
941
|
+
wrapData(
|
|
942
|
+
await loadCommandCatalog(directory),
|
|
943
|
+
),
|
|
944
|
+
},
|
|
945
|
+
mcp: {
|
|
946
|
+
add: async ({ directory, name, config }: any) =>
|
|
947
|
+
wrapData(
|
|
948
|
+
await (async () => {
|
|
949
|
+
const { MCP } = await import("../../mcp")
|
|
950
|
+
return withDirectory(directory, async () => MCP.add(name, config))
|
|
951
|
+
})(),
|
|
952
|
+
),
|
|
953
|
+
},
|
|
954
|
+
session: {
|
|
955
|
+
create: async ({ directory, title, permission, workspaceID, parentID }: any) =>
|
|
956
|
+
wrapData(
|
|
957
|
+
await (async () => {
|
|
958
|
+
await ensureProjectors()
|
|
959
|
+
return withDirectory(directory, async (ctx) => {
|
|
960
|
+
const { Session } = await import("../../session")
|
|
961
|
+
console.error("[opencode-acp] sdk.session.create:start", directory ?? process.cwd())
|
|
962
|
+
try {
|
|
963
|
+
const result = await runServiceWithCurrentInstance(Session, ctx, (service) =>
|
|
964
|
+
service.create({
|
|
965
|
+
...(title ? { title } : {}),
|
|
966
|
+
...(permission ? { permission } : {}),
|
|
967
|
+
...(workspaceID ? { workspaceID } : {}),
|
|
968
|
+
...(parentID ? { parentID } : {}),
|
|
969
|
+
}),
|
|
970
|
+
)
|
|
971
|
+
console.error("[opencode-acp] sdk.session.create:done", directory ?? process.cwd())
|
|
972
|
+
return result
|
|
973
|
+
} catch (error) {
|
|
974
|
+
console.error(
|
|
975
|
+
"[opencode-acp] sdk.session.create:error",
|
|
976
|
+
error instanceof Error ? error.stack ?? error.message : String(error),
|
|
977
|
+
)
|
|
978
|
+
throw error
|
|
979
|
+
}
|
|
980
|
+
})
|
|
981
|
+
})(),
|
|
982
|
+
),
|
|
983
|
+
get: async ({ sessionID, directory }: any) =>
|
|
984
|
+
wrapData(
|
|
985
|
+
await (async () => {
|
|
986
|
+
const { Session } = await import("../../session")
|
|
987
|
+
return withDirectory(directory, async (ctx) =>
|
|
988
|
+
runServiceWithCurrentInstance(Session, ctx, (service) => service.get(sessionID)),
|
|
989
|
+
)
|
|
990
|
+
})(),
|
|
991
|
+
),
|
|
992
|
+
list: async ({ directory, roots, start, search, limit }: any) =>
|
|
993
|
+
wrapData(
|
|
994
|
+
await (async () => {
|
|
995
|
+
const { Session } = await import("../../session")
|
|
996
|
+
return withDirectory(directory, async (ctx) => {
|
|
997
|
+
return runServiceWithCurrentInstance(Session, ctx, (service) =>
|
|
998
|
+
service.list({ directory, roots, start, search, limit }),
|
|
999
|
+
)
|
|
1000
|
+
})
|
|
1001
|
+
})(),
|
|
1002
|
+
),
|
|
1003
|
+
fork: async ({ sessionID, messageID, directory }: any) =>
|
|
1004
|
+
wrapData(
|
|
1005
|
+
await (async () => {
|
|
1006
|
+
const { Session } = await import("../../session")
|
|
1007
|
+
await ensureProjectors()
|
|
1008
|
+
return withDirectory(directory, async (ctx) =>
|
|
1009
|
+
runServiceWithCurrentInstance(Session, ctx, (service) =>
|
|
1010
|
+
service.fork({
|
|
1011
|
+
sessionID,
|
|
1012
|
+
...(messageID ? { messageID } : {}),
|
|
1013
|
+
}),
|
|
1014
|
+
),
|
|
1015
|
+
)
|
|
1016
|
+
})(),
|
|
1017
|
+
),
|
|
1018
|
+
messages: async ({ sessionID, limit, directory }: any) =>
|
|
1019
|
+
wrapData(
|
|
1020
|
+
await (async () => {
|
|
1021
|
+
const { Session } = await import("../../session")
|
|
1022
|
+
return withDirectory(directory, async (ctx) =>
|
|
1023
|
+
runServiceWithCurrentInstance(Session, ctx, (service) =>
|
|
1024
|
+
service.messages({ sessionID, ...(limit ? { limit } : {}) }),
|
|
1025
|
+
),
|
|
1026
|
+
)
|
|
1027
|
+
})(),
|
|
1028
|
+
),
|
|
1029
|
+
message: async ({ sessionID, messageID, directory }: any) =>
|
|
1030
|
+
wrapData(
|
|
1031
|
+
await (async () => {
|
|
1032
|
+
const { MessageV2 } = await import("../../session/message-v2")
|
|
1033
|
+
return withDirectory(directory, async () => MessageV2.get({ sessionID, messageID }))
|
|
1034
|
+
})(),
|
|
1035
|
+
),
|
|
1036
|
+
prompt: async ({ sessionID, directory, ...input }: any) =>
|
|
1037
|
+
wrapData(
|
|
1038
|
+
await (async () => {
|
|
1039
|
+
const { SessionPrompt } = await import("../../session/prompt")
|
|
1040
|
+
await ensureProjectors()
|
|
1041
|
+
return withDirectory(directory, async (ctx) =>
|
|
1042
|
+
runServiceWithCurrentInstance(SessionPrompt, ctx, (service) =>
|
|
1043
|
+
service.prompt({ sessionID, ...input }),
|
|
1044
|
+
),
|
|
1045
|
+
)
|
|
1046
|
+
})(),
|
|
1047
|
+
),
|
|
1048
|
+
command: async ({ sessionID, directory, ...input }: any) =>
|
|
1049
|
+
wrapData(
|
|
1050
|
+
await (async () => {
|
|
1051
|
+
const { SessionPrompt } = await import("../../session/prompt")
|
|
1052
|
+
await ensureProjectors()
|
|
1053
|
+
return withDirectory(directory, async (ctx) =>
|
|
1054
|
+
runServiceWithCurrentInstance(SessionPrompt, ctx, (service) =>
|
|
1055
|
+
service.command({ sessionID, ...input }),
|
|
1056
|
+
),
|
|
1057
|
+
)
|
|
1058
|
+
})(),
|
|
1059
|
+
),
|
|
1060
|
+
summarize: async ({ sessionID, directory, providerID, modelID, auto = false }: any) =>
|
|
1061
|
+
wrapData(
|
|
1062
|
+
await (async () => {
|
|
1063
|
+
const [{ Session }, { SessionRevert }, { SessionCompaction }, { SessionPrompt }, { Agent }] =
|
|
1064
|
+
await Promise.all([
|
|
1065
|
+
import("../../session"),
|
|
1066
|
+
import("../../session/revert"),
|
|
1067
|
+
import("../../session/compaction"),
|
|
1068
|
+
import("../../session/prompt"),
|
|
1069
|
+
import("../../agent/agent"),
|
|
1070
|
+
])
|
|
1071
|
+
await ensureProjectors()
|
|
1072
|
+
return withDirectory(directory, async (ctx) => {
|
|
1073
|
+
const session = await Session.get(sessionID)
|
|
1074
|
+
await SessionRevert.cleanup(session)
|
|
1075
|
+
const messages = await Session.messages({ sessionID })
|
|
1076
|
+
let currentAgent = await Agent.defaultAgent()
|
|
1077
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
1078
|
+
const info = messages[i].info
|
|
1079
|
+
if (info.role === "user") {
|
|
1080
|
+
currentAgent = info.agent || (await Agent.defaultAgent())
|
|
1081
|
+
break
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
await SessionCompaction.create({
|
|
1085
|
+
sessionID,
|
|
1086
|
+
agent: currentAgent,
|
|
1087
|
+
model: { providerID, modelID },
|
|
1088
|
+
auto,
|
|
1089
|
+
})
|
|
1090
|
+
await runServiceWithCurrentInstance(SessionPrompt, ctx, (service) =>
|
|
1091
|
+
service.loop({ sessionID }),
|
|
1092
|
+
)
|
|
1093
|
+
return true
|
|
1094
|
+
})
|
|
1095
|
+
})(),
|
|
1096
|
+
),
|
|
1097
|
+
abort: async ({ sessionID, directory }: any) =>
|
|
1098
|
+
wrapData(
|
|
1099
|
+
await (async () => {
|
|
1100
|
+
const { SessionPrompt } = await import("../../session/prompt")
|
|
1101
|
+
return withDirectory(directory, async (ctx) => {
|
|
1102
|
+
await runServiceWithCurrentInstance(SessionPrompt, ctx, (service) =>
|
|
1103
|
+
service.cancel(sessionID),
|
|
1104
|
+
)
|
|
1105
|
+
return true
|
|
1106
|
+
})
|
|
1107
|
+
})(),
|
|
1108
|
+
),
|
|
1109
|
+
},
|
|
1110
|
+
} as any
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
export const AcpCommand = cmd({
|
|
1114
|
+
command: "acp",
|
|
1115
|
+
describe: "start ACP (Agent Client Protocol) server",
|
|
1116
|
+
builder: (yargs) => {
|
|
1117
|
+
return withNetworkOptions(yargs).option("cwd", {
|
|
1118
|
+
describe: "working directory",
|
|
1119
|
+
type: "string",
|
|
1120
|
+
default: process.cwd(),
|
|
1121
|
+
})
|
|
1122
|
+
},
|
|
1123
|
+
handler: async (args: NetworkOptions) => {
|
|
1124
|
+
process.env.OPENCODE_CLIENT = "acp"
|
|
1125
|
+
process.env.OPENCODE_DISABLE_MODELS_FETCH = process.env.OPENCODE_DISABLE_MODELS_FETCH ?? "1"
|
|
1126
|
+
process.env.OPENCODE_DISABLE_DEFAULT_PLUGINS = process.env.OPENCODE_DISABLE_DEFAULT_PLUGINS ?? "1"
|
|
1127
|
+
const [{ ACP }] = await Promise.all([import("../../acp/agent")])
|
|
1128
|
+
|
|
1129
|
+
const log = Log.create({ service: "acp-command" })
|
|
1130
|
+
console.error("[opencode-acp] bootstrap:entered")
|
|
1131
|
+
console.error("[opencode-acp] network:resolve:start")
|
|
1132
|
+
const opts = await resolveNetworkOptions(args)
|
|
1133
|
+
console.error("[opencode-acp] network:resolve:done", JSON.stringify(opts))
|
|
1134
|
+
|
|
1135
|
+
console.error("[opencode-acp] sdk:create:start")
|
|
1136
|
+
const sdk = createLocalSdk()
|
|
1137
|
+
console.error("[opencode-acp] sdk:create:done")
|
|
1138
|
+
|
|
1139
|
+
console.error("[opencode-acp] streams:create:start")
|
|
1140
|
+
const input = new WritableStream<Uint8Array>({
|
|
1141
|
+
write(chunk) {
|
|
1142
|
+
return new Promise<void>((resolve, reject) => {
|
|
1143
|
+
process.stdout.write(chunk, (err) => {
|
|
1144
|
+
if (err) {
|
|
1145
|
+
reject(err)
|
|
1146
|
+
} else {
|
|
1147
|
+
resolve()
|
|
1148
|
+
}
|
|
1149
|
+
})
|
|
1150
|
+
})
|
|
1151
|
+
},
|
|
1152
|
+
})
|
|
1153
|
+
const output = new ReadableStream<Uint8Array>({
|
|
1154
|
+
start(controller) {
|
|
1155
|
+
process.stdin.on("data", (chunk: Buffer) => {
|
|
1156
|
+
controller.enqueue(new Uint8Array(chunk))
|
|
1157
|
+
})
|
|
1158
|
+
process.stdin.on("end", () => controller.close())
|
|
1159
|
+
process.stdin.on("error", (err) => controller.error(err))
|
|
1160
|
+
},
|
|
1161
|
+
})
|
|
1162
|
+
console.error("[opencode-acp] streams:create:done")
|
|
1163
|
+
|
|
1164
|
+
console.error("[opencode-acp] ndjson:start")
|
|
1165
|
+
const stream = ndJsonStream(input, output)
|
|
1166
|
+
console.error("[opencode-acp] ndjson:done")
|
|
1167
|
+
console.error("[opencode-acp] agent:init:start")
|
|
1168
|
+
const agent = await ACP.init({ sdk })
|
|
1169
|
+
console.error("[opencode-acp] agent:init:done")
|
|
1170
|
+
|
|
1171
|
+
console.error("[opencode-acp] connection:start")
|
|
1172
|
+
new AgentSideConnection((conn) => {
|
|
1173
|
+
return agent.create(conn, { sdk })
|
|
1174
|
+
}, stream)
|
|
1175
|
+
console.error("[opencode-acp] connection:done")
|
|
1176
|
+
|
|
1177
|
+
log.info("setup connection")
|
|
1178
|
+
process.stdin.resume()
|
|
1179
|
+
await new Promise((resolve, reject) => {
|
|
1180
|
+
process.stdin.on("end", resolve)
|
|
1181
|
+
process.stdin.on("error", reject)
|
|
1182
|
+
})
|
|
1183
|
+
},
|
|
1184
|
+
})
|
|
1185
|
+
`,
|
|
1186
|
+
);
|
|
1187
|
+
|
|
1188
|
+
await rewriteSourceFile(
|
|
1189
|
+
sourceRoot,
|
|
1190
|
+
"packages/opencode/src/server/instance.ts",
|
|
1191
|
+
(contents) =>
|
|
1192
|
+
contents
|
|
1193
|
+
.replace('import { TuiRoutes } from "./routes/tui"\n', "")
|
|
1194
|
+
.replace('import { PtyRoutes } from "./routes/pty"\n', "")
|
|
1195
|
+
.replace(' .route("/pty", PtyRoutes())\n', "")
|
|
1196
|
+
.replace(' .route("/tui", TuiRoutes())\n', ""),
|
|
1197
|
+
);
|
|
1198
|
+
|
|
1199
|
+
await rewriteSourceFile(
|
|
1200
|
+
sourceRoot,
|
|
1201
|
+
"packages/opencode/src/agent/agent.ts",
|
|
1202
|
+
(contents) =>
|
|
1203
|
+
contents.replace(
|
|
1204
|
+
` [path.relative(Instance.worktree, path.join(Global.Path.data, path.join("plans", "*.md")))]:
|
|
1205
|
+
"allow",`,
|
|
1206
|
+
` [path.relative(ctx.worktree, path.join(Global.Path.data, path.join("plans", "*.md")))]:
|
|
1207
|
+
"allow",`,
|
|
1208
|
+
),
|
|
1209
|
+
);
|
|
1210
|
+
|
|
1211
|
+
await rewriteSourceFile(
|
|
1212
|
+
sourceRoot,
|
|
1213
|
+
"packages/opencode/src/shell/shell.ts",
|
|
1214
|
+
(contents) => contents.replaceAll("Bun.which(", "which("),
|
|
1215
|
+
);
|
|
1216
|
+
|
|
1217
|
+
await rewriteSourceFile(
|
|
1218
|
+
sourceRoot,
|
|
1219
|
+
"packages/opencode/src/server/server.ts",
|
|
1220
|
+
(contents) =>
|
|
1221
|
+
contents
|
|
1222
|
+
.replace('import { MDNS } from "./mdns"\n', "")
|
|
1223
|
+
.replace(
|
|
1224
|
+
` if (shouldPublishMDNS) {
|
|
1225
|
+
MDNS.publish(server.port, opts.mdnsDomain)
|
|
1226
|
+
} else if (opts.mdns) {`,
|
|
1227
|
+
` let mdns:
|
|
1228
|
+
| {
|
|
1229
|
+
publish(port: number, domain?: string): void
|
|
1230
|
+
unpublish(): void
|
|
1231
|
+
}
|
|
1232
|
+
| undefined
|
|
1233
|
+
if (shouldPublishMDNS) {
|
|
1234
|
+
;({ MDNS: mdns } = await import("./mdns"))
|
|
1235
|
+
mdns.publish(server.port, opts.mdnsDomain)
|
|
1236
|
+
} else if (opts.mdns) {`,
|
|
1237
|
+
)
|
|
1238
|
+
.replace(
|
|
1239
|
+
" if (shouldPublishMDNS) MDNS.unpublish()\n",
|
|
1240
|
+
" if (shouldPublishMDNS) mdns?.unpublish()\n",
|
|
1241
|
+
),
|
|
1242
|
+
);
|
|
1243
|
+
|
|
1244
|
+
await rewriteSourceFile(
|
|
1245
|
+
sourceRoot,
|
|
1246
|
+
"packages/opencode/src/project/bootstrap.ts",
|
|
1247
|
+
(contents) =>
|
|
1248
|
+
contents.replace(
|
|
1249
|
+
` Bus.subscribe(Command.Event.Executed, async (payload) => {
|
|
1250
|
+
if (payload.properties.name === Command.Default.INIT) {
|
|
1251
|
+
Project.setInitialized(Instance.project.id)
|
|
1252
|
+
}
|
|
1253
|
+
})
|
|
1254
|
+
`,
|
|
1255
|
+
` Log.Default.info("bootstrap step", { step: "bus.subscribe:skipped" })
|
|
1256
|
+
`,
|
|
1257
|
+
),
|
|
1258
|
+
);
|
|
1259
|
+
|
|
1260
|
+
await rewriteSourceFile(
|
|
1261
|
+
sourceRoot,
|
|
1262
|
+
"packages/opencode/src/acp/agent.ts",
|
|
1263
|
+
(contents) =>
|
|
1264
|
+
contents
|
|
1265
|
+
.replaceAll(
|
|
1266
|
+
` console.error("[opencode-acp] agent.loadSessionMode:commands:done", directory, commands.length)
|
|
1267
|
+
`,
|
|
1268
|
+
"",
|
|
1269
|
+
)
|
|
1270
|
+
.replace(
|
|
1271
|
+
` const defaultAgentName = await AgentModule.defaultAgent()
|
|
1272
|
+
const resolvedModeId =
|
|
1273
|
+
availableModes.find((mode) => mode.name === defaultAgentName)?.id ?? availableModes[0].id
|
|
1274
|
+
`,
|
|
1275
|
+
` const resolvedModeId = availableModes[0].id
|
|
1276
|
+
`,
|
|
1277
|
+
)
|
|
1278
|
+
.replace(
|
|
1279
|
+
` const model = await defaultModel(this.config, directory)
|
|
1280
|
+
`,
|
|
1281
|
+
` console.error("[opencode-acp] agent.newSession:defaultModel:start", directory)
|
|
1282
|
+
const model = await defaultModel(this.config, directory)
|
|
1283
|
+
console.error("[opencode-acp] agent.newSession:defaultModel:done", directory, model.providerID, model.modelID)
|
|
1284
|
+
`,
|
|
1285
|
+
)
|
|
1286
|
+
.replace(
|
|
1287
|
+
` const state = await this.sessionManager.create(params.cwd, params.mcpServers, model)
|
|
1288
|
+
`,
|
|
1289
|
+
` console.error("[opencode-acp] agent.newSession:sessionManager.create:start", directory)
|
|
1290
|
+
const state = await this.sessionManager.create(params.cwd, params.mcpServers, model)
|
|
1291
|
+
console.error("[opencode-acp] agent.newSession:sessionManager.create:done", directory, state.id)
|
|
1292
|
+
`,
|
|
1293
|
+
)
|
|
1294
|
+
.replace(
|
|
1295
|
+
` const load = await this.loadSessionMode({
|
|
1296
|
+
`,
|
|
1297
|
+
` console.error("[opencode-acp] agent.newSession:loadSessionMode:start", directory)
|
|
1298
|
+
const load = await this.loadSessionMode({
|
|
1299
|
+
`,
|
|
1300
|
+
)
|
|
1301
|
+
.replace(
|
|
1302
|
+
` const providers = await this.sdk.config.providers({ directory }).then((x) => x.data!.providers)
|
|
1303
|
+
`,
|
|
1304
|
+
` console.error("[opencode-acp] agent.loadSessionMode:providers:start", directory)
|
|
1305
|
+
const providers = await this.sdk.config.providers({ directory }).then((x) => x.data!.providers)
|
|
1306
|
+
console.error("[opencode-acp] agent.loadSessionMode:providers:done", directory, providers.length)
|
|
1307
|
+
`,
|
|
1308
|
+
)
|
|
1309
|
+
.replace(
|
|
1310
|
+
` const modeState = await this.resolveModeState(directory, sessionId)
|
|
1311
|
+
`,
|
|
1312
|
+
` console.error("[opencode-acp] agent.loadSessionMode:resolveModeState:start", directory)
|
|
1313
|
+
const modeState = await this.resolveModeState(directory, sessionId)
|
|
1314
|
+
console.error("[opencode-acp] agent.loadSessionMode:resolveModeState:done", directory, modeState.availableModes.length)
|
|
1315
|
+
`,
|
|
1316
|
+
)
|
|
1317
|
+
.replace(
|
|
1318
|
+
` const commands = await this.config.sdk.command
|
|
1319
|
+
`,
|
|
1320
|
+
` console.error("[opencode-acp] agent.loadSessionMode:commands:start", directory)
|
|
1321
|
+
const commands = await this.config.sdk.command
|
|
1322
|
+
`,
|
|
1323
|
+
),
|
|
1324
|
+
);
|
|
1325
|
+
|
|
1326
|
+
await rewriteSourceFile(
|
|
1327
|
+
sourceRoot,
|
|
1328
|
+
"packages/opencode/src/project/bootstrap.ts",
|
|
1329
|
+
(contents) =>
|
|
1330
|
+
contents.replace(
|
|
1331
|
+
` Log.Default.info("bootstrap step", { step: "snapshot.init:start" })
|
|
1332
|
+
Snapshot.init()
|
|
1333
|
+
Log.Default.info("bootstrap step", { step: "snapshot.init:done" })
|
|
1334
|
+
`,
|
|
1335
|
+
` Log.Default.info("bootstrap step", { step: "snapshot.init:skipped" })
|
|
1336
|
+
`,
|
|
1337
|
+
),
|
|
1338
|
+
);
|
|
1339
|
+
|
|
1340
|
+
await rewriteSourceFile(
|
|
1341
|
+
sourceRoot,
|
|
1342
|
+
"packages/opencode/src/util/filesystem.ts",
|
|
1343
|
+
(contents) => {
|
|
1344
|
+
if (contents.includes("cachedAgentOsGuestPathMappings")) {
|
|
1345
|
+
return contents;
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
return contents
|
|
1349
|
+
.replace(
|
|
1350
|
+
'import { dirname, join, relative, resolve as pathResolve, win32 } from "path"\n',
|
|
1351
|
+
`import { dirname, join, relative, resolve as pathResolve, win32 } from "path"
|
|
1352
|
+
|
|
1353
|
+
type AgentOsGuestPathMapping = {
|
|
1354
|
+
guestPath?: string
|
|
1355
|
+
hostPath?: string
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
let cachedAgentOsGuestPathMappings:
|
|
1359
|
+
| Array<{ guestPath: string; hostPath: string }>
|
|
1360
|
+
| undefined
|
|
1361
|
+
|
|
1362
|
+
function runtimeWindowsPath(p: string): string {
|
|
1363
|
+
if (process.platform !== "win32") return p
|
|
1364
|
+
return p
|
|
1365
|
+
.replace(/^\\/([a-zA-Z]):(?:[\\\\/]|$)/, (_, drive) => \`\${drive.toUpperCase()}:/\`)
|
|
1366
|
+
.replace(/^\\/([a-zA-Z])(?:\\/|$)/, (_, drive) => \`\${drive.toUpperCase()}:/\`)
|
|
1367
|
+
.replace(/^\\/cygdrive\\/([a-zA-Z])(?:\\/|$)/, (_, drive) => \`\${drive.toUpperCase()}:/\`)
|
|
1368
|
+
.replace(/^\\/mnt\\/([a-zA-Z])(?:\\/|$)/, (_, drive) => \`\${drive.toUpperCase()}:/\`)
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
function agentOsGuestPathMappings() {
|
|
1372
|
+
if (cachedAgentOsGuestPathMappings) return cachedAgentOsGuestPathMappings
|
|
1373
|
+
const raw = process.env.AGENT_OS_GUEST_PATH_MAPPINGS
|
|
1374
|
+
if (!raw) {
|
|
1375
|
+
cachedAgentOsGuestPathMappings = []
|
|
1376
|
+
return cachedAgentOsGuestPathMappings
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
try {
|
|
1380
|
+
const parsed = JSON.parse(raw)
|
|
1381
|
+
if (!Array.isArray(parsed)) {
|
|
1382
|
+
cachedAgentOsGuestPathMappings = []
|
|
1383
|
+
return cachedAgentOsGuestPathMappings
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
cachedAgentOsGuestPathMappings = parsed
|
|
1387
|
+
.filter(
|
|
1388
|
+
(item): item is AgentOsGuestPathMapping =>
|
|
1389
|
+
typeof item === "object" &&
|
|
1390
|
+
item !== null &&
|
|
1391
|
+
typeof item.guestPath === "string" &&
|
|
1392
|
+
typeof item.hostPath === "string",
|
|
1393
|
+
)
|
|
1394
|
+
.map((item) => ({
|
|
1395
|
+
guestPath: item.guestPath === "/" ? "/" : pathResolve(runtimeWindowsPath(item.guestPath)),
|
|
1396
|
+
hostPath: pathResolve(runtimeWindowsPath(item.hostPath)),
|
|
1397
|
+
}))
|
|
1398
|
+
.sort((left, right) => right.guestPath.length - left.guestPath.length)
|
|
1399
|
+
return cachedAgentOsGuestPathMappings
|
|
1400
|
+
} catch {
|
|
1401
|
+
cachedAgentOsGuestPathMappings = []
|
|
1402
|
+
return cachedAgentOsGuestPathMappings
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
function runtimePath(p: string): string {
|
|
1407
|
+
if (!p.startsWith("/")) return p
|
|
1408
|
+
|
|
1409
|
+
const normalized = pathResolve(runtimeWindowsPath(p))
|
|
1410
|
+
for (const mapping of agentOsGuestPathMappings()) {
|
|
1411
|
+
if (
|
|
1412
|
+
mapping.guestPath !== "/" &&
|
|
1413
|
+
normalized !== mapping.guestPath &&
|
|
1414
|
+
!normalized.startsWith(\`\${mapping.guestPath}/\`)
|
|
1415
|
+
) {
|
|
1416
|
+
continue
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
const suffix =
|
|
1420
|
+
mapping.guestPath === "/"
|
|
1421
|
+
? normalized.slice(1)
|
|
1422
|
+
: normalized.slice(mapping.guestPath.length).replace(/^[/\\\\]+/, "")
|
|
1423
|
+
return suffix ? join(mapping.hostPath, suffix) : mapping.hostPath
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
return p
|
|
1427
|
+
}
|
|
1428
|
+
`,
|
|
1429
|
+
)
|
|
1430
|
+
.replace(
|
|
1431
|
+
` return existsSync(p)
|
|
1432
|
+
`,
|
|
1433
|
+
` return existsSync(runtimePath(p))
|
|
1434
|
+
`,
|
|
1435
|
+
)
|
|
1436
|
+
.replace(
|
|
1437
|
+
` return statSync(p).isDirectory()
|
|
1438
|
+
`,
|
|
1439
|
+
` return statSync(runtimePath(p)).isDirectory()
|
|
1440
|
+
`,
|
|
1441
|
+
)
|
|
1442
|
+
.replace(
|
|
1443
|
+
` return statSync(p, { throwIfNoEntry: false }) ?? undefined
|
|
1444
|
+
`,
|
|
1445
|
+
` return statSync(runtimePath(p), { throwIfNoEntry: false }) ?? undefined
|
|
1446
|
+
`,
|
|
1447
|
+
)
|
|
1448
|
+
.replace(
|
|
1449
|
+
` return statFile(p).catch((e) => {
|
|
1450
|
+
`,
|
|
1451
|
+
` return statFile(runtimePath(p)).catch((e) => {
|
|
1452
|
+
`,
|
|
1453
|
+
)
|
|
1454
|
+
.replace(
|
|
1455
|
+
` return readFile(p, "utf-8")
|
|
1456
|
+
`,
|
|
1457
|
+
` return readFile(runtimePath(p), "utf-8")
|
|
1458
|
+
`,
|
|
1459
|
+
)
|
|
1460
|
+
.replace(
|
|
1461
|
+
` return JSON.parse(await readFile(p, "utf-8"))
|
|
1462
|
+
`,
|
|
1463
|
+
` return JSON.parse(await readFile(runtimePath(p), "utf-8"))
|
|
1464
|
+
`,
|
|
1465
|
+
)
|
|
1466
|
+
.replace(
|
|
1467
|
+
` return readFile(p)
|
|
1468
|
+
`,
|
|
1469
|
+
` return readFile(runtimePath(p))
|
|
1470
|
+
`,
|
|
1471
|
+
)
|
|
1472
|
+
.replace(
|
|
1473
|
+
` const buf = await readFile(p)
|
|
1474
|
+
`,
|
|
1475
|
+
` const buf = await readFile(runtimePath(p))
|
|
1476
|
+
`,
|
|
1477
|
+
)
|
|
1478
|
+
.replace(
|
|
1479
|
+
` try {
|
|
1480
|
+
if (mode) {
|
|
1481
|
+
await writeFile(p, content, { mode })
|
|
1482
|
+
} else {
|
|
1483
|
+
await writeFile(p, content)
|
|
1484
|
+
}
|
|
1485
|
+
} catch (e) {
|
|
1486
|
+
if (isEnoent(e)) {
|
|
1487
|
+
await mkdir(dirname(p), { recursive: true })
|
|
1488
|
+
if (mode) {
|
|
1489
|
+
await writeFile(p, content, { mode })
|
|
1490
|
+
} else {
|
|
1491
|
+
await writeFile(p, content)
|
|
1492
|
+
}
|
|
1493
|
+
return
|
|
1494
|
+
}
|
|
1495
|
+
throw e
|
|
1496
|
+
}
|
|
1497
|
+
`,
|
|
1498
|
+
` const target = runtimePath(p)
|
|
1499
|
+
try {
|
|
1500
|
+
if (mode) {
|
|
1501
|
+
await writeFile(target, content, { mode })
|
|
1502
|
+
} else {
|
|
1503
|
+
await writeFile(target, content)
|
|
1504
|
+
}
|
|
1505
|
+
} catch (e) {
|
|
1506
|
+
if (isEnoent(e)) {
|
|
1507
|
+
await mkdir(dirname(target), { recursive: true })
|
|
1508
|
+
if (mode) {
|
|
1509
|
+
await writeFile(target, content, { mode })
|
|
1510
|
+
} else {
|
|
1511
|
+
await writeFile(target, content)
|
|
1512
|
+
}
|
|
1513
|
+
return
|
|
1514
|
+
}
|
|
1515
|
+
throw e
|
|
1516
|
+
}
|
|
1517
|
+
`,
|
|
1518
|
+
)
|
|
1519
|
+
.replace(
|
|
1520
|
+
` const dir = dirname(p)
|
|
1521
|
+
`,
|
|
1522
|
+
` const target = runtimePath(p)
|
|
1523
|
+
const dir = dirname(target)
|
|
1524
|
+
`,
|
|
1525
|
+
)
|
|
1526
|
+
.replace(
|
|
1527
|
+
` const writeStream = createWriteStream(p)
|
|
1528
|
+
`,
|
|
1529
|
+
` const writeStream = createWriteStream(target)
|
|
1530
|
+
`,
|
|
1531
|
+
)
|
|
1532
|
+
.replace(
|
|
1533
|
+
` await chmod(p, mode)
|
|
1534
|
+
`,
|
|
1535
|
+
` await chmod(target, mode)
|
|
1536
|
+
`,
|
|
1537
|
+
);
|
|
1538
|
+
},
|
|
1539
|
+
);
|
|
1540
|
+
|
|
1541
|
+
for (const relativePath of [
|
|
1542
|
+
"packages/opencode/src/cli/cmd/mcp.ts",
|
|
1543
|
+
"packages/opencode/src/config/config.ts",
|
|
1544
|
+
"packages/opencode/src/config/migrate-tui-config.ts",
|
|
1545
|
+
"packages/opencode/src/config/paths.ts",
|
|
1546
|
+
"packages/opencode/src/plugin/install.ts",
|
|
1547
|
+
]) {
|
|
1548
|
+
await rewriteSourceFile(sourceRoot, relativePath, (contents) =>
|
|
1549
|
+
contents.replaceAll(
|
|
1550
|
+
'"jsonc-parser"',
|
|
1551
|
+
'"jsonc-parser/lib/esm/main.js"',
|
|
1552
|
+
),
|
|
1553
|
+
);
|
|
1554
|
+
|
|
1555
|
+
await rewriteSourceFile(
|
|
1556
|
+
sourceRoot,
|
|
1557
|
+
"packages/opencode/src/plugin/index.ts",
|
|
1558
|
+
(contents) =>
|
|
1559
|
+
contents.replace(
|
|
1560
|
+
` const state = Instance.state(async () => {
|
|
1561
|
+
const client = createOpencodeClient({
|
|
1562
|
+
`,
|
|
1563
|
+
` const state = Instance.state(async () => {
|
|
1564
|
+
if (Flag.OPENCODE_CLIENT === "acp") {
|
|
1565
|
+
log.info("skipping plugin runtime in ACP mode")
|
|
1566
|
+
return {
|
|
1567
|
+
hooks: [],
|
|
1568
|
+
input: {
|
|
1569
|
+
client: undefined as any,
|
|
1570
|
+
project: Instance.project,
|
|
1571
|
+
worktree: Instance.worktree,
|
|
1572
|
+
directory: Instance.directory,
|
|
1573
|
+
serverUrl: "",
|
|
1574
|
+
$: Bun.$,
|
|
1575
|
+
} as PluginInput,
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
const client = createOpencodeClient({
|
|
1580
|
+
`,
|
|
1581
|
+
),
|
|
1582
|
+
);
|
|
1583
|
+
|
|
1584
|
+
await rewriteSourceFile(
|
|
1585
|
+
sourceRoot,
|
|
1586
|
+
"packages/opencode/src/session/index.ts",
|
|
1587
|
+
(contents) =>
|
|
1588
|
+
contents
|
|
1589
|
+
.replace('import { SessionPrompt } from "./prompt"\n', "")
|
|
1590
|
+
.replace('import { Command } from "../command"\n', "")
|
|
1591
|
+
.replace(
|
|
1592
|
+
` const initialize = Effect.fn("Session.initialize")(function* (input: {
|
|
1593
|
+
sessionID: SessionID
|
|
1594
|
+
modelID: ModelID
|
|
1595
|
+
providerID: ProviderID
|
|
1596
|
+
messageID: MessageID
|
|
1597
|
+
}) {
|
|
1598
|
+
yield* Effect.promise(() =>
|
|
1599
|
+
SessionPrompt.command({
|
|
1600
|
+
sessionID: input.sessionID,
|
|
1601
|
+
messageID: input.messageID,
|
|
1602
|
+
model: input.providerID + "/" + input.modelID,
|
|
1603
|
+
command: Command.Default.INIT,
|
|
1604
|
+
arguments: "",
|
|
1605
|
+
}),
|
|
1606
|
+
)
|
|
1607
|
+
})
|
|
1608
|
+
`,
|
|
1609
|
+
` const initialize = Effect.fn("Session.initialize")(function* (input: {
|
|
1610
|
+
sessionID: SessionID
|
|
1611
|
+
modelID: ModelID
|
|
1612
|
+
providerID: ProviderID
|
|
1613
|
+
messageID: MessageID
|
|
1614
|
+
}) {
|
|
1615
|
+
const [{ SessionPrompt }, { Command }] = yield* Effect.promise(() =>
|
|
1616
|
+
Promise.all([import("./prompt"), import("../command")]),
|
|
1617
|
+
)
|
|
1618
|
+
yield* Effect.promise(() =>
|
|
1619
|
+
SessionPrompt.command({
|
|
1620
|
+
sessionID: input.sessionID,
|
|
1621
|
+
messageID: input.messageID,
|
|
1622
|
+
model: input.providerID + "/" + input.modelID,
|
|
1623
|
+
command: Command.Default.INIT,
|
|
1624
|
+
arguments: "",
|
|
1625
|
+
}),
|
|
1626
|
+
)
|
|
1627
|
+
})
|
|
1628
|
+
`,
|
|
1629
|
+
),
|
|
1630
|
+
);
|
|
1631
|
+
|
|
1632
|
+
await rewriteSourceFile(
|
|
1633
|
+
sourceRoot,
|
|
1634
|
+
"packages/opencode/src/session/prompt.ts",
|
|
1635
|
+
(contents) =>
|
|
1636
|
+
contents
|
|
1637
|
+
.replace(
|
|
1638
|
+
/ const text = yield\* Effect\.promise\(async \(signal\) => \{\n[\s\S]*? return result\.text\n \}\)\n/,
|
|
1639
|
+
` const instanceCtx = yield* InstanceState.context
|
|
1640
|
+
const text = yield* Effect.promise((signal) =>
|
|
1641
|
+
Instance.restore(instanceCtx, async () => {
|
|
1642
|
+
const mdl = ag.model
|
|
1643
|
+
? await Provider.getModel(ag.model.providerID, ag.model.modelID)
|
|
1644
|
+
: ((await Provider.getSmallModel(input.providerID)) ??
|
|
1645
|
+
(await Provider.getModel(input.providerID, input.modelID)))
|
|
1646
|
+
const msgs = onlySubtasks
|
|
1647
|
+
? [{ role: "user" as const, content: subtasks.map((p) => p.prompt).join("\\n") }]
|
|
1648
|
+
: await MessageV2.toModelMessages(context, mdl)
|
|
1649
|
+
const result = await LLM.stream({
|
|
1650
|
+
agent: ag,
|
|
1651
|
+
user: firstInfo,
|
|
1652
|
+
system: [],
|
|
1653
|
+
small: true,
|
|
1654
|
+
tools: {},
|
|
1655
|
+
model: mdl,
|
|
1656
|
+
abort: signal,
|
|
1657
|
+
sessionID: input.session.id,
|
|
1658
|
+
retries: 2,
|
|
1659
|
+
messages: [{ role: "user", content: "Generate a title for this conversation:\\n" }, ...msgs],
|
|
1660
|
+
})
|
|
1661
|
+
return result.text
|
|
1662
|
+
}),
|
|
1663
|
+
)
|
|
1664
|
+
`,
|
|
1665
|
+
)
|
|
1666
|
+
.replace(
|
|
1667
|
+
` const getModel = (providerID: ProviderID, modelID: ModelID, sessionID: SessionID) =>
|
|
1668
|
+
Effect.promise(() =>
|
|
1669
|
+
Provider.getModel(providerID, modelID).catch((e) => {
|
|
1670
|
+
if (Provider.ModelNotFoundError.isInstance(e)) {
|
|
1671
|
+
const hint = e.data.suggestions?.length ? \` Did you mean: \${e.data.suggestions.join(", ")}?\` : ""
|
|
1672
|
+
Bus.publish(Session.Event.Error, {
|
|
1673
|
+
sessionID,
|
|
1674
|
+
error: new NamedError.Unknown({
|
|
1675
|
+
message: \`Model not found: \${e.data.providerID}/\${e.data.modelID}.\${hint}\`,
|
|
1676
|
+
}).toObject(),
|
|
1677
|
+
})
|
|
1678
|
+
}
|
|
1679
|
+
throw e
|
|
1680
|
+
}),
|
|
1681
|
+
)
|
|
1682
|
+
`,
|
|
1683
|
+
` const getModel = (providerID: ProviderID, modelID: ModelID, sessionID: SessionID) =>
|
|
1684
|
+
Effect.gen(function* () {
|
|
1685
|
+
const instanceCtx = yield* InstanceState.context
|
|
1686
|
+
return yield* Effect.promise(() =>
|
|
1687
|
+
Instance.restore(instanceCtx, () =>
|
|
1688
|
+
Provider.getModel(providerID, modelID).catch((e) => {
|
|
1689
|
+
if (Provider.ModelNotFoundError.isInstance(e)) {
|
|
1690
|
+
const hint = e.data.suggestions?.length ? \` Did you mean: \${e.data.suggestions.join(", ")}?\` : ""
|
|
1691
|
+
Bus.publish(Session.Event.Error, {
|
|
1692
|
+
sessionID,
|
|
1693
|
+
error: new NamedError.Unknown({
|
|
1694
|
+
message: \`Model not found: \${e.data.providerID}/\${e.data.modelID}.\${hint}\`,
|
|
1695
|
+
}).toObject(),
|
|
1696
|
+
})
|
|
1697
|
+
}
|
|
1698
|
+
throw e
|
|
1699
|
+
}),
|
|
1700
|
+
),
|
|
1701
|
+
)
|
|
1702
|
+
})
|
|
1703
|
+
`,
|
|
1704
|
+
)
|
|
1705
|
+
.replace(
|
|
1706
|
+
` const model = input.model ?? ag.model ?? (yield* lastModel(input.sessionID))
|
|
1707
|
+
const full =
|
|
1708
|
+
!input.variant && ag.variant
|
|
1709
|
+
? yield* Effect.promise(() => Provider.getModel(model.providerID, model.modelID).catch(() => undefined))
|
|
1710
|
+
: undefined
|
|
1711
|
+
`,
|
|
1712
|
+
` const model = input.model ?? ag.model ?? (yield* lastModel(input.sessionID))
|
|
1713
|
+
const instanceCtx = yield* InstanceState.context
|
|
1714
|
+
const full =
|
|
1715
|
+
!input.variant && ag.variant
|
|
1716
|
+
? yield* Effect.promise(() =>
|
|
1717
|
+
Instance.restore(instanceCtx, () =>
|
|
1718
|
+
Provider.getModel(model.providerID, model.modelID).catch(() => undefined),
|
|
1719
|
+
),
|
|
1720
|
+
)
|
|
1721
|
+
: undefined
|
|
1722
|
+
`,
|
|
1723
|
+
)
|
|
1724
|
+
.replace(
|
|
1725
|
+
` Effect.promise(() => Provider.getModel(info.model.providerID, info.model.modelID)).pipe(
|
|
1726
|
+
`,
|
|
1727
|
+
` Effect.gen(function* () {
|
|
1728
|
+
const instanceCtx = yield* InstanceState.context
|
|
1729
|
+
return yield* Effect.promise(() =>
|
|
1730
|
+
Instance.restore(instanceCtx, () =>
|
|
1731
|
+
Provider.getModel(info.model.providerID, info.model.modelID),
|
|
1732
|
+
),
|
|
1733
|
+
)
|
|
1734
|
+
}).pipe(
|
|
1735
|
+
`,
|
|
1736
|
+
)
|
|
1737
|
+
.replace(
|
|
1738
|
+
` const tools = yield* resolveTools({
|
|
1739
|
+
agent,
|
|
1740
|
+
session,
|
|
1741
|
+
model,
|
|
1742
|
+
tools: lastUser.tools,
|
|
1743
|
+
processor: handle,
|
|
1744
|
+
bypassAgentCheck,
|
|
1745
|
+
messages: msgs,
|
|
1746
|
+
})
|
|
1747
|
+
`,
|
|
1748
|
+
` const tools = yield* resolveTools({
|
|
1749
|
+
agent,
|
|
1750
|
+
session,
|
|
1751
|
+
model,
|
|
1752
|
+
tools: lastUser.tools,
|
|
1753
|
+
processor: handle,
|
|
1754
|
+
bypassAgentCheck,
|
|
1755
|
+
messages: msgs,
|
|
1756
|
+
})
|
|
1757
|
+
`,
|
|
1758
|
+
)
|
|
1759
|
+
.replace(
|
|
1760
|
+
` const [skills, env, instructions, modelMsgs] = yield* Effect.promise(() =>
|
|
1761
|
+
Promise.all([
|
|
1762
|
+
SystemPrompt.skills(agent),
|
|
1763
|
+
SystemPrompt.environment(model),
|
|
1764
|
+
InstructionPrompt.system(),
|
|
1765
|
+
MessageV2.toModelMessages(msgs, model),
|
|
1766
|
+
]),
|
|
1767
|
+
)
|
|
1768
|
+
`,
|
|
1769
|
+
` const instanceCtx = yield* InstanceState.context
|
|
1770
|
+
const [skills, env, instructions, modelMsgs] = yield* Effect.promise(() =>
|
|
1771
|
+
Instance.restore(instanceCtx, () =>
|
|
1772
|
+
(async () => {
|
|
1773
|
+
console.error("[opencode-acp] prompt.system:skills:start", sessionID)
|
|
1774
|
+
const skills = await Instance.restore(instanceCtx, () => SystemPrompt.skills(agent))
|
|
1775
|
+
console.error("[opencode-acp] prompt.system:skills:done", sessionID)
|
|
1776
|
+
console.error("[opencode-acp] prompt.system:env:start", sessionID)
|
|
1777
|
+
const env = await Instance.restore(instanceCtx, () => SystemPrompt.environment(model))
|
|
1778
|
+
console.error("[opencode-acp] prompt.system:env:done", sessionID)
|
|
1779
|
+
console.error("[opencode-acp] prompt.system:instructions:start", sessionID)
|
|
1780
|
+
const instructions = await Instance.restore(instanceCtx, () => InstructionPrompt.system())
|
|
1781
|
+
console.error("[opencode-acp] prompt.system:instructions:done", sessionID)
|
|
1782
|
+
console.error("[opencode-acp] prompt.system:modelMessages:start", sessionID)
|
|
1783
|
+
const modelMsgs = await Instance.restore(instanceCtx, () =>
|
|
1784
|
+
MessageV2.toModelMessages(msgs, model),
|
|
1785
|
+
)
|
|
1786
|
+
console.error("[opencode-acp] prompt.system:modelMessages:done", sessionID)
|
|
1787
|
+
return [skills, env, instructions, modelMsgs] as const
|
|
1788
|
+
})(),
|
|
1789
|
+
),
|
|
1790
|
+
)
|
|
1791
|
+
`,
|
|
1792
|
+
)
|
|
1793
|
+
.replace(
|
|
1794
|
+
` const defaultLayer = Layer.unwrap(
|
|
1795
|
+
Effect.sync(() =>
|
|
1796
|
+
layer.pipe(
|
|
1797
|
+
Layer.provide(SessionStatus.layer),
|
|
1798
|
+
Layer.provide(SessionCompaction.defaultLayer),
|
|
1799
|
+
Layer.provide(SessionProcessor.defaultLayer),
|
|
1800
|
+
Layer.provide(Command.defaultLayer),
|
|
1801
|
+
Layer.provide(Permission.layer),
|
|
1802
|
+
Layer.provide(MCP.defaultLayer),
|
|
1803
|
+
Layer.provide(LSP.defaultLayer),
|
|
1804
|
+
Layer.provide(FileTime.defaultLayer),
|
|
1805
|
+
Layer.provide(ToolRegistry.defaultLayer),
|
|
1806
|
+
Layer.provide(Truncate.layer),
|
|
1807
|
+
Layer.provide(AppFileSystem.defaultLayer),
|
|
1808
|
+
Layer.provide(Plugin.defaultLayer),
|
|
1809
|
+
Layer.provide(Session.defaultLayer),
|
|
1810
|
+
Layer.provide(Agent.defaultLayer),
|
|
1811
|
+
Layer.provide(Bus.layer),
|
|
1812
|
+
),
|
|
1813
|
+
),
|
|
1814
|
+
)
|
|
1815
|
+
`,
|
|
1816
|
+
` export const defaultLayer = Layer.unwrap(
|
|
1817
|
+
Effect.sync(() =>
|
|
1818
|
+
layer.pipe(
|
|
1819
|
+
Layer.provide(SessionStatus.layer),
|
|
1820
|
+
Layer.provide(SessionCompaction.defaultLayer),
|
|
1821
|
+
Layer.provide(SessionProcessor.defaultLayer),
|
|
1822
|
+
Layer.provide(Command.defaultLayer),
|
|
1823
|
+
Layer.provide(Permission.layer),
|
|
1824
|
+
Layer.provide(MCP.defaultLayer),
|
|
1825
|
+
Layer.provide(LSP.defaultLayer),
|
|
1826
|
+
Layer.provide(FileTime.defaultLayer),
|
|
1827
|
+
Layer.provide(ToolRegistry.defaultLayer),
|
|
1828
|
+
Layer.provide(Truncate.layer),
|
|
1829
|
+
Layer.provide(AppFileSystem.defaultLayer),
|
|
1830
|
+
Layer.provide(Plugin.defaultLayer),
|
|
1831
|
+
Layer.provide(Session.defaultLayer),
|
|
1832
|
+
Layer.provide(Agent.defaultLayer),
|
|
1833
|
+
Layer.provide(Bus.layer),
|
|
1834
|
+
),
|
|
1835
|
+
),
|
|
1836
|
+
)
|
|
1837
|
+
`,
|
|
1838
|
+
),
|
|
1839
|
+
);
|
|
1840
|
+
|
|
1841
|
+
await rewriteSourceFile(
|
|
1842
|
+
sourceRoot,
|
|
1843
|
+
"packages/opencode/src/session/llm.ts",
|
|
1844
|
+
(contents) => {
|
|
1845
|
+
let updated = contents;
|
|
1846
|
+
if (
|
|
1847
|
+
!updated.includes(
|
|
1848
|
+
'import { InstanceState } from "@/effect/instance-state"\n',
|
|
1849
|
+
)
|
|
1850
|
+
) {
|
|
1851
|
+
updated = updated.replace(
|
|
1852
|
+
'import { Installation } from "@/installation"\n',
|
|
1853
|
+
'import { Installation } from "@/installation"\nimport { InstanceState } from "@/effect/instance-state"\n',
|
|
1854
|
+
);
|
|
1855
|
+
}
|
|
1856
|
+
updated = updated
|
|
1857
|
+
.replace(
|
|
1858
|
+
` stream(input) {
|
|
1859
|
+
return Stream.scoped(
|
|
1860
|
+
Stream.unwrap(
|
|
1861
|
+
Effect.gen(function* () {
|
|
1862
|
+
const ctrl = yield* Effect.acquireRelease(
|
|
1863
|
+
Effect.sync(() => new AbortController()),
|
|
1864
|
+
(ctrl) => Effect.sync(() => ctrl.abort()),
|
|
1865
|
+
)
|
|
1866
|
+
|
|
1867
|
+
const result = yield* Effect.promise(() => LLM.stream({ ...input, abort: ctrl.signal }))
|
|
1868
|
+
|
|
1869
|
+
return Stream.fromAsyncIterable(result.fullStream, (e) =>
|
|
1870
|
+
e instanceof Error ? e : new Error(String(e)),
|
|
1871
|
+
)
|
|
1872
|
+
}),
|
|
1873
|
+
),
|
|
1874
|
+
)
|
|
1875
|
+
},
|
|
1876
|
+
`,
|
|
1877
|
+
` stream(input) {
|
|
1878
|
+
return Stream.scoped(
|
|
1879
|
+
Stream.unwrap(
|
|
1880
|
+
Effect.gen(function* () {
|
|
1881
|
+
const instanceCtx = yield* InstanceState.context
|
|
1882
|
+
const ctrl = yield* Effect.acquireRelease(
|
|
1883
|
+
Effect.sync(() => new AbortController()),
|
|
1884
|
+
(ctrl) => Effect.sync(() => ctrl.abort()),
|
|
1885
|
+
)
|
|
1886
|
+
|
|
1887
|
+
const result = yield* Effect.promise(() =>
|
|
1888
|
+
Instance.restore(instanceCtx, () => LLM.stream({ ...input, abort: ctrl.signal })),
|
|
1889
|
+
)
|
|
1890
|
+
|
|
1891
|
+
return Stream.fromAsyncIterable(result.fullStream, (e) =>
|
|
1892
|
+
e instanceof Error ? e : new Error(String(e)),
|
|
1893
|
+
)
|
|
1894
|
+
}),
|
|
1895
|
+
),
|
|
1896
|
+
)
|
|
1897
|
+
},
|
|
1898
|
+
`,
|
|
1899
|
+
)
|
|
1900
|
+
.replace(
|
|
1901
|
+
` const [language, cfg, provider, auth] = await Promise.all([
|
|
1902
|
+
Provider.getLanguage(input.model),
|
|
1903
|
+
Config.get(),
|
|
1904
|
+
Provider.getProvider(input.model.providerID),
|
|
1905
|
+
Auth.get(input.model.providerID),
|
|
1906
|
+
])
|
|
1907
|
+
`,
|
|
1908
|
+
` const instanceCtx = Instance.current
|
|
1909
|
+
const [language, cfg, provider, auth] = await Instance.restore(instanceCtx, () =>
|
|
1910
|
+
Promise.all([
|
|
1911
|
+
Provider.getLanguage(input.model),
|
|
1912
|
+
Config.get(),
|
|
1913
|
+
Provider.getProvider(input.model.providerID),
|
|
1914
|
+
Auth.get(input.model.providerID),
|
|
1915
|
+
]),
|
|
1916
|
+
)
|
|
1917
|
+
`,
|
|
1918
|
+
)
|
|
1919
|
+
.replace(
|
|
1920
|
+
` await Plugin.trigger(
|
|
1921
|
+
"experimental.chat.system.transform",
|
|
1922
|
+
{ sessionID: input.sessionID, model: input.model },
|
|
1923
|
+
{ system },
|
|
1924
|
+
)
|
|
1925
|
+
`,
|
|
1926
|
+
` await Instance.restore(instanceCtx, () =>
|
|
1927
|
+
Plugin.trigger(
|
|
1928
|
+
"experimental.chat.system.transform",
|
|
1929
|
+
{ sessionID: input.sessionID, model: input.model },
|
|
1930
|
+
{ system },
|
|
1931
|
+
),
|
|
1932
|
+
)
|
|
1933
|
+
`,
|
|
1934
|
+
)
|
|
1935
|
+
.replace(
|
|
1936
|
+
` const params = await Plugin.trigger(
|
|
1937
|
+
"chat.params",
|
|
1938
|
+
{
|
|
1939
|
+
sessionID: input.sessionID,
|
|
1940
|
+
agent: input.agent.name,
|
|
1941
|
+
model: input.model,
|
|
1942
|
+
provider,
|
|
1943
|
+
message: input.user,
|
|
1944
|
+
},
|
|
1945
|
+
{
|
|
1946
|
+
temperature: input.model.capabilities.temperature
|
|
1947
|
+
? (input.agent.temperature ?? ProviderTransform.temperature(input.model))
|
|
1948
|
+
: undefined,
|
|
1949
|
+
topP: input.agent.topP ?? ProviderTransform.topP(input.model),
|
|
1950
|
+
topK: ProviderTransform.topK(input.model),
|
|
1951
|
+
options,
|
|
1952
|
+
},
|
|
1953
|
+
)
|
|
1954
|
+
`,
|
|
1955
|
+
` const params = await Instance.restore(instanceCtx, () =>
|
|
1956
|
+
Plugin.trigger(
|
|
1957
|
+
"chat.params",
|
|
1958
|
+
{
|
|
1959
|
+
sessionID: input.sessionID,
|
|
1960
|
+
agent: input.agent.name,
|
|
1961
|
+
model: input.model,
|
|
1962
|
+
provider,
|
|
1963
|
+
message: input.user,
|
|
1964
|
+
},
|
|
1965
|
+
{
|
|
1966
|
+
temperature: input.model.capabilities.temperature
|
|
1967
|
+
? (input.agent.temperature ?? ProviderTransform.temperature(input.model))
|
|
1968
|
+
: undefined,
|
|
1969
|
+
topP: input.agent.topP ?? ProviderTransform.topP(input.model),
|
|
1970
|
+
topK: ProviderTransform.topK(input.model),
|
|
1971
|
+
options,
|
|
1972
|
+
},
|
|
1973
|
+
),
|
|
1974
|
+
)
|
|
1975
|
+
`,
|
|
1976
|
+
)
|
|
1977
|
+
.replace(
|
|
1978
|
+
` const { headers } = await Plugin.trigger(
|
|
1979
|
+
"chat.headers",
|
|
1980
|
+
{
|
|
1981
|
+
sessionID: input.sessionID,
|
|
1982
|
+
agent: input.agent.name,
|
|
1983
|
+
model: input.model,
|
|
1984
|
+
provider,
|
|
1985
|
+
message: input.user,
|
|
1986
|
+
},
|
|
1987
|
+
{
|
|
1988
|
+
headers: {},
|
|
1989
|
+
},
|
|
1990
|
+
)
|
|
1991
|
+
`,
|
|
1992
|
+
` const { headers } = await Instance.restore(instanceCtx, () =>
|
|
1993
|
+
Plugin.trigger(
|
|
1994
|
+
"chat.headers",
|
|
1995
|
+
{
|
|
1996
|
+
sessionID: input.sessionID,
|
|
1997
|
+
agent: input.agent.name,
|
|
1998
|
+
model: input.model,
|
|
1999
|
+
provider,
|
|
2000
|
+
message: input.user,
|
|
2001
|
+
},
|
|
2002
|
+
{
|
|
2003
|
+
headers: {},
|
|
2004
|
+
},
|
|
2005
|
+
),
|
|
2006
|
+
)
|
|
2007
|
+
`,
|
|
2008
|
+
)
|
|
2009
|
+
.replace(
|
|
2010
|
+
` const tools = await resolveTools(input)
|
|
2011
|
+
`,
|
|
2012
|
+
` const tools = await Instance.restore(instanceCtx, () => resolveTools(input))
|
|
2013
|
+
`,
|
|
2014
|
+
)
|
|
2015
|
+
.replace(
|
|
2016
|
+
` headers: {
|
|
2017
|
+
...(input.model.providerID.startsWith("opencode")
|
|
2018
|
+
? {
|
|
2019
|
+
"x-opencode-project": Instance.project.id,
|
|
2020
|
+
"x-opencode-session": input.sessionID,
|
|
2021
|
+
"x-opencode-request": input.user.id,
|
|
2022
|
+
"x-opencode-client": Flag.OPENCODE_CLIENT,
|
|
2023
|
+
}
|
|
2024
|
+
: {
|
|
2025
|
+
"User-Agent": \`opencode/\${Installation.VERSION}\`,
|
|
2026
|
+
}),
|
|
2027
|
+
...input.model.headers,
|
|
2028
|
+
...headers,
|
|
2029
|
+
},
|
|
2030
|
+
`,
|
|
2031
|
+
` headers: {
|
|
2032
|
+
...(input.model.providerID.startsWith("opencode")
|
|
2033
|
+
? Instance.restore(instanceCtx, () => ({
|
|
2034
|
+
"x-opencode-project": Instance.project.id,
|
|
2035
|
+
"x-opencode-session": input.sessionID,
|
|
2036
|
+
"x-opencode-request": input.user.id,
|
|
2037
|
+
"x-opencode-client": Flag.OPENCODE_CLIENT,
|
|
2038
|
+
}))
|
|
2039
|
+
: {
|
|
2040
|
+
"User-Agent": \`opencode/\${Installation.VERSION}\`,
|
|
2041
|
+
}),
|
|
2042
|
+
...input.model.headers,
|
|
2043
|
+
...headers,
|
|
2044
|
+
},
|
|
2045
|
+
`,
|
|
2046
|
+
)
|
|
2047
|
+
.replace(
|
|
2048
|
+
` return streamText({
|
|
2049
|
+
`,
|
|
2050
|
+
` return Instance.restore(instanceCtx, () => streamText({
|
|
2051
|
+
`,
|
|
2052
|
+
)
|
|
2053
|
+
.replace(
|
|
2054
|
+
` })
|
|
2055
|
+
}
|
|
2056
|
+
`,
|
|
2057
|
+
` }))
|
|
2058
|
+
}
|
|
2059
|
+
`,
|
|
2060
|
+
);
|
|
2061
|
+
return updated;
|
|
2062
|
+
},
|
|
2063
|
+
);
|
|
2064
|
+
|
|
2065
|
+
await rewriteSourceFile(
|
|
2066
|
+
sourceRoot,
|
|
2067
|
+
"packages/opencode/src/session/prompt.ts",
|
|
2068
|
+
(contents) =>
|
|
2069
|
+
contents.replace(
|
|
2070
|
+
` execute(args, options) {
|
|
2071
|
+
return Effect.runPromise(
|
|
2072
|
+
Effect.gen(function* () {
|
|
2073
|
+
const ctx = context(args, options)
|
|
2074
|
+
yield* plugin.trigger(
|
|
2075
|
+
"tool.execute.before",
|
|
2076
|
+
{ tool: item.id, sessionID: ctx.sessionID, callID: ctx.callID },
|
|
2077
|
+
{ args },
|
|
2078
|
+
)
|
|
2079
|
+
const result = yield* Effect.promise(() => item.execute(args, ctx))
|
|
2080
|
+
const output = {
|
|
2081
|
+
...result,
|
|
2082
|
+
attachments: result.attachments?.map((attachment) => ({
|
|
2083
|
+
...attachment,
|
|
2084
|
+
id: PartID.ascending(),
|
|
2085
|
+
sessionID: ctx.sessionID,
|
|
2086
|
+
messageID: input.processor.message.id,
|
|
2087
|
+
})),
|
|
2088
|
+
}
|
|
2089
|
+
yield* plugin.trigger(
|
|
2090
|
+
"tool.execute.after",
|
|
2091
|
+
{ tool: item.id, sessionID: ctx.sessionID, callID: ctx.callID, args },
|
|
2092
|
+
output,
|
|
2093
|
+
)
|
|
2094
|
+
return output
|
|
2095
|
+
}),
|
|
2096
|
+
)
|
|
2097
|
+
},
|
|
2098
|
+
`,
|
|
2099
|
+
` execute(args, options) {
|
|
2100
|
+
const instanceCtx =
|
|
2101
|
+
((globalThis as typeof globalThis & { __agentosOpencodeInstanceFallback?: unknown })
|
|
2102
|
+
.__agentosOpencodeInstanceFallback ??
|
|
2103
|
+
Instance.current) as any
|
|
2104
|
+
return Instance.restore(instanceCtx, () =>
|
|
2105
|
+
Effect.runPromise(
|
|
2106
|
+
Effect.gen(function* () {
|
|
2107
|
+
const ctx = context(args, options)
|
|
2108
|
+
yield* plugin.trigger(
|
|
2109
|
+
"tool.execute.before",
|
|
2110
|
+
{ tool: item.id, sessionID: ctx.sessionID, callID: ctx.callID },
|
|
2111
|
+
{ args },
|
|
2112
|
+
)
|
|
2113
|
+
const result = yield* Effect.promise(() => item.execute(args, ctx))
|
|
2114
|
+
const output = {
|
|
2115
|
+
...result,
|
|
2116
|
+
attachments: result.attachments?.map((attachment) => ({
|
|
2117
|
+
...attachment,
|
|
2118
|
+
id: PartID.ascending(),
|
|
2119
|
+
sessionID: ctx.sessionID,
|
|
2120
|
+
messageID: input.processor.message.id,
|
|
2121
|
+
})),
|
|
2122
|
+
}
|
|
2123
|
+
yield* plugin.trigger(
|
|
2124
|
+
"tool.execute.after",
|
|
2125
|
+
{ tool: item.id, sessionID: ctx.sessionID, callID: ctx.callID, args },
|
|
2126
|
+
output,
|
|
2127
|
+
)
|
|
2128
|
+
return output
|
|
2129
|
+
}),
|
|
2130
|
+
),
|
|
2131
|
+
)
|
|
2132
|
+
},
|
|
2133
|
+
`,
|
|
2134
|
+
),
|
|
2135
|
+
);
|
|
2136
|
+
|
|
2137
|
+
await rewriteSourceFile(
|
|
2138
|
+
sourceRoot,
|
|
2139
|
+
"packages/opencode/src/session/instruction.ts",
|
|
2140
|
+
(contents) =>
|
|
2141
|
+
contents
|
|
2142
|
+
.replace(
|
|
2143
|
+
`async function resolveRelative(instruction: string): Promise<string[]> {
|
|
2144
|
+
if (!Flag.OPENCODE_DISABLE_PROJECT_CONFIG) {
|
|
2145
|
+
return Filesystem.globUp(instruction, Instance.directory, Instance.worktree).catch(() => [])
|
|
2146
|
+
}
|
|
2147
|
+
if (!Flag.OPENCODE_CONFIG_DIR) {
|
|
2148
|
+
log.warn(
|
|
2149
|
+
\`Skipping relative instruction "\${instruction}" - no OPENCODE_CONFIG_DIR set while project config is disabled\`,
|
|
2150
|
+
)
|
|
2151
|
+
return []
|
|
2152
|
+
}
|
|
2153
|
+
return Filesystem.globUp(instruction, Flag.OPENCODE_CONFIG_DIR, Flag.OPENCODE_CONFIG_DIR).catch(() => [])
|
|
2154
|
+
}
|
|
2155
|
+
`,
|
|
2156
|
+
`async function resolveRelative(instruction: string): Promise<string[]> {
|
|
2157
|
+
const ctx = Instance.current
|
|
2158
|
+
if (!Flag.OPENCODE_DISABLE_PROJECT_CONFIG) {
|
|
2159
|
+
return Instance.restore(ctx, () =>
|
|
2160
|
+
Filesystem.globUp(instruction, ctx.directory, ctx.worktree).catch(() => []),
|
|
2161
|
+
)
|
|
2162
|
+
}
|
|
2163
|
+
if (!Flag.OPENCODE_CONFIG_DIR) {
|
|
2164
|
+
log.warn(
|
|
2165
|
+
\`Skipping relative instruction "\${instruction}" - no OPENCODE_CONFIG_DIR set while project config is disabled\`,
|
|
2166
|
+
)
|
|
2167
|
+
return []
|
|
2168
|
+
}
|
|
2169
|
+
return Filesystem.globUp(instruction, Flag.OPENCODE_CONFIG_DIR, Flag.OPENCODE_CONFIG_DIR).catch(() => [])
|
|
2170
|
+
}
|
|
2171
|
+
`,
|
|
2172
|
+
)
|
|
2173
|
+
.replace(
|
|
2174
|
+
` export async function systemPaths() {
|
|
2175
|
+
const config = await Config.get()
|
|
2176
|
+
`,
|
|
2177
|
+
` export async function systemPaths() {
|
|
2178
|
+
const ctx = Instance.current
|
|
2179
|
+
const config = await Instance.restore(ctx, () => Config.get())
|
|
2180
|
+
`,
|
|
2181
|
+
)
|
|
2182
|
+
.replace(
|
|
2183
|
+
` const matches = await Filesystem.findUp(file, Instance.directory, Instance.worktree)
|
|
2184
|
+
`,
|
|
2185
|
+
` const matches = await Instance.restore(ctx, () =>
|
|
2186
|
+
Filesystem.findUp(file, ctx.directory, ctx.worktree),
|
|
2187
|
+
)
|
|
2188
|
+
`,
|
|
2189
|
+
)
|
|
2190
|
+
.replace(
|
|
2191
|
+
` : await resolveRelative(instruction)
|
|
2192
|
+
`,
|
|
2193
|
+
` : await Instance.restore(ctx, () => resolveRelative(instruction))
|
|
2194
|
+
`,
|
|
2195
|
+
)
|
|
2196
|
+
.replace(
|
|
2197
|
+
` export async function system() {
|
|
2198
|
+
const config = await Config.get()
|
|
2199
|
+
const paths = await systemPaths()
|
|
2200
|
+
`,
|
|
2201
|
+
` export async function system() {
|
|
2202
|
+
const ctx = Instance.current
|
|
2203
|
+
const config = await Instance.restore(ctx, () => Config.get())
|
|
2204
|
+
const paths = await Instance.restore(ctx, () => systemPaths())
|
|
2205
|
+
`,
|
|
2206
|
+
)
|
|
2207
|
+
.replace(
|
|
2208
|
+
` export async function resolve(messages: MessageV2.WithParts[], filepath: string, messageID: string) {
|
|
2209
|
+
const system = await systemPaths()
|
|
2210
|
+
const already = loaded(messages)
|
|
2211
|
+
const results: { filepath: string; content: string }[] = []
|
|
2212
|
+
|
|
2213
|
+
const target = path.resolve(filepath)
|
|
2214
|
+
let current = path.dirname(target)
|
|
2215
|
+
const root = path.resolve(Instance.directory)
|
|
2216
|
+
`,
|
|
2217
|
+
` export async function resolve(messages: MessageV2.WithParts[], filepath: string, messageID: string) {
|
|
2218
|
+
const ctx = Instance.current
|
|
2219
|
+
const system = await Instance.restore(ctx, () => systemPaths())
|
|
2220
|
+
const already = loaded(messages)
|
|
2221
|
+
const results: { filepath: string; content: string }[] = []
|
|
2222
|
+
|
|
2223
|
+
const target = path.resolve(filepath)
|
|
2224
|
+
let current = path.dirname(target)
|
|
2225
|
+
const root = path.resolve(ctx.directory)
|
|
2226
|
+
`,
|
|
2227
|
+
),
|
|
2228
|
+
);
|
|
2229
|
+
|
|
2230
|
+
await rewriteSourceFile(
|
|
2231
|
+
sourceRoot,
|
|
2232
|
+
"packages/opencode/src/provider/provider.ts",
|
|
2233
|
+
(contents) =>
|
|
2234
|
+
contents
|
|
2235
|
+
.replace(
|
|
2236
|
+
'import { Config } from "../config/config"\n',
|
|
2237
|
+
'import { Config } from "../config/config"\nimport { Instance } from "../project/instance"\n',
|
|
2238
|
+
)
|
|
2239
|
+
.replace(
|
|
2240
|
+
` async function loadProviders() {
|
|
2241
|
+
const cfg = await Config.get()
|
|
2242
|
+
`,
|
|
2243
|
+
` async function loadProviders() {
|
|
2244
|
+
const ctx = Instance.current
|
|
2245
|
+
const cfg = await Instance.restore(ctx, () => Config.get())
|
|
2246
|
+
`,
|
|
2247
|
+
)
|
|
2248
|
+
.replace(
|
|
2249
|
+
` export async function defaultModel() {
|
|
2250
|
+
const cfg = await Config.get()
|
|
2251
|
+
`,
|
|
2252
|
+
` export async function defaultModel() {
|
|
2253
|
+
const ctx = Instance.current
|
|
2254
|
+
const cfg = await Instance.restore(ctx, () => Config.get())
|
|
2255
|
+
`,
|
|
2256
|
+
)
|
|
2257
|
+
.replace(
|
|
2258
|
+
` const providers = await loadProviders()
|
|
2259
|
+
for (const provider of Object.values(providers)) {
|
|
2260
|
+
`,
|
|
2261
|
+
` const providers = await Instance.restore(ctx, () => loadProviders())
|
|
2262
|
+
for (const provider of Object.values(providers)) {
|
|
2263
|
+
`,
|
|
2264
|
+
),
|
|
2265
|
+
);
|
|
2266
|
+
|
|
2267
|
+
await rewriteSourceFile(
|
|
2268
|
+
sourceRoot,
|
|
2269
|
+
"packages/opencode/src/effect/instance-state.ts",
|
|
2270
|
+
(contents) =>
|
|
2271
|
+
contents.replace(
|
|
2272
|
+
` const fiber = Fiber.getCurrent()
|
|
2273
|
+
const ctx = fiber ? ServiceMap.getReferenceUnsafe(fiber.services, InstanceRef) : undefined
|
|
2274
|
+
if (!ctx) return fn
|
|
2275
|
+
return ((...args: any[]) => Instance.restore(ctx, () => fn(...args))) as F
|
|
2276
|
+
`,
|
|
2277
|
+
` const fiber = Fiber.getCurrent()
|
|
2278
|
+
const ctx = fiber ? ServiceMap.getReferenceUnsafe(fiber.services, InstanceRef) : undefined
|
|
2279
|
+
const fallback = (globalThis as typeof globalThis & { __agentosOpencodeInstanceFallback?: unknown })
|
|
2280
|
+
.__agentosOpencodeInstanceFallback as typeof ctx
|
|
2281
|
+
const boundCtx = ctx ?? fallback
|
|
2282
|
+
if (!boundCtx) return fn
|
|
2283
|
+
return ((...args: any[]) => Instance.restore(boundCtx, () => fn(...args))) as F
|
|
2284
|
+
`,
|
|
2285
|
+
).replace(
|
|
2286
|
+
` export const context = Effect.fnUntraced(function* () {
|
|
2287
|
+
return (yield* InstanceRef) ?? Instance.current
|
|
2288
|
+
})()
|
|
2289
|
+
`,
|
|
2290
|
+
` export const context = Effect.fnUntraced(function* () {
|
|
2291
|
+
const ref = yield* InstanceRef
|
|
2292
|
+
if (ref) return ref
|
|
2293
|
+
const fallback = (globalThis as typeof globalThis & { __agentosOpencodeInstanceFallback?: unknown })
|
|
2294
|
+
.__agentosOpencodeInstanceFallback
|
|
2295
|
+
if (fallback) return fallback as typeof ref
|
|
2296
|
+
try {
|
|
2297
|
+
return Instance.current
|
|
2298
|
+
} catch (error) {
|
|
2299
|
+
console.error("[opencode-acp] missing-instance-context", new Error().stack)
|
|
2300
|
+
throw error
|
|
2301
|
+
}
|
|
2302
|
+
})()
|
|
2303
|
+
`,
|
|
2304
|
+
),
|
|
2305
|
+
);
|
|
2306
|
+
|
|
2307
|
+
await writeFile(
|
|
2308
|
+
resolve(sourceRoot, "packages/opencode/src/effect/run-service.ts"),
|
|
2309
|
+
`import { Effect, Layer, ManagedRuntime } from "effect"
|
|
2310
|
+
import * as ServiceMap from "effect/ServiceMap"
|
|
2311
|
+
import { Instance } from "@/project/instance"
|
|
2312
|
+
import { Context } from "@/util/context"
|
|
2313
|
+
import { InstanceRef } from "./instance-ref"
|
|
2314
|
+
|
|
2315
|
+
export const memoMap = Layer.makeMemoMapUnsafe()
|
|
2316
|
+
|
|
2317
|
+
function attach<A, E, R>(effect: Effect.Effect<A, E, R>) {
|
|
2318
|
+
try {
|
|
2319
|
+
const ctx = Instance.current
|
|
2320
|
+
return {
|
|
2321
|
+
ctx,
|
|
2322
|
+
effect: Effect.provideService(effect, InstanceRef, ctx),
|
|
2323
|
+
}
|
|
2324
|
+
} catch (err) {
|
|
2325
|
+
if (!(err instanceof Context.NotFound)) throw err
|
|
2326
|
+
}
|
|
2327
|
+
return { ctx: undefined, effect }
|
|
2328
|
+
}
|
|
2329
|
+
|
|
2330
|
+
export function makeRuntime<I, S, E>(service: ServiceMap.Service<I, S>, layer: Layer.Layer<I, E>) {
|
|
2331
|
+
let rt: ManagedRuntime.ManagedRuntime<I, E> | undefined
|
|
2332
|
+
const getRuntime = () => (rt ??= ManagedRuntime.make(layer, { memoMap }))
|
|
2333
|
+
|
|
2334
|
+
return {
|
|
2335
|
+
runSync: <A, Err>(fn: (svc: S) => Effect.Effect<A, Err, I>) => {
|
|
2336
|
+
const attached = attach(service.use(fn))
|
|
2337
|
+
return attached.ctx
|
|
2338
|
+
? Instance.restore(attached.ctx, () => getRuntime().runSync(attached.effect))
|
|
2339
|
+
: getRuntime().runSync(attached.effect)
|
|
2340
|
+
},
|
|
2341
|
+
runPromiseExit: <A, Err>(fn: (svc: S) => Effect.Effect<A, Err, I>, options?: Effect.RunOptions) => {
|
|
2342
|
+
const attached = attach(service.use(fn))
|
|
2343
|
+
return attached.ctx
|
|
2344
|
+
? Instance.restore(attached.ctx, () => getRuntime().runPromiseExit(attached.effect, options))
|
|
2345
|
+
: getRuntime().runPromiseExit(attached.effect, options)
|
|
2346
|
+
},
|
|
2347
|
+
runPromise: <A, Err>(fn: (svc: S) => Effect.Effect<A, Err, I>, options?: Effect.RunOptions) => {
|
|
2348
|
+
const attached = attach(service.use(fn))
|
|
2349
|
+
return attached.ctx
|
|
2350
|
+
? Instance.restore(attached.ctx, () => getRuntime().runPromise(attached.effect, options))
|
|
2351
|
+
: getRuntime().runPromise(attached.effect, options)
|
|
2352
|
+
},
|
|
2353
|
+
runFork: <A, Err>(fn: (svc: S) => Effect.Effect<A, Err, I>) => {
|
|
2354
|
+
const attached = attach(service.use(fn))
|
|
2355
|
+
return attached.ctx
|
|
2356
|
+
? Instance.restore(attached.ctx, () => getRuntime().runFork(attached.effect))
|
|
2357
|
+
: getRuntime().runFork(attached.effect)
|
|
2358
|
+
},
|
|
2359
|
+
runCallback: <A, Err>(fn: (svc: S) => Effect.Effect<A, Err, I>) => {
|
|
2360
|
+
const attached = attach(service.use(fn))
|
|
2361
|
+
return attached.ctx
|
|
2362
|
+
? Instance.restore(attached.ctx, () => getRuntime().runCallback(attached.effect))
|
|
2363
|
+
: getRuntime().runCallback(attached.effect)
|
|
2364
|
+
},
|
|
2365
|
+
}
|
|
2366
|
+
}
|
|
2367
|
+
`,
|
|
2368
|
+
);
|
|
2369
|
+
|
|
2370
|
+
await rewriteSourceFile(
|
|
2371
|
+
sourceRoot,
|
|
2372
|
+
"packages/opencode/src/tool/tool.ts",
|
|
2373
|
+
(contents) =>
|
|
2374
|
+
contents
|
|
2375
|
+
.replace(
|
|
2376
|
+
'import { Truncate } from "./truncate"\n',
|
|
2377
|
+
'import { Truncate } from "./truncate"\nimport { InstanceState } from "@/effect/instance-state"\n',
|
|
2378
|
+
)
|
|
2379
|
+
.replace(
|
|
2380
|
+
` const toolInfo = init instanceof Function ? await init(initCtx) : init
|
|
2381
|
+
const execute = toolInfo.execute
|
|
2382
|
+
toolInfo.execute = async (args, ctx) => {
|
|
2383
|
+
`,
|
|
2384
|
+
` const toolInfo =
|
|
2385
|
+
init instanceof Function ? await InstanceState.bind(() => init(initCtx))() : init
|
|
2386
|
+
const execute = toolInfo.execute
|
|
2387
|
+
toolInfo.execute = InstanceState.bind(async (args, ctx) => {
|
|
2388
|
+
`,
|
|
2389
|
+
)
|
|
2390
|
+
.replace(
|
|
2391
|
+
` }
|
|
2392
|
+
return toolInfo
|
|
2393
|
+
`,
|
|
2394
|
+
` })
|
|
2395
|
+
return toolInfo
|
|
2396
|
+
`,
|
|
2397
|
+
),
|
|
2398
|
+
);
|
|
2399
|
+
|
|
2400
|
+
await rewriteSourceFile(
|
|
2401
|
+
sourceRoot,
|
|
2402
|
+
"packages/opencode/src/storage/db.ts",
|
|
2403
|
+
(contents) =>
|
|
2404
|
+
contents.replace(
|
|
2405
|
+
` db.run("PRAGMA journal_mode = WAL")
|
|
2406
|
+
db.run("PRAGMA synchronous = NORMAL")
|
|
2407
|
+
db.run("PRAGMA busy_timeout = 5000")
|
|
2408
|
+
db.run("PRAGMA cache_size = -64000")
|
|
2409
|
+
db.run("PRAGMA foreign_keys = ON")
|
|
2410
|
+
db.run("PRAGMA wal_checkpoint(PASSIVE)")`,
|
|
2411
|
+
` db.$client.exec("PRAGMA journal_mode = WAL")
|
|
2412
|
+
db.$client.exec("PRAGMA synchronous = NORMAL")
|
|
2413
|
+
db.$client.exec("PRAGMA busy_timeout = 5000")
|
|
2414
|
+
db.$client.exec("PRAGMA cache_size = -64000")
|
|
2415
|
+
db.$client.exec("PRAGMA foreign_keys = ON")
|
|
2416
|
+
db.$client.exec("PRAGMA wal_checkpoint(PASSIVE)")`,
|
|
2417
|
+
),
|
|
2418
|
+
);
|
|
2419
|
+
}
|
|
2420
|
+
|
|
2421
|
+
await rewriteSourceFile(
|
|
2422
|
+
sourceRoot,
|
|
2423
|
+
"packages/opencode/src/provider/provider.ts",
|
|
2424
|
+
(contents) =>
|
|
2425
|
+
contents
|
|
2426
|
+
.replace(
|
|
2427
|
+
`// Direct imports for bundled providers
|
|
2428
|
+
import { createAmazonBedrock, type AmazonBedrockProviderSettings } from "@ai-sdk/amazon-bedrock"
|
|
2429
|
+
import { createAnthropic } from "@ai-sdk/anthropic"
|
|
2430
|
+
import { createAzure } from "@ai-sdk/azure"
|
|
2431
|
+
import { createGoogleGenerativeAI } from "@ai-sdk/google"
|
|
2432
|
+
import { createVertex } from "@ai-sdk/google-vertex"
|
|
2433
|
+
import { createVertexAnthropic } from "@ai-sdk/google-vertex/anthropic"
|
|
2434
|
+
import { createOpenAI } from "@ai-sdk/openai"
|
|
2435
|
+
import { createOpenAICompatible } from "@ai-sdk/openai-compatible"
|
|
2436
|
+
import { createOpenRouter, type LanguageModelV2 } from "@openrouter/ai-sdk-provider"
|
|
2437
|
+
import { createOpenaiCompatible as createGitHubCopilotOpenAICompatible } from "./sdk/openai-compatible/src"
|
|
2438
|
+
import { createXai } from "@ai-sdk/xai"
|
|
2439
|
+
import { createMistral } from "@ai-sdk/mistral"
|
|
2440
|
+
import { createGroq } from "@ai-sdk/groq"
|
|
2441
|
+
import { createDeepInfra } from "@ai-sdk/deepinfra"
|
|
2442
|
+
import { createCerebras } from "@ai-sdk/cerebras"
|
|
2443
|
+
import { createCohere } from "@ai-sdk/cohere"
|
|
2444
|
+
import { createGateway } from "@ai-sdk/gateway"
|
|
2445
|
+
import { createTogetherAI } from "@ai-sdk/togetherai"
|
|
2446
|
+
import { createPerplexity } from "@ai-sdk/perplexity"
|
|
2447
|
+
import { createVercel } from "@ai-sdk/vercel"
|
|
2448
|
+
import { createGitLab } from "@gitlab/gitlab-ai-provider"
|
|
2449
|
+
import { ProviderTransform } from "./transform"
|
|
2450
|
+
`,
|
|
2451
|
+
`import type { LanguageModelV2 } from "@openrouter/ai-sdk-provider"
|
|
2452
|
+
import { ProviderTransform } from "./transform"
|
|
2453
|
+
`,
|
|
2454
|
+
)
|
|
2455
|
+
.replace(
|
|
2456
|
+
` const BUNDLED_PROVIDERS: Record<string, (options: any) => SDK> = {
|
|
2457
|
+
"@ai-sdk/amazon-bedrock": createAmazonBedrock,
|
|
2458
|
+
"@ai-sdk/anthropic": createAnthropic,
|
|
2459
|
+
"@ai-sdk/azure": createAzure,
|
|
2460
|
+
"@ai-sdk/google": createGoogleGenerativeAI,
|
|
2461
|
+
"@ai-sdk/google-vertex": createVertex,
|
|
2462
|
+
"@ai-sdk/google-vertex/anthropic": createVertexAnthropic,
|
|
2463
|
+
"@ai-sdk/openai": createOpenAI,
|
|
2464
|
+
"@ai-sdk/openai-compatible": createOpenAICompatible,
|
|
2465
|
+
"@openrouter/ai-sdk-provider": createOpenRouter,
|
|
2466
|
+
"@ai-sdk/xai": createXai,
|
|
2467
|
+
"@ai-sdk/mistral": createMistral,
|
|
2468
|
+
"@ai-sdk/groq": createGroq,
|
|
2469
|
+
"@ai-sdk/deepinfra": createDeepInfra,
|
|
2470
|
+
"@ai-sdk/cerebras": createCerebras,
|
|
2471
|
+
"@ai-sdk/cohere": createCohere,
|
|
2472
|
+
"@ai-sdk/gateway": createGateway,
|
|
2473
|
+
"@ai-sdk/togetherai": createTogetherAI,
|
|
2474
|
+
"@ai-sdk/perplexity": createPerplexity,
|
|
2475
|
+
"@ai-sdk/vercel": createVercel,
|
|
2476
|
+
"@gitlab/gitlab-ai-provider": createGitLab,
|
|
2477
|
+
// @ts-ignore (TODO: kill this code so we dont have to maintain it)
|
|
2478
|
+
"@ai-sdk/github-copilot": createGitHubCopilotOpenAICompatible,
|
|
2479
|
+
}
|
|
2480
|
+
`,
|
|
2481
|
+
` const BUNDLED_PROVIDERS: Record<string, (options: any) => Promise<SDK>> = {
|
|
2482
|
+
"@ai-sdk/amazon-bedrock": async (options) =>
|
|
2483
|
+
(await import("@ai-sdk/amazon-bedrock")).createAmazonBedrock(options),
|
|
2484
|
+
"@ai-sdk/anthropic": async (options) => (await import("@ai-sdk/anthropic")).createAnthropic(options),
|
|
2485
|
+
"@ai-sdk/azure": async (options) => (await import("@ai-sdk/azure")).createAzure(options),
|
|
2486
|
+
"@ai-sdk/google": async (options) => (await import("@ai-sdk/google")).createGoogleGenerativeAI(options),
|
|
2487
|
+
"@ai-sdk/google-vertex": async (options) => (await import("@ai-sdk/google-vertex")).createVertex(options),
|
|
2488
|
+
"@ai-sdk/google-vertex/anthropic": async (options) =>
|
|
2489
|
+
(await import("@ai-sdk/google-vertex/anthropic")).createVertexAnthropic(options),
|
|
2490
|
+
"@ai-sdk/openai": async (options) => (await import("@ai-sdk/openai")).createOpenAI(options),
|
|
2491
|
+
"@ai-sdk/openai-compatible": async (options) =>
|
|
2492
|
+
(await import("@ai-sdk/openai-compatible")).createOpenAICompatible(options),
|
|
2493
|
+
"@openrouter/ai-sdk-provider": async (options) =>
|
|
2494
|
+
(await import("@openrouter/ai-sdk-provider")).createOpenRouter(options),
|
|
2495
|
+
"@ai-sdk/xai": async (options) => (await import("@ai-sdk/xai")).createXai(options),
|
|
2496
|
+
"@ai-sdk/mistral": async (options) => (await import("@ai-sdk/mistral")).createMistral(options),
|
|
2497
|
+
"@ai-sdk/groq": async (options) => (await import("@ai-sdk/groq")).createGroq(options),
|
|
2498
|
+
"@ai-sdk/deepinfra": async (options) => (await import("@ai-sdk/deepinfra")).createDeepInfra(options),
|
|
2499
|
+
"@ai-sdk/cerebras": async (options) => (await import("@ai-sdk/cerebras")).createCerebras(options),
|
|
2500
|
+
"@ai-sdk/cohere": async (options) => (await import("@ai-sdk/cohere")).createCohere(options),
|
|
2501
|
+
"@ai-sdk/gateway": async (options) => (await import("@ai-sdk/gateway")).createGateway(options),
|
|
2502
|
+
"@ai-sdk/togetherai": async (options) => (await import("@ai-sdk/togetherai")).createTogetherAI(options),
|
|
2503
|
+
"@ai-sdk/perplexity": async (options) => (await import("@ai-sdk/perplexity")).createPerplexity(options),
|
|
2504
|
+
"@ai-sdk/vercel": async (options) => (await import("@ai-sdk/vercel")).createVercel(options),
|
|
2505
|
+
"@gitlab/gitlab-ai-provider": async (options) =>
|
|
2506
|
+
(await import("@gitlab/gitlab-ai-provider")).createGitLab(options),
|
|
2507
|
+
"@ai-sdk/github-copilot": async (options) =>
|
|
2508
|
+
(await import("./sdk/openai-compatible/src")).createOpenaiCompatible(options),
|
|
2509
|
+
}
|
|
2510
|
+
`,
|
|
2511
|
+
)
|
|
2512
|
+
.replace(
|
|
2513
|
+
" async getModel(sdk: ReturnType<typeof createGitLab>, modelID: string) {\n",
|
|
2514
|
+
" async getModel(sdk: any, modelID: string) {\n",
|
|
2515
|
+
)
|
|
2516
|
+
.replace(
|
|
2517
|
+
` const loaded = bundledFn({
|
|
2518
|
+
name: model.providerID,
|
|
2519
|
+
...options,
|
|
2520
|
+
})
|
|
2521
|
+
`,
|
|
2522
|
+
` const loaded = await bundledFn({
|
|
2523
|
+
name: model.providerID,
|
|
2524
|
+
...options,
|
|
2525
|
+
})
|
|
2526
|
+
`,
|
|
2527
|
+
),
|
|
2528
|
+
);
|
|
2529
|
+
|
|
2530
|
+
await writeFile(
|
|
2531
|
+
resolve(sourceRoot, "packages/opencode/src/provider/provider.ts"),
|
|
2532
|
+
`import z from "zod"
|
|
2533
|
+
import { createAnthropic } from "@ai-sdk/anthropic"
|
|
2534
|
+
import { createGoogleGenerativeAI } from "@ai-sdk/google"
|
|
2535
|
+
import { createVertex } from "@ai-sdk/google-vertex"
|
|
2536
|
+
import { createVertexAnthropic } from "@ai-sdk/google-vertex/anthropic"
|
|
2537
|
+
import { createGroq } from "@ai-sdk/groq"
|
|
2538
|
+
import { createMistral } from "@ai-sdk/mistral"
|
|
2539
|
+
import { createOpenAI } from "@ai-sdk/openai"
|
|
2540
|
+
import type { LanguageModelV3 } from "@ai-sdk/provider"
|
|
2541
|
+
import { NamedError } from "@opencode-ai/util/error"
|
|
2542
|
+
import { Config } from "../config/config"
|
|
2543
|
+
import { ModelID, ProviderID } from "./schema"
|
|
2544
|
+
|
|
2545
|
+
type ProviderConfig = {
|
|
2546
|
+
name?: string
|
|
2547
|
+
env?: string[]
|
|
2548
|
+
npm?: string
|
|
2549
|
+
api?: string
|
|
2550
|
+
options?: Record<string, any>
|
|
2551
|
+
models?: Record<string, any>
|
|
2552
|
+
}
|
|
2553
|
+
|
|
2554
|
+
type ProviderSeed = {
|
|
2555
|
+
name: string
|
|
2556
|
+
env: string[]
|
|
2557
|
+
npm: string
|
|
2558
|
+
models: Array<{
|
|
2559
|
+
id: string
|
|
2560
|
+
name: string
|
|
2561
|
+
family?: string
|
|
2562
|
+
reasoning?: boolean
|
|
2563
|
+
attachment?: boolean
|
|
2564
|
+
input?: Partial<{ text: boolean; audio: boolean; image: boolean; video: boolean; pdf: boolean }>
|
|
2565
|
+
output?: Partial<{ text: boolean; audio: boolean; image: boolean; video: boolean; pdf: boolean }>
|
|
2566
|
+
limit?: Partial<{ context: number; input: number; output: number }>
|
|
2567
|
+
cost?: Partial<{
|
|
2568
|
+
input: number
|
|
2569
|
+
output: number
|
|
2570
|
+
cache: Partial<{ read: number; write: number }>
|
|
2571
|
+
}>
|
|
2572
|
+
releaseDate?: string
|
|
2573
|
+
}>
|
|
2574
|
+
}
|
|
2575
|
+
|
|
2576
|
+
export namespace Provider {
|
|
2577
|
+
export const Model = z
|
|
2578
|
+
.object({
|
|
2579
|
+
id: ModelID.zod,
|
|
2580
|
+
providerID: ProviderID.zod,
|
|
2581
|
+
api: z.object({
|
|
2582
|
+
id: z.string(),
|
|
2583
|
+
url: z.string(),
|
|
2584
|
+
npm: z.string(),
|
|
2585
|
+
}),
|
|
2586
|
+
name: z.string(),
|
|
2587
|
+
family: z.string().optional(),
|
|
2588
|
+
capabilities: z.object({
|
|
2589
|
+
temperature: z.boolean(),
|
|
2590
|
+
reasoning: z.boolean(),
|
|
2591
|
+
attachment: z.boolean(),
|
|
2592
|
+
toolcall: z.boolean(),
|
|
2593
|
+
input: z.object({
|
|
2594
|
+
text: z.boolean(),
|
|
2595
|
+
audio: z.boolean(),
|
|
2596
|
+
image: z.boolean(),
|
|
2597
|
+
video: z.boolean(),
|
|
2598
|
+
pdf: z.boolean(),
|
|
2599
|
+
}),
|
|
2600
|
+
output: z.object({
|
|
2601
|
+
text: z.boolean(),
|
|
2602
|
+
audio: z.boolean(),
|
|
2603
|
+
image: z.boolean(),
|
|
2604
|
+
video: z.boolean(),
|
|
2605
|
+
pdf: z.boolean(),
|
|
2606
|
+
}),
|
|
2607
|
+
interleaved: z.union([
|
|
2608
|
+
z.boolean(),
|
|
2609
|
+
z.object({
|
|
2610
|
+
field: z.enum(["reasoning_content", "reasoning_details"]),
|
|
2611
|
+
}),
|
|
2612
|
+
]),
|
|
2613
|
+
}),
|
|
2614
|
+
cost: z.object({
|
|
2615
|
+
input: z.number(),
|
|
2616
|
+
output: z.number(),
|
|
2617
|
+
cache: z.object({
|
|
2618
|
+
read: z.number(),
|
|
2619
|
+
write: z.number(),
|
|
2620
|
+
}),
|
|
2621
|
+
experimentalOver200K: z
|
|
2622
|
+
.object({
|
|
2623
|
+
input: z.number(),
|
|
2624
|
+
output: z.number(),
|
|
2625
|
+
cache: z.object({
|
|
2626
|
+
read: z.number(),
|
|
2627
|
+
write: z.number(),
|
|
2628
|
+
}),
|
|
2629
|
+
})
|
|
2630
|
+
.optional(),
|
|
2631
|
+
}),
|
|
2632
|
+
limit: z.object({
|
|
2633
|
+
context: z.number(),
|
|
2634
|
+
input: z.number().optional(),
|
|
2635
|
+
output: z.number(),
|
|
2636
|
+
}),
|
|
2637
|
+
status: z.enum(["alpha", "beta", "deprecated", "active"]),
|
|
2638
|
+
options: z.record(z.string(), z.any()),
|
|
2639
|
+
headers: z.record(z.string(), z.string()),
|
|
2640
|
+
release_date: z.string(),
|
|
2641
|
+
variants: z.record(z.string(), z.record(z.string(), z.any())).optional(),
|
|
2642
|
+
})
|
|
2643
|
+
.meta({
|
|
2644
|
+
ref: "Model",
|
|
2645
|
+
})
|
|
2646
|
+
export type Model = z.infer<typeof Model>
|
|
2647
|
+
|
|
2648
|
+
export const Info = z
|
|
2649
|
+
.object({
|
|
2650
|
+
id: ProviderID.zod,
|
|
2651
|
+
name: z.string(),
|
|
2652
|
+
source: z.enum(["env", "config", "custom", "api"]),
|
|
2653
|
+
env: z.string().array(),
|
|
2654
|
+
key: z.string().optional(),
|
|
2655
|
+
options: z.record(z.string(), z.any()),
|
|
2656
|
+
models: z.record(z.string(), Model),
|
|
2657
|
+
})
|
|
2658
|
+
.meta({
|
|
2659
|
+
ref: "Provider",
|
|
2660
|
+
})
|
|
2661
|
+
export type Info = z.infer<typeof Info>
|
|
2662
|
+
|
|
2663
|
+
const DEFAULT_CONTEXT_LIMIT = 200_000
|
|
2664
|
+
const DEFAULT_OUTPUT_LIMIT = 32_000
|
|
2665
|
+
|
|
2666
|
+
const PROVIDER_SEEDS: Record<string, ProviderSeed> = {
|
|
2667
|
+
anthropic: {
|
|
2668
|
+
name: "Anthropic",
|
|
2669
|
+
env: ["ANTHROPIC_API_KEY"],
|
|
2670
|
+
npm: "@ai-sdk/anthropic",
|
|
2671
|
+
models: [
|
|
2672
|
+
{
|
|
2673
|
+
id: "claude-sonnet-4-20250514",
|
|
2674
|
+
name: "Claude Sonnet 4",
|
|
2675
|
+
family: "claude-sonnet-4",
|
|
2676
|
+
reasoning: true,
|
|
2677
|
+
attachment: true,
|
|
2678
|
+
input: { image: true, pdf: true },
|
|
2679
|
+
releaseDate: "2025-05-14",
|
|
2680
|
+
},
|
|
2681
|
+
{
|
|
2682
|
+
id: "claude-opus-4-1-20250805",
|
|
2683
|
+
name: "Claude Opus 4.1",
|
|
2684
|
+
family: "claude-opus-4-1",
|
|
2685
|
+
reasoning: true,
|
|
2686
|
+
attachment: true,
|
|
2687
|
+
input: { image: true, pdf: true },
|
|
2688
|
+
releaseDate: "2025-08-05",
|
|
2689
|
+
},
|
|
2690
|
+
{
|
|
2691
|
+
id: "claude-haiku-4-5-20251001",
|
|
2692
|
+
name: "Claude Haiku 4.5",
|
|
2693
|
+
family: "claude-haiku-4-5",
|
|
2694
|
+
reasoning: false,
|
|
2695
|
+
attachment: true,
|
|
2696
|
+
input: { image: true, pdf: true },
|
|
2697
|
+
limit: { output: 16_000 },
|
|
2698
|
+
releaseDate: "2025-10-01",
|
|
2699
|
+
},
|
|
2700
|
+
],
|
|
2701
|
+
},
|
|
2702
|
+
openai: {
|
|
2703
|
+
name: "OpenAI",
|
|
2704
|
+
env: ["OPENAI_API_KEY"],
|
|
2705
|
+
npm: "@ai-sdk/openai",
|
|
2706
|
+
models: [
|
|
2707
|
+
{
|
|
2708
|
+
id: "gpt-5",
|
|
2709
|
+
name: "GPT-5",
|
|
2710
|
+
family: "gpt-5",
|
|
2711
|
+
reasoning: true,
|
|
2712
|
+
attachment: true,
|
|
2713
|
+
input: { image: true, pdf: true },
|
|
2714
|
+
releaseDate: "2025-01-01",
|
|
2715
|
+
},
|
|
2716
|
+
{
|
|
2717
|
+
id: "gpt-5-mini",
|
|
2718
|
+
name: "GPT-5 Mini",
|
|
2719
|
+
family: "gpt-5-mini",
|
|
2720
|
+
reasoning: true,
|
|
2721
|
+
attachment: true,
|
|
2722
|
+
input: { image: true, pdf: true },
|
|
2723
|
+
limit: { output: 16_000 },
|
|
2724
|
+
releaseDate: "2025-01-01",
|
|
2725
|
+
},
|
|
2726
|
+
{
|
|
2727
|
+
id: "gpt-5-nano",
|
|
2728
|
+
name: "GPT-5 Nano",
|
|
2729
|
+
family: "gpt-5-nano",
|
|
2730
|
+
reasoning: false,
|
|
2731
|
+
attachment: true,
|
|
2732
|
+
input: { image: true, pdf: true },
|
|
2733
|
+
limit: { output: 8_000 },
|
|
2734
|
+
releaseDate: "2025-01-01",
|
|
2735
|
+
},
|
|
2736
|
+
],
|
|
2737
|
+
},
|
|
2738
|
+
google: {
|
|
2739
|
+
name: "Google",
|
|
2740
|
+
env: ["GOOGLE_GENERATIVE_AI_API_KEY"],
|
|
2741
|
+
npm: "@ai-sdk/google",
|
|
2742
|
+
models: [
|
|
2743
|
+
{
|
|
2744
|
+
id: "gemini-2.5-pro",
|
|
2745
|
+
name: "Gemini 2.5 Pro",
|
|
2746
|
+
family: "gemini-2.5-pro",
|
|
2747
|
+
reasoning: true,
|
|
2748
|
+
attachment: true,
|
|
2749
|
+
input: { image: true, pdf: true },
|
|
2750
|
+
releaseDate: "2025-01-01",
|
|
2751
|
+
},
|
|
2752
|
+
{
|
|
2753
|
+
id: "gemini-2.5-flash",
|
|
2754
|
+
name: "Gemini 2.5 Flash",
|
|
2755
|
+
family: "gemini-2.5-flash",
|
|
2756
|
+
reasoning: true,
|
|
2757
|
+
attachment: true,
|
|
2758
|
+
input: { image: true, pdf: true },
|
|
2759
|
+
limit: { output: 16_000 },
|
|
2760
|
+
releaseDate: "2025-01-01",
|
|
2761
|
+
},
|
|
2762
|
+
],
|
|
2763
|
+
},
|
|
2764
|
+
"google-vertex": {
|
|
2765
|
+
name: "Google Vertex",
|
|
2766
|
+
env: [],
|
|
2767
|
+
npm: "@ai-sdk/google-vertex",
|
|
2768
|
+
models: [
|
|
2769
|
+
{
|
|
2770
|
+
id: "gemini-2.5-pro",
|
|
2771
|
+
name: "Gemini 2.5 Pro",
|
|
2772
|
+
family: "gemini-2.5-pro",
|
|
2773
|
+
reasoning: true,
|
|
2774
|
+
attachment: true,
|
|
2775
|
+
input: { image: true, pdf: true },
|
|
2776
|
+
releaseDate: "2025-01-01",
|
|
2777
|
+
},
|
|
2778
|
+
],
|
|
2779
|
+
},
|
|
2780
|
+
groq: {
|
|
2781
|
+
name: "Groq",
|
|
2782
|
+
env: ["GROQ_API_KEY"],
|
|
2783
|
+
npm: "@ai-sdk/groq",
|
|
2784
|
+
models: [
|
|
2785
|
+
{
|
|
2786
|
+
id: "llama-3.3-70b-versatile",
|
|
2787
|
+
name: "Llama 3.3 70B Versatile",
|
|
2788
|
+
family: "llama-3.3-70b",
|
|
2789
|
+
reasoning: true,
|
|
2790
|
+
releaseDate: "2025-01-01",
|
|
2791
|
+
},
|
|
2792
|
+
],
|
|
2793
|
+
},
|
|
2794
|
+
mistral: {
|
|
2795
|
+
name: "Mistral",
|
|
2796
|
+
env: ["MISTRAL_API_KEY"],
|
|
2797
|
+
npm: "@ai-sdk/mistral",
|
|
2798
|
+
models: [
|
|
2799
|
+
{
|
|
2800
|
+
id: "mistral-small-latest",
|
|
2801
|
+
name: "Mistral Small Latest",
|
|
2802
|
+
family: "mistral-small",
|
|
2803
|
+
reasoning: true,
|
|
2804
|
+
attachment: true,
|
|
2805
|
+
input: { image: true, pdf: true },
|
|
2806
|
+
releaseDate: "2025-01-01",
|
|
2807
|
+
},
|
|
2808
|
+
],
|
|
2809
|
+
},
|
|
2810
|
+
}
|
|
2811
|
+
|
|
2812
|
+
const providerCache = new Map<string, any>()
|
|
2813
|
+
const languageCache = new Map<string, LanguageModelV3>()
|
|
2814
|
+
const SDK_FACTORIES: Record<string, (options: Record<string, any>) => any> = {
|
|
2815
|
+
"@ai-sdk/anthropic": createAnthropic,
|
|
2816
|
+
"@ai-sdk/google": createGoogleGenerativeAI,
|
|
2817
|
+
"@ai-sdk/google-vertex": createVertex,
|
|
2818
|
+
"@ai-sdk/google-vertex/anthropic": createVertexAnthropic,
|
|
2819
|
+
"@ai-sdk/groq": createGroq,
|
|
2820
|
+
"@ai-sdk/mistral": createMistral,
|
|
2821
|
+
"@ai-sdk/openai": createOpenAI,
|
|
2822
|
+
}
|
|
2823
|
+
const priority = ["gpt-5", "claude-sonnet-4", "big-pickle", "gemini-3-pro"]
|
|
2824
|
+
|
|
2825
|
+
function firstEnv(names: string[]) {
|
|
2826
|
+
for (const name of names) {
|
|
2827
|
+
const value = process.env[name]
|
|
2828
|
+
if (typeof value === "string" && value.length > 0) {
|
|
2829
|
+
return value
|
|
2830
|
+
}
|
|
2831
|
+
}
|
|
2832
|
+
return undefined
|
|
2833
|
+
}
|
|
2834
|
+
|
|
2835
|
+
function cloneRecord<T extends Record<string, any>>(value: T | undefined): T {
|
|
2836
|
+
return { ...(value ?? ({} as T)) }
|
|
2837
|
+
}
|
|
2838
|
+
|
|
2839
|
+
function buildModel(
|
|
2840
|
+
providerID: ProviderID,
|
|
2841
|
+
seed: ProviderSeed,
|
|
2842
|
+
input: {
|
|
2843
|
+
id: string
|
|
2844
|
+
name?: string
|
|
2845
|
+
family?: string
|
|
2846
|
+
reasoning?: boolean
|
|
2847
|
+
attachment?: boolean
|
|
2848
|
+
toolCall?: boolean
|
|
2849
|
+
status?: "alpha" | "beta" | "deprecated" | "active"
|
|
2850
|
+
input?: Partial<{ text: boolean; audio: boolean; image: boolean; video: boolean; pdf: boolean }>
|
|
2851
|
+
output?: Partial<{ text: boolean; audio: boolean; image: boolean; video: boolean; pdf: boolean }>
|
|
2852
|
+
limit?: Partial<{ context: number; input: number; output: number }>
|
|
2853
|
+
cost?: Partial<{
|
|
2854
|
+
input: number
|
|
2855
|
+
output: number
|
|
2856
|
+
cache: Partial<{ read: number; write: number }>
|
|
2857
|
+
}>
|
|
2858
|
+
headers?: Record<string, string>
|
|
2859
|
+
options?: Record<string, any>
|
|
2860
|
+
api?: {
|
|
2861
|
+
id?: string
|
|
2862
|
+
url?: string
|
|
2863
|
+
npm?: string
|
|
2864
|
+
}
|
|
2865
|
+
releaseDate?: string
|
|
2866
|
+
variants?: Record<string, Record<string, any>>
|
|
2867
|
+
},
|
|
2868
|
+
): Model {
|
|
2869
|
+
return {
|
|
2870
|
+
id: ModelID.make(input.id),
|
|
2871
|
+
providerID,
|
|
2872
|
+
api: {
|
|
2873
|
+
id: input.api?.id ?? input.id,
|
|
2874
|
+
url: input.api?.url ?? "",
|
|
2875
|
+
npm: input.api?.npm ?? seed.npm,
|
|
2876
|
+
},
|
|
2877
|
+
name: input.name ?? input.id,
|
|
2878
|
+
family: input.family,
|
|
2879
|
+
capabilities: {
|
|
2880
|
+
temperature: true,
|
|
2881
|
+
reasoning: input.reasoning ?? false,
|
|
2882
|
+
attachment: input.attachment ?? false,
|
|
2883
|
+
toolcall: input.toolCall ?? true,
|
|
2884
|
+
input: {
|
|
2885
|
+
text: input.input?.text ?? true,
|
|
2886
|
+
audio: input.input?.audio ?? false,
|
|
2887
|
+
image: input.input?.image ?? false,
|
|
2888
|
+
video: input.input?.video ?? false,
|
|
2889
|
+
pdf: input.input?.pdf ?? false,
|
|
2890
|
+
},
|
|
2891
|
+
output: {
|
|
2892
|
+
text: input.output?.text ?? true,
|
|
2893
|
+
audio: input.output?.audio ?? false,
|
|
2894
|
+
image: input.output?.image ?? false,
|
|
2895
|
+
video: input.output?.video ?? false,
|
|
2896
|
+
pdf: input.output?.pdf ?? false,
|
|
2897
|
+
},
|
|
2898
|
+
interleaved: false,
|
|
2899
|
+
},
|
|
2900
|
+
cost: {
|
|
2901
|
+
input: input.cost?.input ?? 0,
|
|
2902
|
+
output: input.cost?.output ?? 0,
|
|
2903
|
+
cache: {
|
|
2904
|
+
read: input.cost?.cache?.read ?? 0,
|
|
2905
|
+
write: input.cost?.cache?.write ?? 0,
|
|
2906
|
+
},
|
|
2907
|
+
},
|
|
2908
|
+
limit: {
|
|
2909
|
+
context: input.limit?.context ?? DEFAULT_CONTEXT_LIMIT,
|
|
2910
|
+
...(input.limit?.input !== undefined ? { input: input.limit.input } : {}),
|
|
2911
|
+
output: input.limit?.output ?? DEFAULT_OUTPUT_LIMIT,
|
|
2912
|
+
},
|
|
2913
|
+
status: input.status ?? "active",
|
|
2914
|
+
options: cloneRecord(input.options),
|
|
2915
|
+
headers: cloneRecord(input.headers),
|
|
2916
|
+
release_date: input.releaseDate ?? "2025-01-01",
|
|
2917
|
+
variants: input.variants ?? {},
|
|
2918
|
+
}
|
|
2919
|
+
}
|
|
2920
|
+
|
|
2921
|
+
function buildSeedModels(providerID: ProviderID, seed: ProviderSeed) {
|
|
2922
|
+
return Object.fromEntries(
|
|
2923
|
+
seed.models.map((model) => [model.id, buildModel(providerID, seed, model)]),
|
|
2924
|
+
) as Record<string, Model>
|
|
2925
|
+
}
|
|
2926
|
+
|
|
2927
|
+
function applyConfiguredModels(
|
|
2928
|
+
providerID: ProviderID,
|
|
2929
|
+
seed: ProviderSeed,
|
|
2930
|
+
provider: Info,
|
|
2931
|
+
configuredProvider: ProviderConfig | undefined,
|
|
2932
|
+
) {
|
|
2933
|
+
for (const [modelID, raw] of Object.entries(configuredProvider?.models ?? {})) {
|
|
2934
|
+
const existing = provider.models[modelID]
|
|
2935
|
+
provider.models[modelID] = buildModel(providerID, seed, {
|
|
2936
|
+
id: modelID,
|
|
2937
|
+
name: raw?.name ?? existing?.name ?? modelID,
|
|
2938
|
+
family: raw?.family ?? existing?.family,
|
|
2939
|
+
reasoning: raw?.reasoning ?? existing?.capabilities.reasoning ?? false,
|
|
2940
|
+
attachment: raw?.attachment ?? existing?.capabilities.attachment ?? false,
|
|
2941
|
+
toolCall: raw?.tool_call ?? existing?.capabilities.toolcall ?? true,
|
|
2942
|
+
status: raw?.status ?? existing?.status ?? "active",
|
|
2943
|
+
input: {
|
|
2944
|
+
text: raw?.modalities?.input?.includes("text") ?? existing?.capabilities.input.text ?? true,
|
|
2945
|
+
audio: raw?.modalities?.input?.includes("audio") ?? existing?.capabilities.input.audio ?? false,
|
|
2946
|
+
image: raw?.modalities?.input?.includes("image") ?? existing?.capabilities.input.image ?? false,
|
|
2947
|
+
video: raw?.modalities?.input?.includes("video") ?? existing?.capabilities.input.video ?? false,
|
|
2948
|
+
pdf: raw?.modalities?.input?.includes("pdf") ?? existing?.capabilities.input.pdf ?? false,
|
|
2949
|
+
},
|
|
2950
|
+
output: {
|
|
2951
|
+
text: raw?.modalities?.output?.includes("text") ?? existing?.capabilities.output.text ?? true,
|
|
2952
|
+
audio: raw?.modalities?.output?.includes("audio") ?? existing?.capabilities.output.audio ?? false,
|
|
2953
|
+
image: raw?.modalities?.output?.includes("image") ?? existing?.capabilities.output.image ?? false,
|
|
2954
|
+
video: raw?.modalities?.output?.includes("video") ?? existing?.capabilities.output.video ?? false,
|
|
2955
|
+
pdf: raw?.modalities?.output?.includes("pdf") ?? existing?.capabilities.output.pdf ?? false,
|
|
2956
|
+
},
|
|
2957
|
+
limit: {
|
|
2958
|
+
context: raw?.limit?.context ?? existing?.limit.context,
|
|
2959
|
+
input: raw?.limit?.input ?? existing?.limit.input,
|
|
2960
|
+
output: raw?.limit?.output ?? existing?.limit.output,
|
|
2961
|
+
},
|
|
2962
|
+
cost: {
|
|
2963
|
+
input: raw?.cost?.input ?? existing?.cost.input,
|
|
2964
|
+
output: raw?.cost?.output ?? existing?.cost.output,
|
|
2965
|
+
cache: {
|
|
2966
|
+
read: raw?.cost?.cache_read ?? existing?.cost.cache.read,
|
|
2967
|
+
write: raw?.cost?.cache_write ?? existing?.cost.cache.write,
|
|
2968
|
+
},
|
|
2969
|
+
},
|
|
2970
|
+
headers: {
|
|
2971
|
+
...existing?.headers,
|
|
2972
|
+
...cloneRecord(raw?.headers),
|
|
2973
|
+
},
|
|
2974
|
+
options: {
|
|
2975
|
+
...existing?.options,
|
|
2976
|
+
...cloneRecord(raw?.options),
|
|
2977
|
+
},
|
|
2978
|
+
api: {
|
|
2979
|
+
id: raw?.id ?? existing?.api.id ?? modelID,
|
|
2980
|
+
url: raw?.provider?.api ?? configuredProvider?.api ?? existing?.api.url ?? "",
|
|
2981
|
+
npm: raw?.provider?.npm ?? configuredProvider?.npm ?? existing?.api.npm ?? seed.npm,
|
|
2982
|
+
},
|
|
2983
|
+
releaseDate: raw?.release_date ?? existing?.release_date,
|
|
2984
|
+
variants: raw?.variants ?? existing?.variants,
|
|
2985
|
+
})
|
|
2986
|
+
}
|
|
2987
|
+
}
|
|
2988
|
+
|
|
2989
|
+
async function loadProviders() {
|
|
2990
|
+
const cfg = await Config.get()
|
|
2991
|
+
const configured = (cfg.provider ?? {}) as Record<string, ProviderConfig>
|
|
2992
|
+
const providers: Record<string, Info> = {}
|
|
2993
|
+
|
|
2994
|
+
for (const [providerName, seed] of Object.entries(PROVIDER_SEEDS)) {
|
|
2995
|
+
const providerID = ProviderID.make(providerName)
|
|
2996
|
+
const configuredProvider = configured[providerName]
|
|
2997
|
+
const configuredModel = typeof cfg.model === "string" && cfg.model.startsWith(providerName + "/")
|
|
2998
|
+
const key = firstEnv(configuredProvider?.env ?? seed.env) ?? configuredProvider?.options?.apiKey
|
|
2999
|
+
|
|
3000
|
+
if (!configuredProvider && !configuredModel && !key) {
|
|
3001
|
+
continue
|
|
3002
|
+
}
|
|
3003
|
+
|
|
3004
|
+
const info: Info = {
|
|
3005
|
+
id: providerID,
|
|
3006
|
+
name: configuredProvider?.name ?? seed.name,
|
|
3007
|
+
source: configuredProvider ? "config" : key ? "env" : "custom",
|
|
3008
|
+
env: configuredProvider?.env ?? seed.env,
|
|
3009
|
+
...(typeof key === "string" && key.length > 0 ? { key } : {}),
|
|
3010
|
+
options: {
|
|
3011
|
+
...cloneRecord(configuredProvider?.options),
|
|
3012
|
+
},
|
|
3013
|
+
models: buildSeedModels(providerID, seed),
|
|
3014
|
+
}
|
|
3015
|
+
|
|
3016
|
+
applyConfiguredModels(providerID, seed, info, configuredProvider)
|
|
3017
|
+
providers[providerID] = info
|
|
3018
|
+
}
|
|
3019
|
+
|
|
3020
|
+
if (Object.keys(providers).length === 0) {
|
|
3021
|
+
const fallback = PROVIDER_SEEDS.anthropic
|
|
3022
|
+
providers[ProviderID.anthropic] = {
|
|
3023
|
+
id: ProviderID.anthropic,
|
|
3024
|
+
name: fallback.name,
|
|
3025
|
+
source: "custom",
|
|
3026
|
+
env: fallback.env,
|
|
3027
|
+
options: {},
|
|
3028
|
+
models: buildSeedModels(ProviderID.anthropic, fallback),
|
|
3029
|
+
}
|
|
3030
|
+
}
|
|
3031
|
+
|
|
3032
|
+
return providers as Record<ProviderID, Info>
|
|
3033
|
+
}
|
|
3034
|
+
|
|
3035
|
+
function modelKey(providerID: ProviderID, modelID: ModelID) {
|
|
3036
|
+
return String(providerID) + "/" + String(modelID)
|
|
3037
|
+
}
|
|
3038
|
+
|
|
3039
|
+
export async function list() {
|
|
3040
|
+
return loadProviders()
|
|
3041
|
+
}
|
|
3042
|
+
|
|
3043
|
+
export async function getProvider(providerID: ProviderID) {
|
|
3044
|
+
const providers = await loadProviders()
|
|
3045
|
+
const provider = providers[providerID]
|
|
3046
|
+
if (!provider) {
|
|
3047
|
+
throw new InitError({ providerID })
|
|
3048
|
+
}
|
|
3049
|
+
return provider
|
|
3050
|
+
}
|
|
3051
|
+
|
|
3052
|
+
export async function getModel(providerID: ProviderID, modelID: ModelID) {
|
|
3053
|
+
const provider = await getProvider(providerID)
|
|
3054
|
+
const model = provider.models[modelID]
|
|
3055
|
+
if (model) return model
|
|
3056
|
+
|
|
3057
|
+
const suggestions = Object.keys(provider.models).filter(
|
|
3058
|
+
(candidate) => candidate.includes(String(modelID)) || String(modelID).includes(candidate),
|
|
3059
|
+
)
|
|
3060
|
+
throw new ModelNotFoundError({
|
|
3061
|
+
providerID,
|
|
3062
|
+
modelID,
|
|
3063
|
+
...(suggestions.length ? { suggestions: suggestions.slice(0, 3) } : {}),
|
|
3064
|
+
})
|
|
3065
|
+
}
|
|
3066
|
+
|
|
3067
|
+
async function getSdk(provider: Info, model: Model) {
|
|
3068
|
+
const cacheKey = JSON.stringify({
|
|
3069
|
+
providerID: provider.id,
|
|
3070
|
+
apiId: model.api.id,
|
|
3071
|
+
baseURL: provider.options?.baseURL,
|
|
3072
|
+
headers: provider.options?.headers,
|
|
3073
|
+
key: provider.key,
|
|
3074
|
+
})
|
|
3075
|
+
if (providerCache.has(cacheKey)) {
|
|
3076
|
+
return providerCache.get(cacheKey)
|
|
3077
|
+
}
|
|
3078
|
+
|
|
3079
|
+
const options = {
|
|
3080
|
+
...cloneRecord(provider.options),
|
|
3081
|
+
...(provider.key ? { apiKey: provider.key } : {}),
|
|
3082
|
+
headers: {
|
|
3083
|
+
...cloneRecord(provider.options?.headers),
|
|
3084
|
+
...cloneRecord(model.headers),
|
|
3085
|
+
},
|
|
3086
|
+
}
|
|
3087
|
+
|
|
3088
|
+
const factory = SDK_FACTORIES[model.api.npm]
|
|
3089
|
+
if (!factory) {
|
|
3090
|
+
throw new InitError(
|
|
3091
|
+
{ providerID: provider.id },
|
|
3092
|
+
{
|
|
3093
|
+
cause: new Error(
|
|
3094
|
+
"Unsupported provider in ACP VM build: " + provider.id + " (" + model.api.npm + ")",
|
|
3095
|
+
),
|
|
3096
|
+
},
|
|
3097
|
+
)
|
|
3098
|
+
}
|
|
3099
|
+
|
|
3100
|
+
const sdk = factory(options)
|
|
3101
|
+
|
|
3102
|
+
providerCache.set(cacheKey, sdk)
|
|
3103
|
+
return sdk
|
|
3104
|
+
}
|
|
3105
|
+
|
|
3106
|
+
export async function getLanguage(model: Model) {
|
|
3107
|
+
const key = modelKey(model.providerID, model.id)
|
|
3108
|
+
const cached = languageCache.get(key)
|
|
3109
|
+
if (cached) return cached
|
|
3110
|
+
|
|
3111
|
+
try {
|
|
3112
|
+
const provider = await getProvider(model.providerID)
|
|
3113
|
+
const sdk = await getSdk(provider, model)
|
|
3114
|
+
const language =
|
|
3115
|
+
model.providerID === ProviderID.openai && typeof sdk.responses === "function"
|
|
3116
|
+
? sdk.responses(model.api.id)
|
|
3117
|
+
: sdk.languageModel(model.api.id)
|
|
3118
|
+
languageCache.set(key, language)
|
|
3119
|
+
return language
|
|
3120
|
+
} catch (cause) {
|
|
3121
|
+
throw new InitError({ providerID: model.providerID }, { cause })
|
|
3122
|
+
}
|
|
3123
|
+
}
|
|
3124
|
+
|
|
3125
|
+
export async function closest(providerID: ProviderID, query: string[]) {
|
|
3126
|
+
const provider = await getProvider(providerID).catch(() => undefined)
|
|
3127
|
+
if (!provider) return undefined
|
|
3128
|
+
for (const item of query) {
|
|
3129
|
+
const match = Object.keys(provider.models).find((modelID) => modelID.includes(item))
|
|
3130
|
+
if (match) return { providerID, modelID: ModelID.make(match) }
|
|
3131
|
+
}
|
|
3132
|
+
return undefined
|
|
3133
|
+
}
|
|
3134
|
+
|
|
3135
|
+
export async function getSmallModel(providerID: ProviderID) {
|
|
3136
|
+
const provider = await getProvider(providerID).catch(() => undefined)
|
|
3137
|
+
if (!provider) return undefined
|
|
3138
|
+
|
|
3139
|
+
const preferred =
|
|
3140
|
+
providerID === ProviderID.anthropic
|
|
3141
|
+
? ["haiku", "mini", "nano"]
|
|
3142
|
+
: ["mini", "nano", "haiku"]
|
|
3143
|
+
|
|
3144
|
+
for (const token of preferred) {
|
|
3145
|
+
const match = Object.values(provider.models).find((model) => model.id.includes(token))
|
|
3146
|
+
if (match) return match
|
|
3147
|
+
}
|
|
3148
|
+
|
|
3149
|
+
return undefined
|
|
3150
|
+
}
|
|
3151
|
+
|
|
3152
|
+
export async function defaultModel() {
|
|
3153
|
+
const cfg = await Config.get()
|
|
3154
|
+
if (cfg.model) {
|
|
3155
|
+
const parsed = parseModel(cfg.model)
|
|
3156
|
+
const model = await getModel(parsed.providerID, parsed.modelID).catch(() => undefined)
|
|
3157
|
+
if (model) {
|
|
3158
|
+
return {
|
|
3159
|
+
providerID: parsed.providerID,
|
|
3160
|
+
modelID: parsed.modelID,
|
|
3161
|
+
}
|
|
3162
|
+
}
|
|
3163
|
+
}
|
|
3164
|
+
|
|
3165
|
+
const providers = await loadProviders()
|
|
3166
|
+
for (const provider of Object.values(providers)) {
|
|
3167
|
+
const [model] = sort(Object.values(provider.models))
|
|
3168
|
+
if (model) {
|
|
3169
|
+
return {
|
|
3170
|
+
providerID: provider.id,
|
|
3171
|
+
modelID: model.id,
|
|
3172
|
+
}
|
|
3173
|
+
}
|
|
3174
|
+
}
|
|
3175
|
+
|
|
3176
|
+
return {
|
|
3177
|
+
providerID: ProviderID.anthropic,
|
|
3178
|
+
modelID: ModelID.make("claude-sonnet-4-20250514"),
|
|
3179
|
+
}
|
|
3180
|
+
}
|
|
3181
|
+
|
|
3182
|
+
export function sort<T extends { id: string }>(models: T[]) {
|
|
3183
|
+
return [...models].sort((a, b) => {
|
|
3184
|
+
const aPriority = priority.findIndex((item) => a.id.includes(item))
|
|
3185
|
+
const bPriority = priority.findIndex((item) => b.id.includes(item))
|
|
3186
|
+
const aRank = aPriority === -1 ? Number.MAX_SAFE_INTEGER : aPriority
|
|
3187
|
+
const bRank = bPriority === -1 ? Number.MAX_SAFE_INTEGER : bPriority
|
|
3188
|
+
if (aRank !== bRank) return aRank - bRank
|
|
3189
|
+
|
|
3190
|
+
const aLatest = a.id.includes("latest") ? 1 : 0
|
|
3191
|
+
const bLatest = b.id.includes("latest") ? 1 : 0
|
|
3192
|
+
if (aLatest !== bLatest) return aLatest - bLatest
|
|
3193
|
+
|
|
3194
|
+
return a.id.localeCompare(b.id)
|
|
3195
|
+
})
|
|
3196
|
+
}
|
|
3197
|
+
|
|
3198
|
+
export function parseModel(model: string) {
|
|
3199
|
+
const [providerID, ...rest] = model.split("/")
|
|
3200
|
+
return {
|
|
3201
|
+
providerID: ProviderID.make(providerID),
|
|
3202
|
+
modelID: ModelID.make(rest.join("/")),
|
|
3203
|
+
}
|
|
3204
|
+
}
|
|
3205
|
+
|
|
3206
|
+
export const ModelNotFoundError = NamedError.create(
|
|
3207
|
+
"ProviderModelNotFoundError",
|
|
3208
|
+
z.object({
|
|
3209
|
+
providerID: ProviderID.zod,
|
|
3210
|
+
modelID: ModelID.zod,
|
|
3211
|
+
suggestions: z.array(z.string()).optional(),
|
|
3212
|
+
}),
|
|
3213
|
+
)
|
|
3214
|
+
|
|
3215
|
+
export const InitError = NamedError.create(
|
|
3216
|
+
"ProviderInitError",
|
|
3217
|
+
z.object({
|
|
3218
|
+
providerID: ProviderID.zod,
|
|
3219
|
+
}),
|
|
3220
|
+
)
|
|
3221
|
+
}
|
|
3222
|
+
`,
|
|
3223
|
+
);
|
|
3224
|
+
|
|
3225
|
+
await writeFile(
|
|
3226
|
+
resolve(sourceRoot, "packages/opencode/src/plugin/index.ts"),
|
|
3227
|
+
`import { Effect, Layer, ServiceMap } from "effect"
|
|
3228
|
+
|
|
3229
|
+
export namespace Plugin {
|
|
3230
|
+
export interface Interface {
|
|
3231
|
+
readonly trigger: <Name extends string, Input, Output>(
|
|
3232
|
+
name: Name,
|
|
3233
|
+
input: Input,
|
|
3234
|
+
output: Output,
|
|
3235
|
+
) => Effect.Effect<Output>
|
|
3236
|
+
readonly list: () => Effect.Effect<any[]>
|
|
3237
|
+
readonly init: () => Effect.Effect<void>
|
|
3238
|
+
}
|
|
3239
|
+
|
|
3240
|
+
export class Service extends ServiceMap.Service<Service, Interface>()("@opencode/Plugin") {}
|
|
3241
|
+
|
|
3242
|
+
const noop = Service.of({
|
|
3243
|
+
trigger: <Name extends string, Input, Output>(_name: Name, _input: Input, output: Output) =>
|
|
3244
|
+
Effect.succeed(output),
|
|
3245
|
+
list: () => Effect.succeed([]),
|
|
3246
|
+
init: () => Effect.void,
|
|
3247
|
+
})
|
|
3248
|
+
|
|
3249
|
+
export const layer = Layer.succeed(Service, noop)
|
|
3250
|
+
export const defaultLayer = layer
|
|
3251
|
+
|
|
3252
|
+
export async function trigger<Name extends string, Input, Output>(
|
|
3253
|
+
_name: Name,
|
|
3254
|
+
_input: Input,
|
|
3255
|
+
output: Output,
|
|
3256
|
+
): Promise<Output> {
|
|
3257
|
+
return output
|
|
3258
|
+
}
|
|
3259
|
+
|
|
3260
|
+
export async function list(): Promise<any[]> {
|
|
3261
|
+
return []
|
|
3262
|
+
}
|
|
3263
|
+
|
|
3264
|
+
export async function init(): Promise<void> {}
|
|
3265
|
+
}
|
|
3266
|
+
`,
|
|
3267
|
+
);
|
|
3268
|
+
|
|
3269
|
+
await writeFile(
|
|
3270
|
+
resolve(sourceRoot, "packages/opencode/src/project/bootstrap.ts"),
|
|
3271
|
+
`import { Instance } from "./instance"
|
|
3272
|
+
import { Log } from "@/util/log"
|
|
3273
|
+
|
|
3274
|
+
export async function InstanceBootstrap() {
|
|
3275
|
+
Log.Default.info("bootstrapping", { directory: Instance.directory })
|
|
3276
|
+
Log.Default.info("bootstrap step", { step: "minimal:init" })
|
|
3277
|
+
}
|
|
3278
|
+
`,
|
|
3279
|
+
);
|
|
3280
|
+
|
|
3281
|
+
await rewriteSourceFile(
|
|
3282
|
+
sourceRoot,
|
|
3283
|
+
"packages/opencode/src/project/instance.ts",
|
|
3284
|
+
(contents) =>
|
|
3285
|
+
contents
|
|
3286
|
+
.replace(
|
|
3287
|
+
`function boot(input: { directory: string; init?: () => Promise<any>; project?: Project.Info; worktree?: string }) {
|
|
3288
|
+
return iife(async () => {
|
|
3289
|
+
`,
|
|
3290
|
+
`function boot(input: { directory: string; init?: () => Promise<any>; project?: Project.Info; worktree?: string }) {
|
|
3291
|
+
return iife(async () => {
|
|
3292
|
+
Log.Default.info("instance boot:start", { directory: input.directory })
|
|
3293
|
+
`,
|
|
3294
|
+
)
|
|
3295
|
+
.replace(
|
|
3296
|
+
` await context.provide(ctx, async () => {
|
|
3297
|
+
await input.init?.()
|
|
3298
|
+
})
|
|
3299
|
+
return ctx
|
|
3300
|
+
`,
|
|
3301
|
+
` Log.Default.info("instance boot:ctx", {
|
|
3302
|
+
directory: ctx.directory,
|
|
3303
|
+
worktree: ctx.worktree,
|
|
3304
|
+
projectID: ctx.project.id,
|
|
3305
|
+
})
|
|
3306
|
+
await context.provide(ctx, async () => {
|
|
3307
|
+
Log.Default.info("instance boot:init:start", { directory: ctx.directory })
|
|
3308
|
+
await input.init?.()
|
|
3309
|
+
Log.Default.info("instance boot:init:done", { directory: ctx.directory })
|
|
3310
|
+
})
|
|
3311
|
+
Log.Default.info("instance boot:done", { directory: ctx.directory })
|
|
3312
|
+
return ctx
|
|
3313
|
+
`,
|
|
3314
|
+
)
|
|
3315
|
+
.replace(
|
|
3316
|
+
` const ctx = await existing
|
|
3317
|
+
return context.provide(ctx, async () => {
|
|
3318
|
+
return input.fn()
|
|
3319
|
+
})
|
|
3320
|
+
`,
|
|
3321
|
+
` Log.Default.info("instance provide:await:start", { directory })
|
|
3322
|
+
const ctx = await existing
|
|
3323
|
+
Log.Default.info("instance provide:await:done", { directory })
|
|
3324
|
+
return context.provide(ctx, async () => {
|
|
3325
|
+
Log.Default.info("instance provide:fn:start", { directory })
|
|
3326
|
+
const result = await input.fn()
|
|
3327
|
+
Log.Default.info("instance provide:fn:done", { directory })
|
|
3328
|
+
return result
|
|
3329
|
+
})
|
|
3330
|
+
`,
|
|
3331
|
+
),
|
|
3332
|
+
);
|
|
3333
|
+
|
|
3334
|
+
await rewriteSourceFile(
|
|
3335
|
+
sourceRoot,
|
|
3336
|
+
"packages/opencode/src/project/project.ts",
|
|
3337
|
+
(contents) =>
|
|
3338
|
+
contents.includes('log.info("phase2 select project"')
|
|
3339
|
+
? contents
|
|
3340
|
+
: contents
|
|
3341
|
+
.replace(
|
|
3342
|
+
` // Phase 2: upsert
|
|
3343
|
+
const row = yield* db((d) => d.select().from(ProjectTable).where(eq(ProjectTable.id, data.id)).get())
|
|
3344
|
+
`,
|
|
3345
|
+
` // Phase 2: upsert
|
|
3346
|
+
log.info("phase2 select project", { projectID: data.id })
|
|
3347
|
+
const row = yield* db((d) => d.select().from(ProjectTable).where(eq(ProjectTable.id, data.id)).get())
|
|
3348
|
+
log.info("phase2 select project done", { projectID: data.id, found: !!row })
|
|
3349
|
+
`,
|
|
3350
|
+
)
|
|
3351
|
+
.replace(
|
|
3352
|
+
` yield* db((d) =>
|
|
3353
|
+
d
|
|
3354
|
+
.insert(ProjectTable)
|
|
3355
|
+
.values({
|
|
3356
|
+
`,
|
|
3357
|
+
` log.info("phase2 upsert project", {
|
|
3358
|
+
projectID: result.id,
|
|
3359
|
+
sandboxes: result.sandboxes.length,
|
|
3360
|
+
})
|
|
3361
|
+
yield* db((d) =>
|
|
3362
|
+
d
|
|
3363
|
+
.insert(ProjectTable)
|
|
3364
|
+
.values({
|
|
3365
|
+
`,
|
|
3366
|
+
)
|
|
3367
|
+
.replace(
|
|
3368
|
+
` if (data.id !== ProjectID.global) {
|
|
3369
|
+
`,
|
|
3370
|
+
` log.info("phase2 upsert project done", { projectID: result.id })
|
|
3371
|
+
if (data.id !== ProjectID.global) {
|
|
3372
|
+
`,
|
|
3373
|
+
),
|
|
3374
|
+
);
|
|
3375
|
+
|
|
3376
|
+
await writeFile(
|
|
3377
|
+
resolve(sourceRoot, "packages/opencode/src/share/share-next.ts"),
|
|
3378
|
+
`export namespace ShareNext {
|
|
3379
|
+
const EMPTY_API = {
|
|
3380
|
+
create: "",
|
|
3381
|
+
sync: () => "",
|
|
3382
|
+
remove: () => "",
|
|
3383
|
+
data: () => "",
|
|
3384
|
+
}
|
|
3385
|
+
|
|
3386
|
+
export async function url() {
|
|
3387
|
+
return ""
|
|
3388
|
+
}
|
|
3389
|
+
|
|
3390
|
+
export async function request() {
|
|
3391
|
+
return {
|
|
3392
|
+
headers: {},
|
|
3393
|
+
api: EMPTY_API,
|
|
3394
|
+
baseUrl: "",
|
|
3395
|
+
}
|
|
3396
|
+
}
|
|
3397
|
+
|
|
3398
|
+
export async function init() {}
|
|
3399
|
+
|
|
3400
|
+
export async function create(_sessionID: string) {
|
|
3401
|
+
return { id: "", url: "", secret: "" }
|
|
3402
|
+
}
|
|
3403
|
+
|
|
3404
|
+
export async function remove(_sessionID: string) {}
|
|
3405
|
+
}
|
|
3406
|
+
`,
|
|
3407
|
+
);
|
|
3408
|
+
|
|
3409
|
+
await writeFile(
|
|
3410
|
+
resolve(sourceRoot, "packages/opencode/src/cli/cmd/tui/win32.ts"),
|
|
3411
|
+
`export function win32DisableProcessedInput() {}
|
|
3412
|
+
export function win32FlushInputBuffer() {}
|
|
3413
|
+
export function win32InstallCtrlCGuard() {
|
|
3414
|
+
return
|
|
3415
|
+
}
|
|
3416
|
+
`,
|
|
3417
|
+
);
|
|
3418
|
+
}
|
|
3419
|
+
|
|
3420
|
+
function patchBuiltBundle(bundlePath) {
|
|
3421
|
+
const original = readFileSync(bundlePath, "utf-8");
|
|
3422
|
+
if (
|
|
3423
|
+
original.includes(
|
|
3424
|
+
"bash tool command scan failed, falling back to raw permission request",
|
|
3425
|
+
)
|
|
3426
|
+
) {
|
|
3427
|
+
return;
|
|
3428
|
+
}
|
|
3429
|
+
|
|
3430
|
+
const updated = original.replace(
|
|
3431
|
+
` async execute(params, ctx) {
|
|
3432
|
+
const cwd = params.workdir ? await resolvePath(params.workdir, Instance.directory, shell2) : Instance.directory;
|
|
3433
|
+
if (params.timeout !== undefined && params.timeout < 0) {
|
|
3434
|
+
throw new Error(\`Invalid timeout value: \${params.timeout}. Timeout must be a positive number.\`);
|
|
3435
|
+
}
|
|
3436
|
+
const timeout4 = params.timeout ?? DEFAULT_TIMEOUT;
|
|
3437
|
+
const ps2 = PS.has(name21);
|
|
3438
|
+
const root = await parse10(params.command, ps2);
|
|
3439
|
+
const scan5 = await collect6(root, cwd, ps2, shell2);
|
|
3440
|
+
if (!Instance.containsPath(cwd))
|
|
3441
|
+
scan5.dirs.add(cwd);
|
|
3442
|
+
await ask(ctx, scan5);
|
|
3443
|
+
return run7({
|
|
3444
|
+
`,
|
|
3445
|
+
` async execute(params, ctx) {
|
|
3446
|
+
const cwd = params.workdir ? await resolvePath(params.workdir, Instance.directory, shell2) : Instance.directory;
|
|
3447
|
+
if (params.timeout !== undefined && params.timeout < 0) {
|
|
3448
|
+
throw new Error(\`Invalid timeout value: \${params.timeout}. Timeout must be a positive number.\`);
|
|
3449
|
+
}
|
|
3450
|
+
const timeout4 = params.timeout ?? DEFAULT_TIMEOUT;
|
|
3451
|
+
const ps2 = PS.has(name21);
|
|
3452
|
+
let scan5;
|
|
3453
|
+
try {
|
|
3454
|
+
const root = await parse10(params.command, ps2);
|
|
3455
|
+
scan5 = await collect6(root, cwd, ps2, shell2);
|
|
3456
|
+
} catch (error48) {
|
|
3457
|
+
log7.warn("bash tool command scan failed, falling back to raw permission request", {
|
|
3458
|
+
command: params.command,
|
|
3459
|
+
error: error48 instanceof Error ? error48.message : String(error48)
|
|
3460
|
+
});
|
|
3461
|
+
scan5 = {
|
|
3462
|
+
dirs: new Set,
|
|
3463
|
+
patterns: new Set([params.command]),
|
|
3464
|
+
always: new Set([params.command])
|
|
3465
|
+
};
|
|
3466
|
+
}
|
|
3467
|
+
if (!Instance.containsPath(cwd))
|
|
3468
|
+
scan5.dirs.add(cwd);
|
|
3469
|
+
await ask(ctx, scan5);
|
|
3470
|
+
return run7({
|
|
3471
|
+
`,
|
|
3472
|
+
);
|
|
3473
|
+
|
|
3474
|
+
if (updated === original) {
|
|
3475
|
+
throw new Error(
|
|
3476
|
+
"Failed to patch built OpenCode ACP bundle for bash scan fallback",
|
|
3477
|
+
);
|
|
3478
|
+
}
|
|
3479
|
+
|
|
3480
|
+
writeFileSync(bundlePath, updated);
|
|
3481
|
+
}
|
|
3482
|
+
|
|
3483
|
+
async function assertPreparedSource(sourceRoot) {
|
|
3484
|
+
const instanceSource = await readFile(
|
|
3485
|
+
resolve(sourceRoot, "packages/opencode/src/server/instance.ts"),
|
|
3486
|
+
"utf8",
|
|
3487
|
+
);
|
|
3488
|
+
if (
|
|
3489
|
+
instanceSource.includes('import { PtyRoutes } from "./routes/pty"') ||
|
|
3490
|
+
instanceSource.includes('import { TuiRoutes } from "./routes/tui"') ||
|
|
3491
|
+
instanceSource.includes('.route("/pty", PtyRoutes())') ||
|
|
3492
|
+
instanceSource.includes('.route("/tui", TuiRoutes())')
|
|
3493
|
+
) {
|
|
3494
|
+
throw new Error("Prepared OpenCode source still exposes PTY/TUI routes in the ACP build");
|
|
3495
|
+
}
|
|
3496
|
+
|
|
3497
|
+
const shellSource = await readFile(
|
|
3498
|
+
resolve(sourceRoot, "packages/opencode/src/shell/shell.ts"),
|
|
3499
|
+
"utf8",
|
|
3500
|
+
);
|
|
3501
|
+
if (shellSource.includes("Bun.which(")) {
|
|
3502
|
+
throw new Error("Prepared OpenCode source still references Bun.which in shell.ts");
|
|
3503
|
+
}
|
|
3504
|
+
|
|
3505
|
+
const win32Source = await readFile(
|
|
3506
|
+
resolve(sourceRoot, "packages/opencode/src/cli/cmd/tui/win32.ts"),
|
|
3507
|
+
"utf8",
|
|
3508
|
+
);
|
|
3509
|
+
if (win32Source.includes("bun:ffi")) {
|
|
3510
|
+
throw new Error("Prepared OpenCode source still references bun:ffi in the Win32 TUI shim");
|
|
3511
|
+
}
|
|
3512
|
+
|
|
3513
|
+
const dbNodeSource = await readFile(
|
|
3514
|
+
resolve(sourceRoot, "packages/opencode/src/storage/db.node.ts"),
|
|
3515
|
+
"utf8",
|
|
3516
|
+
);
|
|
3517
|
+
if (
|
|
3518
|
+
!dbNodeSource.includes('from "drizzle-orm/node-sqlite"') ||
|
|
3519
|
+
!dbNodeSource.includes('from "node:sqlite"')
|
|
3520
|
+
) {
|
|
3521
|
+
throw new Error("Prepared OpenCode source does not use the native node:sqlite database path");
|
|
3522
|
+
}
|
|
3523
|
+
}
|
|
3524
|
+
|
|
3525
|
+
async function assertBundleClean(bundlePath) {
|
|
3526
|
+
const bundle = await readFile(bundlePath, "utf8");
|
|
3527
|
+
for (const pattern of [
|
|
3528
|
+
"bun:ffi",
|
|
3529
|
+
"bun-pty",
|
|
3530
|
+
"hono/bun",
|
|
3531
|
+
'.route("/pty", PtyRoutes())',
|
|
3532
|
+
'.route("/tui", TuiRoutes())',
|
|
3533
|
+
"Bun.which(",
|
|
3534
|
+
"bun:sqlite",
|
|
3535
|
+
]) {
|
|
3536
|
+
if (bundle.includes(pattern)) {
|
|
3537
|
+
throw new Error(
|
|
3538
|
+
`OpenCode ACP bundle still contains forbidden runtime dependency: ${pattern}`,
|
|
3539
|
+
);
|
|
3540
|
+
}
|
|
3541
|
+
}
|
|
3542
|
+
}
|
|
3543
|
+
|
|
3544
|
+
async function main() {
|
|
3545
|
+
if (!existsSync(bunBin)) {
|
|
3546
|
+
throw new Error(
|
|
3547
|
+
`bun is not installed for @agentos-software/opencode (expected ${bunBin}). Run pnpm install first.`,
|
|
3548
|
+
);
|
|
3549
|
+
}
|
|
3550
|
+
|
|
3551
|
+
mkdirSync(distDir, { recursive: true });
|
|
3552
|
+
mkdirSync(cacheDir, { recursive: true });
|
|
3553
|
+
rmSync(bundleDir, { recursive: true, force: true });
|
|
3554
|
+
|
|
3555
|
+
const patch = readFileSync(patchPath, "utf-8");
|
|
3556
|
+
const buildScript = readFileSync(fileURLToPath(import.meta.url), "utf-8");
|
|
3557
|
+
const patchHash = createHash("sha256")
|
|
3558
|
+
.update(`${SOURCE_VERSION}\n${patch}\n${buildScript}`)
|
|
3559
|
+
.digest("hex")
|
|
3560
|
+
.slice(0, 16);
|
|
3561
|
+
const sourceRoot = join(cacheDir, `source-v${SOURCE_VERSION}-${patchHash}`);
|
|
3562
|
+
const preparedMarker = join(sourceRoot, ".agentos-prepared.json");
|
|
3563
|
+
const tarballPath = join(cacheDir, `opencode-v${SOURCE_VERSION}.tar.gz`);
|
|
3564
|
+
|
|
3565
|
+
if (!existsSync(preparedMarker)) {
|
|
3566
|
+
rmSync(sourceRoot, { recursive: true, force: true });
|
|
3567
|
+
mkdirSync(sourceRoot, { recursive: true });
|
|
3568
|
+
|
|
3569
|
+
if (!existsSync(tarballPath)) {
|
|
3570
|
+
process.stdout.write(`Downloading OpenCode v${SOURCE_VERSION} source...\n`);
|
|
3571
|
+
await downloadFile(SOURCE_TARBALL_URL, tarballPath);
|
|
3572
|
+
}
|
|
3573
|
+
|
|
3574
|
+
run("tar", ["-xzf", tarballPath, "--strip-components=1", "-C", sourceRoot]);
|
|
3575
|
+
pinGhosttyWebRef(sourceRoot);
|
|
3576
|
+
run(bunBin, ["install", "--frozen-lockfile"], { cwd: sourceRoot });
|
|
3577
|
+
await ensureNodeAcpPatch(sourceRoot, tarballPath);
|
|
3578
|
+
await applyNodeAcpRuntimeTweaks(sourceRoot);
|
|
3579
|
+
await assertPreparedSource(sourceRoot);
|
|
3580
|
+
|
|
3581
|
+
writeFileSync(
|
|
3582
|
+
preparedMarker,
|
|
3583
|
+
JSON.stringify(
|
|
3584
|
+
{
|
|
3585
|
+
sourceVersion: SOURCE_VERSION,
|
|
3586
|
+
sourceRepository: SOURCE_REPOSITORY,
|
|
3587
|
+
patchHash,
|
|
3588
|
+
},
|
|
3589
|
+
null,
|
|
3590
|
+
2,
|
|
3591
|
+
) + "\n",
|
|
3592
|
+
);
|
|
3593
|
+
}
|
|
3594
|
+
|
|
3595
|
+
await ensureNodeAcpPatch(sourceRoot, tarballPath);
|
|
3596
|
+
await applyNodeAcpRuntimeTweaks(sourceRoot);
|
|
3597
|
+
await assertPreparedSource(sourceRoot);
|
|
3598
|
+
|
|
3599
|
+
const migrations = await readMigrations(sourceRoot);
|
|
3600
|
+
const buildHelperDir = await mkdtemp(join(tmpdir(), "agentos-opencode-build-"));
|
|
3601
|
+
const buildHelperPath = join(buildHelperDir, "build-opencode-acp.mjs");
|
|
3602
|
+
const bunVersion =
|
|
3603
|
+
spawnSync(bunBin, ["--version"], { encoding: "utf-8" }).stdout?.trim() ??
|
|
3604
|
+
"unknown";
|
|
3605
|
+
|
|
3606
|
+
try {
|
|
3607
|
+
await writeFile(
|
|
3608
|
+
buildHelperPath,
|
|
3609
|
+
`
|
|
3610
|
+
import { mkdir } from "node:fs/promises";
|
|
3611
|
+
import { dirname, join } from "node:path";
|
|
3612
|
+
|
|
3613
|
+
const outdir = process.env.OUTDIR;
|
|
3614
|
+
if (!outdir) {
|
|
3615
|
+
throw new Error("OUTDIR is required");
|
|
3616
|
+
}
|
|
3617
|
+
|
|
3618
|
+
const result = await Bun.build({
|
|
3619
|
+
target: "node",
|
|
3620
|
+
format: "esm",
|
|
3621
|
+
outdir,
|
|
3622
|
+
entrypoints: ["./packages/opencode/src/cli/cmd/acp.ts"],
|
|
3623
|
+
define: {
|
|
3624
|
+
OPENCODE_MIGRATIONS: ${JSON.stringify(JSON.stringify(migrations))},
|
|
3625
|
+
OPENCODE_LIBC: ${JSON.stringify(JSON.stringify("glibc"))},
|
|
3626
|
+
},
|
|
3627
|
+
});
|
|
3628
|
+
if (!result.success) {
|
|
3629
|
+
for (const log of result.logs) {
|
|
3630
|
+
console.error(log);
|
|
3631
|
+
}
|
|
3632
|
+
throw new Error("OpenCode ACP bundle build failed");
|
|
3633
|
+
}
|
|
3634
|
+
for (const output of result.outputs) {
|
|
3635
|
+
const filePath = join(outdir, output.path);
|
|
3636
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
3637
|
+
await Bun.write(filePath, output);
|
|
3638
|
+
}
|
|
3639
|
+
`,
|
|
3640
|
+
);
|
|
3641
|
+
|
|
3642
|
+
run(bunBin, [buildHelperPath], {
|
|
3643
|
+
cwd: sourceRoot,
|
|
3644
|
+
env: {
|
|
3645
|
+
...process.env,
|
|
3646
|
+
OUTDIR: bundleDir,
|
|
3647
|
+
},
|
|
3648
|
+
});
|
|
3649
|
+
} finally {
|
|
3650
|
+
rmSync(buildHelperDir, { recursive: true, force: true });
|
|
3651
|
+
}
|
|
3652
|
+
|
|
3653
|
+
await assertBundleClean(join(bundleDir, "acp.js"));
|
|
3654
|
+
patchBuiltBundle(join(bundleDir, "acp.js"));
|
|
3655
|
+
|
|
3656
|
+
writeFileSync(
|
|
3657
|
+
manifestPath,
|
|
3658
|
+
JSON.stringify(
|
|
3659
|
+
{
|
|
3660
|
+
source: {
|
|
3661
|
+
repository: SOURCE_REPOSITORY,
|
|
3662
|
+
version: SOURCE_VERSION,
|
|
3663
|
+
tarballUrl: SOURCE_TARBALL_URL,
|
|
3664
|
+
},
|
|
3665
|
+
build: {
|
|
3666
|
+
bunVersion,
|
|
3667
|
+
patchHash,
|
|
3668
|
+
externalDependencies: [],
|
|
3669
|
+
entry: "./opencode-acp/acp.js",
|
|
3670
|
+
},
|
|
3671
|
+
},
|
|
3672
|
+
null,
|
|
3673
|
+
2,
|
|
3674
|
+
) + "\n",
|
|
3675
|
+
);
|
|
3676
|
+
}
|
|
3677
|
+
|
|
3678
|
+
void main().catch((error) => {
|
|
3679
|
+
process.stderr.write(`${error instanceof Error ? error.stack ?? error.message : String(error)}\n`);
|
|
3680
|
+
process.exitCode = 1;
|
|
3681
|
+
});
|