@ai-sdk/harness-opencode 1.0.71 → 1.0.73
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 +21 -0
- package/dist/bridge/index.mjs +16 -1
- package/dist/bridge/index.mjs.map +1 -1
- package/dist/index.d.ts +8 -2
- package/dist/index.js +218 -77
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/bridge/index.ts +3 -0
- package/src/bridge/opencode-server-auth.ts +15 -0
- package/src/index.ts +4 -1
- package/src/opencode-auth.ts +154 -15
- package/src/opencode-bootstrap.ts +58 -0
- package/src/opencode-harness.ts +75 -76
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,12 @@ import * as _ai_sdk_provider_utils from '@ai-sdk/provider-utils';
|
|
|
2
2
|
import * as _ai_sdk_harness from '@ai-sdk/harness';
|
|
3
3
|
import { HarnessV1, HarnessV1BuiltinTool } from '@ai-sdk/harness';
|
|
4
4
|
|
|
5
|
-
type
|
|
5
|
+
type OpenCodeResolvedAuthenticationMode = 'anthropic' | 'openai' | 'ai-gateway';
|
|
6
|
+
type OpenCodeAuthenticationMode = OpenCodeResolvedAuthenticationMode | 'auto';
|
|
7
|
+
/**
|
|
8
|
+
* @deprecated Passing an object to auth options is deprecated. Use an `OpenCodeAuthenticationMode` string value ("auto" | "anthropic" | "openai" | "ai-gateway") instead, and pass credentials via environment variables.
|
|
9
|
+
*/
|
|
10
|
+
type LegacyOpenCodeAuthOptions = {
|
|
6
11
|
readonly gateway?: {
|
|
7
12
|
readonly apiKey?: string;
|
|
8
13
|
readonly baseUrl?: string;
|
|
@@ -25,6 +30,7 @@ type OpenCodeAuthOptions = {
|
|
|
25
30
|
readonly queryParams?: Record<string, string>;
|
|
26
31
|
};
|
|
27
32
|
};
|
|
33
|
+
type OpenCodeAuthOptions = OpenCodeAuthenticationMode | LegacyOpenCodeAuthOptions;
|
|
28
34
|
|
|
29
35
|
type OpenCodeHarnessSettings = {
|
|
30
36
|
readonly auth?: OpenCodeAuthOptions;
|
|
@@ -184,4 +190,4 @@ declare const openCode: _ai_sdk_harness.HarnessV1<{
|
|
|
184
190
|
}, any, any>;
|
|
185
191
|
}>;
|
|
186
192
|
|
|
187
|
-
export { type OpenCodeAuthOptions, type OpenCodeHarnessSettings, VERSION, createOpenCode, openCode };
|
|
193
|
+
export { type OpenCodeAuthOptions, type OpenCodeAuthenticationMode, type OpenCodeHarnessSettings, VERSION, createOpenCode, openCode };
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
// src/opencode-harness.ts
|
|
2
2
|
import { randomBytes } from "crypto";
|
|
3
|
-
import { readFile } from "fs/promises";
|
|
4
3
|
import path from "path";
|
|
5
|
-
import { fileURLToPath } from "url";
|
|
6
4
|
import {
|
|
7
5
|
commonTool,
|
|
8
6
|
HarnessCapabilityUnsupportedError,
|
|
@@ -15,9 +13,11 @@ import {
|
|
|
15
13
|
drainBridgeProcessStream,
|
|
16
14
|
forwardBridgeProcessStream,
|
|
17
15
|
markBridgeStarting,
|
|
16
|
+
maskSandboxCredentials,
|
|
18
17
|
resolveSandboxHomeDir,
|
|
19
18
|
SandboxChannel,
|
|
20
19
|
shellQuote,
|
|
20
|
+
warnCredentialBrokeringUnavailable,
|
|
21
21
|
waitForBridgeReady,
|
|
22
22
|
writeSkills as writeHarnessSkills
|
|
23
23
|
} from "@ai-sdk/harness/utils";
|
|
@@ -27,8 +27,106 @@ import {
|
|
|
27
27
|
import { WebSocket } from "ws";
|
|
28
28
|
import { z as z2 } from "zod/v4";
|
|
29
29
|
|
|
30
|
+
// src/opencode-bootstrap.ts
|
|
31
|
+
import { readFile } from "fs/promises";
|
|
32
|
+
import { fileURLToPath } from "url";
|
|
33
|
+
var OPENCODE_BOOTSTRAP_DIR = ".harness-bootstrap/opencode";
|
|
34
|
+
var cachedBootstrap;
|
|
35
|
+
async function getOpenCodeBootstrap() {
|
|
36
|
+
if (cachedBootstrap != null) return cachedBootstrap;
|
|
37
|
+
const [pkg, lock, bridge, hostToolMcp] = await Promise.all([
|
|
38
|
+
readBridgeAsset("package.json"),
|
|
39
|
+
readBridgeAsset("pnpm-lock.yaml"),
|
|
40
|
+
readBridgeAsset("index.mjs"),
|
|
41
|
+
readBridgeAsset("host-tool-mcp.mjs")
|
|
42
|
+
]);
|
|
43
|
+
cachedBootstrap = {
|
|
44
|
+
harnessId: "opencode",
|
|
45
|
+
bootstrapDir: OPENCODE_BOOTSTRAP_DIR,
|
|
46
|
+
files: [
|
|
47
|
+
{ path: `${OPENCODE_BOOTSTRAP_DIR}/package.json`, content: pkg },
|
|
48
|
+
{ path: `${OPENCODE_BOOTSTRAP_DIR}/pnpm-lock.yaml`, content: lock },
|
|
49
|
+
{ path: `${OPENCODE_BOOTSTRAP_DIR}/bridge.mjs`, content: bridge },
|
|
50
|
+
{
|
|
51
|
+
path: `${OPENCODE_BOOTSTRAP_DIR}/host-tool-mcp.mjs`,
|
|
52
|
+
content: hostToolMcp
|
|
53
|
+
}
|
|
54
|
+
],
|
|
55
|
+
commands: [
|
|
56
|
+
{
|
|
57
|
+
command: "pnpm install --frozen-lockfile --store-dir .pnpm-store"
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
command: "node node_modules/opencode-ai/postinstall.mjs && ./node_modules/.bin/opencode --version"
|
|
61
|
+
}
|
|
62
|
+
]
|
|
63
|
+
};
|
|
64
|
+
return cachedBootstrap;
|
|
65
|
+
}
|
|
66
|
+
async function readBridgeAsset(name) {
|
|
67
|
+
const candidates = [
|
|
68
|
+
new URL(`./bridge/${name}`, import.meta.url),
|
|
69
|
+
new URL(`../bridge/${name}`, import.meta.url)
|
|
70
|
+
];
|
|
71
|
+
let lastErr;
|
|
72
|
+
for (const url of candidates) {
|
|
73
|
+
try {
|
|
74
|
+
return await readFile(fileURLToPath(url), "utf8");
|
|
75
|
+
} catch (err) {
|
|
76
|
+
const code = err.code;
|
|
77
|
+
if (code !== "ENOENT") throw err;
|
|
78
|
+
lastErr = err;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
throw lastErr ?? new Error(`bridge asset not found: ${name}`);
|
|
82
|
+
}
|
|
83
|
+
|
|
30
84
|
// src/opencode-auth.ts
|
|
31
|
-
import {
|
|
85
|
+
import {
|
|
86
|
+
createCredentialRequestTransformation,
|
|
87
|
+
getAiGatewayAuthFromEnv
|
|
88
|
+
} from "@ai-sdk/harness/utils";
|
|
89
|
+
var OPENCODE_CREDENTIAL_ENVIRONMENT_VARIABLES = [
|
|
90
|
+
"AI_GATEWAY_API_KEY",
|
|
91
|
+
"OPENAI_API_KEY",
|
|
92
|
+
"ANTHROPIC_API_KEY",
|
|
93
|
+
"ANTHROPIC_AUTH_TOKEN"
|
|
94
|
+
];
|
|
95
|
+
function createOpenCodeRequestTransformations(env, auth) {
|
|
96
|
+
switch (auth) {
|
|
97
|
+
case "ai-gateway":
|
|
98
|
+
return env.AI_GATEWAY_API_KEY ? [
|
|
99
|
+
createCredentialRequestTransformation({
|
|
100
|
+
baseUrl: env.AI_GATEWAY_BASE_URL,
|
|
101
|
+
headers: {
|
|
102
|
+
Authorization: `Bearer ${env.AI_GATEWAY_API_KEY}`
|
|
103
|
+
}
|
|
104
|
+
})
|
|
105
|
+
] : [];
|
|
106
|
+
case "openai":
|
|
107
|
+
return env.OPENAI_API_KEY ? [
|
|
108
|
+
createCredentialRequestTransformation({
|
|
109
|
+
baseUrl: env.OPENAI_BASE_URL ?? "https://api.openai.com/v1",
|
|
110
|
+
headers: { Authorization: `Bearer ${env.OPENAI_API_KEY}` }
|
|
111
|
+
})
|
|
112
|
+
] : [];
|
|
113
|
+
case "anthropic": {
|
|
114
|
+
const headers = {};
|
|
115
|
+
if (env.ANTHROPIC_API_KEY) {
|
|
116
|
+
headers["x-api-key"] = env.ANTHROPIC_API_KEY;
|
|
117
|
+
}
|
|
118
|
+
if (env.ANTHROPIC_AUTH_TOKEN) {
|
|
119
|
+
headers.Authorization = `Bearer ${env.ANTHROPIC_AUTH_TOKEN}`;
|
|
120
|
+
}
|
|
121
|
+
return Object.keys(headers).length === 0 ? [] : [
|
|
122
|
+
createCredentialRequestTransformation({
|
|
123
|
+
baseUrl: env.ANTHROPIC_BASE_URL ?? "https://api.anthropic.com",
|
|
124
|
+
headers
|
|
125
|
+
})
|
|
126
|
+
];
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
32
130
|
function resolveOpenCodeProvider({
|
|
33
131
|
model,
|
|
34
132
|
provider
|
|
@@ -66,26 +164,75 @@ function resolveOpenCodeEnv({
|
|
|
66
164
|
provider,
|
|
67
165
|
processEnv = process.env
|
|
68
166
|
}) {
|
|
167
|
+
const normalizedAuth = normalizeOpenCodeAuthToLegacyAuth(auth);
|
|
69
168
|
const selectedProvider = resolveOpenCodeProvider({ model, provider });
|
|
70
|
-
if (
|
|
71
|
-
return pickOpenAICompatible(
|
|
169
|
+
if (normalizedAuth?.openaiCompatible) {
|
|
170
|
+
return pickOpenAICompatible(normalizedAuth.openaiCompatible, processEnv);
|
|
72
171
|
}
|
|
73
172
|
if (selectedProvider === "openai") {
|
|
74
|
-
if (
|
|
75
|
-
return pickOpenAI({ explicit:
|
|
173
|
+
if (normalizedAuth?.openai) {
|
|
174
|
+
return pickOpenAI({ explicit: normalizedAuth.openai, processEnv });
|
|
76
175
|
}
|
|
77
|
-
} else if (
|
|
78
|
-
return pickAnthropic({ explicit:
|
|
176
|
+
} else if (normalizedAuth?.anthropic) {
|
|
177
|
+
return pickAnthropic({ explicit: normalizedAuth.anthropic, processEnv });
|
|
79
178
|
}
|
|
80
179
|
const gatewayAuthFromEnv = getAiGatewayAuthFromEnv({ env: processEnv });
|
|
81
|
-
if (
|
|
82
|
-
return pickGateway({
|
|
180
|
+
if (normalizedAuth?.gateway) {
|
|
181
|
+
return pickGateway({
|
|
182
|
+
explicit: normalizedAuth.gateway,
|
|
183
|
+
gatewayAuthFromEnv
|
|
184
|
+
});
|
|
83
185
|
}
|
|
84
186
|
if (gatewayAuthFromEnv.apiKey) {
|
|
85
187
|
return pickGateway({ explicit: {}, gatewayAuthFromEnv });
|
|
86
188
|
}
|
|
87
189
|
return selectedProvider === "openai" ? pickOpenAI({ processEnv }) : pickAnthropic({ processEnv });
|
|
88
190
|
}
|
|
191
|
+
function resolveOpenCodeAuthenticationMode({
|
|
192
|
+
auth,
|
|
193
|
+
model,
|
|
194
|
+
provider,
|
|
195
|
+
processEnv = process.env
|
|
196
|
+
}) {
|
|
197
|
+
if (typeof auth !== "string" && auth?.openaiCompatible) {
|
|
198
|
+
return "openai";
|
|
199
|
+
}
|
|
200
|
+
const selectedProvider = resolveOpenCodeProvider({ model, provider });
|
|
201
|
+
if (selectedProvider === "openai" && (auth === "openai" || typeof auth !== "string" && auth?.openai)) {
|
|
202
|
+
return "openai";
|
|
203
|
+
}
|
|
204
|
+
if (selectedProvider === "anthropic" && (auth === "anthropic" || typeof auth !== "string" && auth?.anthropic)) {
|
|
205
|
+
return "anthropic";
|
|
206
|
+
}
|
|
207
|
+
if (auth === "ai-gateway" || typeof auth !== "string" && auth?.gateway) {
|
|
208
|
+
return "ai-gateway";
|
|
209
|
+
}
|
|
210
|
+
if (getAiGatewayAuthFromEnv({ env: processEnv }).apiKey) {
|
|
211
|
+
return "ai-gateway";
|
|
212
|
+
}
|
|
213
|
+
return selectedProvider;
|
|
214
|
+
}
|
|
215
|
+
function normalizeOpenCodeAuthToLegacyAuth(auth) {
|
|
216
|
+
if (auth == null || auth === "auto") {
|
|
217
|
+
return void 0;
|
|
218
|
+
}
|
|
219
|
+
if (typeof auth === "string") {
|
|
220
|
+
switch (auth) {
|
|
221
|
+
case "anthropic":
|
|
222
|
+
return { anthropic: {} };
|
|
223
|
+
case "openai":
|
|
224
|
+
return { openai: {} };
|
|
225
|
+
case "ai-gateway":
|
|
226
|
+
return { gateway: {} };
|
|
227
|
+
default:
|
|
228
|
+
return void 0;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
console.warn(
|
|
232
|
+
'[opencode] Passing an object to auth options is deprecated. Use a string mode ("auto" | "anthropic" | "openai" | "ai-gateway") instead, and pass credentials via environment variables.'
|
|
233
|
+
);
|
|
234
|
+
return auth;
|
|
235
|
+
}
|
|
89
236
|
function pickOpenAICompatible(explicit, processEnv) {
|
|
90
237
|
const env = {};
|
|
91
238
|
const apiKey = explicit.apiKey ?? processEnv.OPENAI_API_KEY;
|
|
@@ -136,9 +283,10 @@ function pickGateway({
|
|
|
136
283
|
const env = {};
|
|
137
284
|
const apiKey = explicit.apiKey ?? gatewayAuthFromEnv.apiKey;
|
|
138
285
|
if (apiKey) env.AI_GATEWAY_API_KEY = apiKey;
|
|
139
|
-
|
|
286
|
+
const baseUrl = toOpenCodeGatewayBaseUrl(
|
|
140
287
|
explicit.baseUrl ?? gatewayAuthFromEnv.baseUrl
|
|
141
288
|
);
|
|
289
|
+
env.AI_GATEWAY_BASE_URL = baseUrl;
|
|
142
290
|
return env;
|
|
143
291
|
}
|
|
144
292
|
function toOpenCodeGatewayBaseUrl(baseUrl) {
|
|
@@ -169,7 +317,7 @@ var inboundMessageSchema = z.discriminatedUnion("type", [
|
|
|
169
317
|
]);
|
|
170
318
|
|
|
171
319
|
// src/version.ts
|
|
172
|
-
var VERSION = true ? "1.0.
|
|
320
|
+
var VERSION = true ? "1.0.73" : "0.0.0-test";
|
|
173
321
|
|
|
174
322
|
// src/opencode-harness.ts
|
|
175
323
|
var OPENCODE_CLIENT_APP = `ai-sdk/harness-opencode/${VERSION}`;
|
|
@@ -272,7 +420,6 @@ var OPENCODE_BUILTIN_TOOLS = {
|
|
|
272
420
|
})
|
|
273
421
|
})
|
|
274
422
|
};
|
|
275
|
-
var BOOTSTRAP_DIR = ".harness-bootstrap/opencode";
|
|
276
423
|
var openCodeBridgeCoordsSchema = z2.object({
|
|
277
424
|
port: z2.number(),
|
|
278
425
|
token: z2.string(),
|
|
@@ -289,51 +436,48 @@ function createOpenCode(settings = {}) {
|
|
|
289
436
|
'OpenCode MCP server name "harness-tools" is reserved for HarnessAgent tools.'
|
|
290
437
|
);
|
|
291
438
|
}
|
|
292
|
-
let cachedBootstrap;
|
|
293
439
|
return {
|
|
294
440
|
specificationVersion: "harness-v1",
|
|
295
441
|
harnessId: "opencode",
|
|
296
442
|
builtinTools: OPENCODE_BUILTIN_TOOLS,
|
|
297
443
|
supportsBuiltinToolApprovals: true,
|
|
298
444
|
lifecycleStateSchema: openCodeResumeStateSchema,
|
|
299
|
-
getBootstrap:
|
|
300
|
-
if (cachedBootstrap != null) return cachedBootstrap;
|
|
301
|
-
const [pkg, lock, bridge, hostToolMcp] = await Promise.all([
|
|
302
|
-
readBridgeAsset("package.json"),
|
|
303
|
-
readBridgeAsset("pnpm-lock.yaml"),
|
|
304
|
-
readBridgeAsset("index.mjs"),
|
|
305
|
-
readBridgeAsset("host-tool-mcp.mjs")
|
|
306
|
-
]);
|
|
307
|
-
cachedBootstrap = {
|
|
308
|
-
harnessId: "opencode",
|
|
309
|
-
bootstrapDir: BOOTSTRAP_DIR,
|
|
310
|
-
files: [
|
|
311
|
-
{ path: `${BOOTSTRAP_DIR}/package.json`, content: pkg },
|
|
312
|
-
{ path: `${BOOTSTRAP_DIR}/pnpm-lock.yaml`, content: lock },
|
|
313
|
-
{ path: `${BOOTSTRAP_DIR}/bridge.mjs`, content: bridge },
|
|
314
|
-
{
|
|
315
|
-
path: `${BOOTSTRAP_DIR}/host-tool-mcp.mjs`,
|
|
316
|
-
content: hostToolMcp
|
|
317
|
-
}
|
|
318
|
-
],
|
|
319
|
-
commands: [
|
|
320
|
-
{
|
|
321
|
-
command: "pnpm install --frozen-lockfile --store-dir .pnpm-store"
|
|
322
|
-
},
|
|
323
|
-
{
|
|
324
|
-
command: "node node_modules/opencode-ai/postinstall.mjs && ./node_modules/.bin/opencode --version"
|
|
325
|
-
}
|
|
326
|
-
]
|
|
327
|
-
};
|
|
328
|
-
return cachedBootstrap;
|
|
329
|
-
},
|
|
445
|
+
getBootstrap: getOpenCodeBootstrap,
|
|
330
446
|
doStart: async (startOpts) => {
|
|
331
447
|
const sandboxSession = startOpts.sandboxSession;
|
|
448
|
+
const authenticationMode = resolveOpenCodeAuthenticationMode({
|
|
449
|
+
auth: settings.auth,
|
|
450
|
+
model: settings.model,
|
|
451
|
+
provider: settings.provider
|
|
452
|
+
});
|
|
453
|
+
const resolvedAuthEnvironment = resolveOpenCodeEnv({
|
|
454
|
+
auth: settings.auth,
|
|
455
|
+
model: settings.model,
|
|
456
|
+
provider: settings.provider
|
|
457
|
+
});
|
|
458
|
+
let sandboxAuthEnvironment = resolvedAuthEnvironment;
|
|
459
|
+
if (sandboxSession.addRequestTransformations != null) {
|
|
460
|
+
const requestTransformations = createOpenCodeRequestTransformations(
|
|
461
|
+
resolvedAuthEnvironment,
|
|
462
|
+
authenticationMode
|
|
463
|
+
);
|
|
464
|
+
if (requestTransformations.length > 0) {
|
|
465
|
+
await sandboxSession.addRequestTransformations(
|
|
466
|
+
requestTransformations
|
|
467
|
+
);
|
|
468
|
+
}
|
|
469
|
+
sandboxAuthEnvironment = maskSandboxCredentials({
|
|
470
|
+
environment: resolvedAuthEnvironment,
|
|
471
|
+
credentialEnvironmentVariables: OPENCODE_CREDENTIAL_ENVIRONMENT_VARIABLES
|
|
472
|
+
});
|
|
473
|
+
} else {
|
|
474
|
+
warnCredentialBrokeringUnavailable();
|
|
475
|
+
}
|
|
332
476
|
const session = sandboxSession.restricted();
|
|
333
477
|
const sandboxId = sandboxSession.id;
|
|
334
478
|
const bootstrapDir = path.posix.resolve(
|
|
335
479
|
sandboxSession.defaultWorkingDirectory,
|
|
336
|
-
|
|
480
|
+
OPENCODE_BOOTSTRAP_DIR
|
|
337
481
|
);
|
|
338
482
|
const lifecycleState = startOpts.continueFrom ?? startOpts.resumeFrom;
|
|
339
483
|
const isResume = lifecycleState != null;
|
|
@@ -359,12 +503,16 @@ function createOpenCode(settings = {}) {
|
|
|
359
503
|
});
|
|
360
504
|
if (coords) {
|
|
361
505
|
try {
|
|
362
|
-
const
|
|
506
|
+
const endpoint2 = await sandboxSession.getPortEndpoint({
|
|
363
507
|
port: coords.port,
|
|
364
508
|
protocol: "ws"
|
|
365
|
-
})
|
|
509
|
+
});
|
|
510
|
+
const attachEndpoint = withBridgeToken({
|
|
511
|
+
endpoint: endpoint2,
|
|
512
|
+
token: coords.token
|
|
513
|
+
});
|
|
366
514
|
const attachChannel = new SandboxChannel({
|
|
367
|
-
connect: () => openWebSocket(
|
|
515
|
+
connect: () => openWebSocket(attachEndpoint),
|
|
368
516
|
outboundSchema: outboundMessageSchema,
|
|
369
517
|
initialLastSeenEventId: coords.lastSeenEventId,
|
|
370
518
|
onDiagnostic,
|
|
@@ -422,11 +570,7 @@ function createOpenCode(settings = {}) {
|
|
|
422
570
|
abortSignal: startOpts.abortSignal
|
|
423
571
|
}) : void 0;
|
|
424
572
|
const env = {
|
|
425
|
-
...
|
|
426
|
-
auth: settings.auth,
|
|
427
|
-
model: settings.model,
|
|
428
|
-
provider: settings.provider
|
|
429
|
-
}),
|
|
573
|
+
...sandboxAuthEnvironment,
|
|
430
574
|
AI_SDK_HARNESS_CLIENT_APP: OPENCODE_CLIENT_APP,
|
|
431
575
|
BRIDGE_CHANNEL_TOKEN: token,
|
|
432
576
|
BRIDGE_WS_PORT: String(port),
|
|
@@ -485,12 +629,13 @@ function createOpenCode(settings = {}) {
|
|
|
485
629
|
})
|
|
486
630
|
});
|
|
487
631
|
void drainBridgeProcessStream(proc.stdout);
|
|
488
|
-
const
|
|
632
|
+
const endpoint = await sandboxSession.getPortEndpoint({
|
|
489
633
|
port: boundPort,
|
|
490
634
|
protocol: "ws"
|
|
491
|
-
})
|
|
635
|
+
});
|
|
636
|
+
const bridgeEndpoint = withBridgeToken({ endpoint, token });
|
|
492
637
|
const channel = new SandboxChannel({
|
|
493
|
-
connect: () => openWebSocket(
|
|
638
|
+
connect: () => openWebSocket(bridgeEndpoint),
|
|
494
639
|
outboundSchema: outboundMessageSchema,
|
|
495
640
|
onDiagnostic,
|
|
496
641
|
onBridgeError,
|
|
@@ -529,23 +674,6 @@ function resolveBridgePort(sandboxSession, override) {
|
|
|
529
674
|
message: "The OpenCode harness needs a TCP port exposed by the sandbox. Create the sandbox with `ports: [<port>]` or pass `createOpenCode({ port })`."
|
|
530
675
|
});
|
|
531
676
|
}
|
|
532
|
-
async function readBridgeAsset(name) {
|
|
533
|
-
const candidates = [
|
|
534
|
-
new URL(`./bridge/${name}`, import.meta.url),
|
|
535
|
-
new URL(`../bridge/${name}`, import.meta.url)
|
|
536
|
-
];
|
|
537
|
-
let lastErr;
|
|
538
|
-
for (const url of candidates) {
|
|
539
|
-
try {
|
|
540
|
-
return await readFile(fileURLToPath(url), "utf8");
|
|
541
|
-
} catch (err) {
|
|
542
|
-
const code = err.code;
|
|
543
|
-
if (code !== "ENOENT") throw err;
|
|
544
|
-
lastErr = err;
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
throw lastErr ?? new Error(`bridge asset not found: ${name}`);
|
|
548
|
-
}
|
|
549
677
|
async function writeOpenCodeSkills({
|
|
550
678
|
sandbox,
|
|
551
679
|
skills,
|
|
@@ -563,9 +691,14 @@ async function writeOpenCodeSkills({
|
|
|
563
691
|
});
|
|
564
692
|
return { skillsDir };
|
|
565
693
|
}
|
|
566
|
-
function openWebSocket(
|
|
694
|
+
function openWebSocket({
|
|
695
|
+
url,
|
|
696
|
+
headers
|
|
697
|
+
}) {
|
|
567
698
|
return new Promise((resolve, reject) => {
|
|
568
|
-
const ws = new WebSocket(url
|
|
699
|
+
const ws = new WebSocket(url, {
|
|
700
|
+
headers: headers == null ? void 0 : { ...headers }
|
|
701
|
+
});
|
|
569
702
|
const onOpen = () => {
|
|
570
703
|
ws.off("error", onError);
|
|
571
704
|
resolve(ws);
|
|
@@ -578,6 +711,14 @@ function openWebSocket(url) {
|
|
|
578
711
|
ws.once("error", onError);
|
|
579
712
|
});
|
|
580
713
|
}
|
|
714
|
+
function withBridgeToken({
|
|
715
|
+
endpoint,
|
|
716
|
+
token
|
|
717
|
+
}) {
|
|
718
|
+
const bridgeUrl = new URL(endpoint.url);
|
|
719
|
+
bridgeUrl.searchParams.set("agent_bridge_token", token);
|
|
720
|
+
return { ...endpoint, url: bridgeUrl.toString() };
|
|
721
|
+
}
|
|
581
722
|
function createSession({
|
|
582
723
|
sessionId,
|
|
583
724
|
channel,
|