@ai-sdk/harness-claude-code 1.0.103 → 1.0.105
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 +18 -0
- package/dist/bridge/index.mjs +213 -22
- package/dist/bridge/index.mjs.map +1 -1
- package/dist/index.d.ts +37 -36
- package/dist/index.js +14 -31
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/bridge/create-emit-stream-event.ts +61 -22
- 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 +18 -34
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.105",
|
|
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.101",
|
|
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)) {
|
|
@@ -298,25 +306,15 @@ export function createEmitStreamEvent({
|
|
|
298
306
|
const toolName = toCommonName(nativeName);
|
|
299
307
|
const dynamic = state.externalMcpToolUseIds.delete(block.tool_use_id);
|
|
300
308
|
const isError = !!block.is_error;
|
|
301
|
-
const content = stringifyContent(block.content);
|
|
302
|
-
/*
|
|
303
|
-
* Claude Code's Bash tool does not report the command's real
|
|
304
|
-
* numeric exit code — the SDK exposes only stdout/stderr text and
|
|
305
|
-
* an is_error flag. Consumers (and the example UI) render bash
|
|
306
|
-
* failures from an `exitCode` field on a structured result, the
|
|
307
|
-
* shape Codex's shell tool provides natively. When Claude omits
|
|
308
|
-
* `tool_use_result`, derive a binary code from is_error: 1 on
|
|
309
|
-
* failure, 0 on success. This fallback is a stand-in for
|
|
310
|
-
* failed/succeeded, not the process's true exit status.
|
|
311
|
-
*/
|
|
312
|
-
const contentResult =
|
|
313
|
-
toolName === 'bash'
|
|
314
|
-
? { exitCode: isError ? 1 : 0, stdout: content }
|
|
315
|
-
: dynamic
|
|
316
|
-
? parseMcpToolResult(content)
|
|
317
|
-
: content;
|
|
318
309
|
const result =
|
|
319
|
-
toolUseResult !== undefined
|
|
310
|
+
toolUseResult !== undefined
|
|
311
|
+
? toolUseResult
|
|
312
|
+
: resolveToolResult({
|
|
313
|
+
toolName,
|
|
314
|
+
dynamic,
|
|
315
|
+
isError,
|
|
316
|
+
rawContent: block.content,
|
|
317
|
+
});
|
|
320
318
|
emit({
|
|
321
319
|
type: 'tool-result',
|
|
322
320
|
toolCallId: block.tool_use_id,
|
|
@@ -438,6 +436,9 @@ function handleStreamEvent({
|
|
|
438
436
|
if (nativeName === 'StructuredOutput') {
|
|
439
437
|
return;
|
|
440
438
|
}
|
|
439
|
+
if (nativeName === 'AskUserQuestion') {
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
441
442
|
const hostToolName = nativeName.startsWith(HOST_TOOL_PREFIX)
|
|
442
443
|
? nativeName.slice(HOST_TOOL_PREFIX.length)
|
|
443
444
|
: undefined;
|
|
@@ -501,23 +502,61 @@ function handleStreamEvent({
|
|
|
501
502
|
}
|
|
502
503
|
}
|
|
503
504
|
|
|
505
|
+
function isTextEntry(entry: unknown): entry is { text?: unknown } {
|
|
506
|
+
return entry != null && typeof entry === 'object' && 'text' in entry;
|
|
507
|
+
}
|
|
508
|
+
|
|
504
509
|
function stringifyContent(content: unknown): string {
|
|
505
510
|
if (typeof content === 'string') return content;
|
|
506
511
|
if (Array.isArray(content)) {
|
|
507
512
|
return content
|
|
508
513
|
.map(entry =>
|
|
509
|
-
entry
|
|
510
|
-
? String((entry as { text?: unknown }).text ?? '')
|
|
511
|
-
: JSON.stringify(entry),
|
|
514
|
+
isTextEntry(entry) ? String(entry.text ?? '') : JSON.stringify(entry),
|
|
512
515
|
)
|
|
513
516
|
.join('');
|
|
514
517
|
}
|
|
515
518
|
return JSON.stringify(content);
|
|
516
519
|
}
|
|
517
520
|
|
|
521
|
+
function hasNonTextContent(content: unknown): boolean {
|
|
522
|
+
return Array.isArray(content) && content.some(entry => !isTextEntry(entry));
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
function resolveToolResult({
|
|
526
|
+
toolName,
|
|
527
|
+
dynamic,
|
|
528
|
+
isError,
|
|
529
|
+
rawContent,
|
|
530
|
+
}: {
|
|
531
|
+
toolName: string;
|
|
532
|
+
dynamic: boolean;
|
|
533
|
+
isError: boolean;
|
|
534
|
+
rawContent: unknown;
|
|
535
|
+
}): unknown {
|
|
536
|
+
/*
|
|
537
|
+
* Claude Code's Bash tool does not report the command's real numeric exit
|
|
538
|
+
* code — the SDK exposes only stdout/stderr text and an is_error flag.
|
|
539
|
+
* Consumers (and the example UI) render bash failures from an `exitCode`
|
|
540
|
+
* field on a structured result, the shape Codex's shell tool provides
|
|
541
|
+
* natively. When Claude omits `tool_use_result`, derive a binary code from
|
|
542
|
+
* is_error: 1 on failure, 0 on success. This fallback is a stand-in for
|
|
543
|
+
* failed/succeeded, not the process's true exit status.
|
|
544
|
+
*/
|
|
545
|
+
if (toolName === 'bash') {
|
|
546
|
+
return { exitCode: isError ? 1 : 0, stdout: stringifyContent(rawContent) };
|
|
547
|
+
}
|
|
548
|
+
// Must precede the MCP branch: flattening a non-text block to base64 text
|
|
549
|
+
// is not recoverable by parsing it back.
|
|
550
|
+
if (hasNonTextContent(rawContent)) return rawContent;
|
|
551
|
+
const content = stringifyContent(rawContent);
|
|
552
|
+
return dynamic ? parseMcpToolResult(content) : content;
|
|
553
|
+
}
|
|
554
|
+
|
|
518
555
|
function parseMcpToolResult(content: string): unknown {
|
|
519
556
|
try {
|
|
520
|
-
|
|
557
|
+
const parsed = JSON.parse(content);
|
|
558
|
+
// `JSON.parse` also succeeds on scalars; keep those as the string sent.
|
|
559
|
+
return parsed !== null && typeof parsed === 'object' ? parsed : content;
|
|
521
560
|
} catch {
|
|
522
561
|
return content;
|
|
523
562
|
}
|
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,
|
|
@@ -461,39 +462,14 @@ const CLAUDE_CODE_BUILTIN_TOOLS = {
|
|
|
461
462
|
discard_changes: z.boolean().optional(),
|
|
462
463
|
}),
|
|
463
464
|
}),
|
|
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
|
-
}),
|
|
465
|
+
askUserQuestions: {
|
|
466
|
+
...HARNESS_V1_BUILTIN_TOOLS.askUserQuestions,
|
|
467
|
+
nativeName: 'AskUserQuestion',
|
|
468
|
+
toolUseKind: 'readonly',
|
|
469
|
+
} as typeof HARNESS_V1_BUILTIN_TOOLS.askUserQuestions & {
|
|
470
|
+
readonly nativeName: 'AskUserQuestion';
|
|
471
|
+
readonly toolUseKind: 'readonly';
|
|
472
|
+
},
|
|
497
473
|
Skill: {
|
|
498
474
|
...tool({
|
|
499
475
|
description: 'Activate a skill by name',
|
|
@@ -921,13 +897,18 @@ export function createClaudeCode(
|
|
|
921
897
|
);
|
|
922
898
|
}
|
|
923
899
|
} else {
|
|
924
|
-
warnCredentialBrokeringUnavailable();
|
|
925
900
|
sandboxClaudeEnvironment = await applyCredentialForwarding({
|
|
926
901
|
environment: sandboxClaudeEnvironment,
|
|
927
902
|
credentialEnvironmentVariables:
|
|
928
903
|
CLAUDE_CODE_CREDENTIAL_ENVIRONMENT_VARIABLES,
|
|
929
904
|
credentialForwarding: settings.credentialForwarding,
|
|
930
905
|
});
|
|
906
|
+
warnCredentialBrokeringUnavailable({
|
|
907
|
+
environment: claudeEnvironment,
|
|
908
|
+
forwardedEnvironment: sandboxClaudeEnvironment,
|
|
909
|
+
credentialEnvironmentVariables:
|
|
910
|
+
CLAUDE_CODE_CREDENTIAL_ENVIRONMENT_VARIABLES,
|
|
911
|
+
});
|
|
931
912
|
}
|
|
932
913
|
const bootstrapDir = posix.resolve(
|
|
933
914
|
defaultWorkingDirectory,
|
|
@@ -1692,6 +1673,9 @@ function createSession({
|
|
|
1692
1673
|
toolCallId: input.toolCallId,
|
|
1693
1674
|
output: input.output,
|
|
1694
1675
|
isError: input.isError,
|
|
1676
|
+
...(input.toolResult !== undefined
|
|
1677
|
+
? { toolResult: input.toolResult }
|
|
1678
|
+
: {}),
|
|
1695
1679
|
});
|
|
1696
1680
|
},
|
|
1697
1681
|
submitToolApproval: async input => {
|