@ai-sdk/harness-opencode 1.0.72 → 1.0.74
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 +22 -0
- package/dist/bridge/index.mjs +68 -28
- package/dist/bridge/index.mjs.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +199 -69
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/bridge/create-emit-stream-event.ts +3 -0
- package/src/bridge/index.ts +47 -7
- package/src/bridge/opencode-server-auth.ts +15 -0
- package/src/opencode-auth.ts +101 -4
- package/src/opencode-bootstrap.ts +58 -0
- package/src/opencode-harness.ts +101 -76
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,8 @@ 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';
|
|
6
7
|
/**
|
|
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.
|
|
8
9
|
*/
|
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
|
|
@@ -90,6 +188,30 @@ function resolveOpenCodeEnv({
|
|
|
90
188
|
}
|
|
91
189
|
return selectedProvider === "openai" ? pickOpenAI({ processEnv }) : pickAnthropic({ processEnv });
|
|
92
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
|
+
}
|
|
93
215
|
function normalizeOpenCodeAuthToLegacyAuth(auth) {
|
|
94
216
|
if (auth == null || auth === "auto") {
|
|
95
217
|
return void 0;
|
|
@@ -161,9 +283,10 @@ function pickGateway({
|
|
|
161
283
|
const env = {};
|
|
162
284
|
const apiKey = explicit.apiKey ?? gatewayAuthFromEnv.apiKey;
|
|
163
285
|
if (apiKey) env.AI_GATEWAY_API_KEY = apiKey;
|
|
164
|
-
|
|
286
|
+
const baseUrl = toOpenCodeGatewayBaseUrl(
|
|
165
287
|
explicit.baseUrl ?? gatewayAuthFromEnv.baseUrl
|
|
166
288
|
);
|
|
289
|
+
env.AI_GATEWAY_BASE_URL = baseUrl;
|
|
167
290
|
return env;
|
|
168
291
|
}
|
|
169
292
|
function toOpenCodeGatewayBaseUrl(baseUrl) {
|
|
@@ -194,7 +317,7 @@ var inboundMessageSchema = z.discriminatedUnion("type", [
|
|
|
194
317
|
]);
|
|
195
318
|
|
|
196
319
|
// src/version.ts
|
|
197
|
-
var VERSION = true ? "1.0.
|
|
320
|
+
var VERSION = true ? "1.0.74" : "0.0.0-test";
|
|
198
321
|
|
|
199
322
|
// src/opencode-harness.ts
|
|
200
323
|
var OPENCODE_CLIENT_APP = `ai-sdk/harness-opencode/${VERSION}`;
|
|
@@ -297,7 +420,6 @@ var OPENCODE_BUILTIN_TOOLS = {
|
|
|
297
420
|
})
|
|
298
421
|
})
|
|
299
422
|
};
|
|
300
|
-
var BOOTSTRAP_DIR = ".harness-bootstrap/opencode";
|
|
301
423
|
var openCodeBridgeCoordsSchema = z2.object({
|
|
302
424
|
port: z2.number(),
|
|
303
425
|
token: z2.string(),
|
|
@@ -314,51 +436,48 @@ function createOpenCode(settings = {}) {
|
|
|
314
436
|
'OpenCode MCP server name "harness-tools" is reserved for HarnessAgent tools.'
|
|
315
437
|
);
|
|
316
438
|
}
|
|
317
|
-
let cachedBootstrap;
|
|
318
439
|
return {
|
|
319
440
|
specificationVersion: "harness-v1",
|
|
320
441
|
harnessId: "opencode",
|
|
321
442
|
builtinTools: OPENCODE_BUILTIN_TOOLS,
|
|
322
443
|
supportsBuiltinToolApprovals: true,
|
|
323
444
|
lifecycleStateSchema: openCodeResumeStateSchema,
|
|
324
|
-
getBootstrap:
|
|
325
|
-
if (cachedBootstrap != null) return cachedBootstrap;
|
|
326
|
-
const [pkg, lock, bridge, hostToolMcp] = await Promise.all([
|
|
327
|
-
readBridgeAsset("package.json"),
|
|
328
|
-
readBridgeAsset("pnpm-lock.yaml"),
|
|
329
|
-
readBridgeAsset("index.mjs"),
|
|
330
|
-
readBridgeAsset("host-tool-mcp.mjs")
|
|
331
|
-
]);
|
|
332
|
-
cachedBootstrap = {
|
|
333
|
-
harnessId: "opencode",
|
|
334
|
-
bootstrapDir: BOOTSTRAP_DIR,
|
|
335
|
-
files: [
|
|
336
|
-
{ path: `${BOOTSTRAP_DIR}/package.json`, content: pkg },
|
|
337
|
-
{ path: `${BOOTSTRAP_DIR}/pnpm-lock.yaml`, content: lock },
|
|
338
|
-
{ path: `${BOOTSTRAP_DIR}/bridge.mjs`, content: bridge },
|
|
339
|
-
{
|
|
340
|
-
path: `${BOOTSTRAP_DIR}/host-tool-mcp.mjs`,
|
|
341
|
-
content: hostToolMcp
|
|
342
|
-
}
|
|
343
|
-
],
|
|
344
|
-
commands: [
|
|
345
|
-
{
|
|
346
|
-
command: "pnpm install --frozen-lockfile --store-dir .pnpm-store"
|
|
347
|
-
},
|
|
348
|
-
{
|
|
349
|
-
command: "node node_modules/opencode-ai/postinstall.mjs && ./node_modules/.bin/opencode --version"
|
|
350
|
-
}
|
|
351
|
-
]
|
|
352
|
-
};
|
|
353
|
-
return cachedBootstrap;
|
|
354
|
-
},
|
|
445
|
+
getBootstrap: getOpenCodeBootstrap,
|
|
355
446
|
doStart: async (startOpts) => {
|
|
356
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
|
+
}
|
|
357
476
|
const session = sandboxSession.restricted();
|
|
358
477
|
const sandboxId = sandboxSession.id;
|
|
359
478
|
const bootstrapDir = path.posix.resolve(
|
|
360
479
|
sandboxSession.defaultWorkingDirectory,
|
|
361
|
-
|
|
480
|
+
OPENCODE_BOOTSTRAP_DIR
|
|
362
481
|
);
|
|
363
482
|
const lifecycleState = startOpts.continueFrom ?? startOpts.resumeFrom;
|
|
364
483
|
const isResume = lifecycleState != null;
|
|
@@ -384,12 +503,16 @@ function createOpenCode(settings = {}) {
|
|
|
384
503
|
});
|
|
385
504
|
if (coords) {
|
|
386
505
|
try {
|
|
387
|
-
const
|
|
506
|
+
const endpoint2 = await sandboxSession.getPortEndpoint({
|
|
388
507
|
port: coords.port,
|
|
389
508
|
protocol: "ws"
|
|
390
|
-
})
|
|
509
|
+
});
|
|
510
|
+
const attachEndpoint = withBridgeToken({
|
|
511
|
+
endpoint: endpoint2,
|
|
512
|
+
token: coords.token
|
|
513
|
+
});
|
|
391
514
|
const attachChannel = new SandboxChannel({
|
|
392
|
-
connect: () => openWebSocket(
|
|
515
|
+
connect: () => openWebSocket(attachEndpoint),
|
|
393
516
|
outboundSchema: outboundMessageSchema,
|
|
394
517
|
initialLastSeenEventId: coords.lastSeenEventId,
|
|
395
518
|
onDiagnostic,
|
|
@@ -447,11 +570,7 @@ function createOpenCode(settings = {}) {
|
|
|
447
570
|
abortSignal: startOpts.abortSignal
|
|
448
571
|
}) : void 0;
|
|
449
572
|
const env = {
|
|
450
|
-
...
|
|
451
|
-
auth: settings.auth,
|
|
452
|
-
model: settings.model,
|
|
453
|
-
provider: settings.provider
|
|
454
|
-
}),
|
|
573
|
+
...sandboxAuthEnvironment,
|
|
455
574
|
AI_SDK_HARNESS_CLIENT_APP: OPENCODE_CLIENT_APP,
|
|
456
575
|
BRIDGE_CHANNEL_TOKEN: token,
|
|
457
576
|
BRIDGE_WS_PORT: String(port),
|
|
@@ -510,12 +629,13 @@ function createOpenCode(settings = {}) {
|
|
|
510
629
|
})
|
|
511
630
|
});
|
|
512
631
|
void drainBridgeProcessStream(proc.stdout);
|
|
513
|
-
const
|
|
632
|
+
const endpoint = await sandboxSession.getPortEndpoint({
|
|
514
633
|
port: boundPort,
|
|
515
634
|
protocol: "ws"
|
|
516
|
-
})
|
|
635
|
+
});
|
|
636
|
+
const bridgeEndpoint = withBridgeToken({ endpoint, token });
|
|
517
637
|
const channel = new SandboxChannel({
|
|
518
|
-
connect: () => openWebSocket(
|
|
638
|
+
connect: () => openWebSocket(bridgeEndpoint),
|
|
519
639
|
outboundSchema: outboundMessageSchema,
|
|
520
640
|
onDiagnostic,
|
|
521
641
|
onBridgeError,
|
|
@@ -554,23 +674,6 @@ function resolveBridgePort(sandboxSession, override) {
|
|
|
554
674
|
message: "The OpenCode harness needs a TCP port exposed by the sandbox. Create the sandbox with `ports: [<port>]` or pass `createOpenCode({ port })`."
|
|
555
675
|
});
|
|
556
676
|
}
|
|
557
|
-
async function readBridgeAsset(name) {
|
|
558
|
-
const candidates = [
|
|
559
|
-
new URL(`./bridge/${name}`, import.meta.url),
|
|
560
|
-
new URL(`../bridge/${name}`, import.meta.url)
|
|
561
|
-
];
|
|
562
|
-
let lastErr;
|
|
563
|
-
for (const url of candidates) {
|
|
564
|
-
try {
|
|
565
|
-
return await readFile(fileURLToPath(url), "utf8");
|
|
566
|
-
} catch (err) {
|
|
567
|
-
const code = err.code;
|
|
568
|
-
if (code !== "ENOENT") throw err;
|
|
569
|
-
lastErr = err;
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
throw lastErr ?? new Error(`bridge asset not found: ${name}`);
|
|
573
|
-
}
|
|
574
677
|
async function writeOpenCodeSkills({
|
|
575
678
|
sandbox,
|
|
576
679
|
skills,
|
|
@@ -588,9 +691,14 @@ async function writeOpenCodeSkills({
|
|
|
588
691
|
});
|
|
589
692
|
return { skillsDir };
|
|
590
693
|
}
|
|
591
|
-
function openWebSocket(
|
|
694
|
+
function openWebSocket({
|
|
695
|
+
url,
|
|
696
|
+
headers
|
|
697
|
+
}) {
|
|
592
698
|
return new Promise((resolve, reject) => {
|
|
593
|
-
const ws = new WebSocket(url
|
|
699
|
+
const ws = new WebSocket(url, {
|
|
700
|
+
headers: headers == null ? void 0 : { ...headers }
|
|
701
|
+
});
|
|
594
702
|
const onOpen = () => {
|
|
595
703
|
ws.off("error", onError);
|
|
596
704
|
resolve(ws);
|
|
@@ -603,6 +711,14 @@ function openWebSocket(url) {
|
|
|
603
711
|
ws.once("error", onError);
|
|
604
712
|
});
|
|
605
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
|
+
}
|
|
606
722
|
function createSession({
|
|
607
723
|
sessionId,
|
|
608
724
|
channel,
|
|
@@ -767,6 +883,12 @@ function createSession({
|
|
|
767
883
|
isResume,
|
|
768
884
|
modelId: model,
|
|
769
885
|
doPromptTurn: async (promptOpts) => {
|
|
886
|
+
if (promptOpts.responseFormat?.type === "json" && promptOpts.responseFormat.schema == null) {
|
|
887
|
+
throw new HarnessCapabilityUnsupportedError({
|
|
888
|
+
message: "Harness 'opencode' requires a JSON schema for structured output.",
|
|
889
|
+
harnessId: "opencode"
|
|
890
|
+
});
|
|
891
|
+
}
|
|
770
892
|
const control = wireTurn({
|
|
771
893
|
emit: promptOpts.emit,
|
|
772
894
|
abortSignal: promptOpts.abortSignal
|
|
@@ -780,6 +902,7 @@ function createSession({
|
|
|
780
902
|
description: t.description,
|
|
781
903
|
inputSchema: t.inputSchema
|
|
782
904
|
})),
|
|
905
|
+
...promptOpts.responseFormat == null ? {} : { responseFormat: promptOpts.responseFormat },
|
|
783
906
|
...promptOpts.instructions ? { instructions: promptOpts.instructions } : {},
|
|
784
907
|
...startBase()
|
|
785
908
|
});
|
|
@@ -787,6 +910,12 @@ function createSession({
|
|
|
787
910
|
return control;
|
|
788
911
|
},
|
|
789
912
|
doContinueTurn: async (continueOpts) => {
|
|
913
|
+
if (continueOpts.responseFormat?.type === "json" && continueOpts.responseFormat.schema == null) {
|
|
914
|
+
throw new HarnessCapabilityUnsupportedError({
|
|
915
|
+
message: "Harness 'opencode' requires a JSON schema for structured output.",
|
|
916
|
+
harnessId: "opencode"
|
|
917
|
+
});
|
|
918
|
+
}
|
|
790
919
|
const control = wireTurn({
|
|
791
920
|
emit: continueOpts.emit,
|
|
792
921
|
abortSignal: continueOpts.abortSignal
|
|
@@ -801,6 +930,7 @@ function createSession({
|
|
|
801
930
|
description: t.description,
|
|
802
931
|
inputSchema: t.inputSchema
|
|
803
932
|
})),
|
|
933
|
+
...continueOpts.responseFormat == null ? {} : { responseFormat: continueOpts.responseFormat },
|
|
804
934
|
...continueOpts.instructions ? { instructions: continueOpts.instructions } : {},
|
|
805
935
|
...startBase()
|
|
806
936
|
});
|