@ai-sdk/harness-claude-code 1.0.75 → 1.0.77

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 CHANGED
@@ -1,5 +1,21 @@
1
1
  # @ai-sdk/harness-claude-code
2
2
 
3
+ ## 1.0.77
4
+
5
+ ### Patch Changes
6
+
7
+ - @ai-sdk/harness@1.0.74
8
+
9
+ ## 1.0.76
10
+
11
+ ### Patch Changes
12
+
13
+ - 62a9c2a: feat(harness): add support for structured output to `HarnessAgent` via `output` property
14
+ - a5ddbb0: fix(harness-claude-code): preserve general `anyOf` and `oneOf` constraints when registering custom tools with the MCP bridge
15
+ - Updated dependencies [62a9c2a]
16
+ - Updated dependencies [d25cae2]
17
+ - @ai-sdk/harness@1.0.73
18
+
3
19
  ## 1.0.75
4
20
 
5
21
  ### Patch Changes
@@ -53,7 +53,6 @@ async function runBridge(options) {
53
53
  let currentBoundPort = 0;
54
54
  let currentTurnState = "init";
55
55
  let activeSocket;
56
- let activeSocketReadyForLiveEvents = false;
57
56
  let isFirstTurn = true;
58
57
  let turnAbort;
59
58
  let currentUserMessages;
@@ -137,14 +136,6 @@ async function runBridge(options) {
137
136
  } catch {
138
137
  }
139
138
  };
