@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.
@@ -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
- async function waitForBridgeHello({
1187
- ws,
1188
- timeoutMs,
1187
+ function openWebSocketAndWaitForBridgeHello({
1188
+ url,
1189
+ openTimeoutMs,
1190
+ getHelloTimeoutMs,
1189
1191
  }: {
1190
- ws: WebSocket;
1191
- timeoutMs: number;
1192
- }): Promise<void> {
1193
- await new Promise<void>((resolve, reject) => {
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) reject(err);
1206
- else resolve();
1216
+ if (err) {
1217
+ try {
1218
+ ws.terminate();
1219
+ } catch {}
1220
+ reject(err);
1221
+ } else {
1222
+ resolve(ws);
1223
+ }
1207
1224
  };
1208
- const onMessage = (raw: unknown) => {
1209
- try {
1210
- const text =
1211
- typeof raw === 'string'
1212
- ? raw
1213
- : (raw as Buffer | ArrayBufferLike).toString
1214
- ? (raw as Buffer).toString('utf8')
1215
- : String(raw);
1216
- const parsed = JSON.parse(text) as { type?: unknown };
1217
- if (parsed?.type === 'bridge-hello') settle();
1218
- } catch {
1219
- // Ignore malformed frames while waiting.
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
- const timer = setTimeout(
1275
+ openTimer = setTimeout(
1229
1276
  () =>
1230
- settle(
1231
- new Error(
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
- timer.unref?.();
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
- ws = await openWebSocket({
1303
+ return await openWebSocketAndWaitForBridgeHello({
1261
1304
  url: wsUrl,
1262
- timeoutMs: Math.min(10_000, remaining),
1263
- });
1264
- await waitForBridgeHello({
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 openWebSocket({
1286
- url,
1287
- timeoutMs,
1288
- }: {
1289
- url: string;
1290
- timeoutMs: number;
1291
- }): Promise<WebSocket> {
1292
- return new Promise((resolve, reject) => {
1293
- const ws = new WebSocket(url);
1294
- const timer = setTimeout(() => {
1295
- cleanup();
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> {