@ai-sdk/harness-claude-code 1.0.75 → 1.0.76
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 +10 -0
- package/dist/bridge/index.mjs +43 -28
- package/dist/bridge/index.mjs.map +1 -1
- package/dist/index.js +15 -1
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/bridge/create-emit-stream-event.ts +10 -0
- package/src/bridge/index.ts +23 -0
- package/src/bridge/json-schema-to-zod.ts +8 -21
- package/src/claude-code-harness.ts +26 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# @ai-sdk/harness-claude-code
|
|
2
2
|
|
|
3
|
+
## 1.0.76
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 62a9c2a: feat(harness): add support for structured output to `HarnessAgent` via `output` property
|
|
8
|
+
- a5ddbb0: fix(harness-claude-code): preserve general `anyOf` and `oneOf` constraints when registering custom tools with the MCP bridge
|
|
9
|
+
- Updated dependencies [62a9c2a]
|
|
10
|
+
- Updated dependencies [d25cae2]
|
|
11
|
+
- @ai-sdk/harness@1.0.73
|
|
12
|
+
|
|
3
13
|
## 1.0.75
|
|
4
14
|
|
|
5
15
|
### Patch Changes
|
package/dist/bridge/index.mjs
CHANGED
|
@@ -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 (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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) ??
|
|
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
|
|
849
|
+
function zodForUnion(schema) {
|
|
848
850
|
const unionSchemas = schema.anyOf ?? schema.oneOf;
|
|
849
851
|
if (!unionSchemas || unionSchemas.length < 2) return void 0;
|
|
850
|
-
const
|
|
851
|
-
|
|
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,
|