@diologue/local-agent 0.1.5 → 0.3.0
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/dist/cli.mjs +333 -43
- package/dist/cli.mjs.map +3 -3
- package/package.json +3 -5
- package/scripts/postinstall.ts +0 -191
- package/src/lib/engine-bundle.ts +0 -123
package/dist/cli.mjs
CHANGED
|
@@ -81,9 +81,11 @@ var init_engine_release = __esm({
|
|
|
81
81
|
|
|
82
82
|
// src/lib/engine-bundle.ts
|
|
83
83
|
import { access as access2, constants } from "node:fs/promises";
|
|
84
|
+
import { readFileSync } from "node:fs";
|
|
85
|
+
import { createRequire } from "node:module";
|
|
84
86
|
import path3 from "node:path";
|
|
85
87
|
import { fileURLToPath } from "node:url";
|
|
86
|
-
var engineRelease, __filename, __dirname, LOCAL_AGENT_ROOT, REPO_ROOT, BUNDLE_DIR_INSTALLED, BUNDLE_DIR_LOCAL_BUILD, ENGINE_BUNDLE_ENV, exists2, bundleFilename, findEngineBundle;
|
|
88
|
+
var engineRelease, __filename, __dirname, LOCAL_AGENT_ROOT, REPO_ROOT, BUNDLE_DIR_INSTALLED, BUNDLE_DIR_LOCAL_BUILD, ENGINE_BUNDLE_ENV, exists2, resolveOpencodeAiBinary, bundleFilename, findEngineBundle;
|
|
87
89
|
var init_engine_bundle = __esm({
|
|
88
90
|
"src/lib/engine-bundle.ts"() {
|
|
89
91
|
"use strict";
|
|
@@ -111,6 +113,19 @@ var init_engine_bundle = __esm({
|
|
|
111
113
|
return false;
|
|
112
114
|
}
|
|
113
115
|
};
|
|
116
|
+
resolveOpencodeAiBinary = () => {
|
|
117
|
+
try {
|
|
118
|
+
const require2 = createRequire(import.meta.url);
|
|
119
|
+
const pkgJsonPath = require2.resolve("opencode-ai/package.json");
|
|
120
|
+
const pkgDir = path3.dirname(pkgJsonPath);
|
|
121
|
+
const pkg = JSON.parse(readFileSync(pkgJsonPath, "utf-8"));
|
|
122
|
+
const binRel = typeof pkg.bin === "string" ? pkg.bin : pkg.bin?.opencode;
|
|
123
|
+
if (!binRel) return null;
|
|
124
|
+
return path3.join(pkgDir, binRel);
|
|
125
|
+
} catch {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
};
|
|
114
129
|
bundleFilename = (platform = process.platform, arch = process.arch) => {
|
|
115
130
|
const target = engineRelease.targets.find(
|
|
116
131
|
(t) => t.platform === platform && t.arch === arch
|
|
@@ -125,6 +140,10 @@ var init_engine_bundle = __esm({
|
|
|
125
140
|
return { path: fromEnv, source: "env" };
|
|
126
141
|
}
|
|
127
142
|
}
|
|
143
|
+
const opencodeAi = resolveOpencodeAiBinary();
|
|
144
|
+
if (opencodeAi && await exists2(opencodeAi)) {
|
|
145
|
+
return { path: opencodeAi, source: "opencode-ai" };
|
|
146
|
+
}
|
|
128
147
|
const filename = bundleFilename();
|
|
129
148
|
const installed = path3.join(BUNDLE_DIR_INSTALLED, filename);
|
|
130
149
|
if (await exists2(installed)) {
|
|
@@ -148,7 +167,7 @@ import { access as access3, constants as constants2 } from "node:fs/promises";
|
|
|
148
167
|
import { spawn as spawn2 } from "node:child_process";
|
|
149
168
|
import path4 from "node:path";
|
|
150
169
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
151
|
-
var __filename2, __dirname2, REPO_ROOT2, DEFAULT_ENGINE_DIR, ENTRY_REL, DEFAULT_STARTUP_TIMEOUT_MS, LISTENING_REGEX, defaultRuntimeCheck, exists3, defaultClientFactory, LocalEngineLocator, truncate;
|
|
170
|
+
var __filename2, __dirname2, REPO_ROOT2, DEFAULT_ENGINE_DIR, ENTRY_REL, DEFAULT_STARTUP_TIMEOUT_MS, LISTENING_REGEX, defaultRuntimeCheck, exists3, isRecord, parseExistingInlineConfig, mergeInlineConfig, buildEngineEnv, defaultClientFactory, LocalEngineLocator, truncate;
|
|
152
171
|
var init_engine_locator_local = __esm({
|
|
153
172
|
"src/adapters/engine-locator-local.ts"() {
|
|
154
173
|
"use strict";
|
|
@@ -173,6 +192,37 @@ var init_engine_locator_local = __esm({
|
|
|
173
192
|
return false;
|
|
174
193
|
}
|
|
175
194
|
};
|
|
195
|
+
isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
196
|
+
parseExistingInlineConfig = (existing) => {
|
|
197
|
+
if (!existing) return {};
|
|
198
|
+
try {
|
|
199
|
+
const parsed = JSON.parse(existing);
|
|
200
|
+
return isRecord(parsed) ? parsed : {};
|
|
201
|
+
} catch {
|
|
202
|
+
return {};
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
mergeInlineConfig = (existing, injected) => {
|
|
206
|
+
if (!injected) return existing;
|
|
207
|
+
const base = parseExistingInlineConfig(existing);
|
|
208
|
+
const baseProvider = isRecord(base.provider) ? base.provider : {};
|
|
209
|
+
return JSON.stringify({
|
|
210
|
+
...base,
|
|
211
|
+
...injected,
|
|
212
|
+
provider: {
|
|
213
|
+
...baseProvider,
|
|
214
|
+
...injected.provider ?? {}
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
};
|
|
218
|
+
buildEngineEnv = (config) => {
|
|
219
|
+
const env = { ...process.env };
|
|
220
|
+
const content = mergeInlineConfig(env.OPENCODE_CONFIG_CONTENT, config);
|
|
221
|
+
if (content) {
|
|
222
|
+
env.OPENCODE_CONFIG_CONTENT = content;
|
|
223
|
+
}
|
|
224
|
+
return env;
|
|
225
|
+
};
|
|
176
226
|
defaultClientFactory = async (baseUrl) => {
|
|
177
227
|
const sdk = await import("@opencode-ai/sdk");
|
|
178
228
|
return sdk.createOpencodeClient({ baseUrl });
|
|
@@ -262,13 +312,12 @@ var init_engine_locator_local = __esm({
|
|
|
262
312
|
const child = spawn2(command, args, {
|
|
263
313
|
cwd,
|
|
264
314
|
stdio: ["ignore", "pipe", "pipe"],
|
|
265
|
-
// Inherit env so the engine sees any LOCAL_AGENT_* /
|
|
266
|
-
// credentials the parent has,
|
|
267
|
-
//
|
|
268
|
-
//
|
|
269
|
-
//
|
|
270
|
-
|
|
271
|
-
env: process.env
|
|
315
|
+
// Inherit env so the engine sees any LOCAL_AGENT_* / provider
|
|
316
|
+
// credentials the parent has, then inject the brokered provider as
|
|
317
|
+
// inline opencode config. Without this, the default local engine path
|
|
318
|
+
// does not know the diologue/diologue-routed model, so the prompt
|
|
319
|
+
// fails before edit tools can create files in the selected repo.
|
|
320
|
+
env: buildEngineEnv(options.config)
|
|
272
321
|
});
|
|
273
322
|
let url;
|
|
274
323
|
try {
|
|
@@ -485,6 +534,9 @@ var createCorsMiddleware = (options) => {
|
|
|
485
534
|
`Content-Type, ${LOCAL_AGENT_TOKEN_HEADER}`
|
|
486
535
|
);
|
|
487
536
|
res.setHeader("Access-Control-Max-Age", "300");
|
|
537
|
+
if (req.header("access-control-request-private-network") === "true") {
|
|
538
|
+
res.setHeader("Access-Control-Allow-Private-Network", "true");
|
|
539
|
+
}
|
|
488
540
|
}
|
|
489
541
|
if (req.method === "OPTIONS") {
|
|
490
542
|
res.status(204).end();
|
|
@@ -622,8 +674,56 @@ var getStatusShort = async (cwd) => {
|
|
|
622
674
|
const out = await runGit(cwd, ["status", "--short", "-uall"]);
|
|
623
675
|
return parseShortStatus(out);
|
|
624
676
|
};
|
|
677
|
+
var runGitAllowingExit = (cwd, args, allowedExitCodes) => new Promise((resolve, reject) => {
|
|
678
|
+
execFile(
|
|
679
|
+
"git",
|
|
680
|
+
args,
|
|
681
|
+
{
|
|
682
|
+
cwd,
|
|
683
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
684
|
+
env: { ...process.env, GIT_PAGER: "cat", PAGER: "cat" }
|
|
685
|
+
},
|
|
686
|
+
(err, stdout, stderr) => {
|
|
687
|
+
if (!err) {
|
|
688
|
+
resolve(stdout);
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
const e = err;
|
|
692
|
+
const exitCode = e.code ?? -1;
|
|
693
|
+
if (allowedExitCodes.has(exitCode)) {
|
|
694
|
+
resolve(stdout);
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
reject(new GitCommandError(args, exitCode, stderr));
|
|
698
|
+
}
|
|
699
|
+
);
|
|
700
|
+
});
|
|
701
|
+
var getUntrackedFiles = async (cwd) => {
|
|
702
|
+
const out = await runGit(cwd, [
|
|
703
|
+
"ls-files",
|
|
704
|
+
"--others",
|
|
705
|
+
"--exclude-standard",
|
|
706
|
+
"-z"
|
|
707
|
+
]);
|
|
708
|
+
return out.split("\0").filter(Boolean);
|
|
709
|
+
};
|
|
710
|
+
var getUntrackedFileDiff = async (cwd, file) => {
|
|
711
|
+
const diff = await runGitAllowingExit(
|
|
712
|
+
cwd,
|
|
713
|
+
["diff", "--no-index", "--", "/dev/null", file],
|
|
714
|
+
/* @__PURE__ */ new Set([1])
|
|
715
|
+
);
|
|
716
|
+
return diff.replace(/^diff --git a\/dev\/null b\/(.+)$/m, "diff --git a/$1 b/$1").replace(/^--- \/dev\/null$/m, "--- /dev/null");
|
|
717
|
+
};
|
|
625
718
|
var getDiff = async (cwd) => {
|
|
626
|
-
|
|
719
|
+
const [trackedDiff, untrackedFiles] = await Promise.all([
|
|
720
|
+
runGit(cwd, ["diff", "HEAD"]),
|
|
721
|
+
getUntrackedFiles(cwd)
|
|
722
|
+
]);
|
|
723
|
+
const untrackedDiffs = await Promise.all(
|
|
724
|
+
untrackedFiles.map((file) => getUntrackedFileDiff(cwd, file))
|
|
725
|
+
);
|
|
726
|
+
return [trackedDiff, ...untrackedDiffs].filter((part) => part.trim().length > 0).join("\n");
|
|
627
727
|
};
|
|
628
728
|
var runGitWithStdin = async (cwd, args, input) => {
|
|
629
729
|
const { execFile: execFile2 } = await import("node:child_process");
|
|
@@ -663,6 +763,20 @@ var canApplyDiff = async (cwd, unified) => {
|
|
|
663
763
|
var applyDiff = async (cwd, unified) => {
|
|
664
764
|
await runGitWithStdin(cwd, ["apply"], unified);
|
|
665
765
|
};
|
|
766
|
+
var canRevertDiff = async (cwd, unified) => {
|
|
767
|
+
try {
|
|
768
|
+
await runGitWithStdin(cwd, ["apply", "--reverse", "--check"], unified);
|
|
769
|
+
return { ok: true };
|
|
770
|
+
} catch (err) {
|
|
771
|
+
if (err instanceof GitCommandError) {
|
|
772
|
+
return { ok: false, stderr: err.stderr };
|
|
773
|
+
}
|
|
774
|
+
throw err;
|
|
775
|
+
}
|
|
776
|
+
};
|
|
777
|
+
var revertDiff = async (cwd, unified) => {
|
|
778
|
+
await runGitWithStdin(cwd, ["apply", "--reverse"], unified);
|
|
779
|
+
};
|
|
666
780
|
var parseDiffPaths = (unified) => {
|
|
667
781
|
const paths = [];
|
|
668
782
|
for (const line of unified.split("\n")) {
|
|
@@ -742,6 +856,9 @@ var applyPatchSchema = z.object({
|
|
|
742
856
|
unified: z.string().min(1).max(8 * 1024 * 1024),
|
|
743
857
|
baselineHash: z.string().optional()
|
|
744
858
|
});
|
|
859
|
+
var revertPatchSchema = z.object({
|
|
860
|
+
unified: z.string().min(1).max(8 * 1024 * 1024)
|
|
861
|
+
});
|
|
745
862
|
var buildRepoStatus = async (resolvedPath) => {
|
|
746
863
|
const [branch, head, dirty] = await Promise.all([
|
|
747
864
|
getBranch(resolvedPath),
|
|
@@ -891,6 +1008,40 @@ var createRepoRouter = (state) => {
|
|
|
891
1008
|
throw err;
|
|
892
1009
|
}
|
|
893
1010
|
});
|
|
1011
|
+
router.post("/revert", async (req, res) => {
|
|
1012
|
+
const repo = requireSelectedRepo(state, res);
|
|
1013
|
+
if (!repo) return;
|
|
1014
|
+
const parsed = revertPatchSchema.safeParse(req.body);
|
|
1015
|
+
if (!parsed.success) {
|
|
1016
|
+
res.status(400).json({ error: "invalid_body", issues: parsed.error.issues });
|
|
1017
|
+
return;
|
|
1018
|
+
}
|
|
1019
|
+
try {
|
|
1020
|
+
const check = await canRevertDiff(repo.path, parsed.data.unified);
|
|
1021
|
+
if (!check.ok) {
|
|
1022
|
+
res.status(409).json({
|
|
1023
|
+
error: "diff_does_not_revert",
|
|
1024
|
+
message: "These changes can't be reverted cleanly. The working tree may have changed since the agent wrote them, or they were already reverted.",
|
|
1025
|
+
stderr: check.stderr
|
|
1026
|
+
});
|
|
1027
|
+
return;
|
|
1028
|
+
}
|
|
1029
|
+
await revertDiff(repo.path, parsed.data.unified);
|
|
1030
|
+
const paths = parseDiffPaths(parsed.data.unified);
|
|
1031
|
+
const body = {
|
|
1032
|
+
ok: true,
|
|
1033
|
+
filesChanged: paths.length,
|
|
1034
|
+
paths
|
|
1035
|
+
};
|
|
1036
|
+
res.json(body);
|
|
1037
|
+
} catch (err) {
|
|
1038
|
+
if (err instanceof GitCommandError) {
|
|
1039
|
+
sendGitError(res, err);
|
|
1040
|
+
return;
|
|
1041
|
+
}
|
|
1042
|
+
throw err;
|
|
1043
|
+
}
|
|
1044
|
+
});
|
|
894
1045
|
return router;
|
|
895
1046
|
};
|
|
896
1047
|
|
|
@@ -900,6 +1051,9 @@ import { z as z2 } from "zod";
|
|
|
900
1051
|
|
|
901
1052
|
// src/broker.ts
|
|
902
1053
|
import { randomUUID } from "node:crypto";
|
|
1054
|
+
var logBroker = (message) => {
|
|
1055
|
+
console.error(`[llm-broker] ${message}`);
|
|
1056
|
+
};
|
|
903
1057
|
var LlmBroker = class {
|
|
904
1058
|
pending = /* @__PURE__ */ new Map();
|
|
905
1059
|
emit;
|
|
@@ -929,13 +1083,18 @@ var LlmBroker = class {
|
|
|
929
1083
|
return new Promise((resolve, reject) => {
|
|
930
1084
|
const timer = setTimeout(() => {
|
|
931
1085
|
this.pending.delete(requestId);
|
|
1086
|
+
logBroker(
|
|
1087
|
+
`timeout request=${requestId.slice(0, 8)} pending=${this.pending.size} after=${this.timeoutMs}ms`
|
|
1088
|
+
);
|
|
932
1089
|
reject(
|
|
933
1090
|
new Error(
|
|
934
1091
|
`llm_broker_timeout: no response within ${this.timeoutMs}ms`
|
|
935
1092
|
)
|
|
936
1093
|
);
|
|
937
1094
|
}, this.timeoutMs);
|
|
938
|
-
|
|
1095
|
+
if (this.timeoutMs >= 1e3) {
|
|
1096
|
+
timer.unref?.();
|
|
1097
|
+
}
|
|
939
1098
|
this.pending.set(requestId, { resolve, reject, timer, observer });
|
|
940
1099
|
const effectiveProvider = this.overrideProvider ?? payload.provider;
|
|
941
1100
|
const effectiveModel = this.overrideModel ?? payload.model;
|
|
@@ -952,6 +1111,9 @@ var LlmBroker = class {
|
|
|
952
1111
|
}
|
|
953
1112
|
};
|
|
954
1113
|
try {
|
|
1114
|
+
logBroker(
|
|
1115
|
+
`emit request=${requestId.slice(0, 8)} provider=${effectiveProvider ?? "(default)"} model=${effectiveModel ?? "(default)"} messages=${payload.messages.length} tools=${payload.tools?.length ?? 0}`
|
|
1116
|
+
);
|
|
955
1117
|
this.emit(envelope);
|
|
956
1118
|
} catch (err) {
|
|
957
1119
|
clearTimeout(timer);
|
|
@@ -976,7 +1138,14 @@ var LlmBroker = class {
|
|
|
976
1138
|
* route (e.g. for providers that don't support streaming). */
|
|
977
1139
|
fulfill(requestId, response) {
|
|
978
1140
|
const entry = this.pending.get(requestId);
|
|
979
|
-
if (!entry)
|
|
1141
|
+
if (!entry) {
|
|
1142
|
+
logBroker(`fulfill_miss request=${requestId.slice(0, 8)}`);
|
|
1143
|
+
return false;
|
|
1144
|
+
}
|
|
1145
|
+
const toolNames = (response.toolCalls ?? []).map((tc) => tc.function?.name ?? "?").join(",");
|
|
1146
|
+
logBroker(
|
|
1147
|
+
`fulfill request=${requestId.slice(0, 8)} text=${response.text.length} toolCalls=${response.toolCalls?.length ?? 0}${toolNames ? ` tools=[${toolNames}]` : ""} finish=${response.finishReason ?? "(none)"}`
|
|
1148
|
+
);
|
|
980
1149
|
clearTimeout(entry.timer);
|
|
981
1150
|
this.pending.delete(requestId);
|
|
982
1151
|
if (entry.observer) {
|
|
@@ -1005,7 +1174,15 @@ var LlmBroker = class {
|
|
|
1005
1174
|
* invoked; a `complete` chunk also resolves the pending Promise. */
|
|
1006
1175
|
pushChunk(requestId, chunk) {
|
|
1007
1176
|
const entry = this.pending.get(requestId);
|
|
1008
|
-
if (!entry)
|
|
1177
|
+
if (!entry) {
|
|
1178
|
+
logBroker(
|
|
1179
|
+
`chunk_miss request=${requestId.slice(0, 8)} type=${chunk.type}`
|
|
1180
|
+
);
|
|
1181
|
+
return false;
|
|
1182
|
+
}
|
|
1183
|
+
logBroker(
|
|
1184
|
+
`chunk request=${requestId.slice(0, 8)} type=${chunk.type}${chunk.type === "text_delta" ? ` chars=${chunk.text.length}` : ""}${chunk.type === "tool_call_delta" ? ` index=${chunk.index} name=${chunk.name ?? "(delta)"} argsDelta=${chunk.argumentsDelta?.length ?? 0}` : ""}`
|
|
1185
|
+
);
|
|
1009
1186
|
if (entry.observer) {
|
|
1010
1187
|
try {
|
|
1011
1188
|
entry.observer(chunk);
|
|
@@ -1027,7 +1204,11 @@ var LlmBroker = class {
|
|
|
1027
1204
|
* failure rather than a completion. */
|
|
1028
1205
|
fail(requestId, message) {
|
|
1029
1206
|
const entry = this.pending.get(requestId);
|
|
1030
|
-
if (!entry)
|
|
1207
|
+
if (!entry) {
|
|
1208
|
+
logBroker(`fail_miss request=${requestId.slice(0, 8)}`);
|
|
1209
|
+
return false;
|
|
1210
|
+
}
|
|
1211
|
+
logBroker(`fail request=${requestId.slice(0, 8)} message=${message}`);
|
|
1031
1212
|
clearTimeout(entry.timer);
|
|
1032
1213
|
this.pending.delete(requestId);
|
|
1033
1214
|
entry.reject(new Error(message));
|
|
@@ -1036,6 +1217,9 @@ var LlmBroker = class {
|
|
|
1036
1217
|
/** Reject all in-flight requests. Called when the parent SSE stream
|
|
1037
1218
|
* closes so a stranded request doesn't sit forever. */
|
|
1038
1219
|
close(reason = "broker_closed") {
|
|
1220
|
+
if (this.pending.size > 0) {
|
|
1221
|
+
logBroker(`close reason=${reason} pending=${this.pending.size}`);
|
|
1222
|
+
}
|
|
1039
1223
|
this.closed = true;
|
|
1040
1224
|
for (const [, entry] of this.pending.entries()) {
|
|
1041
1225
|
clearTimeout(entry.timer);
|
|
@@ -1092,6 +1276,9 @@ var writeEvent = (res, event) => {
|
|
|
1092
1276
|
var writeDone = (res) => {
|
|
1093
1277
|
res.write("data: [DONE]\n\n");
|
|
1094
1278
|
};
|
|
1279
|
+
var logAgentRoute = (message) => {
|
|
1280
|
+
console.error(`[agent-route] ${message}`);
|
|
1281
|
+
};
|
|
1095
1282
|
var createAgentRouter = (deps) => {
|
|
1096
1283
|
const router = createRouter2();
|
|
1097
1284
|
router.post("/message", async (req, res) => {
|
|
@@ -1105,6 +1292,9 @@ var createAgentRouter = (deps) => {
|
|
|
1105
1292
|
res.status(409).json({ error: "no_repo_selected" });
|
|
1106
1293
|
return;
|
|
1107
1294
|
}
|
|
1295
|
+
logAgentRoute(
|
|
1296
|
+
`message start session=${parsed.data.sessionId.slice(0, 8)} repo=${repo.path} promptChars=${parsed.data.prompt.length} provider=${parsed.data.preferredProvider ?? "(default)"} model=${parsed.data.preferredModel ?? "(default)"}`
|
|
1297
|
+
);
|
|
1108
1298
|
res.setHeader("Content-Type", "text/event-stream");
|
|
1109
1299
|
res.setHeader("Cache-Control", "no-cache, no-transform");
|
|
1110
1300
|
res.setHeader("Connection", "keep-alive");
|
|
@@ -1130,21 +1320,33 @@ var createAgentRouter = (deps) => {
|
|
|
1130
1320
|
prompt: parsed.data.prompt,
|
|
1131
1321
|
history,
|
|
1132
1322
|
signal: controller.signal,
|
|
1133
|
-
broker: (payload) => broker.request(payload),
|
|
1323
|
+
broker: (payload, observer) => broker.request(payload, observer),
|
|
1134
1324
|
preferredProvider: parsed.data.preferredProvider,
|
|
1135
1325
|
preferredModel: parsed.data.preferredModel
|
|
1136
1326
|
})) {
|
|
1137
1327
|
if (controller.signal.aborted) {
|
|
1328
|
+
logAgentRoute(
|
|
1329
|
+
`message aborted session=${parsed.data.sessionId.slice(0, 8)}`
|
|
1330
|
+
);
|
|
1138
1331
|
break;
|
|
1139
1332
|
}
|
|
1333
|
+
logAgentRoute(
|
|
1334
|
+
`stream event session=${parsed.data.sessionId.slice(0, 8)} type=${event.type}`
|
|
1335
|
+
);
|
|
1140
1336
|
writeEvent(res, event);
|
|
1141
1337
|
}
|
|
1338
|
+
logAgentRoute(
|
|
1339
|
+
`message done session=${parsed.data.sessionId.slice(0, 8)}`
|
|
1340
|
+
);
|
|
1142
1341
|
writeDone(res);
|
|
1143
1342
|
} catch (err) {
|
|
1144
1343
|
if (err && typeof err === "object" && "name" in err && err.name === "AbortError") {
|
|
1145
1344
|
writeDone(res);
|
|
1146
1345
|
} else {
|
|
1147
1346
|
const message = err instanceof Error ? err.message : "Adapter stream failed";
|
|
1347
|
+
logAgentRoute(
|
|
1348
|
+
`message error session=${parsed.data.sessionId.slice(0, 8)} error=${message}`
|
|
1349
|
+
);
|
|
1148
1350
|
writeEvent(res, { type: "error", message });
|
|
1149
1351
|
writeDone(res);
|
|
1150
1352
|
}
|
|
@@ -1161,7 +1363,10 @@ var llmResponseBodySchema = z2.object({
|
|
|
1161
1363
|
text: z2.string(),
|
|
1162
1364
|
provider: z2.string().min(1),
|
|
1163
1365
|
model: z2.string().min(1),
|
|
1164
|
-
tokenUsage: z2.object({
|
|
1366
|
+
tokenUsage: z2.object({
|
|
1367
|
+
input: z2.number().nonnegative(),
|
|
1368
|
+
output: z2.number().nonnegative()
|
|
1369
|
+
}).optional(),
|
|
1165
1370
|
error: z2.string().optional(),
|
|
1166
1371
|
toolCalls: z2.array(
|
|
1167
1372
|
z2.object({
|
|
@@ -2266,6 +2471,11 @@ var createEngineLocator = async (name) => {
|
|
|
2266
2471
|
init_engine_locator_npm();
|
|
2267
2472
|
var DIOLOGUE_PROVIDER_ID = "diologue";
|
|
2268
2473
|
var DIOLOGUE_MODEL_ID = "diologue-routed";
|
|
2474
|
+
var END_OF_TURN_GRACE_MS = 600;
|
|
2475
|
+
var EDIT_DIRECTIVE = "You are an autonomous coding agent operating on a real git repository. Carry out the request by editing files directly with your tools (write / edit / patch / bash) \u2014 do not just describe the change, outline steps, or print code for the user to copy. Read only what you need, make the edits on disk, and finish once the change has actually been written.";
|
|
2476
|
+
var logAdapter = (message) => {
|
|
2477
|
+
console.error(`[opencode-adapter] ${message}`);
|
|
2478
|
+
};
|
|
2269
2479
|
var OpenCodeProcessAdapter = class {
|
|
2270
2480
|
constructor(options = {}) {
|
|
2271
2481
|
this.options = options;
|
|
@@ -2316,7 +2526,16 @@ var OpenCodeProcessAdapter = class {
|
|
|
2316
2526
|
}
|
|
2317
2527
|
}
|
|
2318
2528
|
},
|
|
2319
|
-
model: `${DIOLOGUE_PROVIDER_ID}/${DIOLOGUE_MODEL_ID}
|
|
2529
|
+
model: `${DIOLOGUE_PROVIDER_ID}/${DIOLOGUE_MODEL_ID}`,
|
|
2530
|
+
// Auto-approve tool execution. opencode gates edit/bash/webfetch
|
|
2531
|
+
// behind a permission prompt; this helper runs headless with no
|
|
2532
|
+
// approver, so without this the agent's writes are never applied to
|
|
2533
|
+
// the repo and a turn finishes having changed nothing.
|
|
2534
|
+
permission: {
|
|
2535
|
+
edit: "allow",
|
|
2536
|
+
bash: "allow",
|
|
2537
|
+
webfetch: "allow"
|
|
2538
|
+
}
|
|
2320
2539
|
};
|
|
2321
2540
|
}
|
|
2322
2541
|
async ensureServer() {
|
|
@@ -2325,7 +2544,10 @@ var OpenCodeProcessAdapter = class {
|
|
|
2325
2544
|
}
|
|
2326
2545
|
this.serverPromise = (async () => {
|
|
2327
2546
|
const locator = await this.resolveLocator();
|
|
2328
|
-
|
|
2547
|
+
logAdapter(`starting engine helperBase=${this.resolveHelperBaseUrl()}`);
|
|
2548
|
+
const handle = await locator.start({ config: this.buildShimConfig() });
|
|
2549
|
+
logAdapter(`engine ready url=${handle.url}`);
|
|
2550
|
+
return handle;
|
|
2329
2551
|
})();
|
|
2330
2552
|
try {
|
|
2331
2553
|
return await this.serverPromise;
|
|
@@ -2336,7 +2558,13 @@ var OpenCodeProcessAdapter = class {
|
|
|
2336
2558
|
}
|
|
2337
2559
|
async ensureOpencodeSession(handle, ourSessionId, repoPath, title) {
|
|
2338
2560
|
const existing = this.sessionMap.get(ourSessionId);
|
|
2339
|
-
if (existing)
|
|
2561
|
+
if (existing) {
|
|
2562
|
+
logAdapter(
|
|
2563
|
+
`reuse opencodeSession=${existing.slice(0, 8)} session=${ourSessionId.slice(0, 8)} repo=${repoPath}`
|
|
2564
|
+
);
|
|
2565
|
+
return existing;
|
|
2566
|
+
}
|
|
2567
|
+
logAdapter(`create session=${ourSessionId.slice(0, 8)} repo=${repoPath}`);
|
|
2340
2568
|
const created = await handle.client.session.create({
|
|
2341
2569
|
body: { title },
|
|
2342
2570
|
query: { directory: repoPath }
|
|
@@ -2346,6 +2574,9 @@ var OpenCodeProcessAdapter = class {
|
|
|
2346
2574
|
throw new Error("opencode session.create returned no id");
|
|
2347
2575
|
}
|
|
2348
2576
|
this.sessionMap.set(ourSessionId, id);
|
|
2577
|
+
logAdapter(
|
|
2578
|
+
`created opencodeSession=${id.slice(0, 8)} session=${ourSessionId.slice(0, 8)}`
|
|
2579
|
+
);
|
|
2349
2580
|
return id;
|
|
2350
2581
|
}
|
|
2351
2582
|
async *streamMessage(request) {
|
|
@@ -2391,16 +2622,16 @@ var OpenCodeProcessAdapter = class {
|
|
|
2391
2622
|
}
|
|
2392
2623
|
});
|
|
2393
2624
|
const realCallback = callback;
|
|
2394
|
-
streamScopedBroker.request = async (payload) => {
|
|
2395
|
-
return realCallback(payload);
|
|
2625
|
+
streamScopedBroker.request = async (payload, observer) => {
|
|
2626
|
+
return realCallback(payload, observer);
|
|
2396
2627
|
};
|
|
2397
2628
|
activeBrokerHandle = bindActiveBroker(streamScopedBroker);
|
|
2398
|
-
|
|
2399
|
-
`
|
|
2629
|
+
logAdapter(
|
|
2630
|
+
`bound active broker token="${activeBrokerHandle.token.slice(0, 12)}\u2026" session=${request.sessionId.slice(0, 8)}`
|
|
2400
2631
|
);
|
|
2401
2632
|
} else {
|
|
2402
|
-
|
|
2403
|
-
`
|
|
2633
|
+
logAdapter(
|
|
2634
|
+
`WARNING request.broker missing session=${request.sessionId.slice(0, 8)}; shim will 401`
|
|
2404
2635
|
);
|
|
2405
2636
|
}
|
|
2406
2637
|
const eventController = new AbortController();
|
|
@@ -2417,6 +2648,9 @@ var OpenCodeProcessAdapter = class {
|
|
|
2417
2648
|
signal: eventController.signal
|
|
2418
2649
|
});
|
|
2419
2650
|
eventStream = res.stream;
|
|
2651
|
+
logAdapter(
|
|
2652
|
+
`subscribed events session=${request.sessionId.slice(0, 8)} opencodeSession=${openCodeSessionId.slice(0, 8)}`
|
|
2653
|
+
);
|
|
2420
2654
|
} catch (err) {
|
|
2421
2655
|
yield {
|
|
2422
2656
|
type: "error",
|
|
@@ -2427,7 +2661,11 @@ var OpenCodeProcessAdapter = class {
|
|
|
2427
2661
|
return;
|
|
2428
2662
|
}
|
|
2429
2663
|
const promptBody = {
|
|
2430
|
-
parts: [
|
|
2664
|
+
parts: [
|
|
2665
|
+
{ type: "text", text: `${EDIT_DIRECTIVE}
|
|
2666
|
+
|
|
2667
|
+
${request.prompt}` }
|
|
2668
|
+
]
|
|
2431
2669
|
};
|
|
2432
2670
|
if (streamScopedBroker && activeBrokerHandle) {
|
|
2433
2671
|
promptBody.model = {
|
|
@@ -2435,22 +2673,63 @@ var OpenCodeProcessAdapter = class {
|
|
|
2435
2673
|
modelID: DIOLOGUE_MODEL_ID
|
|
2436
2674
|
};
|
|
2437
2675
|
}
|
|
2676
|
+
logAdapter(
|
|
2677
|
+
`prompt start session=${request.sessionId.slice(0, 8)} opencodeSession=${openCodeSessionId.slice(0, 8)} promptChars=${request.prompt.length} provider=${request.preferredProvider ?? "(default)"} model=${request.preferredModel ?? "(default)"}`
|
|
2678
|
+
);
|
|
2438
2679
|
const promptPromise = handle.client.session.prompt({
|
|
2439
2680
|
path: { id: openCodeSessionId },
|
|
2440
2681
|
query: { directory: request.repoPath },
|
|
2441
2682
|
body: promptBody
|
|
2683
|
+
}).then((value) => {
|
|
2684
|
+
logAdapter(
|
|
2685
|
+
`prompt accepted session=${request.sessionId.slice(0, 8)} opencodeSession=${openCodeSessionId.slice(0, 8)}`
|
|
2686
|
+
);
|
|
2687
|
+
return value;
|
|
2442
2688
|
}).catch((err) => {
|
|
2443
|
-
|
|
2689
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
2690
|
+
logAdapter(
|
|
2691
|
+
`prompt failed session=${request.sessionId.slice(0, 8)} opencodeSession=${openCodeSessionId.slice(0, 8)} error=${error.message}`
|
|
2692
|
+
);
|
|
2693
|
+
return error;
|
|
2694
|
+
});
|
|
2695
|
+
let stopConsuming = false;
|
|
2696
|
+
const stopSignal = new Promise((resolve) => {
|
|
2697
|
+
void promptPromise.finally(() => {
|
|
2698
|
+
setTimeout(() => {
|
|
2699
|
+
stopConsuming = true;
|
|
2700
|
+
resolve("stop");
|
|
2701
|
+
}, END_OF_TURN_GRACE_MS);
|
|
2702
|
+
});
|
|
2444
2703
|
});
|
|
2704
|
+
let emittedDone = false;
|
|
2705
|
+
const iterator = eventStream[Symbol.asyncIterator]();
|
|
2445
2706
|
try {
|
|
2446
|
-
|
|
2707
|
+
while (!stopConsuming) {
|
|
2447
2708
|
if (request.signal?.aborted) break;
|
|
2709
|
+
const nextP = iterator.next();
|
|
2710
|
+
const next = await Promise.race([
|
|
2711
|
+
nextP,
|
|
2712
|
+
stopSignal.then(() => null)
|
|
2713
|
+
]);
|
|
2714
|
+
if (next === null) {
|
|
2715
|
+
void nextP.catch(() => void 0);
|
|
2716
|
+
break;
|
|
2717
|
+
}
|
|
2718
|
+
if (next.done) break;
|
|
2719
|
+
const rawItem = next.value;
|
|
2720
|
+
const item = "payload" in rawItem && rawItem.payload ? rawItem.payload : rawItem;
|
|
2448
2721
|
const mapped = mapEvent(item, mapState);
|
|
2722
|
+
if (mapped.events.length > 0 || mapped.done) {
|
|
2723
|
+
logAdapter(
|
|
2724
|
+
`event ${item.type} mapped=${mapped.events.map((event) => event.type).join(",") || "(none)"} done=${mapped.done} session=${request.sessionId.slice(0, 8)}`
|
|
2725
|
+
);
|
|
2726
|
+
}
|
|
2449
2727
|
for (const event of mapped.events) {
|
|
2450
2728
|
if (event.type === "diff_proposed" && mapped.patchToFetch) {
|
|
2451
2729
|
sawAnyPatch = true;
|
|
2452
2730
|
lastPatchHash = mapped.patchToFetch.hash;
|
|
2453
2731
|
}
|
|
2732
|
+
if (event.type === "done") emittedDone = true;
|
|
2454
2733
|
yield event;
|
|
2455
2734
|
}
|
|
2456
2735
|
if (mapped.done) {
|
|
@@ -2459,31 +2738,42 @@ var OpenCodeProcessAdapter = class {
|
|
|
2459
2738
|
}
|
|
2460
2739
|
} finally {
|
|
2461
2740
|
eventController.abort();
|
|
2741
|
+
try {
|
|
2742
|
+
await iterator.return?.();
|
|
2743
|
+
} catch {
|
|
2744
|
+
}
|
|
2462
2745
|
request.signal?.removeEventListener("abort", stopOnAbort);
|
|
2463
2746
|
activeBrokerHandle?.release();
|
|
2464
2747
|
streamScopedBroker?.close("turn_ended");
|
|
2465
2748
|
}
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
};
|
|
2479
|
-
}
|
|
2480
|
-
} catch {
|
|
2749
|
+
try {
|
|
2750
|
+
const diffRes = await handle.client.session.diff({
|
|
2751
|
+
path: { id: openCodeSessionId },
|
|
2752
|
+
query: { directory: request.repoPath }
|
|
2753
|
+
});
|
|
2754
|
+
const unified = diffRes.data?.unified ?? "";
|
|
2755
|
+
if (unified) {
|
|
2756
|
+
yield {
|
|
2757
|
+
type: "diff_proposed",
|
|
2758
|
+
unified,
|
|
2759
|
+
files: parseUnifiedDiffFiles(unified)
|
|
2760
|
+
};
|
|
2481
2761
|
}
|
|
2762
|
+
} catch (err) {
|
|
2763
|
+
logAdapter(
|
|
2764
|
+
`diff fetch failed session=${request.sessionId.slice(0, 8)} error=${err instanceof Error ? err.message : String(err)}`
|
|
2765
|
+
);
|
|
2482
2766
|
}
|
|
2483
2767
|
const promptResult = await promptPromise;
|
|
2484
2768
|
if (promptResult instanceof Error) {
|
|
2485
2769
|
yield { type: "error", message: promptResult.message, recoverable: true };
|
|
2770
|
+
} else {
|
|
2771
|
+
logAdapter(`turn complete session=${request.sessionId.slice(0, 8)}`);
|
|
2772
|
+
}
|
|
2773
|
+
if (!emittedDone) {
|
|
2774
|
+
yield { type: "done" };
|
|
2486
2775
|
}
|
|
2776
|
+
void sawAnyPatch;
|
|
2487
2777
|
void lastPatchHash;
|
|
2488
2778
|
}
|
|
2489
2779
|
async close() {
|