subharness 0.0.3 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -9
- package/dist/adapters/approval-context.d.ts +2 -0
- package/dist/adapters/approval-context.js +9 -0
- package/dist/adapters/approval-context.js.map +1 -0
- package/dist/adapters/approval-lifetime.d.ts +8 -0
- package/dist/adapters/approval-lifetime.js +50 -0
- package/dist/adapters/approval-lifetime.js.map +1 -0
- package/dist/adapters/claude-approvals.d.ts +4 -0
- package/dist/adapters/claude-approvals.js +32 -0
- package/dist/adapters/claude-approvals.js.map +1 -0
- package/dist/adapters/claude-permissions.js +1 -1
- package/dist/adapters/claude-permissions.js.map +1 -1
- package/dist/adapters/claude-process.d.ts +3 -0
- package/dist/adapters/claude-process.js +11 -1
- package/dist/adapters/claude-process.js.map +1 -1
- package/dist/adapters/claude-result.d.ts +2 -0
- package/dist/adapters/claude-result.js +22 -0
- package/dist/adapters/claude-result.js.map +1 -0
- package/dist/adapters/claude.js +60 -12
- package/dist/adapters/claude.js.map +1 -1
- package/dist/adapters/codex-approvals.d.ts +6 -0
- package/dist/adapters/codex-approvals.js +111 -0
- package/dist/adapters/codex-approvals.js.map +1 -0
- package/dist/adapters/codex.js +36 -3
- package/dist/adapters/codex.js.map +1 -1
- package/dist/adapters/copilot-permissions.d.ts +26 -0
- package/dist/adapters/copilot-permissions.js +121 -0
- package/dist/adapters/copilot-permissions.js.map +1 -0
- package/dist/adapters/copilot-tools.d.ts +12 -0
- package/dist/adapters/copilot-tools.js +61 -0
- package/dist/adapters/copilot-tools.js.map +1 -0
- package/dist/adapters/copilot.d.ts +108 -0
- package/dist/adapters/copilot.js +819 -0
- package/dist/adapters/copilot.js.map +1 -0
- package/dist/adapters/cursor-cli-approvals.d.ts +2 -0
- package/dist/adapters/cursor-cli-approvals.js +83 -0
- package/dist/adapters/cursor-cli-approvals.js.map +1 -0
- package/dist/adapters/cursor-cli-model.d.ts +4 -0
- package/dist/adapters/cursor-cli-model.js +64 -0
- package/dist/adapters/cursor-cli-model.js.map +1 -0
- package/dist/adapters/cursor-cli-session.d.ts +32 -0
- package/dist/adapters/cursor-cli-session.js +316 -0
- package/dist/adapters/cursor-cli-session.js.map +1 -0
- package/dist/adapters/cursor-cli-tools.d.ts +20 -0
- package/dist/adapters/cursor-cli-tools.js +181 -0
- package/dist/adapters/cursor-cli-tools.js.map +1 -0
- package/dist/adapters/cursor-cli.d.ts +2 -0
- package/dist/adapters/cursor-cli.js +306 -0
- package/dist/adapters/cursor-cli.js.map +1 -0
- package/dist/adapters/cursor-model.d.ts +7 -0
- package/dist/adapters/cursor-model.js +67 -0
- package/dist/adapters/cursor-model.js.map +1 -0
- package/dist/adapters/cursor-rpc.d.ts +42 -0
- package/dist/adapters/cursor-rpc.js +271 -0
- package/dist/adapters/cursor-rpc.js.map +1 -0
- package/dist/adapters/cursor-session.d.ts +4 -0
- package/dist/adapters/cursor-session.js +327 -0
- package/dist/adapters/cursor-session.js.map +1 -0
- package/dist/adapters/cursor-startup.d.ts +16 -0
- package/dist/adapters/cursor-startup.js +74 -0
- package/dist/adapters/cursor-startup.js.map +1 -0
- package/dist/adapters/cursor-tools.d.ts +11 -0
- package/dist/adapters/cursor-tools.js +49 -0
- package/dist/adapters/cursor-tools.js.map +1 -0
- package/dist/adapters/cursor.d.ts +6 -0
- package/dist/adapters/cursor.js +17 -0
- package/dist/adapters/cursor.js.map +1 -0
- package/dist/adapters/fx-approvals.d.ts +2 -0
- package/dist/adapters/fx-approvals.js +14 -0
- package/dist/adapters/fx-approvals.js.map +1 -0
- package/dist/adapters/fx-auth.d.ts +12 -2
- package/dist/adapters/fx-auth.js +51 -61
- package/dist/adapters/fx-auth.js.map +1 -1
- package/dist/adapters/fx-profile.d.ts +8 -0
- package/dist/adapters/fx-profile.js +101 -0
- package/dist/adapters/fx-profile.js.map +1 -0
- package/dist/adapters/fx-rpc.d.ts +2 -0
- package/dist/adapters/fx-rpc.js +30 -4
- package/dist/adapters/fx-rpc.js.map +1 -1
- package/dist/adapters/fx-status.d.ts +2 -0
- package/dist/adapters/fx-status.js +96 -0
- package/dist/adapters/fx-status.js.map +1 -0
- package/dist/adapters/fx.js +77 -30
- package/dist/adapters/fx.js.map +1 -1
- package/dist/adapters/opencode-access.d.ts +13 -0
- package/dist/adapters/opencode-access.js +76 -0
- package/dist/adapters/opencode-access.js.map +1 -0
- package/dist/adapters/opencode-config.d.ts +11 -0
- package/dist/adapters/opencode-config.js +238 -0
- package/dist/adapters/opencode-config.js.map +1 -0
- package/dist/adapters/opencode-http.d.ts +28 -0
- package/dist/adapters/opencode-http.js +297 -0
- package/dist/adapters/opencode-http.js.map +1 -0
- package/dist/adapters/opencode-tools.d.ts +19 -0
- package/dist/adapters/opencode-tools.js +127 -0
- package/dist/adapters/opencode-tools.js.map +1 -0
- package/dist/adapters/opencode.d.ts +2 -0
- package/dist/adapters/opencode.js +569 -0
- package/dist/adapters/opencode.js.map +1 -0
- package/dist/adapters/rpc.d.ts +3 -0
- package/dist/adapters/rpc.js +45 -4
- package/dist/adapters/rpc.js.map +1 -1
- package/dist/adapters/types.d.ts +7 -0
- package/dist/adapters/types.js.map +1 -1
- package/dist/approvals/schema.d.ts +4 -0
- package/dist/approvals/schema.js +70 -0
- package/dist/approvals/schema.js.map +1 -0
- package/dist/approvals/types.d.ts +47 -0
- package/dist/approvals/types.js +2 -0
- package/dist/approvals/types.js.map +1 -0
- package/dist/cli/args.d.ts +3 -2
- package/dist/cli/args.js +24 -3
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/dashboard-client.d.ts +5 -1
- package/dist/cli/dashboard-client.js +162 -15
- package/dist/cli/dashboard-client.js.map +1 -1
- package/dist/cli/dashboard-controller.d.ts +7 -0
- package/dist/cli/dashboard-controller.js +27 -0
- package/dist/cli/dashboard-controller.js.map +1 -0
- package/dist/cli/dashboard-detail-view.d.ts +16 -0
- package/dist/cli/dashboard-detail-view.js +162 -0
- package/dist/cli/dashboard-detail-view.js.map +1 -0
- package/dist/cli/dashboard-history-view.d.ts +29 -0
- package/dist/cli/dashboard-history-view.js +211 -0
- package/dist/cli/dashboard-history-view.js.map +1 -0
- package/dist/cli/dashboard-input.d.ts +14 -0
- package/dist/cli/dashboard-input.js +128 -0
- package/dist/cli/dashboard-input.js.map +1 -0
- package/dist/cli/dashboard-layout.d.ts +12 -1
- package/dist/cli/dashboard-layout.js +32 -30
- package/dist/cli/dashboard-layout.js.map +1 -1
- package/dist/cli/dashboard-renderer.d.ts +30 -2
- package/dist/cli/dashboard-renderer.js +125 -7
- package/dist/cli/dashboard-renderer.js.map +1 -1
- package/dist/cli/dashboard-style.d.ts +9 -0
- package/dist/cli/dashboard-style.js +36 -0
- package/dist/cli/dashboard-style.js.map +1 -0
- package/dist/cli/dashboard.d.ts +5 -3
- package/dist/cli/dashboard.js +102 -12
- package/dist/cli/dashboard.js.map +1 -1
- package/dist/cli/help.d.ts +1 -1
- package/dist/cli/help.js +37 -12
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/input.d.ts +1 -0
- package/dist/cli/input.js +23 -17
- package/dist/cli/input.js.map +1 -1
- package/dist/cli/main.js +6 -2
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/output.js +17 -1
- package/dist/cli/output.js.map +1 -1
- package/dist/config/access-provenance.d.ts +10 -0
- package/dist/config/access-provenance.js +31 -0
- package/dist/config/access-provenance.js.map +1 -0
- package/dist/config/access.d.ts +12 -2
- package/dist/config/access.js +125 -27
- package/dist/config/access.js.map +1 -1
- package/dist/config/loader.js +1 -0
- package/dist/config/loader.js.map +1 -1
- package/dist/config/oidc.js +4 -2
- package/dist/config/oidc.js.map +1 -1
- package/dist/config/project.js +2 -1
- package/dist/config/project.js.map +1 -1
- package/dist/config/resolve-access.js +24 -10
- package/dist/config/resolve-access.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/process-diagnostics.d.ts +2 -0
- package/dist/process-diagnostics.js +11 -0
- package/dist/process-diagnostics.js.map +1 -0
- package/dist/runtime/access-errors.d.ts +4 -0
- package/dist/runtime/access-errors.js +25 -0
- package/dist/runtime/access-errors.js.map +1 -0
- package/dist/runtime/approval-registry.d.ts +27 -0
- package/dist/runtime/approval-registry.js +126 -0
- package/dist/runtime/approval-registry.js.map +1 -0
- package/dist/runtime/client.js +54 -14
- package/dist/runtime/client.js.map +1 -1
- package/dist/runtime/coordinator.d.ts +17 -1
- package/dist/runtime/coordinator.js +124 -6
- package/dist/runtime/coordinator.js.map +1 -1
- package/dist/runtime/dashboard-sanitize.d.ts +2 -0
- package/dist/runtime/dashboard-sanitize.js +7 -0
- package/dist/runtime/dashboard-sanitize.js.map +1 -0
- package/dist/runtime/dashboard.d.ts +43 -1
- package/dist/runtime/dashboard.js +2 -3
- package/dist/runtime/dashboard.js.map +1 -1
- package/dist/runtime/definition.js +11 -1
- package/dist/runtime/definition.js.map +1 -1
- package/dist/runtime/select-native.d.ts +2 -1
- package/dist/runtime/select-native.js +28 -10
- package/dist/runtime/select-native.js.map +1 -1
- package/dist/runtime/service.js +24 -1
- package/dist/runtime/service.js.map +1 -1
- package/dist/runtime/types.d.ts +9 -1
- package/dist/runtime/types.js.map +1 -1
- package/dist/runtime/worker-approvals.d.ts +21 -0
- package/dist/runtime/worker-approvals.js +51 -0
- package/dist/runtime/worker-approvals.js.map +1 -0
- package/dist/runtime/worker-client.js +73 -20
- package/dist/runtime/worker-client.js.map +1 -1
- package/dist/runtime/worker-server.d.ts +2 -0
- package/dist/runtime/worker-server.js +39 -10
- package/dist/runtime/worker-server.js.map +1 -1
- package/dist/sdk/definitions.d.ts +4 -1
- package/dist/sdk/definitions.js +16 -2
- package/dist/sdk/definitions.js.map +1 -1
- package/dist/sdk/permission-validation.js +10 -1
- package/dist/sdk/permission-validation.js.map +1 -1
- package/dist/sdk/types.d.ts +29 -1
- package/dist/sdk/types.js.map +1 -1
- package/package.json +4 -1
- package/sdk/access-config.md +86 -4
- package/sdk/adapter-contract.md +7 -13
- package/sdk/additional-harnesses.md +43 -0
- package/sdk/agent-skill.md +7 -4
- package/sdk/agent.md +5 -3
- package/sdk/approvals.md +120 -0
- package/sdk/authentication.md +1 -1
- package/sdk/cli/dashboard-design.md +44 -4
- package/sdk/cli/dashboard-inspection.md +25 -0
- package/sdk/cli/index.md +20 -11
- package/sdk/cli/output.md +10 -4
- package/sdk/completion-notifications.md +2 -0
- package/sdk/config.md +1 -1
- package/sdk/copilot.md +53 -0
- package/sdk/cursor.md +68 -0
- package/sdk/diagnostics.md +29 -0
- package/sdk/distribution.md +53 -1
- package/sdk/evals.md +10 -4
- package/sdk/fx.md +51 -6
- package/sdk/harnesses.md +13 -13
- package/sdk/index.md +4 -2
- package/sdk/message-delivery.md +1 -1
- package/sdk/opencode.md +57 -0
- package/sdk/permissions.md +7 -5
- package/sdk/pr-integration.md +1 -1
- package/sdk/project-team.md +2 -0
- package/sdk/sessions.md +4 -1
- package/sdk/tools.md +1 -1
- package/sdk/v1-runtime.md +6 -6
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { AgentError } from "../errors.js";
|
|
3
|
+
const startupTimeout = 5_000;
|
|
4
|
+
const versionTimeout = 5_000;
|
|
5
|
+
const outputLimit = 64 * 1024;
|
|
6
|
+
const terminationTimeout = 2_000;
|
|
7
|
+
const ownsProcessGroup = process.platform !== "win32";
|
|
8
|
+
export const supportedOpencodeVersion = "1.18.32";
|
|
9
|
+
export async function startOpencodeProcess(cwd, env) {
|
|
10
|
+
await verifyVersion(cwd, env);
|
|
11
|
+
let child;
|
|
12
|
+
try {
|
|
13
|
+
child = spawn("opencode", ["serve", "--hostname", "127.0.0.1", "--port", "0", "--no-mdns"], {
|
|
14
|
+
cwd,
|
|
15
|
+
env,
|
|
16
|
+
detached: ownsProcessGroup,
|
|
17
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
throw unavailable();
|
|
22
|
+
}
|
|
23
|
+
child.stderr.resume();
|
|
24
|
+
const exited = new Promise(resolve => {
|
|
25
|
+
child.once("close", (code, signal) => resolve({ code, signal }));
|
|
26
|
+
});
|
|
27
|
+
let output = "";
|
|
28
|
+
const url = await new Promise((resolve, reject) => {
|
|
29
|
+
let settled = false;
|
|
30
|
+
const finish = (error, value) => {
|
|
31
|
+
if (settled)
|
|
32
|
+
return;
|
|
33
|
+
settled = true;
|
|
34
|
+
clearTimeout(timer);
|
|
35
|
+
child.stdout.off("data", onData);
|
|
36
|
+
child.off("error", onError);
|
|
37
|
+
child.off("close", onExit);
|
|
38
|
+
if (error)
|
|
39
|
+
reject(error);
|
|
40
|
+
else
|
|
41
|
+
resolve(value);
|
|
42
|
+
};
|
|
43
|
+
const onData = (chunk) => {
|
|
44
|
+
output += chunk.toString("utf8");
|
|
45
|
+
if (Buffer.byteLength(output) > outputLimit)
|
|
46
|
+
return finish(new AgentError("PROTOCOL_ERROR", "OpenCode produced an oversized startup announcement."));
|
|
47
|
+
const match = output.match(/opencode server listening on (http:\/\/127\.0\.0\.1:\d+)/u);
|
|
48
|
+
if (match)
|
|
49
|
+
finish(undefined, match[1]);
|
|
50
|
+
};
|
|
51
|
+
const onError = (error) => finish(error.code === "ENOENT"
|
|
52
|
+
? unavailable()
|
|
53
|
+
: new AgentError("INVALID_CONFIG", "OpenCode native server startup failed before readiness could be verified."));
|
|
54
|
+
const onExit = () => finish(new AgentError("INVALID_CONFIG", "OpenCode exited before its native server readiness could be verified."));
|
|
55
|
+
const timer = setTimeout(() => finish(new AgentError("INVALID_CONFIG", "OpenCode native server readiness could not be verified within five seconds.")), startupTimeout);
|
|
56
|
+
timer.unref();
|
|
57
|
+
child.stdout.on("data", onData);
|
|
58
|
+
child.once("error", onError);
|
|
59
|
+
child.once("close", onExit);
|
|
60
|
+
}).catch(async (error) => {
|
|
61
|
+
await terminate(child, exited).catch(() => { });
|
|
62
|
+
throw error;
|
|
63
|
+
});
|
|
64
|
+
child.stdout.resume();
|
|
65
|
+
let closing;
|
|
66
|
+
return {
|
|
67
|
+
url,
|
|
68
|
+
child,
|
|
69
|
+
exited,
|
|
70
|
+
close() { return closing ??= terminate(child, exited); },
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function unavailable() {
|
|
74
|
+
return new AgentError("HARNESS_UNAVAILABLE", "Cannot start OpenCode. Install OpenCode 1.18.32 and make opencode available on PATH.");
|
|
75
|
+
}
|
|
76
|
+
async function verifyVersion(cwd, env) {
|
|
77
|
+
let child;
|
|
78
|
+
try {
|
|
79
|
+
child = spawn("opencode", ["--version"], {
|
|
80
|
+
cwd,
|
|
81
|
+
env: versionEnvironment(env),
|
|
82
|
+
detached: ownsProcessGroup,
|
|
83
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
throw unavailable();
|
|
88
|
+
}
|
|
89
|
+
child.stderr.resume();
|
|
90
|
+
const exited = new Promise(resolve => {
|
|
91
|
+
child.once("close", (code, signal) => resolve({ code, signal }));
|
|
92
|
+
});
|
|
93
|
+
let output = "";
|
|
94
|
+
await new Promise((resolve, reject) => {
|
|
95
|
+
let settled = false;
|
|
96
|
+
let timer;
|
|
97
|
+
const finish = (error) => {
|
|
98
|
+
if (settled)
|
|
99
|
+
return;
|
|
100
|
+
settled = true;
|
|
101
|
+
if (timer)
|
|
102
|
+
clearTimeout(timer);
|
|
103
|
+
child.stdout.off("data", onData);
|
|
104
|
+
child.off("error", onError);
|
|
105
|
+
child.off("close", onClose);
|
|
106
|
+
if (error)
|
|
107
|
+
reject(error);
|
|
108
|
+
else
|
|
109
|
+
resolve();
|
|
110
|
+
};
|
|
111
|
+
const onData = (chunk) => {
|
|
112
|
+
output += chunk.toString("utf8");
|
|
113
|
+
if (Buffer.byteLength(output) > outputLimit)
|
|
114
|
+
finish(invalidVersion());
|
|
115
|
+
};
|
|
116
|
+
const onError = (error) => finish(error.code === "ENOENT" ? unavailable() : invalidVersion());
|
|
117
|
+
const onClose = (code, signal) => {
|
|
118
|
+
finish(code === 0 && signal === null && output.trim() === supportedOpencodeVersion ? undefined : invalidVersion());
|
|
119
|
+
};
|
|
120
|
+
timer = setTimeout(() => finish(invalidVersion()), versionTimeout);
|
|
121
|
+
timer.unref();
|
|
122
|
+
child.stdout.on("data", onData);
|
|
123
|
+
child.stdout.resume();
|
|
124
|
+
child.once("error", onError);
|
|
125
|
+
child.once("close", onClose);
|
|
126
|
+
}).catch(async (error) => {
|
|
127
|
+
await terminate(child, exited).catch(() => { });
|
|
128
|
+
throw error;
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
function versionEnvironment(env) {
|
|
132
|
+
const result = { ...env };
|
|
133
|
+
for (const key of [
|
|
134
|
+
"AI_GATEWAY_API_KEY",
|
|
135
|
+
"VERCEL_OIDC_TOKEN",
|
|
136
|
+
"OPENCODE_CONFIG",
|
|
137
|
+
"OPENCODE_AUTH_CONTENT",
|
|
138
|
+
"OPENCODE_SERVER_USERNAME",
|
|
139
|
+
"OPENCODE_SERVER_PASSWORD",
|
|
140
|
+
])
|
|
141
|
+
delete result[key];
|
|
142
|
+
return result;
|
|
143
|
+
}
|
|
144
|
+
function invalidVersion() {
|
|
145
|
+
return new AgentError("INVALID_CONFIG", "OpenCode 1.18.32 is required because other native versions have not been verified for isolated execution.");
|
|
146
|
+
}
|
|
147
|
+
async function terminate(child, exited) {
|
|
148
|
+
if (child.pid === undefined) {
|
|
149
|
+
await settledWithin(exited, 250);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
if (child.exitCode !== null || child.signalCode !== null) {
|
|
153
|
+
await exited;
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
signal(child, "SIGTERM");
|
|
157
|
+
if (await settledWithin(exited, terminationTimeout))
|
|
158
|
+
return;
|
|
159
|
+
signal(child, "SIGKILL");
|
|
160
|
+
if (!await settledWithin(exited, terminationTimeout)) {
|
|
161
|
+
throw new AgentError("CANCELLATION_FAILED", "OpenCode did not confirm native process termination.");
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
function signal(child, value) {
|
|
165
|
+
let delivered = false;
|
|
166
|
+
try {
|
|
167
|
+
if (ownsProcessGroup && child.pid !== undefined) {
|
|
168
|
+
process.kill(-child.pid, value);
|
|
169
|
+
delivered = true;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
if (error.code !== "ESRCH")
|
|
174
|
+
throw new AgentError("CANCELLATION_FAILED", "OpenCode native process termination failed.");
|
|
175
|
+
}
|
|
176
|
+
try {
|
|
177
|
+
delivered = child.kill(value) || delivered;
|
|
178
|
+
}
|
|
179
|
+
catch (error) {
|
|
180
|
+
if (error.code !== "ESRCH")
|
|
181
|
+
throw new AgentError("CANCELLATION_FAILED", "OpenCode native process termination failed.");
|
|
182
|
+
}
|
|
183
|
+
if (!delivered && child.exitCode === null && child.signalCode === null) {
|
|
184
|
+
throw new AgentError("CANCELLATION_FAILED", "OpenCode native process termination could not be delivered.");
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
async function settledWithin(promise, milliseconds) {
|
|
188
|
+
let timer;
|
|
189
|
+
return await Promise.race([
|
|
190
|
+
promise.then(() => true),
|
|
191
|
+
new Promise(resolve => { timer = setTimeout(() => resolve(false), milliseconds); timer.unref(); }),
|
|
192
|
+
]).finally(() => { if (timer)
|
|
193
|
+
clearTimeout(timer); });
|
|
194
|
+
}
|
|
195
|
+
export class OpencodeHttp {
|
|
196
|
+
baseUrl;
|
|
197
|
+
directory;
|
|
198
|
+
#authorization;
|
|
199
|
+
constructor(baseUrl, directory, password) {
|
|
200
|
+
this.baseUrl = baseUrl;
|
|
201
|
+
this.directory = directory;
|
|
202
|
+
this.#authorization = `Basic ${Buffer.from(`opencode:${password}`).toString("base64")}`;
|
|
203
|
+
}
|
|
204
|
+
async json(path, init = {}) {
|
|
205
|
+
let response;
|
|
206
|
+
const timeout = init.timeout === false ? undefined : AbortSignal.timeout(init.timeout ?? 5_000);
|
|
207
|
+
const signal = init.signal && timeout ? AbortSignal.any([init.signal, timeout]) : init.signal ?? timeout;
|
|
208
|
+
try {
|
|
209
|
+
response = await fetch(this.baseUrl + path, {
|
|
210
|
+
method: init.method ?? (init.body === undefined ? "GET" : "POST"),
|
|
211
|
+
headers: {
|
|
212
|
+
authorization: this.#authorization,
|
|
213
|
+
"content-type": "application/json",
|
|
214
|
+
"x-opencode-directory": this.directory,
|
|
215
|
+
},
|
|
216
|
+
...(init.body === undefined ? {} : { body: JSON.stringify(init.body) }),
|
|
217
|
+
signal,
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
catch (error) {
|
|
221
|
+
if (init.signal?.aborted)
|
|
222
|
+
throw error;
|
|
223
|
+
if (timeout?.aborted)
|
|
224
|
+
throw new AgentError("HARNESS_ERROR", "The OpenCode native HTTP request timed out.");
|
|
225
|
+
throw new AgentError("HARNESS_ERROR", "The OpenCode native HTTP connection failed.");
|
|
226
|
+
}
|
|
227
|
+
if (!response.ok)
|
|
228
|
+
throw new AgentError("HARNESS_ERROR", `OpenCode native HTTP request failed with status ${response.status}.`);
|
|
229
|
+
const text = await response.text();
|
|
230
|
+
if (Buffer.byteLength(text) > 16 * 1024 * 1024)
|
|
231
|
+
throw new AgentError("PROTOCOL_ERROR", "OpenCode returned an oversized native protocol response.");
|
|
232
|
+
try {
|
|
233
|
+
return JSON.parse(text);
|
|
234
|
+
}
|
|
235
|
+
catch {
|
|
236
|
+
throw new AgentError("PROTOCOL_ERROR", "OpenCode returned malformed native protocol data.");
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
async events(signal, consume, ready) {
|
|
240
|
+
let response;
|
|
241
|
+
try {
|
|
242
|
+
response = await fetch(this.baseUrl + "/event", {
|
|
243
|
+
headers: { authorization: this.#authorization, "x-opencode-directory": this.directory },
|
|
244
|
+
signal,
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
catch (error) {
|
|
248
|
+
if (signal.aborted)
|
|
249
|
+
return;
|
|
250
|
+
throw new AgentError("PROTOCOL_ERROR", "OpenCode could not establish its native event stream.");
|
|
251
|
+
}
|
|
252
|
+
if (!response.ok || !response.body || !response.headers.get("content-type")?.includes("text/event-stream")) {
|
|
253
|
+
throw new AgentError("PROTOCOL_ERROR", "OpenCode returned an invalid native event stream.");
|
|
254
|
+
}
|
|
255
|
+
ready?.();
|
|
256
|
+
const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
|
|
257
|
+
const cancel = () => { void reader.cancel().catch(() => { }); };
|
|
258
|
+
signal.addEventListener("abort", cancel, { once: true });
|
|
259
|
+
let buffer = "";
|
|
260
|
+
try {
|
|
261
|
+
while (!signal.aborted) {
|
|
262
|
+
const { done, value } = await reader.read();
|
|
263
|
+
if (done)
|
|
264
|
+
break;
|
|
265
|
+
buffer += value;
|
|
266
|
+
if (Buffer.byteLength(buffer) > 1024 * 1024)
|
|
267
|
+
throw new AgentError("PROTOCOL_ERROR", "OpenCode returned an oversized native event frame.");
|
|
268
|
+
let end;
|
|
269
|
+
while ((end = buffer.indexOf("\n\n")) >= 0) {
|
|
270
|
+
const frame = buffer.slice(0, end).replaceAll("\r", "");
|
|
271
|
+
buffer = buffer.slice(end + 2);
|
|
272
|
+
for (const line of frame.split("\n")) {
|
|
273
|
+
if (!line.startsWith("data:"))
|
|
274
|
+
continue;
|
|
275
|
+
try {
|
|
276
|
+
consume(JSON.parse(line.slice(5).trim()));
|
|
277
|
+
}
|
|
278
|
+
catch {
|
|
279
|
+
throw new AgentError("PROTOCOL_ERROR", "OpenCode returned malformed native event data.");
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
catch (error) {
|
|
286
|
+
if (!signal.aborted)
|
|
287
|
+
throw error;
|
|
288
|
+
}
|
|
289
|
+
finally {
|
|
290
|
+
signal.removeEventListener("abort", cancel);
|
|
291
|
+
await reader.cancel().catch(() => { });
|
|
292
|
+
}
|
|
293
|
+
if (!signal.aborted)
|
|
294
|
+
throw new AgentError("PROTOCOL_ERROR", "The OpenCode native event stream ended unexpectedly.");
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
//# sourceMappingURL=opencode-http.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"opencode-http.js","sourceRoot":"","sources":["../../src/adapters/opencode-http.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAA4B,MAAM,oBAAoB,CAAC;AAErE,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1C,MAAM,cAAc,GAAG,KAAK,CAAC;AAC7B,MAAM,cAAc,GAAG,KAAK,CAAC;AAC7B,MAAM,WAAW,GAAG,EAAE,GAAG,IAAI,CAAC;AAC9B,MAAM,kBAAkB,GAAG,KAAK,CAAC;AACjC,MAAM,gBAAgB,GAAG,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC;AACtD,MAAM,CAAC,MAAM,wBAAwB,GAAG,SAAS,CAAC;AAUlD,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,GAAW,EAAE,GAAsB;IAC5E,MAAM,aAAa,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IAC9B,IAAI,KAAoB,CAAC;IACzB,IAAI,CAAC;QACH,KAAK,GAAG,KAAK,CAAC,UAAU,EAAE,CAAC,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,QAAQ,EAAE,GAAG,EAAE,WAAW,CAAC,EAAE;YAC1F,GAAG;YACH,GAAG;YACH,QAAQ,EAAE,gBAAgB;YAC1B,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC;SAClC,CAAC,CAAC;IACL,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,WAAW,EAAE,CAAC;IACtB,CAAC;IACD,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;IACtB,MAAM,MAAM,GAAG,IAAI,OAAO,CAAyD,OAAO,CAAC,EAAE;QAC3F,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;IACnE,CAAC,CAAC,CAAC;IAEH,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,MAAM,GAAG,GAAG,MAAM,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACxD,IAAI,OAAO,GAAG,KAAK,CAAC;QACpB,MAAM,MAAM,GAAG,CAAC,KAAkB,EAAE,KAAc,EAAE,EAAE;YACpD,IAAI,OAAO;gBAAE,OAAO;YACpB,OAAO,GAAG,IAAI,CAAC;YACf,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;YACjC,KAAK,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAC5B,KAAK,CAAC,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;YAC3B,IAAI,KAAK;gBAAE,MAAM,CAAC,KAAK,CAAC,CAAC;;gBAAM,OAAO,CAAC,KAAM,CAAC,CAAC;QACjD,CAAC,CAAC;QACF,MAAM,MAAM,GAAG,CAAC,KAAa,EAAE,EAAE;YAC/B,MAAM,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;YACjC,IAAI,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,GAAG,WAAW;gBAAE,OAAO,MAAM,CAAC,IAAI,UAAU,CAAC,gBAAgB,EAAE,sDAAsD,CAAC,CAAC,CAAC;YACrJ,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,2DAA2D,CAAC,CAAC;YACxF,IAAI,KAAK;gBAAE,MAAM,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QACzC,CAAC,CAAC;QACF,MAAM,OAAO,GAAG,CAAC,KAA4B,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,KAAK,QAAQ;YAC9E,CAAC,CAAC,WAAW,EAAE;YACf,CAAC,CAAC,IAAI,UAAU,CAAC,gBAAgB,EAAE,2EAA2E,CAAC,CAAC,CAAC;QACnH,MAAM,MAAM,GAAG,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,UAAU,CAAC,gBAAgB,EAAE,uEAAuE,CAAC,CAAC,CAAC;QACvI,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,UAAU,CAAC,gBAAgB,EAAE,6EAA6E,CAAC,CAAC,EAAE,cAAc,CAAC,CAAC;QACxK,KAAK,CAAC,KAAK,EAAE,CAAC;QACd,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAChC,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAC7B,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAC9B,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,EAAC,KAAK,EAAC,EAAE;QACrB,MAAM,SAAS,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QAC/C,MAAM,KAAK,CAAC;IACd,CAAC,CAAC,CAAC;IACH,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;IAEtB,IAAI,OAAkC,CAAC;IACvC,OAAO;QACL,GAAG;QACH,KAAK;QACL,MAAM;QACN,KAAK,KAAK,OAAO,OAAO,KAAK,SAAS,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;KACzD,CAAC;AACJ,CAAC;AAED,SAAS,WAAW;IAClB,OAAO,IAAI,UAAU,CAAC,qBAAqB,EAAE,sFAAsF,CAAC,CAAC;AACvI,CAAC;AAED,KAAK,UAAU,aAAa,CAAC,GAAW,EAAE,GAAsB;IAC9D,IAAI,KAAoB,CAAC;IACzB,IAAI,CAAC;QACH,KAAK,GAAG,KAAK,CAAC,UAAU,EAAE,CAAC,WAAW,CAAC,EAAE;YACvC,GAAG;YACH,GAAG,EAAE,kBAAkB,CAAC,GAAG,CAAC;YAC5B,QAAQ,EAAE,gBAAgB;YAC1B,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC;SAClC,CAAC,CAAC;IACL,CAAC;IAAC,MAAM,CAAC;QAAC,MAAM,WAAW,EAAE,CAAC;IAAC,CAAC;IAChC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;IACtB,MAAM,MAAM,GAAG,IAAI,OAAO,CAAyD,OAAO,CAAC,EAAE;QAC3F,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;IACnE,CAAC,CAAC,CAAC;IACH,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC1C,IAAI,OAAO,GAAG,KAAK,CAAC;QACpB,IAAI,KAAiC,CAAC;QACtC,MAAM,MAAM,GAAG,CAAC,KAAkB,EAAE,EAAE;YACpC,IAAI,OAAO;gBAAE,OAAO;YACpB,OAAO,GAAG,IAAI,CAAC;YACf,IAAI,KAAK;gBAAE,YAAY,CAAC,KAAK,CAAC,CAAC;YAC/B,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;YACjC,KAAK,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAC5B,KAAK,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAC5B,IAAI,KAAK;gBAAE,MAAM,CAAC,KAAK,CAAC,CAAC;;gBAAM,OAAO,EAAE,CAAC;QAC3C,CAAC,CAAC;QACF,MAAM,MAAM,GAAG,CAAC,KAAa,EAAE,EAAE;YAC/B,MAAM,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;YACjC,IAAI,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,GAAG,WAAW;gBAAE,MAAM,CAAC,cAAc,EAAE,CAAC,CAAC;QACxE,CAAC,CAAC;QACF,MAAM,OAAO,GAAG,CAAC,KAA4B,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,cAAc,EAAE,CAAC,CAAC;QACrH,MAAM,OAAO,GAAG,CAAC,IAAmB,EAAE,MAA6B,EAAE,EAAE;YACrE,MAAM,CAAC,IAAI,KAAK,CAAC,IAAI,MAAM,KAAK,IAAI,IAAI,MAAM,CAAC,IAAI,EAAE,KAAK,wBAAwB,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,cAAc,EAAE,CAAC,CAAC;QACrH,CAAC,CAAC;QACF,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,cAAc,EAAE,CAAC,EAAE,cAAc,CAAC,CAAC;QACnE,KAAK,CAAC,KAAK,EAAE,CAAC;QACd,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAChC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;QACtB,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAC7B,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAC/B,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,EAAC,KAAK,EAAC,EAAE;QACrB,MAAM,SAAS,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QAC/C,MAAM,KAAK,CAAC;IACd,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,kBAAkB,CAAC,GAAsB;IAChD,MAAM,MAAM,GAAG,EAAE,GAAG,GAAG,EAAE,CAAC;IAC1B,KAAK,MAAM,GAAG,IAAI;QAChB,oBAAoB;QACpB,mBAAmB;QACnB,iBAAiB;QACjB,uBAAuB;QACvB,0BAA0B;QAC1B,0BAA0B;KAC3B;QAAE,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC;IACtB,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,cAAc;IACrB,OAAO,IAAI,UAAU,CAAC,gBAAgB,EAAE,2GAA2G,CAAC,CAAC;AACvJ,CAAC;AAED,KAAK,UAAU,SAAS,CAAC,KAAoB,EAAE,MAAiC;IAC9E,IAAI,KAAK,CAAC,GAAG,KAAK,SAAS,EAAE,CAAC;QAC5B,MAAM,aAAa,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QACjC,OAAO;IACT,CAAC;IACD,IAAI,KAAK,CAAC,QAAQ,KAAK,IAAI,IAAI,KAAK,CAAC,UAAU,KAAK,IAAI,EAAE,CAAC;QAAC,MAAM,MAAM,CAAC;QAAC,OAAO;IAAC,CAAC;IACnF,MAAM,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;IACzB,IAAI,MAAM,aAAa,CAAC,MAAM,EAAE,kBAAkB,CAAC;QAAE,OAAO;IAC5D,MAAM,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;IACzB,IAAI,CAAC,MAAM,aAAa,CAAC,MAAM,EAAE,kBAAkB,CAAC,EAAE,CAAC;QACrD,MAAM,IAAI,UAAU,CAAC,qBAAqB,EAAE,sDAAsD,CAAC,CAAC;IACtG,CAAC;AACH,CAAC;AAED,SAAS,MAAM,CAAC,KAAoB,EAAE,KAAqB;IACzD,IAAI,SAAS,GAAG,KAAK,CAAC;IACtB,IAAI,CAAC;QACH,IAAI,gBAAgB,IAAI,KAAK,CAAC,GAAG,KAAK,SAAS,EAAE,CAAC;YAChD,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;YAChC,SAAS,GAAG,IAAI,CAAC;QACnB,CAAC;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAK,KAA+B,CAAC,IAAI,KAAK,OAAO;YAAE,MAAM,IAAI,UAAU,CAAC,qBAAqB,EAAE,6CAA6C,CAAC,CAAC;IACpJ,CAAC;IACD,IAAI,CAAC;QAAC,SAAS,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,SAAS,CAAC;IAAC,CAAC;IACnD,OAAO,KAAK,EAAE,CAAC;QACb,IAAK,KAA+B,CAAC,IAAI,KAAK,OAAO;YAAE,MAAM,IAAI,UAAU,CAAC,qBAAqB,EAAE,6CAA6C,CAAC,CAAC;IACpJ,CAAC;IACD,IAAI,CAAC,SAAS,IAAI,KAAK,CAAC,QAAQ,KAAK,IAAI,IAAI,KAAK,CAAC,UAAU,KAAK,IAAI,EAAE,CAAC;QACvE,MAAM,IAAI,UAAU,CAAC,qBAAqB,EAAE,6DAA6D,CAAC,CAAC;IAC7G,CAAC;AACH,CAAC;AAED,KAAK,UAAU,aAAa,CAAC,OAAyB,EAAE,YAAoB;IAC1E,IAAI,KAAiC,CAAC;IACtC,OAAO,MAAM,OAAO,CAAC,IAAI,CAAC;QACxB,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC;QACxB,IAAI,OAAO,CAAQ,OAAO,CAAC,EAAE,GAAG,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,YAAY,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC;KAC1G,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,IAAI,KAAK;QAAE,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACxD,CAAC;AAED,MAAM,OAAO,YAAY;IAGF,OAAO;IAAmB,SAAS;IAF/C,cAAc,CAAS;IAEhC,YAAqB,OAAe,EAAW,SAAiB,EAAE,QAAgB;uBAA7D,OAAO;yBAAmB,SAAS;QACtD,IAAI,CAAC,cAAc,GAAG,SAAS,MAAM,CAAC,IAAI,CAAC,YAAY,QAAQ,EAAE,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;IAC1F,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,IAAY,EAAE,IAAI,GAAwF,EAAE;QACrH,IAAI,QAAkB,CAAC;QACvB,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,KAAK,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,IAAI,KAAK,CAAC,CAAC;QAChG,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,IAAI,OAAO,CAAC;QACzG,IAAI,CAAC;YACH,QAAQ,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,OAAO,GAAG,IAAI,EAAE;gBAC1C,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC;gBACjE,OAAO,EAAE;oBACP,aAAa,EAAE,IAAI,CAAC,cAAc;oBAClC,cAAc,EAAE,kBAAkB;oBAClC,sBAAsB,EAAE,IAAI,CAAC,SAAS;iBACvC;gBACD,GAAG,CAAC,IAAI,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;gBACvE,MAAM;aACP,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,IAAI,CAAC,MAAM,EAAE,OAAO;gBAAE,MAAM,KAAK,CAAC;YACtC,IAAI,OAAO,EAAE,OAAO;gBAAE,MAAM,IAAI,UAAU,CAAC,eAAe,EAAE,6CAA6C,CAAC,CAAC;YAC3G,MAAM,IAAI,UAAU,CAAC,eAAe,EAAE,6CAA6C,CAAC,CAAC;QACvF,CAAC;QACD,IAAI,CAAC,QAAQ,CAAC,EAAE;YAAE,MAAM,IAAI,UAAU,CAAC,eAAe,EAAE,mDAAmD,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC;QAC/H,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;QACnC,IAAI,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI;YAAE,MAAM,IAAI,UAAU,CAAC,gBAAgB,EAAE,0DAA0D,CAAC,CAAC;QACnJ,IAAI,CAAC;YAAC,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAAC,CAAC;QAChC,MAAM,CAAC;YAAC,MAAM,IAAI,UAAU,CAAC,gBAAgB,EAAE,mDAAmD,CAAC,CAAC;QAAC,CAAC;IACxG,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,MAAmB,EAAE,OAAiC,EAAE,KAAkB;QACrF,IAAI,QAAkB,CAAC;QACvB,IAAI,CAAC;YACH,QAAQ,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,OAAO,GAAG,QAAQ,EAAE;gBAC9C,OAAO,EAAE,EAAE,aAAa,EAAE,IAAI,CAAC,cAAc,EAAE,sBAAsB,EAAE,IAAI,CAAC,SAAS,EAAE;gBACvF,MAAM;aACP,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,MAAM,CAAC,OAAO;gBAAE,OAAO;YAC3B,MAAM,IAAI,UAAU,CAAC,gBAAgB,EAAE,uDAAuD,CAAC,CAAC;QAClG,CAAC;QACD,IAAI,CAAC,QAAQ,CAAC,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,EAAE,QAAQ,CAAC,mBAAmB,CAAC,EAAE,CAAC;YAC3G,MAAM,IAAI,UAAU,CAAC,gBAAgB,EAAE,mDAAmD,CAAC,CAAC;QAC9F,CAAC;QACD,KAAK,EAAE,EAAE,CAAC;QACV,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,iBAAiB,EAAE,CAAC,CAAC,SAAS,EAAE,CAAC;QAC9E,MAAM,MAAM,GAAG,GAAG,EAAE,GAAG,KAAK,MAAM,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC/D,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QACzD,IAAI,MAAM,GAAG,EAAE,CAAC;QAChB,IAAI,CAAC;YACH,OAAO,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;gBACvB,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;gBAC5C,IAAI,IAAI;oBAAE,MAAM;gBAChB,MAAM,IAAI,KAAK,CAAC;gBAChB,IAAI,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,GAAG,IAAI,GAAG,IAAI;oBAAE,MAAM,IAAI,UAAU,CAAC,gBAAgB,EAAE,oDAAoD,CAAC,CAAC;gBAC1I,IAAI,GAAW,CAAC;gBAChB,OAAO,CAAC,GAAG,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;oBAC3C,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,UAAU,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;oBACxD,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;oBAC/B,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;wBACrC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC;4BAAE,SAAS;wBACxC,IAAI,CAAC;4BAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;wBAAC,CAAC;wBAClD,MAAM,CAAC;4BAAC,MAAM,IAAI,UAAU,CAAC,gBAAgB,EAAE,gDAAgD,CAAC,CAAC;wBAAC,CAAC;oBACrG,CAAC;gBACH,CAAC;YACH,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,OAAO;gBAAE,MAAM,KAAK,CAAC;QACnC,CAAC;gBAAS,CAAC;YACT,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;YAC5C,MAAM,MAAM,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QACxC,CAAC;QACD,IAAI,CAAC,MAAM,CAAC,OAAO;YAAE,MAAM,IAAI,UAAU,CAAC,gBAAgB,EAAE,sDAAsD,CAAC,CAAC;IACtH,CAAC;CACF","sourcesContent":["import { spawn, type ChildProcessByStdio } from \"node:child_process\";\nimport type { Readable } from \"node:stream\";\nimport { AgentError } from \"../errors.js\";\n\nconst startupTimeout = 5_000;\nconst versionTimeout = 5_000;\nconst outputLimit = 64 * 1024;\nconst terminationTimeout = 2_000;\nconst ownsProcessGroup = process.platform !== \"win32\";\nexport const supportedOpencodeVersion = \"1.18.32\";\ntype OpencodeChild = ChildProcessByStdio<null, Readable, Readable>;\n\nexport interface OpencodeProcess {\n readonly url: string;\n readonly child: OpencodeChild;\n readonly exited: Promise<{ code: number | null; signal: NodeJS.Signals | null }>;\n close(): Promise<void>;\n}\n\nexport async function startOpencodeProcess(cwd: string, env: NodeJS.ProcessEnv): Promise<OpencodeProcess> {\n await verifyVersion(cwd, env);\n let child: OpencodeChild;\n try {\n child = spawn(\"opencode\", [\"serve\", \"--hostname\", \"127.0.0.1\", \"--port\", \"0\", \"--no-mdns\"], {\n cwd,\n env,\n detached: ownsProcessGroup,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n } catch {\n throw unavailable();\n }\n child.stderr.resume();\n const exited = new Promise<{ code: number | null; signal: NodeJS.Signals | null }>(resolve => {\n child.once(\"close\", (code, signal) => resolve({ code, signal }));\n });\n\n let output = \"\";\n const url = await new Promise<string>((resolve, reject) => {\n let settled = false;\n const finish = (error?: AgentError, value?: string) => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n child.stdout.off(\"data\", onData);\n child.off(\"error\", onError);\n child.off(\"close\", onExit);\n if (error) reject(error); else resolve(value!);\n };\n const onData = (chunk: Buffer) => {\n output += chunk.toString(\"utf8\");\n if (Buffer.byteLength(output) > outputLimit) return finish(new AgentError(\"PROTOCOL_ERROR\", \"OpenCode produced an oversized startup announcement.\"));\n const match = output.match(/opencode server listening on (http:\\/\\/127\\.0\\.0\\.1:\\d+)/u);\n if (match) finish(undefined, match[1]);\n };\n const onError = (error: NodeJS.ErrnoException) => finish(error.code === \"ENOENT\"\n ? unavailable()\n : new AgentError(\"INVALID_CONFIG\", \"OpenCode native server startup failed before readiness could be verified.\"));\n const onExit = () => finish(new AgentError(\"INVALID_CONFIG\", \"OpenCode exited before its native server readiness could be verified.\"));\n const timer = setTimeout(() => finish(new AgentError(\"INVALID_CONFIG\", \"OpenCode native server readiness could not be verified within five seconds.\")), startupTimeout);\n timer.unref();\n child.stdout.on(\"data\", onData);\n child.once(\"error\", onError);\n child.once(\"close\", onExit);\n }).catch(async error => {\n await terminate(child, exited).catch(() => {});\n throw error;\n });\n child.stdout.resume();\n\n let closing: Promise<void> | undefined;\n return {\n url,\n child,\n exited,\n close() { return closing ??= terminate(child, exited); },\n };\n}\n\nfunction unavailable(): AgentError {\n return new AgentError(\"HARNESS_UNAVAILABLE\", \"Cannot start OpenCode. Install OpenCode 1.18.32 and make opencode available on PATH.\");\n}\n\nasync function verifyVersion(cwd: string, env: NodeJS.ProcessEnv): Promise<void> {\n let child: OpencodeChild;\n try {\n child = spawn(\"opencode\", [\"--version\"], {\n cwd,\n env: versionEnvironment(env),\n detached: ownsProcessGroup,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n } catch { throw unavailable(); }\n child.stderr.resume();\n const exited = new Promise<{ code: number | null; signal: NodeJS.Signals | null }>(resolve => {\n child.once(\"close\", (code, signal) => resolve({ code, signal }));\n });\n let output = \"\";\n await new Promise<void>((resolve, reject) => {\n let settled = false;\n let timer: NodeJS.Timeout | undefined;\n const finish = (error?: AgentError) => {\n if (settled) return;\n settled = true;\n if (timer) clearTimeout(timer);\n child.stdout.off(\"data\", onData);\n child.off(\"error\", onError);\n child.off(\"close\", onClose);\n if (error) reject(error); else resolve();\n };\n const onData = (chunk: Buffer) => {\n output += chunk.toString(\"utf8\");\n if (Buffer.byteLength(output) > outputLimit) finish(invalidVersion());\n };\n const onError = (error: NodeJS.ErrnoException) => finish(error.code === \"ENOENT\" ? unavailable() : invalidVersion());\n const onClose = (code: number | null, signal: NodeJS.Signals | null) => {\n finish(code === 0 && signal === null && output.trim() === supportedOpencodeVersion ? undefined : invalidVersion());\n };\n timer = setTimeout(() => finish(invalidVersion()), versionTimeout);\n timer.unref();\n child.stdout.on(\"data\", onData);\n child.stdout.resume();\n child.once(\"error\", onError);\n child.once(\"close\", onClose);\n }).catch(async error => {\n await terminate(child, exited).catch(() => {});\n throw error;\n });\n}\n\nfunction versionEnvironment(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv {\n const result = { ...env };\n for (const key of [\n \"AI_GATEWAY_API_KEY\",\n \"VERCEL_OIDC_TOKEN\",\n \"OPENCODE_CONFIG\",\n \"OPENCODE_AUTH_CONTENT\",\n \"OPENCODE_SERVER_USERNAME\",\n \"OPENCODE_SERVER_PASSWORD\",\n ]) delete result[key];\n return result;\n}\n\nfunction invalidVersion(): AgentError {\n return new AgentError(\"INVALID_CONFIG\", \"OpenCode 1.18.32 is required because other native versions have not been verified for isolated execution.\");\n}\n\nasync function terminate(child: OpencodeChild, exited: OpencodeProcess[\"exited\"]): Promise<void> {\n if (child.pid === undefined) {\n await settledWithin(exited, 250);\n return;\n }\n if (child.exitCode !== null || child.signalCode !== null) { await exited; return; }\n signal(child, \"SIGTERM\");\n if (await settledWithin(exited, terminationTimeout)) return;\n signal(child, \"SIGKILL\");\n if (!await settledWithin(exited, terminationTimeout)) {\n throw new AgentError(\"CANCELLATION_FAILED\", \"OpenCode did not confirm native process termination.\");\n }\n}\n\nfunction signal(child: OpencodeChild, value: NodeJS.Signals): void {\n let delivered = false;\n try {\n if (ownsProcessGroup && child.pid !== undefined) {\n process.kill(-child.pid, value);\n delivered = true;\n }\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== \"ESRCH\") throw new AgentError(\"CANCELLATION_FAILED\", \"OpenCode native process termination failed.\");\n }\n try { delivered = child.kill(value) || delivered; }\n catch (error) {\n if ((error as NodeJS.ErrnoException).code !== \"ESRCH\") throw new AgentError(\"CANCELLATION_FAILED\", \"OpenCode native process termination failed.\");\n }\n if (!delivered && child.exitCode === null && child.signalCode === null) {\n throw new AgentError(\"CANCELLATION_FAILED\", \"OpenCode native process termination could not be delivered.\");\n }\n}\n\nasync function settledWithin(promise: Promise<unknown>, milliseconds: number): Promise<boolean> {\n let timer: NodeJS.Timeout | undefined;\n return await Promise.race([\n promise.then(() => true),\n new Promise<false>(resolve => { timer = setTimeout(() => resolve(false), milliseconds); timer.unref(); }),\n ]).finally(() => { if (timer) clearTimeout(timer); });\n}\n\nexport class OpencodeHttp {\n readonly #authorization: string;\n\n constructor(readonly baseUrl: string, readonly directory: string, password: string) {\n this.#authorization = `Basic ${Buffer.from(`opencode:${password}`).toString(\"base64\")}`;\n }\n\n async json(path: string, init: { method?: string; body?: unknown; signal?: AbortSignal; timeout?: number | false } = {}): Promise<unknown> {\n let response: Response;\n const timeout = init.timeout === false ? undefined : AbortSignal.timeout(init.timeout ?? 5_000);\n const signal = init.signal && timeout ? AbortSignal.any([init.signal, timeout]) : init.signal ?? timeout;\n try {\n response = await fetch(this.baseUrl + path, {\n method: init.method ?? (init.body === undefined ? \"GET\" : \"POST\"),\n headers: {\n authorization: this.#authorization,\n \"content-type\": \"application/json\",\n \"x-opencode-directory\": this.directory,\n },\n ...(init.body === undefined ? {} : { body: JSON.stringify(init.body) }),\n signal,\n });\n } catch (error) {\n if (init.signal?.aborted) throw error;\n if (timeout?.aborted) throw new AgentError(\"HARNESS_ERROR\", \"The OpenCode native HTTP request timed out.\");\n throw new AgentError(\"HARNESS_ERROR\", \"The OpenCode native HTTP connection failed.\");\n }\n if (!response.ok) throw new AgentError(\"HARNESS_ERROR\", `OpenCode native HTTP request failed with status ${response.status}.`);\n const text = await response.text();\n if (Buffer.byteLength(text) > 16 * 1024 * 1024) throw new AgentError(\"PROTOCOL_ERROR\", \"OpenCode returned an oversized native protocol response.\");\n try { return JSON.parse(text); }\n catch { throw new AgentError(\"PROTOCOL_ERROR\", \"OpenCode returned malformed native protocol data.\"); }\n }\n\n async events(signal: AbortSignal, consume: (event: unknown) => void, ready?: () => void): Promise<void> {\n let response: Response;\n try {\n response = await fetch(this.baseUrl + \"/event\", {\n headers: { authorization: this.#authorization, \"x-opencode-directory\": this.directory },\n signal,\n });\n } catch (error) {\n if (signal.aborted) return;\n throw new AgentError(\"PROTOCOL_ERROR\", \"OpenCode could not establish its native event stream.\");\n }\n if (!response.ok || !response.body || !response.headers.get(\"content-type\")?.includes(\"text/event-stream\")) {\n throw new AgentError(\"PROTOCOL_ERROR\", \"OpenCode returned an invalid native event stream.\");\n }\n ready?.();\n const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();\n const cancel = () => { void reader.cancel().catch(() => {}); };\n signal.addEventListener(\"abort\", cancel, { once: true });\n let buffer = \"\";\n try {\n while (!signal.aborted) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += value;\n if (Buffer.byteLength(buffer) > 1024 * 1024) throw new AgentError(\"PROTOCOL_ERROR\", \"OpenCode returned an oversized native event frame.\");\n let end: number;\n while ((end = buffer.indexOf(\"\\n\\n\")) >= 0) {\n const frame = buffer.slice(0, end).replaceAll(\"\\r\", \"\");\n buffer = buffer.slice(end + 2);\n for (const line of frame.split(\"\\n\")) {\n if (!line.startsWith(\"data:\")) continue;\n try { consume(JSON.parse(line.slice(5).trim())); }\n catch { throw new AgentError(\"PROTOCOL_ERROR\", \"OpenCode returned malformed native event data.\"); }\n }\n }\n }\n } catch (error) {\n if (!signal.aborted) throw error;\n } finally {\n signal.removeEventListener(\"abort\", cancel);\n await reader.cancel().catch(() => {});\n }\n if (!signal.aborted) throw new AgentError(\"PROTOCOL_ERROR\", \"The OpenCode native event stream ended unexpectedly.\");\n }\n}\n"]}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { AgentError } from "../errors.js";
|
|
2
|
+
import type { ExecutionDefinition } from "./types.js";
|
|
3
|
+
export interface OpencodeToolEndpoint {
|
|
4
|
+
readonly descriptor?: {
|
|
5
|
+
readonly name: string;
|
|
6
|
+
readonly config: {
|
|
7
|
+
readonly type: "remote";
|
|
8
|
+
readonly url: string;
|
|
9
|
+
readonly headers: Record<string, string>;
|
|
10
|
+
readonly oauth: false;
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
onFailure(handler: (error: AgentError) => void): void;
|
|
14
|
+
beginTurn(): void;
|
|
15
|
+
stopAdmission(): void;
|
|
16
|
+
drain(): Promise<void>;
|
|
17
|
+
close(): Promise<void>;
|
|
18
|
+
}
|
|
19
|
+
export declare function startOpencodeTools(agent: Pick<ExecutionDefinition, "tools">): Promise<OpencodeToolEndpoint>;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
|
|
2
|
+
import { createServer } from "node:http";
|
|
3
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
5
|
+
import { AgentError } from "../errors.js";
|
|
6
|
+
import { executeTool, toolJsonSchema } from "../sdk/tools.js";
|
|
7
|
+
import { mcpToolContent } from "./mcp-tool-content.js";
|
|
8
|
+
export async function startOpencodeTools(agent) {
|
|
9
|
+
const definitions = Object.entries(agent.tools ?? {});
|
|
10
|
+
if (definitions.length === 0)
|
|
11
|
+
return emptyEndpoint();
|
|
12
|
+
for (const [, definition] of definitions)
|
|
13
|
+
toolJsonSchema(definition);
|
|
14
|
+
const token = randomBytes(32).toString("base64url");
|
|
15
|
+
const namespace = `subharness_${randomUUID().replaceAll("-", "")}`;
|
|
16
|
+
const pending = new Set();
|
|
17
|
+
let accepting = false;
|
|
18
|
+
let failure;
|
|
19
|
+
let failureHandler;
|
|
20
|
+
const mcp = new McpServer({ name: namespace, version: "1.0.0" });
|
|
21
|
+
for (const [name, definition] of definitions) {
|
|
22
|
+
mcp.registerTool(name, { description: definition.description, inputSchema: definition.inputSchema }, async (input) => {
|
|
23
|
+
if (!accepting)
|
|
24
|
+
return toolError("The task is not accepting tool calls.");
|
|
25
|
+
const execution = executeTool(definition, input);
|
|
26
|
+
pending.add(execution);
|
|
27
|
+
try {
|
|
28
|
+
return { content: mcpToolContent(await execution) };
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
return toolError(error instanceof AgentError ? `Tool failed (${error.code}).` : "The custom tool failed.");
|
|
32
|
+
}
|
|
33
|
+
finally {
|
|
34
|
+
pending.delete(execution);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: randomUUID, enableJsonResponse: true });
|
|
39
|
+
await mcp.connect(transport);
|
|
40
|
+
let http;
|
|
41
|
+
let closing;
|
|
42
|
+
const close = () => closing ??= (async () => {
|
|
43
|
+
accepting = false;
|
|
44
|
+
await Promise.allSettled([...pending]);
|
|
45
|
+
await mcp.close().catch(() => { });
|
|
46
|
+
if (http)
|
|
47
|
+
await closeHttp(http);
|
|
48
|
+
})();
|
|
49
|
+
const fail = () => {
|
|
50
|
+
if (failure)
|
|
51
|
+
return;
|
|
52
|
+
failure = new AgentError("HARNESS_FAILED", "The private OpenCode tool endpoint failed.");
|
|
53
|
+
accepting = false;
|
|
54
|
+
failureHandler?.(failure);
|
|
55
|
+
void close();
|
|
56
|
+
};
|
|
57
|
+
try {
|
|
58
|
+
http = createServer((request, response) => {
|
|
59
|
+
if (request.url !== "/mcp" || !authorized(request.headers.authorization, token)) {
|
|
60
|
+
response.writeHead(401, { "content-type": "application/json" });
|
|
61
|
+
response.end(JSON.stringify({ error: "unauthorized" }));
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
void transport.handleRequest(request, response).catch(() => {
|
|
65
|
+
if (!response.headersSent)
|
|
66
|
+
response.writeHead(500);
|
|
67
|
+
response.end();
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
await new Promise((resolve, reject) => {
|
|
71
|
+
http.once("error", reject);
|
|
72
|
+
http.listen(0, "127.0.0.1", () => {
|
|
73
|
+
http.off("error", reject);
|
|
74
|
+
http.on("error", fail);
|
|
75
|
+
resolve();
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
await close();
|
|
81
|
+
throw new AgentError("HARNESS_FAILED", "The private OpenCode tool endpoint could not start.");
|
|
82
|
+
}
|
|
83
|
+
const address = http.address();
|
|
84
|
+
if (!address || typeof address === "string") {
|
|
85
|
+
await close();
|
|
86
|
+
throw new AgentError("HARNESS_FAILED", "The private OpenCode tool endpoint did not bind to loopback.");
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
descriptor: {
|
|
90
|
+
name: namespace,
|
|
91
|
+
config: {
|
|
92
|
+
type: "remote",
|
|
93
|
+
url: `http://127.0.0.1:${address.port}/mcp`,
|
|
94
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
95
|
+
oauth: false,
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
onFailure(handler) { failureHandler = handler; if (failure)
|
|
99
|
+
handler(failure); },
|
|
100
|
+
beginTurn() { accepting = true; },
|
|
101
|
+
stopAdmission() { accepting = false; },
|
|
102
|
+
async drain() { accepting = false; await Promise.allSettled([...pending]); },
|
|
103
|
+
close,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
function authorized(header, token) {
|
|
107
|
+
if (typeof header !== "string")
|
|
108
|
+
return false;
|
|
109
|
+
const expected = Buffer.from(`Bearer ${token}`);
|
|
110
|
+
const actual = Buffer.from(header);
|
|
111
|
+
return actual.length === expected.length && timingSafeEqual(actual, expected);
|
|
112
|
+
}
|
|
113
|
+
function toolError(text) {
|
|
114
|
+
return { isError: true, content: [{ type: "text", text }] };
|
|
115
|
+
}
|
|
116
|
+
function emptyEndpoint() {
|
|
117
|
+
return { onFailure() { }, beginTurn() { }, stopAdmission() { }, async drain() { }, async close() { } };
|
|
118
|
+
}
|
|
119
|
+
async function closeHttp(server) {
|
|
120
|
+
if (!server.listening)
|
|
121
|
+
return;
|
|
122
|
+
await new Promise(resolve => {
|
|
123
|
+
server.close(() => resolve());
|
|
124
|
+
server.closeAllConnections();
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=opencode-tools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"opencode-tools.js","sourceRoot":"","sources":["../../src/adapters/opencode-tools.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomBytes, randomUUID, timingSafeEqual } from \"node:crypto\";\nimport { createServer, type Server as HttpServer } from \"node:http\";\nimport { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { StreamableHTTPServerTransport } from \"@modelcontextprotocol/sdk/server/streamableHttp.js\";\nimport { AgentError } from \"../errors.js\";\nimport { executeTool, toolJsonSchema } from \"../sdk/tools.js\";\nimport type { ExecutionDefinition } from \"./types.js\";\nimport { mcpToolContent } from \"./mcp-tool-content.js\";\n\nexport interface OpencodeToolEndpoint {\n readonly descriptor?: {\n readonly name: string;\n readonly config: { readonly type: \"remote\"; readonly url: string; readonly headers: Record<string, string>; readonly oauth: false };\n };\n onFailure(handler: (error: AgentError) => void): void;\n beginTurn(): void;\n stopAdmission(): void;\n drain(): Promise<void>;\n close(): Promise<void>;\n}\n\nexport async function startOpencodeTools(agent: Pick<ExecutionDefinition, \"tools\">): Promise<OpencodeToolEndpoint> {\n const definitions = Object.entries(agent.tools ?? {});\n if (definitions.length === 0) return emptyEndpoint();\n for (const [, definition] of definitions) toolJsonSchema(definition);\n\n const token = randomBytes(32).toString(\"base64url\");\n const namespace = `subharness_${randomUUID().replaceAll(\"-\", \"\")}`;\n const pending = new Set<Promise<unknown>>();\n let accepting = false;\n let failure: AgentError | undefined;\n let failureHandler: ((error: AgentError) => void) | undefined;\n const mcp = new McpServer({ name: namespace, version: \"1.0.0\" });\n for (const [name, definition] of definitions) {\n mcp.registerTool(name, { description: definition.description, inputSchema: definition.inputSchema }, async (input: unknown) => {\n if (!accepting) return toolError(\"The task is not accepting tool calls.\");\n const execution = executeTool(definition, input);\n pending.add(execution);\n try { return { content: mcpToolContent(await execution) }; }\n catch (error) { return toolError(error instanceof AgentError ? `Tool failed (${error.code}).` : \"The custom tool failed.\"); }\n finally { pending.delete(execution); }\n });\n }\n\n const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: randomUUID, enableJsonResponse: true });\n await mcp.connect(transport);\n let http: HttpServer | undefined;\n let closing: Promise<void> | undefined;\n const close = (): Promise<void> => closing ??= (async () => {\n accepting = false;\n await Promise.allSettled([...pending]);\n await mcp.close().catch(() => {});\n if (http) await closeHttp(http);\n })();\n const fail = () => {\n if (failure) return;\n failure = new AgentError(\"HARNESS_FAILED\", \"The private OpenCode tool endpoint failed.\");\n accepting = false;\n failureHandler?.(failure);\n void close();\n };\n\n try {\n http = createServer((request, response) => {\n if (request.url !== \"/mcp\" || !authorized(request.headers.authorization, token)) {\n response.writeHead(401, { \"content-type\": \"application/json\" });\n response.end(JSON.stringify({ error: \"unauthorized\" }));\n return;\n }\n void transport.handleRequest(request, response).catch(() => {\n if (!response.headersSent) response.writeHead(500);\n response.end();\n });\n });\n await new Promise<void>((resolve, reject) => {\n http!.once(\"error\", reject);\n http!.listen(0, \"127.0.0.1\", () => {\n http!.off(\"error\", reject);\n http!.on(\"error\", fail);\n resolve();\n });\n });\n } catch {\n await close();\n throw new AgentError(\"HARNESS_FAILED\", \"The private OpenCode tool endpoint could not start.\");\n }\n const address = http.address();\n if (!address || typeof address === \"string\") {\n await close();\n throw new AgentError(\"HARNESS_FAILED\", \"The private OpenCode tool endpoint did not bind to loopback.\");\n }\n\n return {\n descriptor: {\n name: namespace,\n config: {\n type: \"remote\",\n url: `http://127.0.0.1:${address.port}/mcp`,\n headers: { Authorization: `Bearer ${token}` },\n oauth: false,\n },\n },\n onFailure(handler) { failureHandler = handler; if (failure) handler(failure); },\n beginTurn() { accepting = true; },\n stopAdmission() { accepting = false; },\n async drain() { accepting = false; await Promise.allSettled([...pending]); },\n close,\n };\n}\n\nfunction authorized(header: string | undefined, token: string): boolean {\n if (typeof header !== \"string\") return false;\n const expected = Buffer.from(`Bearer ${token}`);\n const actual = Buffer.from(header);\n return actual.length === expected.length && timingSafeEqual(actual, expected);\n}\n\nfunction toolError(text: string) {\n return { isError: true, content: [{ type: \"text\" as const, text }] };\n}\n\nfunction emptyEndpoint(): OpencodeToolEndpoint {\n return { onFailure() {}, beginTurn() {}, stopAdmission() {}, async drain() {}, async close() {} };\n}\n\nasync function closeHttp(server: HttpServer): Promise<void> {\n if (!server.listening) return;\n await new Promise<void>(resolve => {\n server.close(() => resolve());\n server.closeAllConnections();\n });\n}\n"]}
|