@ai-sdk/harness-claude-code 1.0.40 → 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 CHANGED
@@ -1,5 +1,22 @@
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
+
12
+ ## 1.0.41
13
+
14
+ ### Patch Changes
15
+
16
+ - Updated dependencies [59a2306]
17
+ - Updated dependencies [5f65e61]
18
+ - @ai-sdk/harness@1.0.40
19
+
3
20
  ## 1.0.40
4
21
 
5
22
  ### Patch Changes
@@ -53,6 +53,7 @@ async function runBridge(options) {
53
53
  let currentBoundPort = 0;
54
54
  let currentTurnState = "init";
55
55
  let activeSocket;
56
+ let activeSocketReadyForLiveEvents = false;
56
57
  let isFirstTurn = true;
57
58
  let turnAbort;
58
59
  let currentUserMessages;
@@ -152,7 +153,7 @@ async function runBridge(options) {
152
153
  diskBuffer += `${line}
153
154
  `;
154
155
  scheduleEventFlush();
155
- if (activeSocket?.readyState === WS_OPEN) {
156
+ if (activeSocketReadyForLiveEvents && activeSocket?.readyState === WS_OPEN) {
156
157
  try {
157
158
  activeSocket.send(line);
158
159
  } catch {
@@ -255,6 +256,8 @@ async function runBridge(options) {
255
256
  const handleInbound = async (msg, ws) => {
256
257
  switch (msg.type) {
257
258
  case "start": {
259
+ if (activeSocket !== ws) return;
260
+ activeSocketReadyForLiveEvents = true;
258
261
  const firstTurn = isFirstTurn;
259
262
  isFirstTurn = false;
260
263
  eventLog = [];
@@ -357,7 +360,9 @@ async function runBridge(options) {
357
360
  }
358
361
  return;
359
362
  case "resume":
363
+ if (activeSocket !== ws) return;
360
364
  replay(ws, msg.lastSeenEventId);
365
+ activeSocketReadyForLiveEvents = true;
361
366
  return;
362
367
  case "shutdown":
363
368
  currentTurnState = "done";
@@ -422,6 +427,7 @@ async function runBridge(options) {
422
427
  return;
423
428
  }
424
429
  activeSocket = ws;
430
+ activeSocketReadyForLiveEvents = false;
425
431
  sendControl({
426
432
  type: "bridge-hello",
427
433
  state: currentTurnState,
@@ -444,6 +450,7 @@ async function runBridge(options) {
444
450
  ws.on("close", () => {
445
451
  if (activeSocket === ws) {
446
452
  activeSocket = void 0;
453
+ activeSocketReadyForLiveEvents = false;
447
454
  }
448
455
  });
449
456
  ws.on("error", () => {
@@ -506,7 +513,7 @@ function createCompactionLatch(emit) {
506
513
  }
507
514
 
508
515
  // src/bridge/index.ts
509
- import { randomUUID } from "crypto";
516
+ import { randomUUID as randomUUID2 } from "crypto";
510
517
  import { argv, stdout as stdout2 } from "process";
511
518
  import * as claudeAgentSdk from "@anthropic-ai/claude-agent-sdk";
512
519
  import * as mcpServerModule from "@modelcontextprotocol/sdk/server/mcp.js";
@@ -516,6 +523,263 @@ function toClaudeSkillsOption(skills) {
516
523
  return skills && skills.length > 0 ? "all" : void 0;
517
524
  }
518
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
+
519
783
  // src/bridge/json-schema-to-zod.ts
520
784
  import { z } from "zod/v4";
521
785
  function jsonSchemaToZodShape(input) {
@@ -646,7 +910,6 @@ var PUBLIC_TO_NATIVE = {
646
910
  ToolSearch: "ToolSearch"
647
911
  };
648
912
  var PUBLIC_TOOL_NAMES = Object.keys(PUBLIC_TO_NATIVE);
649
- var UNRECOVERABLE_API_RETRY_STATUSES = /* @__PURE__ */ new Set([401, 403, 404]);
650
913
  var NATIVE_TOOL_KINDS = {
651
914
  Read: "readonly",
652
915
  Glob: "readonly",
@@ -797,31 +1060,7 @@ async function runTurn(start, turn) {
797
1060
  once: true
798
1061
  });
799
1062
  }
800
- const nativeToolCallNames = /* @__PURE__ */ new Map();
801
- const approvalRequestedToolUseIds = /* @__PURE__ */ new Set();
802
- const partialBlocks = /* @__PURE__ */ new Map();
803
- let stepUsage;
804
- let pendingStepToolUseIds = /* @__PURE__ */ new Set();
805
- let pendingStepUsage;
806
- let stepOpen = false;
807
- const emitFinishStep = (usage) => {
808
- emit({
809
- type: "finish-step",
810
- finishReason: { unified: "stop", raw: "stop" },
811
- usage: usage ?? defaultUsage()
812
- });
813
- stepUsage = usage ?? stepUsage;
814
- pendingStepUsage = void 0;
815
- pendingStepToolUseIds = /* @__PURE__ */ new Set();
816
- stepOpen = false;
817
- };
818
- const closeStepIfReady = () => {
819
- if (!stepOpen || pendingStepToolUseIds.size > 0 || partialBlocks.size > 0) {
820
- return;
821
- }
822
- emitFinishStep(pendingStepUsage);
823
- };
824
- const mcpToolUseIds = /* @__PURE__ */ new Set();
1063
+ const streamEventState = createClaudeStreamEventState();
825
1064
  const mcpServers = {};
826
1065
  if (start.tools && start.tools.length > 0) {
827
1066
  const server = new mcpModule.McpServer({
@@ -835,7 +1074,7 @@ async function runTurn(start, turn) {
835
1074
  tool.description ?? "",
836
1075
  shape,
837
1076
  async (input) => {
838
- const toolCallId = randomUUID();
1077
+ const toolCallId = randomUUID2();
839
1078
  emit({
840
1079
  type: "tool-call",
841
1080
  toolCallId,
@@ -879,12 +1118,10 @@ async function runTurn(start, turn) {
879
1118
  turn,
880
1119
  emit,
881
1120
  finishApprovalStep: (approvalId) => {
882
- stepOpen = true;
883
- pendingStepToolUseIds.delete(approvalId);
884
- closeStepIfReady();
1121
+ finishApprovalStep({ state: streamEventState, emit, approvalId });
885
1122
  },
886
- nativeToolCallNames,
887
- approvalRequestedToolUseIds
1123
+ nativeToolCallNames: streamEventState.nativeToolCallNames,
1124
+ approvalRequestedToolUseIds: streamEventState.approvalRequestedToolUseIds
888
1125
  });
889
1126
  const q = claudeSdk.query({
890
1127
  prompt: queryInput.input,
@@ -927,14 +1164,12 @@ async function runTurn(start, turn) {
927
1164
  turn.onInterrupt(() => q.interrupt());
928
1165
  let turnUsage;
929
1166
  let totalCostUsd;
930
- let observedTerminalError;
931
1167
  let emittedTerminalError = false;
932
1168
  let emittedTerminalFinish = false;
933
- let streamStarted = false;
934
1169
  const emitTerminalError = (message) => {
935
1170
  const normalized = message?.trim();
936
1171
  if (!normalized || emittedTerminalError || emittedTerminalFinish) return;
937
- observedTerminalError = normalized;
1172
+ streamEventState.observedTerminalError = normalized;
938
1173
  emittedTerminalError = true;
939
1174
  turn.emitError({
940
1175
  error: normalized,
@@ -943,123 +1178,24 @@ async function runTurn(start, turn) {
943
1178
  queryInput.close();
944
1179
  abortCtl.abort();
945
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
+ });
946
1189
  try {
947
1190
  for await (const msg of q) {
948
1191
  if (abortCtl.signal.aborted) break;
949
1192
  const type = msg.type;
950
- if (!streamStarted) {
951
- const initModel = type === "system" && msg.subtype === "init" && typeof msg.model === "string" ? msg.model : void 0;
952
- emit({
953
- type: "stream-start",
954
- ...initModel ? { modelId: initModel } : {}
955
- });
956
- streamStarted = true;
957
- }
958
- if (type === "system" && msg.subtype === "api_retry") {
959
- if (typeof msg.error_status === "number" && UNRECOVERABLE_API_RETRY_STATUSES.has(msg.error_status)) {
960
- emitTerminalError(
961
- `HTTP ${msg.error_status}: ${msg.error ?? "provider request failed"}`
962
- );
963
- continue;
964
- }
965
- turn.emitWarning({ message: formatApiRetryWarning(msg) });
966
- continue;
967
- }
968
- if (typeof msg.error === "string" && msg.error.trim()) {
969
- observedTerminalError = msg.error.trim();
970
- }
971
- if (type === "auth_status" && typeof msg.error === "string" && msg.error.trim()) {
972
- emitTerminalError(msg.error);
973
- continue;
974
- }
975
- if (type === "system" && msg.subtype === "task_updated" && msg.patch?.status === "failed" && typeof msg.patch.error === "string") {
976
- emitTerminalError(msg.patch.error);
977
- continue;
978
- }
979
- if (type === "system" && msg.subtype === "compact_boundary") {
980
- const meta = msg.compact_metadata;
981
- if (meta) {
982
- compaction.onBoundary({
983
- trigger: meta.trigger,
984
- ...typeof meta.pre_tokens === "number" ? { tokensBefore: meta.pre_tokens } : {},
985
- ...typeof meta.post_tokens === "number" ? { tokensAfter: meta.post_tokens } : {}
986
- });
987
- }
988
- continue;
989
- }
990
- if (type === "stream_event") {
991
- handleStreamEvent(msg.event, partialBlocks, emit);
992
- continue;
993
- }
994
- if (type === "assistant" && msg.message?.content) {
995
- const usage = mapUsage(msg.message.usage);
996
- const toolUseIds = [];
997
- let opensStep = false;
998
- for (const block of msg.message.content) {
999
- if (block.type === "tool_use" && typeof block.id === "string" && typeof block.name === "string") {
1000
- toolUseIds.push(block.id);
1001
- const mcpPrefix = "mcp__harness-tools__";
1002
- if (block.name.startsWith(mcpPrefix)) {
1003
- pendingStepToolUseIds.add(block.id);
1004
- mcpToolUseIds.add(block.id);
1005
- opensStep = true;
1006
- continue;
1007
- }
1008
- nativeToolCallNames.set(block.id, block.name);
1009
- if (approvalRequestedToolUseIds.has(block.id)) {
1010
- continue;
1011
- }
1012
- pendingStepToolUseIds.add(block.id);
1013
- opensStep = true;
1014
- emit({
1015
- type: "tool-call",
1016
- toolCallId: block.id,
1017
- toolName: toCommonName(block.name),
1018
- nativeName: block.name,
1019
- input: JSON.stringify(block.input ?? {}),
1020
- providerExecuted: true
1021
- });
1022
- }
1023
- }
1024
- if (opensStep || toolUseIds.length === 0) {
1025
- stepOpen = true;
1026
- if (usage) pendingStepUsage = usage;
1027
- }
1028
- continue;
1029
- }
1030
- if (type === "user" && msg.message?.content) {
1031
- for (const block of msg.message.content) {
1032
- if (block.type === "tool_result" && typeof block.tool_use_id === "string") {
1033
- if (mcpToolUseIds.has(block.tool_use_id)) {
1034
- mcpToolUseIds.delete(block.tool_use_id);
1035
- pendingStepToolUseIds.delete(block.tool_use_id);
1036
- continue;
1037
- }
1038
- approvalRequestedToolUseIds.delete(block.tool_use_id);
1039
- const nativeName = nativeToolCallNames.get(block.tool_use_id) ?? "unknown";
1040
- nativeToolCallNames.delete(block.tool_use_id);
1041
- const toolName = toCommonName(nativeName);
1042
- const isError = !!block.is_error;
1043
- const content = stringifyContent(block.content);
1044
- const result = toolName === "bash" ? { exitCode: isError ? 1 : 0, stdout: content } : content;
1045
- emit({
1046
- type: "tool-result",
1047
- toolCallId: block.tool_use_id,
1048
- toolName,
1049
- result,
1050
- isError
1051
- });
1052
- pendingStepToolUseIds.delete(block.tool_use_id);
1053
- }
1054
- }
1055
- closeStepIfReady();
1056
- continue;
1057
- }
1193
+ emitStreamEvent(msg);
1058
1194
  if (type === "result") {
1059
1195
  if (msg.subtype === "success") {
1060
1196
  const emptyResult = !msg.result?.trim?.();
1061
- if (emptyResult && observedTerminalError) {
1062
- emitTerminalError(observedTerminalError);
1197
+ if (emptyResult && streamEventState.observedTerminalError) {
1198
+ emitTerminalError(streamEventState.observedTerminalError);
1063
1199
  continue;
1064
1200
  }
1065
1201
  const usage = msg.usage ?? msg.message?.usage;
@@ -1068,12 +1204,18 @@ async function runTurn(start, turn) {
1068
1204
  if (typeof msg.total_cost_usd === "number") {
1069
1205
  totalCostUsd = (totalCostUsd ?? 0) + msg.total_cost_usd;
1070
1206
  }
1071
- if (stepOpen) emitFinishStep(harnessUsage ?? pendingStepUsage);
1207
+ if (streamEventState.stepOpen) {
1208
+ emitFinishStep({
1209
+ state: streamEventState,
1210
+ emit,
1211
+ usage: harnessUsage ?? streamEventState.pendingStepUsage
1212
+ });
1213
+ }
1072
1214
  queryInput.close();
1073
1215
  break;
1074
1216
  } else {
1075
1217
  emitTerminalError(
1076
- (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"
1077
1219
  );
1078
1220
  }
1079
1221
  continue;
@@ -1093,66 +1235,10 @@ async function runTurn(start, turn) {
1093
1235
  emit({
1094
1236
  type: "finish",
1095
1237
  finishReason: { unified: "stop", raw: "stop" },
1096
- totalUsage: turnUsage ?? stepUsage ?? defaultUsage(),
1238
+ totalUsage: turnUsage ?? streamEventState.stepUsage ?? defaultUsage(),
1097
1239
  ...totalCostUsd !== void 0 ? { harnessMetadata: { "claude-code": { costUsd: totalCostUsd } } } : {}
1098
1240
  });
1099
1241
  }
1100
- function formatApiRetryWarning(msg) {
1101
- const details = [];
1102
- if (typeof msg.attempt === "number") {
1103
- const maxRetries = typeof msg.max_retries === "number" ? `/${msg.max_retries}` : "";
1104
- details.push(`attempt ${msg.attempt}${maxRetries}`);
1105
- }
1106
- if (typeof msg.error_status === "number") {
1107
- details.push(`HTTP ${msg.error_status}`);
1108
- }
1109
- if (typeof msg.retry_delay_ms === "number") {
1110
- details.push(`retrying in ${msg.retry_delay_ms}ms`);
1111
- }
1112
- if (msg.error) details.push(msg.error);
1113
- return details.length > 0 ? `Claude Code API retry: ${details.join("; ")}` : "Claude Code API retry";
1114
- }
1115
- function handleStreamEvent(event, partialBlocks, send) {
1116
- if (!event || typeof event.index !== "number") return;
1117
- const index = event.index;
1118
- if (event.type === "content_block_start") {
1119
- const blockType = event.content_block?.type;
1120
- if (blockType === "text") {
1121
- const id = randomUUID();
1122
- partialBlocks.set(index, { id, kind: "text" });
1123
- send({ type: "text-start", id });
1124
- } else if (blockType === "thinking") {
1125
- const id = randomUUID();
1126
- partialBlocks.set(index, { id, kind: "thinking" });
1127
- send({ type: "reasoning-start", id });
1128
- }
1129
- return;
1130
- }
1131
- if (event.type === "content_block_delta") {
1132
- const block = partialBlocks.get(index);
1133
- if (!block) return;
1134
- if (block.kind === "text" && event.delta?.type === "text_delta" && typeof event.delta.text === "string") {
1135
- send({ type: "text-delta", id: block.id, delta: event.delta.text });
1136
- } else if (block.kind === "thinking" && event.delta?.type === "thinking_delta" && typeof event.delta.thinking === "string") {
1137
- send({
1138
- type: "reasoning-delta",
1139
- id: block.id,
1140
- delta: event.delta.thinking
1141
- });
1142
- }
1143
- return;
1144
- }
1145
- if (event.type === "content_block_stop") {
1146
- const block = partialBlocks.get(index);
1147
- if (!block) return;
1148
- partialBlocks.delete(index);
1149
- if (block.kind === "text") {
1150
- send({ type: "text-end", id: block.id });
1151
- } else {
1152
- send({ type: "reasoning-end", id: block.id });
1153
- }
1154
- }
1155
- }
1156
1242
  function createQueryInput({
1157
1243
  initialUserMessage,
1158
1244
  pendingUserMessages,
@@ -1204,37 +1290,6 @@ function createQueryInput({
1204
1290
  }
1205
1291
  };
1206
1292
  }
1207
- function stringifyContent(content) {
1208
- if (typeof content === "string") return content;
1209
- if (Array.isArray(content)) {
1210
- return content.map(
1211
- (entry) => entry && typeof entry === "object" && "text" in entry ? String(entry.text ?? "") : JSON.stringify(entry)
1212
- ).join("");
1213
- }
1214
- return JSON.stringify(content);
1215
- }
1216
- function mapUsage(usage) {
1217
- if (!usage || typeof usage !== "object") return void 0;
1218
- const u = usage;
1219
- return {
1220
- inputTokens: {
1221
- total: (u.input_tokens ?? 0) + (u.cache_creation_input_tokens ?? 0) + (u.cache_read_input_tokens ?? 0),
1222
- noCache: u.input_tokens ?? 0,
1223
- cacheRead: u.cache_read_input_tokens ?? 0,
1224
- cacheWrite: u.cache_creation_input_tokens ?? 0
1225
- },
1226
- outputTokens: {
1227
- total: u.output_tokens ?? 0,
1228
- text: u.output_tokens ?? 0
1229
- }
1230
- };
1231
- }
1232
- function defaultUsage() {
1233
- return {
1234
- inputTokens: { total: 0, noCache: 0, cacheRead: 0, cacheWrite: 0 },
1235
- outputTokens: { total: 0, text: 0 }
1236
- };
1237
- }
1238
1293
  function parseArgs(args2) {
1239
1294
  const out = {};
1240
1295
  for (let i = 0; i < args2.length; i++) {