@ai-sdk/harness-opencode 1.0.102 → 1.0.104
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 +20 -0
- package/dist/bridge/index.mjs +164 -15
- package/dist/bridge/index.mjs.map +1 -1
- package/dist/index.d.ts +683 -1
- package/dist/index.js +43 -52
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/bridge/create-emit-stream-event.ts +7 -3
- package/src/bridge/index.ts +104 -5
- package/src/bridge/question-tool.ts +90 -0
- package/src/opencode-bridge-protocol.ts +1 -0
- package/src/opencode-harness.ts +59 -65
|
@@ -202,7 +202,9 @@ export function createEmitStreamEvent({
|
|
|
202
202
|
if (type === 'session.next.tool.called') {
|
|
203
203
|
const callID = String(props.callID ?? event.id);
|
|
204
204
|
const rawToolName = String(props.tool ?? 'unknown');
|
|
205
|
-
if (rawToolName === 'StructuredOutput')
|
|
205
|
+
if (rawToolName === 'StructuredOutput' || rawToolName === 'question') {
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
206
208
|
const toolName = toWireToolName(rawToolName);
|
|
207
209
|
state.toolNames.set(callID, { rawToolName, toolName });
|
|
208
210
|
const hostToolName = getHostToolName(toolName, props.tool);
|
|
@@ -238,7 +240,9 @@ export function createEmitStreamEvent({
|
|
|
238
240
|
const rawToolName =
|
|
239
241
|
cachedTool?.rawToolName ??
|
|
240
242
|
String((props as { tool?: unknown }).tool ?? '');
|
|
241
|
-
if (rawToolName === 'StructuredOutput')
|
|
243
|
+
if (rawToolName === 'StructuredOutput' || rawToolName === 'question') {
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
242
246
|
const toolName =
|
|
243
247
|
cachedTool?.toolName ?? toWireToolName(rawToolName || 'unknown');
|
|
244
248
|
if (getHostToolName(toolName, rawToolName)) return;
|
|
@@ -397,7 +401,7 @@ function emitLegacyToolPart({
|
|
|
397
401
|
}
|
|
398
402
|
const callID = toolPart.callID;
|
|
399
403
|
const rawToolName = toolPart.tool;
|
|
400
|
-
if (rawToolName === 'StructuredOutput') return;
|
|
404
|
+
if (rawToolName === 'StructuredOutput' || rawToolName === 'question') return;
|
|
401
405
|
const toolName = toWireToolName(rawToolName);
|
|
402
406
|
if (toolName === 'agent') {
|
|
403
407
|
const metadata = {
|
package/src/bridge/index.ts
CHANGED
|
@@ -44,6 +44,12 @@ import {
|
|
|
44
44
|
type OpenCodeObject,
|
|
45
45
|
} from './opencode-types';
|
|
46
46
|
import { startAuthorizedToolRelay, type ToolRelay } from './tool-relay';
|
|
47
|
+
import {
|
|
48
|
+
openCodeQuestionKey,
|
|
49
|
+
toHarnessQuestionsInput,
|
|
50
|
+
toOpenCodeQuestionResponse,
|
|
51
|
+
type OpenCodeQuestionRequest,
|
|
52
|
+
} from './question-tool';
|
|
47
53
|
|
|
48
54
|
type Emit = (msg: Record<string, unknown>) => void;
|
|
49
55
|
|
|
@@ -65,7 +71,8 @@ type CommonBuiltinToolName =
|
|
|
65
71
|
| 'edit'
|
|
66
72
|
| 'bash'
|
|
67
73
|
| 'glob'
|
|
68
|
-
| 'grep'
|
|
74
|
+
| 'grep'
|
|
75
|
+
| 'askUserQuestions';
|
|
69
76
|
|
|
70
77
|
const NATIVE_TO_COMMON: Readonly<Record<string, CommonBuiltinToolName>> = {
|
|
71
78
|
view: 'read',
|
|
@@ -75,6 +82,7 @@ const NATIVE_TO_COMMON: Readonly<Record<string, CommonBuiltinToolName>> = {
|
|
|
75
82
|
bash: 'bash',
|
|
76
83
|
glob: 'glob',
|
|
77
84
|
grep: 'grep',
|
|
85
|
+
question: 'askUserQuestions',
|
|
78
86
|
};
|
|
79
87
|
|
|
80
88
|
const OPENCODE_TO_WIRE: Readonly<Record<string, string>> = {
|
|
@@ -83,6 +91,7 @@ const OPENCODE_TO_WIRE: Readonly<Record<string, string>> = {
|
|
|
83
91
|
webfetch: 'webfetch',
|
|
84
92
|
task: 'agent',
|
|
85
93
|
agent: 'agent',
|
|
94
|
+
askUserQuestions: 'question',
|
|
86
95
|
subtask: 'agent',
|
|
87
96
|
};
|
|
88
97
|
|
|
@@ -256,7 +265,7 @@ function buildOpenCodeConfig({
|
|
|
256
265
|
webfetch: 'ask',
|
|
257
266
|
doom_loop: 'ask',
|
|
258
267
|
task: 'ask',
|
|
259
|
-
question: '
|
|
268
|
+
question: 'allow',
|
|
260
269
|
},
|
|
261
270
|
};
|
|
262
271
|
if (start.model) config.model = start.model;
|
|
@@ -333,8 +342,15 @@ function buildProviderConfig(
|
|
|
333
342
|
apiKey: procEnv.AI_GATEWAY_API_KEY,
|
|
334
343
|
baseURL: toOpenCodeGatewayBaseUrl(procEnv.AI_GATEWAY_BASE_URL),
|
|
335
344
|
...(HARNESS_CLIENT_APP
|
|
336
|
-
? {
|
|
337
|
-
|
|
345
|
+
? {
|
|
346
|
+
headers: {
|
|
347
|
+
...start.headers,
|
|
348
|
+
'x-client-app': HARNESS_CLIENT_APP,
|
|
349
|
+
},
|
|
350
|
+
}
|
|
351
|
+
: start.headers
|
|
352
|
+
? { headers: start.headers }
|
|
353
|
+
: {}),
|
|
338
354
|
},
|
|
339
355
|
...(modelID
|
|
340
356
|
? {
|
|
@@ -360,6 +376,7 @@ function buildProviderConfig(
|
|
|
360
376
|
...(procEnv.OPENAI_BASE_URL
|
|
361
377
|
? { baseURL: procEnv.OPENAI_BASE_URL }
|
|
362
378
|
: {}),
|
|
379
|
+
...(start.headers ? { headers: start.headers } : {}),
|
|
363
380
|
...parseOpenAIQueryParams(),
|
|
364
381
|
},
|
|
365
382
|
...(modelID
|
|
@@ -391,6 +408,7 @@ function buildProviderConfig(
|
|
|
391
408
|
...(procEnv.ANTHROPIC_BASE_URL
|
|
392
409
|
? { baseURL: procEnv.ANTHROPIC_BASE_URL }
|
|
393
410
|
: {}),
|
|
411
|
+
...(start.headers ? { headers: start.headers } : {}),
|
|
394
412
|
},
|
|
395
413
|
},
|
|
396
414
|
};
|
|
@@ -413,6 +431,7 @@ function buildProviderConfig(
|
|
|
413
431
|
...(procEnv.OPENAI_PROJECT
|
|
414
432
|
? { project: procEnv.OPENAI_PROJECT }
|
|
415
433
|
: {}),
|
|
434
|
+
...(start.headers ? { headers: start.headers } : {}),
|
|
416
435
|
...parseOpenAIQueryParams(),
|
|
417
436
|
},
|
|
418
437
|
},
|
|
@@ -1035,7 +1054,14 @@ async function consumeEvents({
|
|
|
1035
1054
|
: undefined;
|
|
1036
1055
|
if (!scopedSessionId) continue;
|
|
1037
1056
|
const isDescendant = scopedSessionId !== sessionId;
|
|
1038
|
-
if (event.type === '
|
|
1057
|
+
if (event.type === 'question.asked') {
|
|
1058
|
+
await handleQuestion({
|
|
1059
|
+
client,
|
|
1060
|
+
turn,
|
|
1061
|
+
emit,
|
|
1062
|
+
event,
|
|
1063
|
+
});
|
|
1064
|
+
} else if (event.type === 'permission.v2.asked') {
|
|
1039
1065
|
await handlePermissionV2({
|
|
1040
1066
|
client,
|
|
1041
1067
|
sessionId: scopedSessionId,
|
|
@@ -1075,6 +1101,79 @@ function getSubagentStepId(event: OpenCodeEvent | undefined) {
|
|
|
1075
1101
|
return stringValue(event.properties?.stepID) ?? event.id;
|
|
1076
1102
|
}
|
|
1077
1103
|
|
|
1104
|
+
async function handleQuestion({
|
|
1105
|
+
client,
|
|
1106
|
+
turn,
|
|
1107
|
+
emit,
|
|
1108
|
+
event,
|
|
1109
|
+
}: {
|
|
1110
|
+
client: OpenCodeClient;
|
|
1111
|
+
turn: BridgeTurn;
|
|
1112
|
+
emit: Emit;
|
|
1113
|
+
event: OpenCodeEvent;
|
|
1114
|
+
}): Promise<void> {
|
|
1115
|
+
const nativeRequest = event.properties as OpenCodeQuestionRequest | undefined;
|
|
1116
|
+
if (
|
|
1117
|
+
nativeRequest == null ||
|
|
1118
|
+
typeof nativeRequest.id !== 'string' ||
|
|
1119
|
+
typeof nativeRequest.sessionID !== 'string' ||
|
|
1120
|
+
!Array.isArray(nativeRequest.questions)
|
|
1121
|
+
) {
|
|
1122
|
+
return;
|
|
1123
|
+
}
|
|
1124
|
+
const toolCallId = nativeRequest.tool?.callID ?? nativeRequest.id;
|
|
1125
|
+
|
|
1126
|
+
emit({
|
|
1127
|
+
type: 'tool-call',
|
|
1128
|
+
toolCallId,
|
|
1129
|
+
toolName: 'askUserQuestions',
|
|
1130
|
+
nativeName: 'question',
|
|
1131
|
+
input: JSON.stringify(toHarnessQuestionsInput(nativeRequest)),
|
|
1132
|
+
providerExecuted: false,
|
|
1133
|
+
providerMetadata: {
|
|
1134
|
+
opencode: {
|
|
1135
|
+
nativeRequest,
|
|
1136
|
+
},
|
|
1137
|
+
},
|
|
1138
|
+
});
|
|
1139
|
+
|
|
1140
|
+
const questionKey = openCodeQuestionKey(nativeRequest);
|
|
1141
|
+
const result = await turn.requestToolResult({
|
|
1142
|
+
toolCallId,
|
|
1143
|
+
matches: candidate => {
|
|
1144
|
+
const continuedRequest = candidate.toolResult?.providerOptions?.opencode
|
|
1145
|
+
?.nativeRequest as OpenCodeQuestionRequest | undefined;
|
|
1146
|
+
return (
|
|
1147
|
+
continuedRequest != null &&
|
|
1148
|
+
openCodeQuestionKey(continuedRequest) === questionKey
|
|
1149
|
+
);
|
|
1150
|
+
},
|
|
1151
|
+
});
|
|
1152
|
+
const nativeResponse = toOpenCodeQuestionResponse({
|
|
1153
|
+
nativeRequest,
|
|
1154
|
+
output: result.output as Parameters<
|
|
1155
|
+
typeof toOpenCodeQuestionResponse
|
|
1156
|
+
>[0]['output'],
|
|
1157
|
+
});
|
|
1158
|
+
|
|
1159
|
+
const response =
|
|
1160
|
+
nativeResponse.action === 'reject'
|
|
1161
|
+
? await client.question.reject({
|
|
1162
|
+
requestID: nativeRequest.id,
|
|
1163
|
+
directory: workdir,
|
|
1164
|
+
})
|
|
1165
|
+
: await client.question.reply({
|
|
1166
|
+
requestID: nativeRequest.id,
|
|
1167
|
+
directory: workdir,
|
|
1168
|
+
answers: nativeResponse.answers,
|
|
1169
|
+
});
|
|
1170
|
+
if (response.error != null) {
|
|
1171
|
+
throw new Error(
|
|
1172
|
+
`OpenCode question response failed: ${formatError(response.error)}`,
|
|
1173
|
+
);
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1078
1177
|
function sanitizeMcpToolName(value: string): string {
|
|
1079
1178
|
return value.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
1080
1179
|
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
HarnessV1QuestionsToolInput,
|
|
3
|
+
HarnessV1QuestionsToolOutput,
|
|
4
|
+
} from '@ai-sdk/harness';
|
|
5
|
+
import type { QuestionInfo } from '@opencode-ai/sdk/v2';
|
|
6
|
+
|
|
7
|
+
export type OpenCodeQuestionRequest = {
|
|
8
|
+
id: string;
|
|
9
|
+
sessionID: string;
|
|
10
|
+
questions: QuestionInfo[];
|
|
11
|
+
tool?: { messageID: string; callID: string };
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export function openCodeQuestionKey(
|
|
15
|
+
nativeRequest: OpenCodeQuestionRequest,
|
|
16
|
+
): string {
|
|
17
|
+
return JSON.stringify(nativeRequest.questions);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function toHarnessQuestionsInput(
|
|
21
|
+
nativeRequest: OpenCodeQuestionRequest,
|
|
22
|
+
): HarnessV1QuestionsToolInput {
|
|
23
|
+
return {
|
|
24
|
+
allowPartialAnswers: true,
|
|
25
|
+
questions: nativeRequest.questions.map((question, questionIndex) => ({
|
|
26
|
+
id: `question-${questionIndex + 1}`,
|
|
27
|
+
question: question.question,
|
|
28
|
+
header: question.header,
|
|
29
|
+
options: question.options.map((option, optionIndex) => ({
|
|
30
|
+
id: `option-${optionIndex + 1}`,
|
|
31
|
+
label: option.label,
|
|
32
|
+
description: option.description,
|
|
33
|
+
})),
|
|
34
|
+
...(question.multiple !== undefined
|
|
35
|
+
? { allowMultiple: question.multiple }
|
|
36
|
+
: {}),
|
|
37
|
+
...(question.custom !== undefined
|
|
38
|
+
? { allowFreeForm: question.custom }
|
|
39
|
+
: {}),
|
|
40
|
+
})),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function toOpenCodeQuestionResponse(input: {
|
|
45
|
+
nativeRequest: OpenCodeQuestionRequest;
|
|
46
|
+
output: HarnessV1QuestionsToolOutput;
|
|
47
|
+
}): { action: 'reply'; answers: string[][] } | { action: 'reject' } {
|
|
48
|
+
if (
|
|
49
|
+
input.output.action === 'declined' ||
|
|
50
|
+
input.output.action === 'cancelled'
|
|
51
|
+
) {
|
|
52
|
+
return { action: 'reject' };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const answers = input.nativeRequest.questions.map(
|
|
56
|
+
(nativeQuestion, questionIndex) => {
|
|
57
|
+
const answer =
|
|
58
|
+
input.output.action === 'answered' ||
|
|
59
|
+
input.output.action === 'partially-answered'
|
|
60
|
+
? input.output.answers[`question-${questionIndex + 1}`]
|
|
61
|
+
: undefined;
|
|
62
|
+
if (answer == null) return [];
|
|
63
|
+
const selectedLabels = answer.optionIds.flatMap(optionId => {
|
|
64
|
+
const index = positionalIdIndex({
|
|
65
|
+
id: optionId,
|
|
66
|
+
prefix: 'option-',
|
|
67
|
+
});
|
|
68
|
+
const option =
|
|
69
|
+
index == null ? undefined : nativeQuestion.options[index];
|
|
70
|
+
return option == null ? [] : [option.label];
|
|
71
|
+
});
|
|
72
|
+
return answer.freeform === undefined
|
|
73
|
+
? selectedLabels
|
|
74
|
+
: [...selectedLabels, answer.freeform];
|
|
75
|
+
},
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
return { action: 'reply', answers };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function positionalIdIndex(input: {
|
|
82
|
+
id: string;
|
|
83
|
+
prefix: string;
|
|
84
|
+
}): number | undefined {
|
|
85
|
+
if (!input.id.startsWith(input.prefix)) return undefined;
|
|
86
|
+
const oneBasedIndex = Number(input.id.slice(input.prefix.length));
|
|
87
|
+
return Number.isInteger(oneBasedIndex) && oneBasedIndex > 0
|
|
88
|
+
? oneBasedIndex - 1
|
|
89
|
+
: undefined;
|
|
90
|
+
}
|
|
@@ -18,6 +18,7 @@ export const startMessageSchema = harnessV1BridgeStartBaseSchema.extend({
|
|
|
18
18
|
resumeSessionId: z.string().optional(),
|
|
19
19
|
openCodeConfig: z.record(z.string(), z.unknown()).optional(),
|
|
20
20
|
mcpServers: z.record(z.string(), z.unknown()).optional(),
|
|
21
|
+
headers: z.record(z.string(), z.string()).optional(),
|
|
21
22
|
});
|
|
22
23
|
|
|
23
24
|
export type StartMessage = z.infer<typeof startMessageSchema>;
|
package/src/opencode-harness.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import {
|
|
3
3
|
commonTool,
|
|
4
|
+
HARNESS_V1_BUILTIN_TOOLS,
|
|
4
5
|
HarnessCapabilityUnsupportedError,
|
|
5
6
|
harnessV1DiagnosticFromBridgeFrame,
|
|
6
7
|
type HarnessV1,
|
|
@@ -13,6 +14,7 @@ import {
|
|
|
13
14
|
type HarnessV1PermissionMode,
|
|
14
15
|
type HarnessV1Prompt,
|
|
15
16
|
type HarnessV1PromptControl,
|
|
17
|
+
type HarnessV1ResponseFormat,
|
|
16
18
|
type HarnessV1PortEndpoint,
|
|
17
19
|
type HarnessV1ResumeSessionState,
|
|
18
20
|
type HarnessV1Session,
|
|
@@ -38,7 +40,7 @@ import {
|
|
|
38
40
|
warnCredentialBrokeringUnavailable,
|
|
39
41
|
waitForBridgeReady,
|
|
40
42
|
withBridgeToken,
|
|
41
|
-
writeSkills
|
|
43
|
+
writeSkills,
|
|
42
44
|
type WriteSkillsResult,
|
|
43
45
|
} from '@ai-sdk/harness/utils';
|
|
44
46
|
import {
|
|
@@ -123,6 +125,11 @@ export type OpenCodeHarnessSettings = {
|
|
|
123
125
|
const optionalStringRecord = z.record(z.string(), z.unknown()).optional();
|
|
124
126
|
|
|
125
127
|
const OPENCODE_BUILTIN_TOOLS = {
|
|
128
|
+
askUserQuestions: {
|
|
129
|
+
...HARNESS_V1_BUILTIN_TOOLS.askUserQuestions,
|
|
130
|
+
nativeName: 'question',
|
|
131
|
+
toolUseKind: 'readonly',
|
|
132
|
+
},
|
|
126
133
|
read: commonTool('read', {
|
|
127
134
|
nativeName: 'view',
|
|
128
135
|
toolUseKind: 'readonly',
|
|
@@ -406,6 +413,7 @@ export function createOpenCode(
|
|
|
406
413
|
reasoningVariant: settings.reasoningVariant,
|
|
407
414
|
openCodeConfig: settings.openCodeConfig,
|
|
408
415
|
mcpServers: settings.mcpServers,
|
|
416
|
+
headers: startOpts.headers,
|
|
409
417
|
openCodeSessionId: resumeSessionId,
|
|
410
418
|
isResume: true,
|
|
411
419
|
seedResumeSessionOnFirstPrompt: false,
|
|
@@ -563,6 +571,7 @@ export function createOpenCode(
|
|
|
563
571
|
reasoningVariant: settings.reasoningVariant,
|
|
564
572
|
openCodeConfig: settings.openCodeConfig,
|
|
565
573
|
mcpServers: settings.mcpServers,
|
|
574
|
+
headers: startOpts.headers,
|
|
566
575
|
openCodeSessionId: resumeSessionId,
|
|
567
576
|
isResume: respawnStrategy !== undefined,
|
|
568
577
|
seedResumeSessionOnFirstPrompt: respawnStrategy !== undefined,
|
|
@@ -647,30 +656,6 @@ async function resolveBridgeEndpoint({
|
|
|
647
656
|
});
|
|
648
657
|
}
|
|
649
658
|
|
|
650
|
-
async function writeOpenCodeSkills({
|
|
651
|
-
sandbox,
|
|
652
|
-
skills,
|
|
653
|
-
homeDir,
|
|
654
|
-
abortSignal,
|
|
655
|
-
}: {
|
|
656
|
-
sandbox: SandboxSession;
|
|
657
|
-
skills: ReadonlyArray<HarnessV1Skill>;
|
|
658
|
-
homeDir: string;
|
|
659
|
-
abortSignal?: AbortSignal;
|
|
660
|
-
}): Promise<WriteSkillsResult> {
|
|
661
|
-
const skillsDir = path.posix.join(homeDir, '.agents', 'skills');
|
|
662
|
-
return writeHarnessSkills({
|
|
663
|
-
sandbox,
|
|
664
|
-
rootDir: skillsDir,
|
|
665
|
-
skills,
|
|
666
|
-
abortSignal,
|
|
667
|
-
invalidSkillNameMessage: ({ name }) =>
|
|
668
|
-
`Invalid OpenCode skill name: ${name}`,
|
|
669
|
-
invalidSkillFilePathMessage: ({ skillName, filePath }) =>
|
|
670
|
-
`Invalid OpenCode skill file path for ${skillName}: ${filePath}`,
|
|
671
|
-
});
|
|
672
|
-
}
|
|
673
|
-
|
|
674
659
|
function openWebSocket({
|
|
675
660
|
endpoint,
|
|
676
661
|
helloTimeoutMs,
|
|
@@ -780,6 +765,7 @@ function createSession({
|
|
|
780
765
|
reasoningVariant,
|
|
781
766
|
openCodeConfig,
|
|
782
767
|
mcpServers,
|
|
768
|
+
headers,
|
|
783
769
|
openCodeSessionId,
|
|
784
770
|
isResume,
|
|
785
771
|
seedResumeSessionOnFirstPrompt,
|
|
@@ -803,6 +789,7 @@ function createSession({
|
|
|
803
789
|
reasoningVariant: string | undefined;
|
|
804
790
|
openCodeConfig: Record<string, unknown> | undefined;
|
|
805
791
|
mcpServers: Record<string, unknown> | undefined;
|
|
792
|
+
headers: Readonly<Record<string, string>> | undefined;
|
|
806
793
|
openCodeSessionId: string | undefined;
|
|
807
794
|
isResume: boolean;
|
|
808
795
|
seedResumeSessionOnFirstPrompt: boolean;
|
|
@@ -954,6 +941,9 @@ function createSession({
|
|
|
954
941
|
toolCallId: input.toolCallId,
|
|
955
942
|
output: input.output,
|
|
956
943
|
isError: input.isError,
|
|
944
|
+
...(input.toolResult !== undefined
|
|
945
|
+
? { toolResult: input.toolResult }
|
|
946
|
+
: {}),
|
|
957
947
|
});
|
|
958
948
|
},
|
|
959
949
|
submitToolApproval: async input => {
|
|
@@ -981,6 +971,7 @@ function createSession({
|
|
|
981
971
|
...(reasoningVariant ? { variant: reasoningVariant } : {}),
|
|
982
972
|
...(openCodeConfig == null ? {} : { openCodeConfig }),
|
|
983
973
|
...(mcpServers == null ? {} : { mcpServers }),
|
|
974
|
+
...(headers == null ? {} : { headers }),
|
|
984
975
|
...(permissionMode ? { permissionMode } : {}),
|
|
985
976
|
...(builtinToolFiltering ? { builtinToolFiltering } : {}),
|
|
986
977
|
...(pendingResumeSessionId
|
|
@@ -991,30 +982,48 @@ function createSession({
|
|
|
991
982
|
...(debug ? { debug } : {}),
|
|
992
983
|
});
|
|
993
984
|
|
|
985
|
+
const prepareTurn = async (opts: {
|
|
986
|
+
responseFormat?: HarnessV1ResponseFormat;
|
|
987
|
+
skills: ReadonlyArray<HarnessV1Skill>;
|
|
988
|
+
emit: (event: HarnessV1StreamPart) => void;
|
|
989
|
+
abortSignal?: AbortSignal;
|
|
990
|
+
}): Promise<{
|
|
991
|
+
control: HarnessV1PromptControl;
|
|
992
|
+
skillWriteResult: WriteSkillsResult;
|
|
993
|
+
}> => {
|
|
994
|
+
if (
|
|
995
|
+
opts.responseFormat?.type === 'json' &&
|
|
996
|
+
opts.responseFormat.schema == null
|
|
997
|
+
) {
|
|
998
|
+
throw new HarnessCapabilityUnsupportedError({
|
|
999
|
+
message:
|
|
1000
|
+
"Harness 'opencode' requires a JSON schema for structured output.",
|
|
1001
|
+
harnessId: 'opencode',
|
|
1002
|
+
});
|
|
1003
|
+
}
|
|
1004
|
+
const skillWriteResult = await writeSkills({
|
|
1005
|
+
sandbox,
|
|
1006
|
+
homePath: sandboxHomeDir,
|
|
1007
|
+
skillsDir: '.agents/skills',
|
|
1008
|
+
skills: opts.skills,
|
|
1009
|
+
abortSignal: opts.abortSignal,
|
|
1010
|
+
invalidSkillNameMessage: ({ name }) =>
|
|
1011
|
+
`Invalid OpenCode skill name: ${name}`,
|
|
1012
|
+
invalidSkillFilePathMessage: ({ skillName, filePath }) =>
|
|
1013
|
+
`Invalid OpenCode skill file path for ${skillName}: ${filePath}`,
|
|
1014
|
+
});
|
|
1015
|
+
const control = wireTurn({
|
|
1016
|
+
emit: opts.emit,
|
|
1017
|
+
abortSignal: opts.abortSignal,
|
|
1018
|
+
});
|
|
1019
|
+
return { control, skillWriteResult };
|
|
1020
|
+
};
|
|
1021
|
+
|
|
994
1022
|
return {
|
|
995
1023
|
sessionId,
|
|
996
1024
|
isResume,
|
|
997
1025
|
doPromptTurn: async promptOpts => {
|
|
998
|
-
|
|
999
|
-
promptOpts.responseFormat?.type === 'json' &&
|
|
1000
|
-
promptOpts.responseFormat.schema == null
|
|
1001
|
-
) {
|
|
1002
|
-
throw new HarnessCapabilityUnsupportedError({
|
|
1003
|
-
message:
|
|
1004
|
-
"Harness 'opencode' requires a JSON schema for structured output.",
|
|
1005
|
-
harnessId: 'opencode',
|
|
1006
|
-
});
|
|
1007
|
-
}
|
|
1008
|
-
const skillWriteResult = await writeOpenCodeSkills({
|
|
1009
|
-
sandbox,
|
|
1010
|
-
skills: promptOpts.skills,
|
|
1011
|
-
homeDir: sandboxHomeDir,
|
|
1012
|
-
abortSignal: promptOpts.abortSignal,
|
|
1013
|
-
});
|
|
1014
|
-
const control = wireTurn({
|
|
1015
|
-
emit: promptOpts.emit,
|
|
1016
|
-
abortSignal: promptOpts.abortSignal,
|
|
1017
|
-
});
|
|
1026
|
+
const { control, skillWriteResult } = await prepareTurn(promptOpts);
|
|
1018
1027
|
const turnModel = promptOpts.model ?? selectedModel;
|
|
1019
1028
|
if (turnModel) selectedModel = turnModel;
|
|
1020
1029
|
channel.send({
|
|
@@ -1039,26 +1048,7 @@ function createSession({
|
|
|
1039
1048
|
return control;
|
|
1040
1049
|
},
|
|
1041
1050
|
doContinueTurn: async continueOpts => {
|
|
1042
|
-
|
|
1043
|
-
continueOpts.responseFormat?.type === 'json' &&
|
|
1044
|
-
continueOpts.responseFormat.schema == null
|
|
1045
|
-
) {
|
|
1046
|
-
throw new HarnessCapabilityUnsupportedError({
|
|
1047
|
-
message:
|
|
1048
|
-
"Harness 'opencode' requires a JSON schema for structured output.",
|
|
1049
|
-
harnessId: 'opencode',
|
|
1050
|
-
});
|
|
1051
|
-
}
|
|
1052
|
-
const skillWriteResult = await writeOpenCodeSkills({
|
|
1053
|
-
sandbox,
|
|
1054
|
-
skills: continueOpts.skills,
|
|
1055
|
-
homeDir: sandboxHomeDir,
|
|
1056
|
-
abortSignal: continueOpts.abortSignal,
|
|
1057
|
-
});
|
|
1058
|
-
const control = wireTurn({
|
|
1059
|
-
emit: continueOpts.emit,
|
|
1060
|
-
abortSignal: continueOpts.abortSignal,
|
|
1061
|
-
});
|
|
1051
|
+
const { control, skillWriteResult } = await prepareTurn(continueOpts);
|
|
1062
1052
|
if (rerunContinue) {
|
|
1063
1053
|
const turnModel = continueOpts.model ?? selectedModel;
|
|
1064
1054
|
if (turnModel) selectedModel = turnModel;
|
|
@@ -1107,6 +1097,7 @@ function createSession({
|
|
|
1107
1097
|
debug,
|
|
1108
1098
|
openCodeConfig,
|
|
1109
1099
|
mcpServers,
|
|
1100
|
+
headers,
|
|
1110
1101
|
resumeSessionId: latestOpenCodeSessionId,
|
|
1111
1102
|
onCompaction: part => pendingCompactionParts.push(part),
|
|
1112
1103
|
});
|
|
@@ -1280,6 +1271,7 @@ async function runCompactOperation({
|
|
|
1280
1271
|
debug,
|
|
1281
1272
|
openCodeConfig,
|
|
1282
1273
|
mcpServers,
|
|
1274
|
+
headers,
|
|
1283
1275
|
resumeSessionId,
|
|
1284
1276
|
onCompaction,
|
|
1285
1277
|
}: {
|
|
@@ -1290,6 +1282,7 @@ async function runCompactOperation({
|
|
|
1290
1282
|
debug: HarnessV1DebugConfig | undefined;
|
|
1291
1283
|
openCodeConfig: Record<string, unknown> | undefined;
|
|
1292
1284
|
mcpServers: Record<string, unknown> | undefined;
|
|
1285
|
+
headers: Readonly<Record<string, string>> | undefined;
|
|
1293
1286
|
resumeSessionId: string | undefined;
|
|
1294
1287
|
onCompaction: (part: HarnessV1StreamPart) => void;
|
|
1295
1288
|
}): Promise<void> {
|
|
@@ -1319,6 +1312,7 @@ async function runCompactOperation({
|
|
|
1319
1312
|
provider,
|
|
1320
1313
|
...(openCodeConfig == null ? {} : { openCodeConfig }),
|
|
1321
1314
|
...(mcpServers == null ? {} : { mcpServers }),
|
|
1315
|
+
...(headers == null ? {} : { headers }),
|
|
1322
1316
|
...(permissionMode ? { permissionMode } : {}),
|
|
1323
1317
|
...(resumeSessionId ? { resumeSessionId } : {}),
|
|
1324
1318
|
...(debug ? { debug } : {}),
|