@ai-sdk/harness-claude-code 1.0.98 → 1.0.100
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 +15 -7
- package/dist/bridge/index.mjs.map +1 -1
- package/dist/index.js +8 -30
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/bridge/create-emit-stream-event.ts +24 -12
- package/src/bridge/index.ts +2 -0
- package/src/claude-code-bootstrap.ts +5 -20
- package/src/claude-code-harness.ts +3 -14
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.100",
|
|
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.96",
|
|
30
30
|
"@ai-sdk/provider-utils": "5.0.34",
|
|
31
31
|
"ws": "^8.21.0"
|
|
32
32
|
},
|
|
@@ -42,6 +42,7 @@ export type ClaudeMessage = {
|
|
|
42
42
|
usage?: Record<string, unknown>;
|
|
43
43
|
total_cost_usd?: number;
|
|
44
44
|
structured_output?: unknown;
|
|
45
|
+
tool_use_result?: unknown;
|
|
45
46
|
};
|
|
46
47
|
|
|
47
48
|
type MessageBlock = {
|
|
@@ -103,6 +104,13 @@ export function createClaudeStreamEventState(): ClaudeStreamEventState {
|
|
|
103
104
|
}
|
|
104
105
|
|
|
105
106
|
const UNRECOVERABLE_API_RETRY_STATUSES = new Set([401, 403, 404]);
|
|
107
|
+
const HOST_TOOL_PREFIX = 'mcp__harness-tools__';
|
|
108
|
+
|
|
109
|
+
export function isExternalMcpTool(nativeName: string): boolean {
|
|
110
|
+
return (
|
|
111
|
+
nativeName.startsWith('mcp__') && !nativeName.startsWith(HOST_TOOL_PREFIX)
|
|
112
|
+
);
|
|
113
|
+
}
|
|
106
114
|
|
|
107
115
|
export function createEmitStreamEvent({
|
|
108
116
|
state,
|
|
@@ -231,15 +239,14 @@ export function createEmitStreamEvent({
|
|
|
231
239
|
state.structuredOutputToolUseIds.add(block.id);
|
|
232
240
|
continue;
|
|
233
241
|
}
|
|
234
|
-
|
|
235
|
-
if (block.name.startsWith(mcpPrefix)) {
|
|
242
|
+
if (block.name.startsWith(HOST_TOOL_PREFIX)) {
|
|
236
243
|
state.pendingStepToolUseIds.add(block.id);
|
|
237
244
|
state.mcpToolUseIds.add(block.id);
|
|
238
245
|
opensStep = true;
|
|
239
246
|
continue;
|
|
240
247
|
}
|
|
241
248
|
state.nativeToolCallNames.set(block.id, block.name);
|
|
242
|
-
const dynamic = block.name
|
|
249
|
+
const dynamic = isExternalMcpTool(block.name);
|
|
243
250
|
if (dynamic) state.externalMcpToolUseIds.add(block.id);
|
|
244
251
|
if (state.approvalRequestedToolUseIds.has(block.id)) {
|
|
245
252
|
continue;
|
|
@@ -265,6 +272,12 @@ export function createEmitStreamEvent({
|
|
|
265
272
|
}
|
|
266
273
|
|
|
267
274
|
if (type === 'user' && msg.message?.content) {
|
|
275
|
+
const toolResultBlocks = msg.message.content.filter(
|
|
276
|
+
block => block.type === 'tool_result',
|
|
277
|
+
);
|
|
278
|
+
const toolUseResult =
|
|
279
|
+
toolResultBlocks.length === 1 ? msg.tool_use_result : undefined;
|
|
280
|
+
|
|
268
281
|
for (const block of msg.message.content) {
|
|
269
282
|
if (
|
|
270
283
|
block.type === 'tool_result' &&
|
|
@@ -291,17 +304,19 @@ export function createEmitStreamEvent({
|
|
|
291
304
|
* numeric exit code — the SDK exposes only stdout/stderr text and
|
|
292
305
|
* an is_error flag. Consumers (and the example UI) render bash
|
|
293
306
|
* failures from an `exitCode` field on a structured result, the
|
|
294
|
-
* shape Codex's shell tool provides natively.
|
|
295
|
-
* a binary code from is_error: 1 on
|
|
296
|
-
*
|
|
297
|
-
* status.
|
|
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.
|
|
298
311
|
*/
|
|
299
|
-
const
|
|
312
|
+
const contentResult =
|
|
300
313
|
toolName === 'bash'
|
|
301
314
|
? { exitCode: isError ? 1 : 0, stdout: content }
|
|
302
315
|
: dynamic
|
|
303
316
|
? parseMcpToolResult(content)
|
|
304
317
|
: content;
|
|
318
|
+
const result =
|
|
319
|
+
toolUseResult !== undefined ? toolUseResult : contentResult;
|
|
305
320
|
emit({
|
|
306
321
|
type: 'tool-result',
|
|
307
322
|
toolCallId: block.tool_use_id,
|
|
@@ -388,8 +403,6 @@ function formatApiRetryWarning(msg: ClaudeMessage): string {
|
|
|
388
403
|
: 'Claude Code API retry';
|
|
389
404
|
}
|
|
390
405
|
|
|
391
|
-
const HOST_TOOL_PREFIX = 'mcp__harness-tools__';
|
|
392
|
-
|
|
393
406
|
function handleStreamEvent({
|
|
394
407
|
event,
|
|
395
408
|
state,
|
|
@@ -428,8 +441,7 @@ function handleStreamEvent({
|
|
|
428
441
|
const hostToolName = nativeName.startsWith(HOST_TOOL_PREFIX)
|
|
429
442
|
? nativeName.slice(HOST_TOOL_PREFIX.length)
|
|
430
443
|
: undefined;
|
|
431
|
-
const dynamic =
|
|
432
|
-
hostToolName === undefined && nativeName.startsWith('mcp__');
|
|
444
|
+
const dynamic = isExternalMcpTool(nativeName);
|
|
433
445
|
partialBlocks.set(index, { id, kind: 'tool-input' });
|
|
434
446
|
send({
|
|
435
447
|
type: 'tool-input-start',
|
package/src/bridge/index.ts
CHANGED
|
@@ -43,6 +43,7 @@ import {
|
|
|
43
43
|
defaultUsage,
|
|
44
44
|
emitFinishStep,
|
|
45
45
|
finishApprovalStep,
|
|
46
|
+
isExternalMcpTool,
|
|
46
47
|
mapUsage,
|
|
47
48
|
type ClaudeMessage,
|
|
48
49
|
} from './create-emit-stream-event';
|
|
@@ -201,6 +202,7 @@ function createPermissionOptions(input: {
|
|
|
201
202
|
nativeName: toolName,
|
|
202
203
|
input: JSON.stringify(toolInput ?? {}),
|
|
203
204
|
providerExecuted: true,
|
|
205
|
+
...(isExternalMcpTool(toolName) ? { dynamic: true } : {}),
|
|
204
206
|
});
|
|
205
207
|
input.emit({
|
|
206
208
|
type: 'tool-approval-request',
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import { readFile } from 'node:fs/promises';
|
|
2
|
-
import { fileURLToPath } from 'node:url';
|
|
3
1
|
import type { HarnessV1Bootstrap } from '@ai-sdk/harness';
|
|
2
|
+
import { createReadBridgeAsset } from '@ai-sdk/harness/utils';
|
|
3
|
+
|
|
4
|
+
const readBridgeAsset = createReadBridgeAsset({
|
|
5
|
+
resolveAssetUrl: name => new URL(`./bridge/${name}`, import.meta.url),
|
|
6
|
+
});
|
|
4
7
|
|
|
5
8
|
/*
|
|
6
9
|
* Bootstrap is derived state stored under the sandbox's default working
|
|
@@ -49,21 +52,3 @@ export async function getClaudeCodeBootstrap(): Promise<HarnessV1Bootstrap> {
|
|
|
49
52
|
};
|
|
50
53
|
return cachedBootstrap;
|
|
51
54
|
}
|
|
52
|
-
|
|
53
|
-
async function readBridgeAsset(name: string): Promise<string> {
|
|
54
|
-
const candidates = [
|
|
55
|
-
new URL(`./bridge/${name}`, import.meta.url),
|
|
56
|
-
new URL(`../bridge/${name}`, import.meta.url),
|
|
57
|
-
];
|
|
58
|
-
let lastErr: unknown;
|
|
59
|
-
for (const url of candidates) {
|
|
60
|
-
try {
|
|
61
|
-
return await readFile(fileURLToPath(url), 'utf8');
|
|
62
|
-
} catch (err) {
|
|
63
|
-
const code = (err as NodeJS.ErrnoException).code;
|
|
64
|
-
if (code !== 'ENOENT') throw err;
|
|
65
|
-
lastErr = err;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
throw lastErr ?? new Error(`bridge asset not found: ${name}`);
|
|
69
|
-
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { randomBytes } from 'node:crypto';
|
|
2
1
|
import { posix } from 'node:path';
|
|
3
2
|
import {
|
|
4
3
|
commonTool,
|
|
@@ -24,6 +23,7 @@ import {
|
|
|
24
23
|
applyCredentialForwarding,
|
|
25
24
|
classifyDiskLog,
|
|
26
25
|
createSandboxCredentialEnvironment,
|
|
26
|
+
createBridgeToken,
|
|
27
27
|
experimental_createBridgeUserMessageSubmitter,
|
|
28
28
|
createBridgeErrorHandler,
|
|
29
29
|
createBridgeStartupError,
|
|
@@ -37,6 +37,7 @@ import {
|
|
|
37
37
|
shellQuote,
|
|
38
38
|
warnCredentialBrokeringUnavailable,
|
|
39
39
|
waitForBridgeReady,
|
|
40
|
+
withBridgeToken,
|
|
40
41
|
writeSkills as writeHarnessSkills,
|
|
41
42
|
} from '@ai-sdk/harness/utils';
|
|
42
43
|
import {
|
|
@@ -1069,7 +1070,7 @@ export function createClaudeCode(
|
|
|
1069
1070
|
});
|
|
1070
1071
|
const token =
|
|
1071
1072
|
settings.mintBridgeToken == null
|
|
1072
|
-
?
|
|
1073
|
+
? createBridgeToken()
|
|
1073
1074
|
: settings.mintBridgeToken(sandboxId!);
|
|
1074
1075
|
const env = {
|
|
1075
1076
|
BRIDGE_CHANNEL_TOKEN: token,
|
|
@@ -1468,18 +1469,6 @@ function webSocketMessageToString(raw: unknown): string {
|
|
|
1468
1469
|
return String(raw);
|
|
1469
1470
|
}
|
|
1470
1471
|
|
|
1471
|
-
function withBridgeToken({
|
|
1472
|
-
endpoint,
|
|
1473
|
-
token,
|
|
1474
|
-
}: {
|
|
1475
|
-
endpoint: HarnessV1PortEndpoint;
|
|
1476
|
-
token: string;
|
|
1477
|
-
}): HarnessV1PortEndpoint {
|
|
1478
|
-
const bridgeUrl = new URL(endpoint.url);
|
|
1479
|
-
bridgeUrl.searchParams.set('agent_bridge_token', token);
|
|
1480
|
-
return { ...endpoint, url: bridgeUrl.toString() };
|
|
1481
|
-
}
|
|
1482
|
-
|
|
1483
1472
|
function sleep(ms: number): Promise<void> {
|
|
1484
1473
|
return new Promise(resolve => {
|
|
1485
1474
|
const timer = setTimeout(resolve, ms);
|