@ai-sdk/harness-claude-code 1.0.41 → 1.0.42
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 +9 -0
- package/dist/bridge/index.mjs +284 -236
- package/dist/bridge/index.mjs.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +2 -2
- package/src/bridge/create-emit-stream-event.ts +448 -0
- package/src/bridge/index.ts +35 -386
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# @ai-sdk/harness-claude-code
|
|
2
2
|
|
|
3
|
+
## 1.0.42
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 07c977a: chore(harness): refactor bridge code to break out stream event emission from launcher to make it testable
|
|
8
|
+
- Updated dependencies [a94425b]
|
|
9
|
+
- Updated dependencies [2de0611]
|
|
10
|
+
- @ai-sdk/harness@1.0.41
|
|
11
|
+
|
|
3
12
|
## 1.0.41
|
|
4
13
|
|
|
5
14
|
### Patch Changes
|
package/dist/bridge/index.mjs
CHANGED
|
@@ -513,7 +513,7 @@ function createCompactionLatch(emit) {
|
|
|
513
513
|
}
|
|
514
514
|
|
|
515
515
|
// src/bridge/index.ts
|
|
516
|
-
import { randomUUID } from "crypto";
|
|
516
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
517
517
|
import { argv, stdout as stdout2 } from "process";
|
|
518
518
|
import * as claudeAgentSdk from "@anthropic-ai/claude-agent-sdk";
|
|
519
519
|
import * as mcpServerModule from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
@@ -523,6 +523,263 @@ function toClaudeSkillsOption(skills) {
|
|
|
523
523
|
return skills && skills.length > 0 ? "all" : void 0;
|
|
524
524
|
}
|
|
525
525
|
|
|
526
|
+
// src/bridge/create-emit-stream-event.ts
|
|
527
|
+
import { randomUUID } from "crypto";
|
|
528
|
+
function createClaudeStreamEventState() {
|
|
529
|
+
return {
|
|
530
|
+
nativeToolCallNames: /* @__PURE__ */ new Map(),
|
|
531
|
+
approvalRequestedToolUseIds: /* @__PURE__ */ new Set(),
|
|
532
|
+
partialBlocks: /* @__PURE__ */ new Map(),
|
|
533
|
+
stepUsage: void 0,
|
|
534
|
+
pendingStepToolUseIds: /* @__PURE__ */ new Set(),
|
|
535
|
+
pendingStepUsage: void 0,
|
|
536
|
+
stepOpen: false,
|
|
537
|
+
mcpToolUseIds: /* @__PURE__ */ new Set(),
|
|
538
|
+
observedTerminalError: void 0
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
var UNRECOVERABLE_API_RETRY_STATUSES = /* @__PURE__ */ new Set([401, 403, 404]);
|
|
542
|
+
function createEmitStreamEvent({
|
|
543
|
+
state,
|
|
544
|
+
emit,
|
|
545
|
+
emitWarning,
|
|
546
|
+
emitTerminalError,
|
|
547
|
+
onCompactionBoundary,
|
|
548
|
+
toCommonName: toCommonName2
|
|
549
|
+
}) {
|
|
550
|
+
let streamStarted = false;
|
|
551
|
+
return (msg) => {
|
|
552
|
+
const type = msg.type;
|
|
553
|
+
if (!streamStarted) {
|
|
554
|
+
const initModel = type === "system" && msg.subtype === "init" && typeof msg.model === "string" ? msg.model : void 0;
|
|
555
|
+
emit({
|
|
556
|
+
type: "stream-start",
|
|
557
|
+
...initModel ? { modelId: initModel } : {}
|
|
558
|
+
});
|
|
559
|
+
streamStarted = true;
|
|
560
|
+
}
|
|
561
|
+
if (type === "system" && msg.subtype === "api_retry") {
|
|
562
|
+
if (typeof msg.error_status === "number" && UNRECOVERABLE_API_RETRY_STATUSES.has(msg.error_status)) {
|
|
563
|
+
emitTerminalError(
|
|
564
|
+
`HTTP ${msg.error_status}: ${msg.error ?? "provider request failed"}`
|
|
565
|
+
);
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
emitWarning({ message: formatApiRetryWarning(msg) });
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
if (typeof msg.error === "string" && msg.error.trim()) {
|
|
572
|
+
state.observedTerminalError = msg.error.trim();
|
|
573
|
+
}
|
|
574
|
+
if (type === "auth_status" && typeof msg.error === "string" && msg.error.trim()) {
|
|
575
|
+
emitTerminalError(msg.error);
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
if (type === "system" && msg.subtype === "task_updated" && msg.patch?.status === "failed" && typeof msg.patch.error === "string") {
|
|
579
|
+
emitTerminalError(msg.patch.error);
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
if (type === "system" && msg.subtype === "compact_boundary") {
|
|
583
|
+
const meta = msg.compact_metadata;
|
|
584
|
+
if (meta) {
|
|
585
|
+
onCompactionBoundary({
|
|
586
|
+
trigger: meta.trigger,
|
|
587
|
+
...typeof meta.pre_tokens === "number" ? { tokensBefore: meta.pre_tokens } : {},
|
|
588
|
+
...typeof meta.post_tokens === "number" ? { tokensAfter: meta.post_tokens } : {}
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
if (type === "stream_event") {
|
|
594
|
+
handleStreamEvent(msg.event, state.partialBlocks, emit);
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
if (type === "assistant" && msg.message?.content) {
|
|
598
|
+
const usage = mapUsage(msg.message.usage);
|
|
599
|
+
const toolUseIds = [];
|
|
600
|
+
let opensStep = false;
|
|
601
|
+
for (const block of msg.message.content) {
|
|
602
|
+
if (block.type === "tool_use" && typeof block.id === "string" && typeof block.name === "string") {
|
|
603
|
+
toolUseIds.push(block.id);
|
|
604
|
+
const mcpPrefix = "mcp__harness-tools__";
|
|
605
|
+
if (block.name.startsWith(mcpPrefix)) {
|
|
606
|
+
state.pendingStepToolUseIds.add(block.id);
|
|
607
|
+
state.mcpToolUseIds.add(block.id);
|
|
608
|
+
opensStep = true;
|
|
609
|
+
continue;
|
|
610
|
+
}
|
|
611
|
+
state.nativeToolCallNames.set(block.id, block.name);
|
|
612
|
+
if (state.approvalRequestedToolUseIds.has(block.id)) {
|
|
613
|
+
continue;
|
|
614
|
+
}
|
|
615
|
+
state.pendingStepToolUseIds.add(block.id);
|
|
616
|
+
opensStep = true;
|
|
617
|
+
emit({
|
|
618
|
+
type: "tool-call",
|
|
619
|
+
toolCallId: block.id,
|
|
620
|
+
toolName: toCommonName2(block.name),
|
|
621
|
+
nativeName: block.name,
|
|
622
|
+
input: JSON.stringify(block.input ?? {}),
|
|
623
|
+
providerExecuted: true
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
if (opensStep || toolUseIds.length === 0) {
|
|
628
|
+
state.stepOpen = true;
|
|
629
|
+
if (usage) state.pendingStepUsage = usage;
|
|
630
|
+
}
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
if (type === "user" && msg.message?.content) {
|
|
634
|
+
for (const block of msg.message.content) {
|
|
635
|
+
if (block.type === "tool_result" && typeof block.tool_use_id === "string") {
|
|
636
|
+
if (state.mcpToolUseIds.has(block.tool_use_id)) {
|
|
637
|
+
state.mcpToolUseIds.delete(block.tool_use_id);
|
|
638
|
+
state.pendingStepToolUseIds.delete(block.tool_use_id);
|
|
639
|
+
continue;
|
|
640
|
+
}
|
|
641
|
+
state.approvalRequestedToolUseIds.delete(block.tool_use_id);
|
|
642
|
+
const nativeName = state.nativeToolCallNames.get(block.tool_use_id) ?? "unknown";
|
|
643
|
+
state.nativeToolCallNames.delete(block.tool_use_id);
|
|
644
|
+
const toolName = toCommonName2(nativeName);
|
|
645
|
+
const isError = !!block.is_error;
|
|
646
|
+
const content = stringifyContent(block.content);
|
|
647
|
+
const result = toolName === "bash" ? { exitCode: isError ? 1 : 0, stdout: content } : content;
|
|
648
|
+
emit({
|
|
649
|
+
type: "tool-result",
|
|
650
|
+
toolCallId: block.tool_use_id,
|
|
651
|
+
toolName,
|
|
652
|
+
result,
|
|
653
|
+
isError
|
|
654
|
+
});
|
|
655
|
+
state.pendingStepToolUseIds.delete(block.tool_use_id);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
closeStepIfReady({ state, emit });
|
|
659
|
+
}
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
function finishApprovalStep({
|
|
663
|
+
state,
|
|
664
|
+
emit,
|
|
665
|
+
approvalId
|
|
666
|
+
}) {
|
|
667
|
+
state.stepOpen = true;
|
|
668
|
+
state.pendingStepToolUseIds.delete(approvalId);
|
|
669
|
+
closeStepIfReady({ state, emit });
|
|
670
|
+
}
|
|
671
|
+
function emitFinishStep({
|
|
672
|
+
state,
|
|
673
|
+
emit,
|
|
674
|
+
usage
|
|
675
|
+
}) {
|
|
676
|
+
emit({
|
|
677
|
+
type: "finish-step",
|
|
678
|
+
finishReason: { unified: "stop", raw: "stop" },
|
|
679
|
+
usage: usage ?? defaultUsage()
|
|
680
|
+
});
|
|
681
|
+
state.stepUsage = usage ?? state.stepUsage;
|
|
682
|
+
state.pendingStepUsage = void 0;
|
|
683
|
+
state.pendingStepToolUseIds = /* @__PURE__ */ new Set();
|
|
684
|
+
state.stepOpen = false;
|
|
685
|
+
}
|
|
686
|
+
function closeStepIfReady({
|
|
687
|
+
state,
|
|
688
|
+
emit
|
|
689
|
+
}) {
|
|
690
|
+
if (!state.stepOpen || state.pendingStepToolUseIds.size > 0 || state.partialBlocks.size > 0) {
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
emitFinishStep({ state, emit, usage: state.pendingStepUsage });
|
|
694
|
+
}
|
|
695
|
+
function formatApiRetryWarning(msg) {
|
|
696
|
+
const details = [];
|
|
697
|
+
if (typeof msg.attempt === "number") {
|
|
698
|
+
const maxRetries = typeof msg.max_retries === "number" ? `/${msg.max_retries}` : "";
|
|
699
|
+
details.push(`attempt ${msg.attempt}${maxRetries}`);
|
|
700
|
+
}
|
|
701
|
+
if (typeof msg.error_status === "number") {
|
|
702
|
+
details.push(`HTTP ${msg.error_status}`);
|
|
703
|
+
}
|
|
704
|
+
if (typeof msg.retry_delay_ms === "number") {
|
|
705
|
+
details.push(`retrying in ${msg.retry_delay_ms}ms`);
|
|
706
|
+
}
|
|
707
|
+
if (msg.error) details.push(msg.error);
|
|
708
|
+
return details.length > 0 ? `Claude Code API retry: ${details.join("; ")}` : "Claude Code API retry";
|
|
709
|
+
}
|
|
710
|
+
function handleStreamEvent(event, partialBlocks, send) {
|
|
711
|
+
if (!event || typeof event.index !== "number") return;
|
|
712
|
+
const index = event.index;
|
|
713
|
+
if (event.type === "content_block_start") {
|
|
714
|
+
const blockType = event.content_block?.type;
|
|
715
|
+
if (blockType === "text") {
|
|
716
|
+
const id = randomUUID();
|
|
717
|
+
partialBlocks.set(index, { id, kind: "text" });
|
|
718
|
+
send({ type: "text-start", id });
|
|
719
|
+
} else if (blockType === "thinking") {
|
|
720
|
+
const id = randomUUID();
|
|
721
|
+
partialBlocks.set(index, { id, kind: "thinking" });
|
|
722
|
+
send({ type: "reasoning-start", id });
|
|
723
|
+
}
|
|
724
|
+
return;
|
|
725
|
+
}
|
|
726
|
+
if (event.type === "content_block_delta") {
|
|
727
|
+
const block = partialBlocks.get(index);
|
|
728
|
+
if (!block) return;
|
|
729
|
+
if (block.kind === "text" && event.delta?.type === "text_delta" && typeof event.delta.text === "string") {
|
|
730
|
+
send({ type: "text-delta", id: block.id, delta: event.delta.text });
|
|
731
|
+
} else if (block.kind === "thinking" && event.delta?.type === "thinking_delta" && typeof event.delta.thinking === "string") {
|
|
732
|
+
send({
|
|
733
|
+
type: "reasoning-delta",
|
|
734
|
+
id: block.id,
|
|
735
|
+
delta: event.delta.thinking
|
|
736
|
+
});
|
|
737
|
+
}
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
740
|
+
if (event.type === "content_block_stop") {
|
|
741
|
+
const block = partialBlocks.get(index);
|
|
742
|
+
if (!block) return;
|
|
743
|
+
partialBlocks.delete(index);
|
|
744
|
+
if (block.kind === "text") {
|
|
745
|
+
send({ type: "text-end", id: block.id });
|
|
746
|
+
} else {
|
|
747
|
+
send({ type: "reasoning-end", id: block.id });
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
function stringifyContent(content) {
|
|
752
|
+
if (typeof content === "string") return content;
|
|
753
|
+
if (Array.isArray(content)) {
|
|
754
|
+
return content.map(
|
|
755
|
+
(entry) => entry && typeof entry === "object" && "text" in entry ? String(entry.text ?? "") : JSON.stringify(entry)
|
|
756
|
+
).join("");
|
|
757
|
+
}
|
|
758
|
+
return JSON.stringify(content);
|
|
759
|
+
}
|
|
760
|
+
function mapUsage(usage) {
|
|
761
|
+
if (!usage || typeof usage !== "object") return void 0;
|
|
762
|
+
const u = usage;
|
|
763
|
+
return {
|
|
764
|
+
inputTokens: {
|
|
765
|
+
total: (u.input_tokens ?? 0) + (u.cache_creation_input_tokens ?? 0) + (u.cache_read_input_tokens ?? 0),
|
|
766
|
+
noCache: u.input_tokens ?? 0,
|
|
767
|
+
cacheRead: u.cache_read_input_tokens ?? 0,
|
|
768
|
+
cacheWrite: u.cache_creation_input_tokens ?? 0
|
|
769
|
+
},
|
|
770
|
+
outputTokens: {
|
|
771
|
+
total: u.output_tokens ?? 0,
|
|
772
|
+
text: u.output_tokens ?? 0
|
|
773
|
+
}
|
|
774
|
+
};
|
|
775
|
+
}
|
|
776
|
+
function defaultUsage() {
|
|
777
|
+
return {
|
|
778
|
+
inputTokens: { total: 0, noCache: 0, cacheRead: 0, cacheWrite: 0 },
|
|
779
|
+
outputTokens: { total: 0, text: 0 }
|
|
780
|
+
};
|
|
781
|
+
}
|
|
782
|
+
|
|
526
783
|
// src/bridge/json-schema-to-zod.ts
|
|
527
784
|
import { z } from "zod/v4";
|
|
528
785
|
function jsonSchemaToZodShape(input) {
|
|
@@ -653,7 +910,6 @@ var PUBLIC_TO_NATIVE = {
|
|
|
653
910
|
ToolSearch: "ToolSearch"
|
|
654
911
|
};
|
|
655
912
|
var PUBLIC_TOOL_NAMES = Object.keys(PUBLIC_TO_NATIVE);
|
|
656
|
-
var UNRECOVERABLE_API_RETRY_STATUSES = /* @__PURE__ */ new Set([401, 403, 404]);
|
|
657
913
|
var NATIVE_TOOL_KINDS = {
|
|
658
914
|
Read: "readonly",
|
|
659
915
|
Glob: "readonly",
|
|
@@ -804,31 +1060,7 @@ async function runTurn(start, turn) {
|
|
|
804
1060
|
once: true
|
|
805
1061
|
});
|
|
806
1062
|
}
|
|
807
|
-
const
|
|
808
|
-
const approvalRequestedToolUseIds = /* @__PURE__ */ new Set();
|
|
809
|
-
const partialBlocks = /* @__PURE__ */ new Map();
|
|
810
|
-
let stepUsage;
|
|
811
|
-
let pendingStepToolUseIds = /* @__PURE__ */ new Set();
|
|
812
|
-
let pendingStepUsage;
|
|
813
|
-
let stepOpen = false;
|
|
814
|
-
const emitFinishStep = (usage) => {
|
|
815
|
-
emit({
|
|
816
|
-
type: "finish-step",
|
|
817
|
-
finishReason: { unified: "stop", raw: "stop" },
|
|
818
|
-
usage: usage ?? defaultUsage()
|
|
819
|
-
});
|
|
820
|
-
stepUsage = usage ?? stepUsage;
|
|
821
|
-
pendingStepUsage = void 0;
|
|
822
|
-
pendingStepToolUseIds = /* @__PURE__ */ new Set();
|
|
823
|
-
stepOpen = false;
|
|
824
|
-
};
|
|
825
|
-
const closeStepIfReady = () => {
|
|
826
|
-
if (!stepOpen || pendingStepToolUseIds.size > 0 || partialBlocks.size > 0) {
|
|
827
|
-
return;
|
|
828
|
-
}
|
|
829
|
-
emitFinishStep(pendingStepUsage);
|
|
830
|
-
};
|
|
831
|
-
const mcpToolUseIds = /* @__PURE__ */ new Set();
|
|
1063
|
+
const streamEventState = createClaudeStreamEventState();
|
|
832
1064
|
const mcpServers = {};
|
|
833
1065
|
if (start.tools && start.tools.length > 0) {
|
|
834
1066
|
const server = new mcpModule.McpServer({
|
|
@@ -842,7 +1074,7 @@ async function runTurn(start, turn) {
|
|
|
842
1074
|
tool.description ?? "",
|
|
843
1075
|
shape,
|
|
844
1076
|
async (input) => {
|
|
845
|
-
const toolCallId =
|
|
1077
|
+
const toolCallId = randomUUID2();
|
|
846
1078
|
emit({
|
|
847
1079
|
type: "tool-call",
|
|
848
1080
|
toolCallId,
|
|
@@ -886,12 +1118,10 @@ async function runTurn(start, turn) {
|
|
|
886
1118
|
turn,
|
|
887
1119
|
emit,
|
|
888
1120
|
finishApprovalStep: (approvalId) => {
|
|
889
|
-
|
|
890
|
-
pendingStepToolUseIds.delete(approvalId);
|
|
891
|
-
closeStepIfReady();
|
|
1121
|
+
finishApprovalStep({ state: streamEventState, emit, approvalId });
|
|
892
1122
|
},
|
|
893
|
-
nativeToolCallNames,
|
|
894
|
-
approvalRequestedToolUseIds
|
|
1123
|
+
nativeToolCallNames: streamEventState.nativeToolCallNames,
|
|
1124
|
+
approvalRequestedToolUseIds: streamEventState.approvalRequestedToolUseIds
|
|
895
1125
|
});
|
|
896
1126
|
const q = claudeSdk.query({
|
|
897
1127
|
prompt: queryInput.input,
|
|
@@ -934,14 +1164,12 @@ async function runTurn(start, turn) {
|
|
|
934
1164
|
turn.onInterrupt(() => q.interrupt());
|
|
935
1165
|
let turnUsage;
|
|
936
1166
|
let totalCostUsd;
|
|
937
|
-
let observedTerminalError;
|
|
938
1167
|
let emittedTerminalError = false;
|
|
939
1168
|
let emittedTerminalFinish = false;
|
|
940
|
-
let streamStarted = false;
|
|
941
1169
|
const emitTerminalError = (message) => {
|
|
942
1170
|
const normalized = message?.trim();
|
|
943
1171
|
if (!normalized || emittedTerminalError || emittedTerminalFinish) return;
|
|
944
|
-
observedTerminalError = normalized;
|
|
1172
|
+
streamEventState.observedTerminalError = normalized;
|
|
945
1173
|
emittedTerminalError = true;
|
|
946
1174
|
turn.emitError({
|
|
947
1175
|
error: normalized,
|
|
@@ -950,123 +1178,24 @@ async function runTurn(start, turn) {
|
|
|
950
1178
|
queryInput.close();
|
|
951
1179
|
abortCtl.abort();
|
|
952
1180
|
};
|
|
1181
|
+
const emitStreamEvent = createEmitStreamEvent({
|
|
1182
|
+
state: streamEventState,
|
|
1183
|
+
emit,
|
|
1184
|
+
emitWarning: turn.emitWarning,
|
|
1185
|
+
emitTerminalError,
|
|
1186
|
+
onCompactionBoundary: (boundary) => compaction.onBoundary(boundary),
|
|
1187
|
+
toCommonName
|
|
1188
|
+
});
|
|
953
1189
|
try {
|
|
954
1190
|
for await (const msg of q) {
|
|
955
1191
|
if (abortCtl.signal.aborted) break;
|
|
956
1192
|
const type = msg.type;
|
|
957
|
-
|
|
958
|
-
const initModel = type === "system" && msg.subtype === "init" && typeof msg.model === "string" ? msg.model : void 0;
|
|
959
|
-
emit({
|
|
960
|
-
type: "stream-start",
|
|
961
|
-
...initModel ? { modelId: initModel } : {}
|
|
962
|
-
});
|
|
963
|
-
streamStarted = true;
|
|
964
|
-
}
|
|
965
|
-
if (type === "system" && msg.subtype === "api_retry") {
|
|
966
|
-
if (typeof msg.error_status === "number" && UNRECOVERABLE_API_RETRY_STATUSES.has(msg.error_status)) {
|
|
967
|
-
emitTerminalError(
|
|
968
|
-
`HTTP ${msg.error_status}: ${msg.error ?? "provider request failed"}`
|
|
969
|
-
);
|
|
970
|
-
continue;
|
|
971
|
-
}
|
|
972
|
-
turn.emitWarning({ message: formatApiRetryWarning(msg) });
|
|
973
|
-
continue;
|
|
974
|
-
}
|
|
975
|
-
if (typeof msg.error === "string" && msg.error.trim()) {
|
|
976
|
-
observedTerminalError = msg.error.trim();
|
|
977
|
-
}
|
|
978
|
-
if (type === "auth_status" && typeof msg.error === "string" && msg.error.trim()) {
|
|
979
|
-
emitTerminalError(msg.error);
|
|
980
|
-
continue;
|
|
981
|
-
}
|
|
982
|
-
if (type === "system" && msg.subtype === "task_updated" && msg.patch?.status === "failed" && typeof msg.patch.error === "string") {
|
|
983
|
-
emitTerminalError(msg.patch.error);
|
|
984
|
-
continue;
|
|
985
|
-
}
|
|
986
|
-
if (type === "system" && msg.subtype === "compact_boundary") {
|
|
987
|
-
const meta = msg.compact_metadata;
|
|
988
|
-
if (meta) {
|
|
989
|
-
compaction.onBoundary({
|
|
990
|
-
trigger: meta.trigger,
|
|
991
|
-
...typeof meta.pre_tokens === "number" ? { tokensBefore: meta.pre_tokens } : {},
|
|
992
|
-
...typeof meta.post_tokens === "number" ? { tokensAfter: meta.post_tokens } : {}
|
|
993
|
-
});
|
|
994
|
-
}
|
|
995
|
-
continue;
|
|
996
|
-
}
|
|
997
|
-
if (type === "stream_event") {
|
|
998
|
-
handleStreamEvent(msg.event, partialBlocks, emit);
|
|
999
|
-
continue;
|
|
1000
|
-
}
|
|
1001
|
-
if (type === "assistant" && msg.message?.content) {
|
|
1002
|
-
const usage = mapUsage(msg.message.usage);
|
|
1003
|
-
const toolUseIds = [];
|
|
1004
|
-
let opensStep = false;
|
|
1005
|
-
for (const block of msg.message.content) {
|
|
1006
|
-
if (block.type === "tool_use" && typeof block.id === "string" && typeof block.name === "string") {
|
|
1007
|
-
toolUseIds.push(block.id);
|
|
1008
|
-
const mcpPrefix = "mcp__harness-tools__";
|
|
1009
|
-
if (block.name.startsWith(mcpPrefix)) {
|
|
1010
|
-
pendingStepToolUseIds.add(block.id);
|
|
1011
|
-
mcpToolUseIds.add(block.id);
|
|
1012
|
-
opensStep = true;
|
|
1013
|
-
continue;
|
|
1014
|
-
}
|
|
1015
|
-
nativeToolCallNames.set(block.id, block.name);
|
|
1016
|
-
if (approvalRequestedToolUseIds.has(block.id)) {
|
|
1017
|
-
continue;
|
|
1018
|
-
}
|
|
1019
|
-
pendingStepToolUseIds.add(block.id);
|
|
1020
|
-
opensStep = true;
|
|
1021
|
-
emit({
|
|
1022
|
-
type: "tool-call",
|
|
1023
|
-
toolCallId: block.id,
|
|
1024
|
-
toolName: toCommonName(block.name),
|
|
1025
|
-
nativeName: block.name,
|
|
1026
|
-
input: JSON.stringify(block.input ?? {}),
|
|
1027
|
-
providerExecuted: true
|
|
1028
|
-
});
|
|
1029
|
-
}
|
|
1030
|
-
}
|
|
1031
|
-
if (opensStep || toolUseIds.length === 0) {
|
|
1032
|
-
stepOpen = true;
|
|
1033
|
-
if (usage) pendingStepUsage = usage;
|
|
1034
|
-
}
|
|
1035
|
-
continue;
|
|
1036
|
-
}
|
|
1037
|
-
if (type === "user" && msg.message?.content) {
|
|
1038
|
-
for (const block of msg.message.content) {
|
|
1039
|
-
if (block.type === "tool_result" && typeof block.tool_use_id === "string") {
|
|
1040
|
-
if (mcpToolUseIds.has(block.tool_use_id)) {
|
|
1041
|
-
mcpToolUseIds.delete(block.tool_use_id);
|
|
1042
|
-
pendingStepToolUseIds.delete(block.tool_use_id);
|
|
1043
|
-
continue;
|
|
1044
|
-
}
|
|
1045
|
-
approvalRequestedToolUseIds.delete(block.tool_use_id);
|
|
1046
|
-
const nativeName = nativeToolCallNames.get(block.tool_use_id) ?? "unknown";
|
|
1047
|
-
nativeToolCallNames.delete(block.tool_use_id);
|
|
1048
|
-
const toolName = toCommonName(nativeName);
|
|
1049
|
-
const isError = !!block.is_error;
|
|
1050
|
-
const content = stringifyContent(block.content);
|
|
1051
|
-
const result = toolName === "bash" ? { exitCode: isError ? 1 : 0, stdout: content } : content;
|
|
1052
|
-
emit({
|
|
1053
|
-
type: "tool-result",
|
|
1054
|
-
toolCallId: block.tool_use_id,
|
|
1055
|
-
toolName,
|
|
1056
|
-
result,
|
|
1057
|
-
isError
|
|
1058
|
-
});
|
|
1059
|
-
pendingStepToolUseIds.delete(block.tool_use_id);
|
|
1060
|
-
}
|
|
1061
|
-
}
|
|
1062
|
-
closeStepIfReady();
|
|
1063
|
-
continue;
|
|
1064
|
-
}
|
|
1193
|
+
emitStreamEvent(msg);
|
|
1065
1194
|
if (type === "result") {
|
|
1066
1195
|
if (msg.subtype === "success") {
|
|
1067
1196
|
const emptyResult = !msg.result?.trim?.();
|
|
1068
|
-
if (emptyResult && observedTerminalError) {
|
|
1069
|
-
emitTerminalError(observedTerminalError);
|
|
1197
|
+
if (emptyResult && streamEventState.observedTerminalError) {
|
|
1198
|
+
emitTerminalError(streamEventState.observedTerminalError);
|
|
1070
1199
|
continue;
|
|
1071
1200
|
}
|
|
1072
1201
|
const usage = msg.usage ?? msg.message?.usage;
|
|
@@ -1075,12 +1204,18 @@ async function runTurn(start, turn) {
|
|
|
1075
1204
|
if (typeof msg.total_cost_usd === "number") {
|
|
1076
1205
|
totalCostUsd = (totalCostUsd ?? 0) + msg.total_cost_usd;
|
|
1077
1206
|
}
|
|
1078
|
-
if (stepOpen)
|
|
1207
|
+
if (streamEventState.stepOpen) {
|
|
1208
|
+
emitFinishStep({
|
|
1209
|
+
state: streamEventState,
|
|
1210
|
+
emit,
|
|
1211
|
+
usage: harnessUsage ?? streamEventState.pendingStepUsage
|
|
1212
|
+
});
|
|
1213
|
+
}
|
|
1079
1214
|
queryInput.close();
|
|
1080
1215
|
break;
|
|
1081
1216
|
} else {
|
|
1082
1217
|
emitTerminalError(
|
|
1083
|
-
(Array.isArray(msg.errors) ? msg.errors.join("\n") : void 0) || observedTerminalError || msg.result || "Unknown error"
|
|
1218
|
+
(Array.isArray(msg.errors) ? msg.errors.join("\n") : void 0) || streamEventState.observedTerminalError || msg.result || "Unknown error"
|
|
1084
1219
|
);
|
|
1085
1220
|
}
|
|
1086
1221
|
continue;
|
|
@@ -1100,66 +1235,10 @@ async function runTurn(start, turn) {
|
|
|
1100
1235
|
emit({
|
|
1101
1236
|
type: "finish",
|
|
1102
1237
|
finishReason: { unified: "stop", raw: "stop" },
|
|
1103
|
-
totalUsage: turnUsage ?? stepUsage ?? defaultUsage(),
|
|
1238
|
+
totalUsage: turnUsage ?? streamEventState.stepUsage ?? defaultUsage(),
|
|
1104
1239
|
...totalCostUsd !== void 0 ? { harnessMetadata: { "claude-code": { costUsd: totalCostUsd } } } : {}
|
|
1105
1240
|
});
|
|
1106
1241
|
}
|
|
1107
|
-
function formatApiRetryWarning(msg) {
|
|
1108
|
-
const details = [];
|
|
1109
|
-
if (typeof msg.attempt === "number") {
|
|
1110
|
-
const maxRetries = typeof msg.max_retries === "number" ? `/${msg.max_retries}` : "";
|
|
1111
|
-
details.push(`attempt ${msg.attempt}${maxRetries}`);
|
|
1112
|
-
}
|
|
1113
|
-
if (typeof msg.error_status === "number") {
|
|
1114
|
-
details.push(`HTTP ${msg.error_status}`);
|
|
1115
|
-
}
|
|
1116
|
-
if (typeof msg.retry_delay_ms === "number") {
|
|
1117
|
-
details.push(`retrying in ${msg.retry_delay_ms}ms`);
|
|
1118
|
-
}
|
|
1119
|
-
if (msg.error) details.push(msg.error);
|
|
1120
|
-
return details.length > 0 ? `Claude Code API retry: ${details.join("; ")}` : "Claude Code API retry";
|
|
1121
|
-
}
|
|
1122
|
-
function handleStreamEvent(event, partialBlocks, send) {
|
|
1123
|
-
if (!event || typeof event.index !== "number") return;
|
|
1124
|
-
const index = event.index;
|
|
1125
|
-
if (event.type === "content_block_start") {
|
|
1126
|
-
const blockType = event.content_block?.type;
|
|
1127
|
-
if (blockType === "text") {
|
|
1128
|
-
const id = randomUUID();
|
|
1129
|
-
partialBlocks.set(index, { id, kind: "text" });
|
|
1130
|
-
send({ type: "text-start", id });
|
|
1131
|
-
} else if (blockType === "thinking") {
|
|
1132
|
-
const id = randomUUID();
|
|
1133
|
-
partialBlocks.set(index, { id, kind: "thinking" });
|
|
1134
|
-
send({ type: "reasoning-start", id });
|
|
1135
|
-
}
|
|
1136
|
-
return;
|
|
1137
|
-
}
|
|
1138
|
-
if (event.type === "content_block_delta") {
|
|
1139
|
-
const block = partialBlocks.get(index);
|
|
1140
|
-
if (!block) return;
|
|
1141
|
-
if (block.kind === "text" && event.delta?.type === "text_delta" && typeof event.delta.text === "string") {
|
|
1142
|
-
send({ type: "text-delta", id: block.id, delta: event.delta.text });
|
|
1143
|
-
} else if (block.kind === "thinking" && event.delta?.type === "thinking_delta" && typeof event.delta.thinking === "string") {
|
|
1144
|
-
send({
|
|
1145
|
-
type: "reasoning-delta",
|
|
1146
|
-
id: block.id,
|
|
1147
|
-
delta: event.delta.thinking
|
|
1148
|
-
});
|
|
1149
|
-
}
|
|
1150
|
-
return;
|
|
1151
|
-
}
|
|
1152
|
-
if (event.type === "content_block_stop") {
|
|
1153
|
-
const block = partialBlocks.get(index);
|
|
1154
|
-
if (!block) return;
|
|
1155
|
-
partialBlocks.delete(index);
|
|
1156
|
-
if (block.kind === "text") {
|
|
1157
|
-
send({ type: "text-end", id: block.id });
|
|
1158
|
-
} else {
|
|
1159
|
-
send({ type: "reasoning-end", id: block.id });
|
|
1160
|
-
}
|
|
1161
|
-
}
|
|
1162
|
-
}
|
|
1163
1242
|
function createQueryInput({
|
|
1164
1243
|
initialUserMessage,
|
|
1165
1244
|
pendingUserMessages,
|
|
@@ -1211,37 +1290,6 @@ function createQueryInput({
|
|
|
1211
1290
|
}
|
|
1212
1291
|
};
|
|
1213
1292
|
}
|
|
1214
|
-
function stringifyContent(content) {
|
|
1215
|
-
if (typeof content === "string") return content;
|
|
1216
|
-
if (Array.isArray(content)) {
|
|
1217
|
-
return content.map(
|
|
1218
|
-
(entry) => entry && typeof entry === "object" && "text" in entry ? String(entry.text ?? "") : JSON.stringify(entry)
|
|
1219
|
-
).join("");
|
|
1220
|
-
}
|
|
1221
|
-
return JSON.stringify(content);
|
|
1222
|
-
}
|
|
1223
|
-
function mapUsage(usage) {
|
|
1224
|
-
if (!usage || typeof usage !== "object") return void 0;
|
|
1225
|
-
const u = usage;
|
|
1226
|
-
return {
|
|
1227
|
-
inputTokens: {
|
|
1228
|
-
total: (u.input_tokens ?? 0) + (u.cache_creation_input_tokens ?? 0) + (u.cache_read_input_tokens ?? 0),
|
|
1229
|
-
noCache: u.input_tokens ?? 0,
|
|
1230
|
-
cacheRead: u.cache_read_input_tokens ?? 0,
|
|
1231
|
-
cacheWrite: u.cache_creation_input_tokens ?? 0
|
|
1232
|
-
},
|
|
1233
|
-
outputTokens: {
|
|
1234
|
-
total: u.output_tokens ?? 0,
|
|
1235
|
-
text: u.output_tokens ?? 0
|
|
1236
|
-
}
|
|
1237
|
-
};
|
|
1238
|
-
}
|
|
1239
|
-
function defaultUsage() {
|
|
1240
|
-
return {
|
|
1241
|
-
inputTokens: { total: 0, noCache: 0, cacheRead: 0, cacheWrite: 0 },
|
|
1242
|
-
outputTokens: { total: 0, text: 0 }
|
|
1243
|
-
};
|
|
1244
|
-
}
|
|
1245
1293
|
function parseArgs(args2) {
|
|
1246
1294
|
const out = {};
|
|
1247
1295
|
for (let i = 0; i < args2.length; i++) {
|