@ai-sdk/harness-claude-code 1.0.69 → 1.0.70
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 +8 -0
- package/dist/bridge/index.mjs +3 -0
- package/dist/bridge/index.mjs.map +1 -1
- package/dist/index.js +79 -65
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/bridge/create-emit-stream-event.ts +8 -0
- package/src/claude-code-harness.ts +89 -74
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
writeSkills as writeHarnessSkills,
|
|
36
36
|
} from '@ai-sdk/harness/utils';
|
|
37
37
|
import {
|
|
38
|
+
safeParseJSON,
|
|
38
39
|
tool,
|
|
39
40
|
type Experimental_SandboxSession,
|
|
40
41
|
type Experimental_SandboxProcess,
|
|
@@ -1183,41 +1184,87 @@ async function readBridgeAsset(name: string): Promise<string> {
|
|
|
1183
1184
|
* `bridge-hello` the instant it accepts the connection, so receiving it
|
|
1184
1185
|
* is the only reliable evidence that the end-to-end link is live.
|
|
1185
1186
|
*/
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1187
|
+
function openWebSocketAndWaitForBridgeHello({
|
|
1188
|
+
url,
|
|
1189
|
+
openTimeoutMs,
|
|
1190
|
+
getHelloTimeoutMs,
|
|
1189
1191
|
}: {
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1192
|
+
url: string;
|
|
1193
|
+
openTimeoutMs: number;
|
|
1194
|
+
getHelloTimeoutMs: () => number;
|
|
1195
|
+
}): Promise<WebSocket> {
|
|
1196
|
+
return new Promise<WebSocket>((resolve, reject) => {
|
|
1197
|
+
const ws = new WebSocket(url);
|
|
1198
|
+
let opened = false;
|
|
1199
|
+
let sawBridgeHello = false;
|
|
1194
1200
|
let settled = false;
|
|
1201
|
+
let openTimer: ReturnType<typeof setTimeout> | undefined;
|
|
1202
|
+
let helloTimer: ReturnType<typeof setTimeout> | undefined;
|
|
1203
|
+
|
|
1195
1204
|
const cleanup = () => {
|
|
1205
|
+
if (openTimer) clearTimeout(openTimer);
|
|
1206
|
+
if (helloTimer) clearTimeout(helloTimer);
|
|
1207
|
+
ws.off('open', onOpen);
|
|
1196
1208
|
ws.off('message', onMessage);
|
|
1197
1209
|
ws.off('close', onClose);
|
|
1198
1210
|
ws.off('error', onError);
|
|
1199
|
-
if (timer) clearTimeout(timer);
|
|
1200
1211
|
};
|
|
1201
1212
|
const settle = (err?: unknown) => {
|
|
1202
1213
|
if (settled) return;
|
|
1203
1214
|
settled = true;
|
|
1204
1215
|
cleanup();
|
|
1205
|
-
if (err)
|
|
1206
|
-
|
|
1216
|
+
if (err) {
|
|
1217
|
+
try {
|
|
1218
|
+
ws.terminate();
|
|
1219
|
+
} catch {}
|
|
1220
|
+
reject(err);
|
|
1221
|
+
} else {
|
|
1222
|
+
resolve(ws);
|
|
1223
|
+
}
|
|
1207
1224
|
};
|
|
1208
|
-
const
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1225
|
+
const tryResolve = () => {
|
|
1226
|
+
if (opened && sawBridgeHello) settle();
|
|
1227
|
+
};
|
|
1228
|
+
const startHelloTimer = () => {
|
|
1229
|
+
if (helloTimer) return;
|
|
1230
|
+
const helloTimeoutMs = getHelloTimeoutMs();
|
|
1231
|
+
helloTimer = setTimeout(
|
|
1232
|
+
() =>
|
|
1233
|
+
settle(
|
|
1234
|
+
new Error(
|
|
1235
|
+
`claude-code bridge did not send bridge-hello within ${helloTimeoutMs}ms`,
|
|
1236
|
+
),
|
|
1237
|
+
),
|
|
1238
|
+
helloTimeoutMs,
|
|
1239
|
+
);
|
|
1240
|
+
helloTimer.unref?.();
|
|
1241
|
+
};
|
|
1242
|
+
const onOpen = () => {
|
|
1243
|
+
opened = true;
|
|
1244
|
+
if (openTimer) {
|
|
1245
|
+
clearTimeout(openTimer);
|
|
1246
|
+
openTimer = undefined;
|
|
1220
1247
|
}
|
|
1248
|
+
startHelloTimer();
|
|
1249
|
+
tryResolve();
|
|
1250
|
+
};
|
|
1251
|
+
const onMessage = (raw: unknown) => {
|
|
1252
|
+
void (async () => {
|
|
1253
|
+
const parsed = await safeParseJSON({
|
|
1254
|
+
text: webSocketMessageToString(raw),
|
|
1255
|
+
});
|
|
1256
|
+
if (!parsed.success || settled) return;
|
|
1257
|
+
const value = parsed.value;
|
|
1258
|
+
if (
|
|
1259
|
+
typeof value === 'object' &&
|
|
1260
|
+
value !== null &&
|
|
1261
|
+
!Array.isArray(value) &&
|
|
1262
|
+
(value as { type?: unknown }).type === 'bridge-hello'
|
|
1263
|
+
) {
|
|
1264
|
+
sawBridgeHello = true;
|
|
1265
|
+
tryResolve();
|
|
1266
|
+
}
|
|
1267
|
+
})();
|
|
1221
1268
|
};
|
|
1222
1269
|
const onClose = () => {
|
|
1223
1270
|
settle(
|
|
@@ -1225,16 +1272,13 @@ async function waitForBridgeHello({
|
|
|
1225
1272
|
);
|
|
1226
1273
|
};
|
|
1227
1274
|
const onError = (err: Error) => settle(err);
|
|
1228
|
-
|
|
1275
|
+
openTimer = setTimeout(
|
|
1229
1276
|
() =>
|
|
1230
|
-
settle(
|
|
1231
|
-
|
|
1232
|
-
`claude-code bridge did not send bridge-hello within ${timeoutMs}ms`,
|
|
1233
|
-
),
|
|
1234
|
-
),
|
|
1235
|
-
timeoutMs,
|
|
1277
|
+
settle(new Error(`WebSocket open timed out after ${openTimeoutMs}ms`)),
|
|
1278
|
+
openTimeoutMs,
|
|
1236
1279
|
);
|
|
1237
|
-
|
|
1280
|
+
openTimer.unref?.();
|
|
1281
|
+
ws.on('open', onOpen);
|
|
1238
1282
|
ws.on('message', onMessage);
|
|
1239
1283
|
ws.on('close', onClose);
|
|
1240
1284
|
ws.on('error', onError);
|
|
@@ -1254,23 +1298,16 @@ async function openBridgeWebSocket({
|
|
|
1254
1298
|
|
|
1255
1299
|
while (Date.now() < deadline) {
|
|
1256
1300
|
attempt++;
|
|
1257
|
-
let ws: WebSocket | undefined;
|
|
1258
1301
|
try {
|
|
1259
1302
|
const remaining = Math.max(1, deadline - Date.now());
|
|
1260
|
-
|
|
1303
|
+
return await openWebSocketAndWaitForBridgeHello({
|
|
1261
1304
|
url: wsUrl,
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
ws,
|
|
1266
|
-
timeoutMs: Math.min(5_000, Math.max(1, deadline - Date.now())),
|
|
1305
|
+
openTimeoutMs: Math.min(10_000, remaining),
|
|
1306
|
+
getHelloTimeoutMs: () =>
|
|
1307
|
+
Math.min(5_000, Math.max(1, deadline - Date.now())),
|
|
1267
1308
|
});
|
|
1268
|
-
return ws;
|
|
1269
1309
|
} catch (err) {
|
|
1270
1310
|
lastError = err;
|
|
1271
|
-
try {
|
|
1272
|
-
ws?.close();
|
|
1273
|
-
} catch {}
|
|
1274
1311
|
const remaining = deadline - Date.now();
|
|
1275
1312
|
if (remaining <= 0) break;
|
|
1276
1313
|
await sleep(Math.min(250 * attempt, 1_000, remaining));
|
|
@@ -1282,39 +1319,17 @@ async function openBridgeWebSocket({
|
|
|
1282
1319
|
);
|
|
1283
1320
|
}
|
|
1284
1321
|
|
|
1285
|
-
function
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
try {
|
|
1297
|
-
ws.terminate();
|
|
1298
|
-
} catch {}
|
|
1299
|
-
reject(new Error(`WebSocket open timed out after ${timeoutMs}ms`));
|
|
1300
|
-
}, timeoutMs);
|
|
1301
|
-
timer.unref?.();
|
|
1302
|
-
const cleanup = () => {
|
|
1303
|
-
clearTimeout(timer);
|
|
1304
|
-
ws.off('open', onOpen);
|
|
1305
|
-
ws.off('error', onError);
|
|
1306
|
-
};
|
|
1307
|
-
const onOpen = () => {
|
|
1308
|
-
cleanup();
|
|
1309
|
-
resolve(ws);
|
|
1310
|
-
};
|
|
1311
|
-
const onError = (err: Error) => {
|
|
1312
|
-
cleanup();
|
|
1313
|
-
reject(err);
|
|
1314
|
-
};
|
|
1315
|
-
ws.once('open', onOpen);
|
|
1316
|
-
ws.once('error', onError);
|
|
1317
|
-
});
|
|
1322
|
+
function webSocketMessageToString(raw: unknown): string {
|
|
1323
|
+
if (typeof raw === 'string') return raw;
|
|
1324
|
+
if (Buffer.isBuffer(raw)) return raw.toString('utf8');
|
|
1325
|
+
if (Array.isArray(raw)) return Buffer.concat(raw).toString('utf8');
|
|
1326
|
+
if (raw instanceof ArrayBuffer) return Buffer.from(raw).toString('utf8');
|
|
1327
|
+
if (ArrayBuffer.isView(raw)) {
|
|
1328
|
+
return Buffer.from(raw.buffer, raw.byteOffset, raw.byteLength).toString(
|
|
1329
|
+
'utf8',
|
|
1330
|
+
);
|
|
1331
|
+
}
|
|
1332
|
+
return String(raw);
|
|
1318
1333
|
}
|
|
1319
1334
|
|
|
1320
1335
|
function sleep(ms: number): Promise<void> {
|