@ai-sdk/harness-claude-code 1.0.104 → 1.0.106
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 +185 -17
- package/dist/bridge/index.mjs.map +1 -1
- package/dist/index.d.ts +37 -36
- package/dist/index.js +38 -80
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/bridge/create-emit-stream-event.ts +11 -0
- package/src/bridge/index.ts +116 -9
- package/src/bridge/question-tool.ts +104 -0
- package/src/bridge/tool-filtering.ts +1 -1
- package/src/claude-code-harness.ts +57 -98
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-sdk/harness-claude-code",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.106",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
}
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@ai-sdk/harness": "1.0.
|
|
29
|
+
"@ai-sdk/harness": "1.0.102",
|
|
30
30
|
"@ai-sdk/provider-utils": "5.0.36",
|
|
31
31
|
"ws": "^8.21.0"
|
|
32
32
|
},
|
|
@@ -38,6 +38,9 @@ export type ClaudeMessage = {
|
|
|
38
38
|
usage?: Record<string, unknown>;
|
|
39
39
|
};
|
|
40
40
|
result?: string;
|
|
41
|
+
/** Result-message error flags; distinct from `error_status` (`api_retry`). */
|
|
42
|
+
is_error?: boolean;
|
|
43
|
+
api_error_status?: number | null;
|
|
41
44
|
errors?: ReadonlyArray<string>;
|
|
42
45
|
usage?: Record<string, unknown>;
|
|
43
46
|
total_cost_usd?: number;
|
|
@@ -246,6 +249,11 @@ export function createEmitStreamEvent({
|
|
|
246
249
|
continue;
|
|
247
250
|
}
|
|
248
251
|
state.nativeToolCallNames.set(block.id, block.name);
|
|
252
|
+
if (block.name === 'AskUserQuestion') {
|
|
253
|
+
state.pendingStepToolUseIds.add(block.id);
|
|
254
|
+
opensStep = true;
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
249
257
|
const dynamic = isExternalMcpTool(block.name);
|
|
250
258
|
if (dynamic) state.externalMcpToolUseIds.add(block.id);
|
|
251
259
|
if (state.approvalRequestedToolUseIds.has(block.id)) {
|
|
@@ -428,6 +436,9 @@ function handleStreamEvent({
|
|
|
428
436
|
if (nativeName === 'StructuredOutput') {
|
|
429
437
|
return;
|
|
430
438
|
}
|
|
439
|
+
if (nativeName === 'AskUserQuestion') {
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
431
442
|
const hostToolName = nativeName.startsWith(HOST_TOOL_PREFIX)
|
|
432
443
|
? nativeName.slice(HOST_TOOL_PREFIX.length)
|
|
433
444
|
: undefined;
|
package/src/bridge/index.ts
CHANGED
|
@@ -34,6 +34,10 @@ import { argv, env as procEnv, stdout } from 'node:process';
|
|
|
34
34
|
* 3. the dependency entry in `src/bridge/package.json`.
|
|
35
35
|
*/
|
|
36
36
|
import * as claudeAgentSdk from '@anthropic-ai/claude-agent-sdk';
|
|
37
|
+
import type {
|
|
38
|
+
HookCallback,
|
|
39
|
+
HookJSONOutput,
|
|
40
|
+
} from '@anthropic-ai/claude-agent-sdk';
|
|
37
41
|
import * as mcpServerModule from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
38
42
|
import { createClaudeCodeSystemPrompt } from './claude-code-system-prompt';
|
|
39
43
|
import { toClaudeSkillsOption } from './claude-skills-option';
|
|
@@ -52,6 +56,11 @@ import {
|
|
|
52
56
|
resolveInactiveNativeTools,
|
|
53
57
|
resolveNativeTools,
|
|
54
58
|
} from './tool-filtering';
|
|
59
|
+
import {
|
|
60
|
+
claudeCodeQuestionKey,
|
|
61
|
+
toClaudeCodeQuestionResult,
|
|
62
|
+
toHarnessQuestionsInput,
|
|
63
|
+
} from './question-tool';
|
|
55
64
|
|
|
56
65
|
/*
|
|
57
66
|
* Native Claude Code tool name → cross-harness common name. Tools outside this
|
|
@@ -65,7 +74,8 @@ type CommonBuiltinToolName =
|
|
|
65
74
|
| 'bash'
|
|
66
75
|
| 'glob'
|
|
67
76
|
| 'grep'
|
|
68
|
-
| 'webSearch'
|
|
77
|
+
| 'webSearch'
|
|
78
|
+
| 'askUserQuestions';
|
|
69
79
|
|
|
70
80
|
const NATIVE_TO_COMMON: Readonly<Record<string, CommonBuiltinToolName>> = {
|
|
71
81
|
Read: 'read',
|
|
@@ -75,6 +85,7 @@ const NATIVE_TO_COMMON: Readonly<Record<string, CommonBuiltinToolName>> = {
|
|
|
75
85
|
Glob: 'glob',
|
|
76
86
|
Grep: 'grep',
|
|
77
87
|
WebSearch: 'webSearch',
|
|
88
|
+
AskUserQuestion: 'askUserQuestions',
|
|
78
89
|
};
|
|
79
90
|
|
|
80
91
|
const NATIVE_TOOL_KINDS: Readonly<
|
|
@@ -162,17 +173,15 @@ function createPermissionOptions(input: {
|
|
|
162
173
|
permissionMode,
|
|
163
174
|
inactiveNativeTools,
|
|
164
175
|
});
|
|
165
|
-
if (permissionMode === 'allow-all' && inactiveNativeTools.size === 0) {
|
|
166
|
-
return {
|
|
167
|
-
permissionMode: 'bypassPermissions',
|
|
168
|
-
allowDangerouslySkipPermissions: true,
|
|
169
|
-
};
|
|
170
|
-
}
|
|
171
176
|
|
|
172
177
|
return {
|
|
173
178
|
permissionMode:
|
|
174
|
-
permissionMode === 'allow-
|
|
175
|
-
|
|
179
|
+
permissionMode === 'allow-all'
|
|
180
|
+
? 'bypassPermissions'
|
|
181
|
+
: permissionMode === 'allow-edits'
|
|
182
|
+
? 'acceptEdits'
|
|
183
|
+
: 'default',
|
|
184
|
+
allowDangerouslySkipPermissions: permissionMode === 'allow-all',
|
|
176
185
|
...(permissionSettings ? { settings: permissionSettings } : {}),
|
|
177
186
|
canUseTool: async (
|
|
178
187
|
toolName: string,
|
|
@@ -223,6 +232,80 @@ function createPermissionOptions(input: {
|
|
|
223
232
|
};
|
|
224
233
|
}
|
|
225
234
|
|
|
235
|
+
function createQuestionPreToolUseHook(input: {
|
|
236
|
+
turn: BridgeTurn;
|
|
237
|
+
emit: Emit;
|
|
238
|
+
nativeToolCallNames: Map<string, string>;
|
|
239
|
+
}): HookCallback {
|
|
240
|
+
return async (hookInput, toolUseID): Promise<HookJSONOutput> => {
|
|
241
|
+
if (
|
|
242
|
+
hookInput.hook_event_name !== 'PreToolUse' ||
|
|
243
|
+
hookInput.tool_name !== 'AskUserQuestion'
|
|
244
|
+
) {
|
|
245
|
+
return {};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const nativeInput = hookInput.tool_input as Parameters<
|
|
249
|
+
typeof toHarnessQuestionsInput
|
|
250
|
+
>[0];
|
|
251
|
+
const canonicalInput = toHarnessQuestionsInput(nativeInput);
|
|
252
|
+
const toolCallId = toolUseID ?? hookInput.tool_use_id;
|
|
253
|
+
input.nativeToolCallNames.set(toolCallId, hookInput.tool_name);
|
|
254
|
+
input.emit({
|
|
255
|
+
type: 'tool-call',
|
|
256
|
+
toolCallId,
|
|
257
|
+
toolName: 'askUserQuestions',
|
|
258
|
+
nativeName: hookInput.tool_name,
|
|
259
|
+
input: JSON.stringify(canonicalInput),
|
|
260
|
+
providerExecuted: false,
|
|
261
|
+
providerMetadata: {
|
|
262
|
+
'claude-code': {
|
|
263
|
+
nativeRequest: nativeInput,
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
const questionKey = claudeCodeQuestionKey(nativeInput);
|
|
269
|
+
const result = await input.turn.requestToolResult({
|
|
270
|
+
toolCallId,
|
|
271
|
+
matches: candidate => {
|
|
272
|
+
const nativeRequest = candidate.toolResult?.providerOptions?.[
|
|
273
|
+
'claude-code'
|
|
274
|
+
]?.nativeRequest as
|
|
275
|
+
| Parameters<typeof claudeCodeQuestionKey>[0]
|
|
276
|
+
| undefined;
|
|
277
|
+
return (
|
|
278
|
+
nativeRequest != null &&
|
|
279
|
+
claudeCodeQuestionKey(nativeRequest) === questionKey
|
|
280
|
+
);
|
|
281
|
+
},
|
|
282
|
+
});
|
|
283
|
+
const nativeResult = toClaudeCodeQuestionResult({
|
|
284
|
+
nativeInput,
|
|
285
|
+
output: result.output as Parameters<
|
|
286
|
+
typeof toClaudeCodeQuestionResult
|
|
287
|
+
>[0]['output'],
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
return {
|
|
291
|
+
hookSpecificOutput: {
|
|
292
|
+
hookEventName: 'PreToolUse',
|
|
293
|
+
...(nativeResult.behavior === 'allow'
|
|
294
|
+
? {
|
|
295
|
+
permissionDecision: 'allow',
|
|
296
|
+
updatedInput: {
|
|
297
|
+
...nativeResult.updatedInput,
|
|
298
|
+
} as Record<string, unknown>,
|
|
299
|
+
}
|
|
300
|
+
: {
|
|
301
|
+
permissionDecision: 'deny',
|
|
302
|
+
permissionDecisionReason: nativeResult.message,
|
|
303
|
+
}),
|
|
304
|
+
},
|
|
305
|
+
};
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
|
|
226
309
|
function createPermissionSettings(input: {
|
|
227
310
|
permissionMode: 'allow-reads' | 'allow-edits' | 'allow-all';
|
|
228
311
|
inactiveNativeTools: ReadonlySet<string>;
|
|
@@ -372,6 +455,11 @@ async function runTurn(start: StartMessage, turn: BridgeTurn): Promise<void> {
|
|
|
372
455
|
nativeToolCallNames: streamEventState.nativeToolCallNames,
|
|
373
456
|
approvalRequestedToolUseIds: streamEventState.approvalRequestedToolUseIds,
|
|
374
457
|
});
|
|
458
|
+
const questionPreToolUseHook = createQuestionPreToolUseHook({
|
|
459
|
+
turn,
|
|
460
|
+
emit,
|
|
461
|
+
nativeToolCallNames: streamEventState.nativeToolCallNames,
|
|
462
|
+
});
|
|
375
463
|
|
|
376
464
|
const q = claudeSdk.query({
|
|
377
465
|
prompt: queryInput.input,
|
|
@@ -401,6 +489,12 @@ async function runTurn(start: StartMessage, turn: BridgeTurn): Promise<void> {
|
|
|
401
489
|
// `compact_boundary` system message does not. Latch it for the unified
|
|
402
490
|
// `compaction` event; return an empty output so compaction proceeds.
|
|
403
491
|
hooks: {
|
|
492
|
+
PreToolUse: [
|
|
493
|
+
{
|
|
494
|
+
matcher: 'AskUserQuestion',
|
|
495
|
+
hooks: [questionPreToolUseHook],
|
|
496
|
+
},
|
|
497
|
+
],
|
|
404
498
|
PostCompact: [
|
|
405
499
|
{
|
|
406
500
|
hooks: [
|
|
@@ -507,6 +601,19 @@ async function runTurn(start: StartMessage, turn: BridgeTurn): Promise<void> {
|
|
|
507
601
|
|
|
508
602
|
if (type === 'result') {
|
|
509
603
|
if (msg.subtype === 'success') {
|
|
604
|
+
// `success` does not mean the turn succeeded: the CLI flags a rejected
|
|
605
|
+
// request with `is_error` and puts the message in `result`, which the
|
|
606
|
+
// empty-result rescue below cannot catch.
|
|
607
|
+
if (msg.is_error) {
|
|
608
|
+
emitTerminalError(
|
|
609
|
+
msg.result?.trim() ||
|
|
610
|
+
streamEventState.observedTerminalError ||
|
|
611
|
+
(typeof msg.api_error_status === 'number'
|
|
612
|
+
? `Claude Code reported an API error (HTTP ${msg.api_error_status})`
|
|
613
|
+
: 'Claude Code reported a failed result'),
|
|
614
|
+
);
|
|
615
|
+
continue;
|
|
616
|
+
}
|
|
510
617
|
const emptyResult = !msg.result?.trim?.();
|
|
511
618
|
if (emptyResult && streamEventState.observedTerminalError) {
|
|
512
619
|
emitTerminalError(streamEventState.observedTerminalError);
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
HarnessV1QuestionsToolInput,
|
|
3
|
+
HarnessV1QuestionsToolOutput,
|
|
4
|
+
} from '@ai-sdk/harness';
|
|
5
|
+
import type { AskUserQuestionInput } from '@anthropic-ai/claude-agent-sdk/sdk-tools.js';
|
|
6
|
+
|
|
7
|
+
type ClaudeCodeQuestion = AskUserQuestionInput['questions'][number];
|
|
8
|
+
|
|
9
|
+
export function claudeCodeQuestionKey(
|
|
10
|
+
nativeInput: AskUserQuestionInput,
|
|
11
|
+
): string {
|
|
12
|
+
return JSON.stringify(nativeInput.questions);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function toHarnessQuestionsInput(
|
|
16
|
+
nativeInput: AskUserQuestionInput,
|
|
17
|
+
): HarnessV1QuestionsToolInput {
|
|
18
|
+
return {
|
|
19
|
+
allowPartialAnswers: true,
|
|
20
|
+
questions: nativeInput.questions.map((question, questionIndex) => ({
|
|
21
|
+
id: `question-${questionIndex + 1}`,
|
|
22
|
+
question: question.question,
|
|
23
|
+
header: question.header,
|
|
24
|
+
options: question.options.map((option, optionIndex) => ({
|
|
25
|
+
id: `option-${optionIndex + 1}`,
|
|
26
|
+
label: option.label,
|
|
27
|
+
description: option.description,
|
|
28
|
+
...(option.preview !== undefined ? { preview: option.preview } : {}),
|
|
29
|
+
})),
|
|
30
|
+
allowMultiple: question.multiSelect,
|
|
31
|
+
allowFreeForm: true,
|
|
32
|
+
})),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function toClaudeCodeQuestionResult(input: {
|
|
37
|
+
nativeInput: AskUserQuestionInput;
|
|
38
|
+
output: HarnessV1QuestionsToolOutput;
|
|
39
|
+
}):
|
|
40
|
+
| {
|
|
41
|
+
behavior: 'allow';
|
|
42
|
+
updatedInput: AskUserQuestionInput & { answers: Record<string, string> };
|
|
43
|
+
}
|
|
44
|
+
| { behavior: 'deny'; message: string } {
|
|
45
|
+
const output = input.output;
|
|
46
|
+
if (output.action === 'declined' || output.action === 'cancelled') {
|
|
47
|
+
return {
|
|
48
|
+
behavior: 'deny',
|
|
49
|
+
message: `The user ${output.action} the questions.`,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const canonicalInput = toHarnessQuestionsInput(input.nativeInput);
|
|
54
|
+
const answers = Object.fromEntries(
|
|
55
|
+
canonicalInput.questions.flatMap((question, questionIndex) => {
|
|
56
|
+
const answer = output.answers[question.id];
|
|
57
|
+
if (answer == null) return [];
|
|
58
|
+
const nativeQuestion = input.nativeInput.questions[questionIndex];
|
|
59
|
+
return [
|
|
60
|
+
[
|
|
61
|
+
nativeQuestion.question,
|
|
62
|
+
toClaudeCodeAnswer({
|
|
63
|
+
nativeQuestion,
|
|
64
|
+
optionIds: answer.optionIds,
|
|
65
|
+
freeform: answer.freeform,
|
|
66
|
+
}),
|
|
67
|
+
],
|
|
68
|
+
];
|
|
69
|
+
}),
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
behavior: 'allow',
|
|
74
|
+
updatedInput: { ...input.nativeInput, answers },
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function toClaudeCodeAnswer(input: {
|
|
79
|
+
nativeQuestion: ClaudeCodeQuestion;
|
|
80
|
+
optionIds: string[];
|
|
81
|
+
freeform: string | undefined;
|
|
82
|
+
}): string {
|
|
83
|
+
if (input.freeform !== undefined) return input.freeform;
|
|
84
|
+
const labels = input.optionIds.flatMap(optionId => {
|
|
85
|
+
const index = positionalIdIndex({ id: optionId, prefix: 'option-' });
|
|
86
|
+
const option =
|
|
87
|
+
index == null ? undefined : input.nativeQuestion.options[index];
|
|
88
|
+
return option == null ? [] : [option.label];
|
|
89
|
+
});
|
|
90
|
+
return input.nativeQuestion.multiSelect
|
|
91
|
+
? labels.join(', ')
|
|
92
|
+
: (labels[0] ?? '');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function positionalIdIndex(input: {
|
|
96
|
+
id: string;
|
|
97
|
+
prefix: string;
|
|
98
|
+
}): number | undefined {
|
|
99
|
+
if (!input.id.startsWith(input.prefix)) return undefined;
|
|
100
|
+
const oneBasedIndex = Number(input.id.slice(input.prefix.length));
|
|
101
|
+
return Number.isInteger(oneBasedIndex) && oneBasedIndex > 0
|
|
102
|
+
? oneBasedIndex - 1
|
|
103
|
+
: undefined;
|
|
104
|
+
}
|
|
@@ -26,7 +26,7 @@ const PUBLIC_TO_NATIVE: Readonly<Record<string, string>> = {
|
|
|
26
26
|
ExitPlanMode: 'ExitPlanMode',
|
|
27
27
|
EnterWorktree: 'EnterWorktree',
|
|
28
28
|
ExitWorktree: 'ExitWorktree',
|
|
29
|
-
|
|
29
|
+
askUserQuestions: 'AskUserQuestion',
|
|
30
30
|
Skill: 'Skill',
|
|
31
31
|
ToolSearch: 'ToolSearch',
|
|
32
32
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { posix } from 'node:path';
|
|
2
2
|
import {
|
|
3
3
|
commonTool,
|
|
4
|
+
HARNESS_V1_BUILTIN_TOOLS,
|
|
4
5
|
harnessV1DiagnosticFromBridgeFrame,
|
|
5
6
|
HarnessCapabilityUnsupportedError,
|
|
6
7
|
type HarnessV1,
|
|
@@ -12,6 +13,7 @@ import {
|
|
|
12
13
|
type HarnessV1PermissionMode,
|
|
13
14
|
type HarnessV1Prompt,
|
|
14
15
|
type HarnessV1PromptControl,
|
|
16
|
+
type HarnessV1ResponseFormat,
|
|
15
17
|
type HarnessV1PortEndpoint,
|
|
16
18
|
type HarnessV1ResumeSessionState,
|
|
17
19
|
type HarnessV1NetworkSandboxSession,
|
|
@@ -38,7 +40,7 @@ import {
|
|
|
38
40
|
warnCredentialBrokeringUnavailable,
|
|
39
41
|
waitForBridgeReady,
|
|
40
42
|
withBridgeToken,
|
|
41
|
-
writeSkills
|
|
43
|
+
writeSkills,
|
|
42
44
|
} from '@ai-sdk/harness/utils';
|
|
43
45
|
import {
|
|
44
46
|
safeParseJSON,
|
|
@@ -461,39 +463,14 @@ const CLAUDE_CODE_BUILTIN_TOOLS = {
|
|
|
461
463
|
discard_changes: z.boolean().optional(),
|
|
462
464
|
}),
|
|
463
465
|
}),
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
options: z.array(
|
|
473
|
-
z.object({
|
|
474
|
-
label: z.string(),
|
|
475
|
-
description: z.string(),
|
|
476
|
-
preview: z.string().optional(),
|
|
477
|
-
}),
|
|
478
|
-
),
|
|
479
|
-
multiSelect: z.boolean(),
|
|
480
|
-
}),
|
|
481
|
-
)
|
|
482
|
-
.min(1)
|
|
483
|
-
.max(4),
|
|
484
|
-
answers: z.record(z.string(), z.string()).optional(),
|
|
485
|
-
annotations: z
|
|
486
|
-
.record(
|
|
487
|
-
z.string(),
|
|
488
|
-
z.object({
|
|
489
|
-
preview: z.string().optional(),
|
|
490
|
-
notes: z.string().optional(),
|
|
491
|
-
}),
|
|
492
|
-
)
|
|
493
|
-
.optional(),
|
|
494
|
-
metadata: z.object({ source: z.string().optional() }).optional(),
|
|
495
|
-
}),
|
|
496
|
-
}),
|
|
466
|
+
askUserQuestions: {
|
|
467
|
+
...HARNESS_V1_BUILTIN_TOOLS.askUserQuestions,
|
|
468
|
+
nativeName: 'AskUserQuestion',
|
|
469
|
+
toolUseKind: 'readonly',
|
|
470
|
+
} as typeof HARNESS_V1_BUILTIN_TOOLS.askUserQuestions & {
|
|
471
|
+
readonly nativeName: 'AskUserQuestion';
|
|
472
|
+
readonly toolUseKind: 'readonly';
|
|
473
|
+
},
|
|
497
474
|
Skill: {
|
|
498
475
|
...tool({
|
|
499
476
|
description: 'Activate a skill by name',
|
|
@@ -891,6 +868,13 @@ export function createClaudeCode(
|
|
|
891
868
|
? { CLAUDE_AGENT_SDK_CLIENT_APP: CLAUDE_CODE_CLIENT_APP }
|
|
892
869
|
: {}),
|
|
893
870
|
...settings.env,
|
|
871
|
+
...(startOpts.headers != null
|
|
872
|
+
? {
|
|
873
|
+
ANTHROPIC_CUSTOM_HEADERS: Object.entries(startOpts.headers)
|
|
874
|
+
.map(([name, value]) => `${name}: ${value}`)
|
|
875
|
+
.join('\n'),
|
|
876
|
+
}
|
|
877
|
+
: {}),
|
|
894
878
|
};
|
|
895
879
|
let sandboxClaudeEnvironment = claudeEnvironment;
|
|
896
880
|
let sandboxCredentialEnvironment: Record<string, string> | undefined;
|
|
@@ -1268,30 +1252,6 @@ async function resolveBridgeEndpoint({
|
|
|
1268
1252
|
* be in place before the bridge is spawned without mutating the session
|
|
1269
1253
|
* workdir. Each file uses the YAML-frontmatter shape the CLI expects.
|
|
1270
1254
|
*/
|
|
1271
|
-
async function writeClaudeCodeSkills({
|
|
1272
|
-
sandbox,
|
|
1273
|
-
homeDir,
|
|
1274
|
-
skills,
|
|
1275
|
-
abortSignal,
|
|
1276
|
-
}: {
|
|
1277
|
-
sandbox: SandboxSession;
|
|
1278
|
-
homeDir: string;
|
|
1279
|
-
skills: ReadonlyArray<HarnessV1Skill>;
|
|
1280
|
-
abortSignal?: AbortSignal;
|
|
1281
|
-
}): Promise<void> {
|
|
1282
|
-
await writeHarnessSkills({
|
|
1283
|
-
sandbox,
|
|
1284
|
-
rootDir: `${homeDir}/.claude/skills`,
|
|
1285
|
-
skills,
|
|
1286
|
-
abortSignal,
|
|
1287
|
-
invalidSkillNameMessage: ({ name }) =>
|
|
1288
|
-
`Invalid Claude Code skill name: ${name}`,
|
|
1289
|
-
invalidSkillFilePathMessage: ({ skillName, filePath }) =>
|
|
1290
|
-
`Invalid Claude Code skill file path for ${skillName}: ${filePath}`,
|
|
1291
|
-
trailingNewline: true,
|
|
1292
|
-
});
|
|
1293
|
-
}
|
|
1294
|
-
|
|
1295
1255
|
/**
|
|
1296
1256
|
* Wait for the bridge's `bridge-hello` message to arrive on the freshly
|
|
1297
1257
|
* opened WebSocket before any other host-side code touches it.
|
|
@@ -1697,6 +1657,9 @@ function createSession({
|
|
|
1697
1657
|
toolCallId: input.toolCallId,
|
|
1698
1658
|
output: input.output,
|
|
1699
1659
|
isError: input.isError,
|
|
1660
|
+
...(input.toolResult !== undefined
|
|
1661
|
+
? { toolResult: input.toolResult }
|
|
1662
|
+
: {}),
|
|
1700
1663
|
});
|
|
1701
1664
|
},
|
|
1702
1665
|
submitToolApproval: async input => {
|
|
@@ -1718,30 +1681,45 @@ function createSession({
|
|
|
1718
1681
|
};
|
|
1719
1682
|
};
|
|
1720
1683
|
|
|
1684
|
+
const prepareTurn = async (turnOpts: {
|
|
1685
|
+
responseFormat?: HarnessV1ResponseFormat;
|
|
1686
|
+
skills: ReadonlyArray<HarnessV1Skill>;
|
|
1687
|
+
emit: (event: HarnessV1StreamPart) => void;
|
|
1688
|
+
abortSignal?: AbortSignal;
|
|
1689
|
+
}): Promise<HarnessV1PromptControl> => {
|
|
1690
|
+
if (
|
|
1691
|
+
turnOpts.responseFormat?.type === 'json' &&
|
|
1692
|
+
turnOpts.responseFormat.schema == null
|
|
1693
|
+
) {
|
|
1694
|
+
throw new HarnessCapabilityUnsupportedError({
|
|
1695
|
+
message:
|
|
1696
|
+
"Harness 'claude-code' requires a JSON schema for structured output.",
|
|
1697
|
+
harnessId: 'claude-code',
|
|
1698
|
+
});
|
|
1699
|
+
}
|
|
1700
|
+
await writeSkills({
|
|
1701
|
+
sandbox,
|
|
1702
|
+
homePath: sandboxHomeDir,
|
|
1703
|
+
skillsDir: '.claude/skills',
|
|
1704
|
+
skills: turnOpts.skills,
|
|
1705
|
+
abortSignal: turnOpts.abortSignal,
|
|
1706
|
+
invalidSkillNameMessage: ({ name }) =>
|
|
1707
|
+
`Invalid Claude Code skill name: ${name}`,
|
|
1708
|
+
invalidSkillFilePathMessage: ({ skillName, filePath }) =>
|
|
1709
|
+
`Invalid Claude Code skill file path for ${skillName}: ${filePath}`,
|
|
1710
|
+
trailingNewline: true,
|
|
1711
|
+
});
|
|
1712
|
+
return wireTurn({
|
|
1713
|
+
emit: turnOpts.emit,
|
|
1714
|
+
abortSignal: turnOpts.abortSignal,
|
|
1715
|
+
});
|
|
1716
|
+
};
|
|
1717
|
+
|
|
1721
1718
|
return {
|
|
1722
1719
|
sessionId,
|
|
1723
1720
|
isResume,
|
|
1724
1721
|
doPromptTurn: async promptOpts => {
|
|
1725
|
-
|
|
1726
|
-
promptOpts.responseFormat?.type === 'json' &&
|
|
1727
|
-
promptOpts.responseFormat.schema == null
|
|
1728
|
-
) {
|
|
1729
|
-
throw new HarnessCapabilityUnsupportedError({
|
|
1730
|
-
message:
|
|
1731
|
-
"Harness 'claude-code' requires a JSON schema for structured output.",
|
|
1732
|
-
harnessId: 'claude-code',
|
|
1733
|
-
});
|
|
1734
|
-
}
|
|
1735
|
-
await writeClaudeCodeSkills({
|
|
1736
|
-
sandbox,
|
|
1737
|
-
homeDir: sandboxHomeDir,
|
|
1738
|
-
skills: promptOpts.skills,
|
|
1739
|
-
abortSignal: promptOpts.abortSignal,
|
|
1740
|
-
});
|
|
1741
|
-
const control = wireTurn({
|
|
1742
|
-
emit: promptOpts.emit,
|
|
1743
|
-
abortSignal: promptOpts.abortSignal,
|
|
1744
|
-
});
|
|
1722
|
+
const control = await prepareTurn(promptOpts);
|
|
1745
1723
|
|
|
1746
1724
|
/*
|
|
1747
1725
|
* A signal that was already aborted has settled the turn inside
|
|
@@ -1791,26 +1769,7 @@ function createSession({
|
|
|
1791
1769
|
return control;
|
|
1792
1770
|
},
|
|
1793
1771
|
doContinueTurn: async continueOpts => {
|
|
1794
|
-
|
|
1795
|
-
continueOpts.responseFormat?.type === 'json' &&
|
|
1796
|
-
continueOpts.responseFormat.schema == null
|
|
1797
|
-
) {
|
|
1798
|
-
throw new HarnessCapabilityUnsupportedError({
|
|
1799
|
-
message:
|
|
1800
|
-
"Harness 'claude-code' requires a JSON schema for structured output.",
|
|
1801
|
-
harnessId: 'claude-code',
|
|
1802
|
-
});
|
|
1803
|
-
}
|
|
1804
|
-
await writeClaudeCodeSkills({
|
|
1805
|
-
sandbox,
|
|
1806
|
-
homeDir: sandboxHomeDir,
|
|
1807
|
-
skills: continueOpts.skills,
|
|
1808
|
-
abortSignal: continueOpts.abortSignal,
|
|
1809
|
-
});
|
|
1810
|
-
const control = wireTurn({
|
|
1811
|
-
emit: continueOpts.emit,
|
|
1812
|
-
abortSignal: continueOpts.abortSignal,
|
|
1813
|
-
});
|
|
1772
|
+
const control = await prepareTurn(continueOpts);
|
|
1814
1773
|
|
|
1815
1774
|
/*
|
|
1816
1775
|
* attach / replay: the still-running (or disk-replayed) turn streams into
|