@ai-sdk/harness-opencode 1.0.26 → 1.0.28
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/CHANGELOG.md +12 -0
- package/dist/bridge/index.mjs +131 -136
- package/dist/bridge/index.mjs.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +2 -2
- package/src/bridge/index.ts +2 -101
- package/src/bridge/tool-relay-auth.ts +59 -86
- package/src/bridge/tool-relay.ts +107 -0
package/CHANGELOG.md
CHANGED
package/dist/bridge/index.mjs
CHANGED
|
@@ -480,7 +480,6 @@ function serialiseError(err) {
|
|
|
480
480
|
// src/bridge/index.ts
|
|
481
481
|
import { randomUUID } from "crypto";
|
|
482
482
|
import { mkdirSync } from "fs";
|
|
483
|
-
import { createServer } from "http";
|
|
484
483
|
import path2 from "path";
|
|
485
484
|
import { argv, env as procEnv2 } from "process";
|
|
486
485
|
import {
|
|
@@ -703,31 +702,71 @@ function prependOpenCodeBinToPath({
|
|
|
703
702
|
].join(path.delimiter);
|
|
704
703
|
}
|
|
705
704
|
|
|
705
|
+
// src/bridge/tool-relay.ts
|
|
706
|
+
import { createServer } from "http";
|
|
707
|
+
|
|
706
708
|
// src/bridge/tool-relay-auth.ts
|
|
707
|
-
import { readdir, readFile, readlink } from "fs/promises";
|
|
708
709
|
var ToolRelayAuthorizer = class {
|
|
709
710
|
constructor({
|
|
710
711
|
ttlMs = 1e4,
|
|
711
712
|
now = Date.now
|
|
712
713
|
} = {}) {
|
|
713
714
|
this.authorizations = [];
|
|
715
|
+
this.pendingRequests = [];
|
|
714
716
|
this.ttlMs = ttlMs;
|
|
715
717
|
this.now = now;
|
|
716
718
|
}
|
|
717
719
|
authorizeToolCall(call) {
|
|
718
720
|
this.pruneExpired();
|
|
721
|
+
const key = toolRelayCallKey(call);
|
|
722
|
+
const pendingRequestIndex = this.pendingRequests.findIndex(
|
|
723
|
+
(request) => request.key === key
|
|
724
|
+
);
|
|
725
|
+
if (pendingRequestIndex !== -1) {
|
|
726
|
+
const [pendingRequest] = this.pendingRequests.splice(
|
|
727
|
+
pendingRequestIndex,
|
|
728
|
+
1
|
|
729
|
+
);
|
|
730
|
+
clearTimeout(pendingRequest.timeout);
|
|
731
|
+
pendingRequest.resolve(true);
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
719
734
|
this.authorizations.push({
|
|
720
|
-
key
|
|
735
|
+
key,
|
|
721
736
|
expiresAt: this.now() + this.ttlMs
|
|
722
737
|
});
|
|
723
738
|
}
|
|
724
|
-
|
|
739
|
+
waitForToolCallAuthorization(call) {
|
|
725
740
|
this.pruneExpired();
|
|
726
741
|
const key = toolRelayCallKey(call);
|
|
727
|
-
const
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
742
|
+
const authorizationIndex = this.authorizations.findIndex(
|
|
743
|
+
(authorization) => authorization.key === key
|
|
744
|
+
);
|
|
745
|
+
if (authorizationIndex !== -1) {
|
|
746
|
+
this.authorizations.splice(authorizationIndex, 1);
|
|
747
|
+
return Promise.resolve(true);
|
|
748
|
+
}
|
|
749
|
+
const expiresAt = this.now() + this.ttlMs;
|
|
750
|
+
return new Promise((resolve) => {
|
|
751
|
+
const pendingRequest = {
|
|
752
|
+
key,
|
|
753
|
+
expiresAt,
|
|
754
|
+
timeout: setTimeout(() => {
|
|
755
|
+
const index = this.pendingRequests.indexOf(pendingRequest);
|
|
756
|
+
if (index !== -1) this.pendingRequests.splice(index, 1);
|
|
757
|
+
resolve(false);
|
|
758
|
+
}, this.ttlMs),
|
|
759
|
+
resolve
|
|
760
|
+
};
|
|
761
|
+
this.pendingRequests.push(pendingRequest);
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
close() {
|
|
765
|
+
this.authorizations.length = 0;
|
|
766
|
+
for (const pendingRequest of this.pendingRequests.splice(0)) {
|
|
767
|
+
clearTimeout(pendingRequest.timeout);
|
|
768
|
+
pendingRequest.resolve(false);
|
|
769
|
+
}
|
|
731
770
|
}
|
|
732
771
|
pruneExpired() {
|
|
733
772
|
const now = this.now();
|
|
@@ -736,6 +775,14 @@ var ToolRelayAuthorizer = class {
|
|
|
736
775
|
this.authorizations.splice(i, 1);
|
|
737
776
|
}
|
|
738
777
|
}
|
|
778
|
+
for (let i = this.pendingRequests.length - 1; i >= 0; i--) {
|
|
779
|
+
const pendingRequest = this.pendingRequests[i];
|
|
780
|
+
if (pendingRequest.expiresAt <= now) {
|
|
781
|
+
this.pendingRequests.splice(i, 1);
|
|
782
|
+
clearTimeout(pendingRequest.timeout);
|
|
783
|
+
pendingRequest.resolve(false);
|
|
784
|
+
}
|
|
785
|
+
}
|
|
739
786
|
}
|
|
740
787
|
};
|
|
741
788
|
function toolRelayCallKey({ toolName, input }) {
|
|
@@ -755,62 +802,87 @@ function normalizeJsonValue(value) {
|
|
|
755
802
|
}
|
|
756
803
|
return value;
|
|
757
804
|
}
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
clientPort: socket.remotePort,
|
|
766
|
-
serverPort: socket.localPort
|
|
767
|
-
});
|
|
768
|
-
if (!inode) return false;
|
|
769
|
-
const cmdline = await findProcessCmdlineForSocketInode({ inode });
|
|
770
|
-
return cmdline?.some((arg) => allowedScriptPaths.has(arg)) ?? false;
|
|
771
|
-
}
|
|
772
|
-
async function findTcpSocketInode({
|
|
773
|
-
clientPort,
|
|
774
|
-
serverPort
|
|
805
|
+
|
|
806
|
+
// src/bridge/tool-relay.ts
|
|
807
|
+
async function startAuthorizedToolRelay({
|
|
808
|
+
tools,
|
|
809
|
+
emit,
|
|
810
|
+
requestToolResult,
|
|
811
|
+
authorizer = new ToolRelayAuthorizer()
|
|
775
812
|
}) {
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
813
|
+
const toolNames = new Set(tools.map((tool) => tool.name));
|
|
814
|
+
const server = createServer(async (req, res) => {
|
|
815
|
+
try {
|
|
816
|
+
if (req.method !== "POST" || req.url !== "/") {
|
|
817
|
+
res.writeHead(401, { "Content-Type": "application/json" });
|
|
818
|
+
res.end(JSON.stringify({ error: "unauthorized tool relay request" }));
|
|
819
|
+
return;
|
|
820
|
+
}
|
|
821
|
+
const chunks = [];
|
|
822
|
+
for await (const chunk of req) {
|
|
823
|
+
chunks.push(chunk);
|
|
824
|
+
}
|
|
825
|
+
const body = Buffer.concat(chunks).toString("utf8");
|
|
826
|
+
const { requestId, toolName, input } = JSON.parse(body);
|
|
827
|
+
if (!toolNames.has(toolName)) {
|
|
828
|
+
res.writeHead(403, { "Content-Type": "application/json" });
|
|
829
|
+
res.end(
|
|
830
|
+
JSON.stringify({ error: `Tool "${toolName}" is not available` })
|
|
831
|
+
);
|
|
832
|
+
return;
|
|
833
|
+
}
|
|
834
|
+
if (!await authorizer.waitForToolCallAuthorization({ toolName, input })) {
|
|
835
|
+
res.writeHead(401, { "Content-Type": "application/json" });
|
|
836
|
+
res.end(JSON.stringify({ error: "unauthorized tool relay request" }));
|
|
837
|
+
return;
|
|
786
838
|
}
|
|
839
|
+
emit({
|
|
840
|
+
type: "tool-call",
|
|
841
|
+
toolCallId: requestId,
|
|
842
|
+
toolName,
|
|
843
|
+
input: JSON.stringify(input ?? {}),
|
|
844
|
+
providerExecuted: false
|
|
845
|
+
});
|
|
846
|
+
const { output, isError } = await requestToolResult(requestId);
|
|
847
|
+
emit({
|
|
848
|
+
type: "tool-result",
|
|
849
|
+
toolCallId: requestId,
|
|
850
|
+
toolName,
|
|
851
|
+
result: output ?? null,
|
|
852
|
+
isError: !!isError
|
|
853
|
+
});
|
|
854
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
855
|
+
res.end(JSON.stringify({ result: output }));
|
|
856
|
+
} catch (error) {
|
|
857
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
858
|
+
res.end(
|
|
859
|
+
JSON.stringify({
|
|
860
|
+
error: error instanceof Error ? error.message : String(error)
|
|
861
|
+
})
|
|
862
|
+
);
|
|
787
863
|
}
|
|
864
|
+
});
|
|
865
|
+
await new Promise(
|
|
866
|
+
(resolve) => server.listen(0, "127.0.0.1", () => resolve())
|
|
867
|
+
);
|
|
868
|
+
const address = server.address();
|
|
869
|
+
if (!address || typeof address === "string") {
|
|
870
|
+
throw new Error("tool relay did not expose a numeric port");
|
|
788
871
|
}
|
|
789
|
-
return
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
872
|
+
return {
|
|
873
|
+
port: address.port,
|
|
874
|
+
close: () => {
|
|
875
|
+
authorizer.close();
|
|
876
|
+
closeServer(server);
|
|
877
|
+
},
|
|
878
|
+
authorizeToolCall: (call) => authorizer.authorizeToolCall(call)
|
|
879
|
+
};
|
|
795
880
|
}
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
() => []
|
|
801
|
-
);
|
|
802
|
-
for (const entry of procEntries) {
|
|
803
|
-
if (!entry.isDirectory() || !/^\d+$/.test(entry.name)) continue;
|
|
804
|
-
const fdDir = `/proc/${entry.name}/fd`;
|
|
805
|
-
const fds = await readdir(fdDir).catch(() => []);
|
|
806
|
-
for (const fd of fds) {
|
|
807
|
-
const target = await readlink(`${fdDir}/${fd}`).catch(() => void 0);
|
|
808
|
-
if (target !== `socket:[${inode}]`) continue;
|
|
809
|
-
const cmdline = await readFile(`/proc/${entry.name}/cmdline`, "utf8").then((value) => value.split("\0").filter(Boolean)).catch(() => void 0);
|
|
810
|
-
if (cmdline) return cmdline;
|
|
811
|
-
}
|
|
881
|
+
function closeServer(server) {
|
|
882
|
+
try {
|
|
883
|
+
server.close();
|
|
884
|
+
} catch {
|
|
812
885
|
}
|
|
813
|
-
return void 0;
|
|
814
886
|
}
|
|
815
887
|
|
|
816
888
|
// src/bridge/index.ts
|
|
@@ -903,7 +975,6 @@ async function ensureRuntime({
|
|
|
903
975
|
if (start.tools && start.tools.length > 0) {
|
|
904
976
|
runtime.toolNames = new Set(start.tools.map((tool) => tool.name));
|
|
905
977
|
runtime.relay = await startToolRelay({
|
|
906
|
-
allowedScriptPaths: [`${bootstrapDir}/host-tool-mcp.mjs`],
|
|
907
978
|
tools: start.tools,
|
|
908
979
|
emit,
|
|
909
980
|
requestToolResult: turn.requestToolResult
|
|
@@ -2153,87 +2224,11 @@ function emitAssistantContentPart(part, emit) {
|
|
|
2153
2224
|
emit({ type: "reasoning-end", id });
|
|
2154
2225
|
}
|
|
2155
2226
|
async function startToolRelay({
|
|
2156
|
-
allowedScriptPaths,
|
|
2157
2227
|
tools,
|
|
2158
2228
|
emit,
|
|
2159
2229
|
requestToolResult
|
|
2160
2230
|
}) {
|
|
2161
|
-
|
|
2162
|
-
const allowedScriptPathSet = new Set(allowedScriptPaths);
|
|
2163
|
-
const authorizer = new ToolRelayAuthorizer();
|
|
2164
|
-
const server = createServer(async (req, res) => {
|
|
2165
|
-
try {
|
|
2166
|
-
if (req.method !== "POST" || req.url !== "/") {
|
|
2167
|
-
res.writeHead(401, { "Content-Type": "application/json" });
|
|
2168
|
-
res.end(JSON.stringify({ error: "unauthorized tool relay request" }));
|
|
2169
|
-
return;
|
|
2170
|
-
}
|
|
2171
|
-
const chunks = [];
|
|
2172
|
-
for await (const chunk of req) {
|
|
2173
|
-
chunks.push(chunk);
|
|
2174
|
-
}
|
|
2175
|
-
const body = Buffer.concat(chunks).toString("utf8");
|
|
2176
|
-
const { requestId, toolName, input } = JSON.parse(body);
|
|
2177
|
-
if (!toolNames.has(toolName)) {
|
|
2178
|
-
res.writeHead(403, { "Content-Type": "application/json" });
|
|
2179
|
-
res.end(
|
|
2180
|
-
JSON.stringify({ error: `Tool "${toolName}" is not available` })
|
|
2181
|
-
);
|
|
2182
|
-
return;
|
|
2183
|
-
}
|
|
2184
|
-
const authorized = authorizer.consumeToolCall({ toolName, input }) || await isToolRelayRequestFromAllowedProcess({
|
|
2185
|
-
socket: req.socket,
|
|
2186
|
-
allowedScriptPaths: allowedScriptPathSet
|
|
2187
|
-
});
|
|
2188
|
-
if (!authorized) {
|
|
2189
|
-
res.writeHead(401, { "Content-Type": "application/json" });
|
|
2190
|
-
res.end(JSON.stringify({ error: "unauthorized tool relay request" }));
|
|
2191
|
-
return;
|
|
2192
|
-
}
|
|
2193
|
-
emit({
|
|
2194
|
-
type: "tool-call",
|
|
2195
|
-
toolCallId: requestId,
|
|
2196
|
-
toolName,
|
|
2197
|
-
input: JSON.stringify(input ?? {}),
|
|
2198
|
-
providerExecuted: false
|
|
2199
|
-
});
|
|
2200
|
-
const { output, isError } = await requestToolResult(requestId);
|
|
2201
|
-
emit({
|
|
2202
|
-
type: "tool-result",
|
|
2203
|
-
toolCallId: requestId,
|
|
2204
|
-
toolName,
|
|
2205
|
-
result: output ?? null,
|
|
2206
|
-
isError: !!isError
|
|
2207
|
-
});
|
|
2208
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
2209
|
-
res.end(JSON.stringify({ result: output }));
|
|
2210
|
-
} catch (error) {
|
|
2211
|
-
res.writeHead(500, { "Content-Type": "application/json" });
|
|
2212
|
-
res.end(
|
|
2213
|
-
JSON.stringify({
|
|
2214
|
-
error: error instanceof Error ? error.message : String(error)
|
|
2215
|
-
})
|
|
2216
|
-
);
|
|
2217
|
-
}
|
|
2218
|
-
});
|
|
2219
|
-
await new Promise(
|
|
2220
|
-
(resolve) => server.listen(0, "127.0.0.1", () => resolve())
|
|
2221
|
-
);
|
|
2222
|
-
const address = server.address();
|
|
2223
|
-
if (!address || typeof address === "string") {
|
|
2224
|
-
throw new Error("tool relay did not expose a numeric port");
|
|
2225
|
-
}
|
|
2226
|
-
return {
|
|
2227
|
-
port: address.port,
|
|
2228
|
-
close: () => closeServer(server),
|
|
2229
|
-
authorizeToolCall: (call) => authorizer.authorizeToolCall(call)
|
|
2230
|
-
};
|
|
2231
|
-
}
|
|
2232
|
-
function closeServer(server) {
|
|
2233
|
-
try {
|
|
2234
|
-
server.close();
|
|
2235
|
-
} catch {
|
|
2236
|
-
}
|
|
2231
|
+
return startAuthorizedToolRelay({ tools, emit, requestToolResult });
|
|
2237
2232
|
}
|
|
2238
2233
|
function createDeferred() {
|
|
2239
2234
|
let resolve;
|