| ... | @@ -1165,6 +1165,15 @@ function textFromClaudeContent(content: SDKAssistantMessage["message"]["content" | ... | @@ -1165,6 +1165,15 @@ function textFromClaudeContent(content: SDKAssistantMessage["message"]["content" |
| 1165 | return content.flatMap((part) => (part.type === "text" ? [part.text] : [])).join(""); | 1165 | return content.flatMap((part) => (part.type === "text" ? [part.text] : [])).join(""); |
| 1166 | } | 1166 | } |
| 1167 | | 1167 | |
| | 1168 | // Claude 5.1 and later return the text written ahead of a tool call as a |
| | 1169 | // thinking block, at most one per call, instead of a text block. |
| | 1170 | function claudeWritesUpdatesAsThinking(model: string): boolean { |
| | 1171 | const match = /claude-[a-z]+-(\d+)(?:-(\d{1,2}))?(?!\d)/u.exec(model); |
| | 1172 | if (match === null) return false; |
| | 1173 | const major = Number(match[1]); |
| | 1174 | return major > 5 || (major === 5 && Number(match[2] ?? 0) >= 1); |
| | 1175 | } |
| | 1176 | |
| 1168 | // In SDK mode Claude reports `/goal` only through the transcript (its | 1177 | // In SDK mode Claude reports `/goal` only through the transcript (its |
| 1169 | // `active_goal` event is remote-only): synthetic command output names the | 1178 | // `active_goal` event is remote-only): synthetic command output names the |
| 1170 | // goal, and each unmet evaluator check returns as Stop hook feedback. | 1179 | // goal, and each unmet evaluator check returns as Stop hook feedback. |
| ... | @@ -2906,6 +2915,12 @@ interface ActiveClaudeTurnContext { | ... | @@ -2906,6 +2915,12 @@ interface ActiveClaudeTurnContext { |
| 2906 | readonly nextBlockIndex: Map<string, number>; | 2915 | readonly nextBlockIndex: Map<string, number>; |
| 2907 | readonly snapshotBlockIndex: Map<string, number>; | 2916 | readonly snapshotBlockIndex: Map<string, number>; |
| 2908 | readonly snapshots: Set<string>; | 2917 | readonly snapshots: Set<string>; |
| | 2918 | // The latest snapshot block, when it was non-empty thinking. |
| | 2919 | lastThinking: { |
| | 2920 | readonly messageId: string; |
| | 2921 | readonly itemId: string; |
| | 2922 | readonly text: string; |
| | 2923 | } | null; |
| 2909 | readonly blocks: Map< | 2924 | readonly blocks: Map< |
| 2910 | string, | 2925 | string, |
| 2911 | { readonly startedAt: DateTime.Utc; readonly ordinal: number; text: string } | 2926 | { readonly startedAt: DateTime.Utc; readonly ordinal: number; text: string } |
| ... | @@ -6752,7 +6767,23 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* ( | ... | @@ -6752,7 +6767,23 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* ( |
| 6752 | // The SDK emits one assistant snapshot per completed content block. | 6767 | // The SDK emits one assistant snapshot per completed content block. |
| 6753 | // Count thinking blocks separately so snapshots share the streamed ID. | 6768 | // Count thinking blocks separately so snapshots share the streamed ID. |
| 6754 | for (const block of message.message.content) { | 6769 | for (const block of message.message.content) { |
| 6755 | if (block.type !== "thinking") continue; | 6770 | if (block.type !== "thinking") { |
| | 6771 | const update = context.reasoning.lastThinking; |
| | 6772 | context.reasoning.lastThinking = null; |
| | 6773 | if ( |
| | 6774 | block.type === "tool_use" && |
| | 6775 | update?.messageId === message.message.id && |
| | 6776 | claudeWritesUpdatesAsThinking(message.message.model) |
| | 6777 | ) { |
| | 6778 | // Same item ID, so the update replaces the Thought in place. |
| | 6779 | yield* emitAssistantTextArtifacts({ |
| | 6780 | context, |
| | 6781 | nativeItemId: update.itemId, |
| | 6782 | text: update.text, |
| | 6783 | }); |
| | 6784 | } |
| | 6785 | continue; |
| | 6786 | } |
| 6756 | const index = context.reasoning.snapshotBlockIndex.get(message.message.id) ?? 0; | 6787 | const index = context.reasoning.snapshotBlockIndex.get(message.message.id) ?? 0; |
| 6757 | context.reasoning.snapshotBlockIndex.set(message.message.id, index + 1); | 6788 | context.reasoning.snapshotBlockIndex.set(message.message.id, index + 1); |
| 6758 | const itemId = `${message.message.id}:thinking:${index}`; | 6789 | const itemId = `${message.message.id}:thinking:${index}`; |
| ... | @@ -6764,6 +6795,9 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* ( | ... | @@ -6764,6 +6795,9 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* ( |
| 6764 | ...(finalText ? { finalText } : {}), | 6795 | ...(finalText ? { finalText } : {}), |
| 6765 | emitEmpty: false, | 6796 | emitEmpty: false, |
| 6766 | }); | 6797 | }); |
| | 6798 | context.reasoning.lastThinking = finalText |
| | 6799 | ? { messageId: message.message.id, itemId, text: finalText } |
| | 6800 | : null; |
| 6767 | } | 6801 | } |
| 6768 | } | 6802 | } |
| 6769 | | 6803 | |
| ... | @@ -8441,6 +8475,7 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* ( | ... | @@ -8441,6 +8475,7 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* ( |
| 8441 | streamBlocks: new Map(), | 8475 | streamBlocks: new Map(), |
| 8442 | nextBlockIndex: new Map(), | 8476 | nextBlockIndex: new Map(), |
| 8443 | snapshotBlockIndex: new Map(), | 8477 | snapshotBlockIndex: new Map(), |
| | 8478 | lastThinking: null, |
| 8444 | snapshots: new Set(), | 8479 | snapshots: new Set(), |
| 8445 | blocks: new Map(), | 8480 | blocks: new Map(), |
| 8446 | }, | 8481 | }, |