140
- const sendControl = (msg) => {
141
- if (activeSocket?.readyState === WS_OPEN) {
142
- try {
143
- activeSocket.send(JSON.stringify(msg));
144
- } catch {
145
- }
146
- }
147
- };
148
139
  const emit = (event) => {
149
140
  const seq = ++seqCounter;
150
141
  const line = JSON.stringify({ ...event, seq });
@@ -152,7 +143,7 @@ async function runBridge(options) {
152
143
  diskBuffer += `${line}
153
144
  `;
154
145
  scheduleEventFlush();
155
- if (activeSocketReadyForLiveEvents && activeSocket?.readyState === WS_OPEN) {
146
+ if (activeSocket?.readyState === WS_OPEN) {
156
147
  try {
157
148
  activeSocket.send(line);
158
149
  } catch {
@@ -255,8 +246,7 @@ async function runBridge(options) {
255
246
  const handleInbound = async (msg, ws) => {
256
247
  switch (msg.type) {
257
248
  case "start": {
258
- if (activeSocket !== ws) return;
259
- activeSocketReadyForLiveEvents = true;
249
+ activeSocket = ws;
260
250
  const firstTurn = isFirstTurn;
261
251
  isFirstTurn = false;
262
252
  eventLog = [];
@@ -336,9 +326,8 @@ async function runBridge(options) {
336
326
  turnAbort?.abort();
337
327
  return;
338
328
  case "resume":
339
- if (activeSocket !== ws) return;
329
+ activeSocket = ws;
340
330
  replay(ws, msg.lastSeenEventId);
341
- activeSocketReadyForLiveEvents = true;
342
331
  return;
343
332
  case "destroy":
344
333
  currentTurnState = "done";
@@ -350,7 +339,7 @@ async function runBridge(options) {
350
339
  currentTurnState = "done";
351
340
  void writeBridgeMeta("done");
352
341
  const data = await onStop?.() ?? {};
353
- sendControl({ type: "bridge-stop", data });
342
+ sendControl(ws, { type: "bridge-stop", data });
354
343
  drainThenExit(ws, 1e3, "stop");
355
344
  return;
356
345
  }
@@ -403,9 +392,7 @@ async function runBridge(options) {
403
392
  ws.close(1008, "unauthorized");
404
393
  return;
405
394
  }
406
- activeSocket = ws;
407
- activeSocketReadyForLiveEvents = false;
408
- sendControl({
395
+ sendControl(ws, {
409
396
  type: "bridge-hello",
410
397
  state: currentTurnState,
411
398
  lastSeq: seqCounter
@@ -416,7 +403,7 @@ async function runBridge(options) {
416
403
  const text = typeof raw === "string" ? raw : Buffer.from(raw).toString("utf8");
417
404
  parsed = JSON.parse(text);
418
405
  } catch (err) {
419
- sendControl({
406
+ sendControl(ws, {
420
407
  type: "error",
421
408
  error: `protocol parse error: ${err.message}`
422
409
  });
@@ -427,7 +414,6 @@ async function runBridge(options) {
427
414
  ws.on("close", () => {
428
415
  if (activeSocket === ws) {
429
416
  activeSocket = void 0;
430
- activeSocketReadyForLiveEvents = false;
431
417
  }
432
418
  });
433
419
  ws.on("error", () => {
@@ -454,6 +440,14 @@ async function runBridge(options) {
454
440
  })
455
441
  };
456
442
  }
443
+ function sendControl(socket, message) {
444
+ if (socket?.readyState === WS_OPEN) {
445
+ try {
446
+ socket.send(JSON.stringify(message));
447
+ } catch {
448
+ }
449
+ }
450
+ }
457
451
  function serialiseError(err) {
458
452
  if (err instanceof Error) {
459
453
  return { name: err.name, message: err.message, stack: err.stack };
@@ -522,6 +516,7 @@ function createClaudeStreamEventState() {
522
516
  stepOpen: false,
523
517
  mcpToolUseIds: /* @__PURE__ */ new Set(),
524
518
  externalMcpToolUseIds: /* @__PURE__ */ new Set(),
519
+ structuredOutputToolUseIds: /* @__PURE__ */ new Set(),
525
520
  observedTerminalError: void 0
526
521
  };
527
522
  }
@@ -591,6 +586,10 @@ function createEmitStreamEvent({
591
586
  for (const block of msg.message.content) {
592
587
  if (block.type === "tool_use" && typeof block.id === "string" && typeof block.name === "string") {
593
588
  toolUseIds.push(block.id);
589
+ if (block.name === "StructuredOutput") {
590
+ state.structuredOutputToolUseIds.add(block.id);
591
+ continue;
592
+ }
594
593
  const mcpPrefix = "mcp__harness-tools__";
595
594
  if (block.name.startsWith(mcpPrefix)) {
596
595
  state.pendingStepToolUseIds.add(block.id);
@@ -626,6 +625,9 @@ function createEmitStreamEvent({
626
625
  if (type === "user" && msg.message?.content) {
627
626
  for (const block of msg.message.content) {
628
627
  if (block.type === "tool_result" && typeof block.tool_use_id === "string") {
628
+ if (state.structuredOutputToolUseIds.delete(block.tool_use_id)) {
629
+ continue;
630
+ }
629
631
  if (state.mcpToolUseIds.has(block.tool_use_id)) {
630
632
  state.mcpToolUseIds.delete(block.tool_use_id);
631
633
  state.pendingStepToolUseIds.delete(block.tool_use_id);
@@ -804,7 +806,7 @@ function toZodShape(schema) {
804
806
  }
805
807
  function toZodType(schema) {
806
808
  if (!schema) return z.any();
807
- let zType = zodForConst(schema) ?? zodForEnum(schema) ?? zodForNullableUnion(schema) ?? zodForType(schema);
809
+ let zType = zodForConst(schema) ?? zodForEnum(schema) ?? zodForUnion(schema) ?? zodForType(schema);
808
810
  if (isNullable(schema)) zType = zType.nullable();
809
811
  if (schema.description) zType = zType.describe(schema.description);
810
812
  return zType;
@@ -844,12 +846,11 @@ function zodForEnum(schema) {
844
846
  if (!schema.enum.every(isJsonLiteral)) return z.any();
845
847
  return zodForLiterals(schema.enum);
846
848
  }
847
- function zodForNullableUnion(schema) {
849
+ function zodForUnion(schema) {
848
850
  const unionSchemas = schema.anyOf ?? schema.oneOf;
849
851
  if (!unionSchemas || unionSchemas.length < 2) return void 0;
850
- const nonNullSchemas = unionSchemas.filter((item) => !isNullOnlySchema(item));
851
- if (nonNullSchemas.length !== 1) return void 0;
852
- return toZodType(nonNullSchemas[0]).nullable();
852
+ const options = unionSchemas.map((item) => toZodType(item));
853
+ return z.union(options);
853
854
  }
854
855
  function zodForLiterals(values) {
855
856
  if (values.length === 0) return z.any();
@@ -863,9 +864,6 @@ function getNonNullTypes(schema) {
863
864
  function isNullable(schema) {
864
865
  return schema.nullable === true || Array.isArray(schema.type) && schema.type.includes("null");
865
866
  }
866
- function isNullOnlySchema(schema) {
867
- return schema.type === "null" || Array.isArray(schema.type) && schema.type.length === 1 && schema.type[0] === "null" || schema.const === null || Array.isArray(schema.enum) && schema.enum.length === 1 && schema.enum[0] === null;
868
- }
869
867
  function isJsonSchemaObject(input) {
870
868
  return input != null && typeof input === "object" && !Array.isArray(input);
871
869
  }
@@ -1136,6 +1134,12 @@ async function runTurn(start, turn) {
1136
1134
  systemPrompt: createClaudeCodeSystemPrompt(start.instructions),
1137
1135
  thinking: start.thinking,
1138
1136
  ...start.effort !== void 0 ? { effort: start.effort } : {},
1137
+ ...start.responseFormat?.type === "json" && start.responseFormat.schema != null ? {
1138
+ outputFormat: {
1139
+ type: "json_schema",
1140
+ schema: start.responseFormat.schema
1141
+ }
1142
+ } : {},
1139
1143
  includePartialMessages: true,
1140
1144
  // The `PostCompact` hook carries the compaction summary, which the
1141
1145
  // `compact_boundary` system message does not. Latch it for the unified
@@ -1207,6 +1211,17 @@ async function runTurn(start, turn) {
1207
1211
  if (typeof msg.total_cost_usd === "number") {
1208
1212
  totalCostUsd = (totalCostUsd ?? 0) + msg.total_cost_usd;
1209
1213
  }
1214
+ if (start.responseFormat?.type === "json" && msg.structured_output !== void 0) {
1215
+ const id = randomUUID2();
1216
+ emit({ type: "text-start", id });
1217
+ emit({
1218
+ type: "text-delta",
1219
+ id,
1220
+ delta: JSON.stringify(msg.structured_output)
1221
+ });
1222
+ emit({ type: "text-end", id });
1223
+ streamEventState.stepOpen = true;
1224
+ }
1210
1225
  if (streamEventState.stepOpen) {
1211
1226
  emitFinishStep({
1212
1227
  state: streamEventState,