| ... | @@ -1275,6 +1275,17 @@ function textFromClaudeContent(content: SDKAssistantMessage["message"]["content" | ... | @@ -1275,6 +1275,17 @@ function textFromClaudeContent(content: SDKAssistantMessage["message"]["content" |
| 1275 | return content.flatMap((part) => (part.type === "text" ? [part.text] : [])).join(""); | 1275 | return content.flatMap((part) => (part.type === "text" ? [part.text] : [])).join(""); |
| 1276 | } | 1276 | } |
| 1277 | | 1277 | |
| | 1278 | // Claude 5.1 and later return the text written ahead of a tool call as a |
| | 1279 | // thinking block, at most one per call, instead of a text block. Summarized |
| | 1280 | // display makes it look like reasoning, so only the second of two thinking |
| | 1281 | // blocks right before a tool call is treated as that text. |
| | 1282 | function claudeWritesUpdatesAsThinking(model: string): boolean { |
| | 1283 | const match = /claude-[a-z]+-(\d+)(?:-(\d{1,2}))?(?!\d)/u.exec(model); |
| | 1284 | if (match === null) return false; |
| | 1285 | const major = Number(match[1]); |
| | 1286 | return major > 5 || (major === 5 && Number(match[2] ?? 0) >= 1); |
| | 1287 | } |
| | 1288 | |
| 1278 | // In SDK mode Claude reports `/goal` only through the transcript (its | 1289 | // In SDK mode Claude reports `/goal` only through the transcript (its |
| 1279 | // `active_goal` event is remote-only): synthetic command output names the | 1290 | // `active_goal` event is remote-only): synthetic command output names the |
| 1280 | // goal, and each unmet evaluator check returns as Stop hook feedback. | 1291 | // goal, and each unmet evaluator check returns as Stop hook feedback. |
| ... | @@ -3016,6 +3027,13 @@ interface ActiveClaudeTurnContext { | ... | @@ -3016,6 +3027,13 @@ interface ActiveClaudeTurnContext { |
| 3016 | readonly nextBlockIndex: Map<string, number>; | 3027 | readonly nextBlockIndex: Map<string, number>; |
| 3017 | readonly snapshotBlockIndex: Map<string, number>; | 3028 | readonly snapshotBlockIndex: Map<string, number>; |
| 3018 | readonly snapshots: Set<string>; | 3029 | readonly snapshots: Set<string>; |
| | 3030 | // The latest snapshot block, when it was non-empty thinking. |
| | 3031 | lastThinking: { |
| | 3032 | readonly messageId: string; |
| | 3033 | readonly itemId: string; |
| | 3034 | readonly text: string; |
| | 3035 | readonly followsThinking: boolean; |
| | 3036 | } | null; |
| 3019 | readonly blocks: Map< | 3037 | readonly blocks: Map< |
| 3020 | string, | 3038 | string, |
| 3021 | { readonly startedAt: DateTime.Utc; readonly ordinal: number; text: string } | 3039 | { readonly startedAt: DateTime.Utc; readonly ordinal: number; text: string } |
| ... | @@ -6962,18 +6980,45 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* ( | ... | @@ -6962,18 +6980,45 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* ( |
| 6962 | // The SDK emits one assistant snapshot per completed content block. | 6980 | // The SDK emits one assistant snapshot per completed content block. |
| 6963 | // Count thinking blocks separately so snapshots share the streamed ID. | 6981 | // Count thinking blocks separately so snapshots share the streamed ID. |
| 6964 | for (const block of message.message.content) { | 6982 | for (const block of message.message.content) { |
| 6965 | if (block.type !== "thinking") continue; | 6983 | if (block.type !== "thinking") { |
| | 6984 | const update = context.reasoning.lastThinking; |
| | 6985 | context.reasoning.lastThinking = null; |
| | 6986 | if ( |
| | 6987 | block.type === "tool_use" && |
| | 6988 | update !== null && |
| | 6989 | update.messageId === message.message.id && |
| | 6990 | update.followsThinking && |
| | 6991 | claudeWritesUpdatesAsThinking(message.message.model) |
| | 6992 | ) { |
| | 6993 | // Same item ID, so the update replaces the Thought in place. |
| | 6994 | yield* emitAssistantTextArtifacts({ |
| | 6995 | context, |
| | 6996 | nativeItemId: update.itemId, |
| | 6997 | text: update.text, |
| | 6998 | }); |
| | 6999 | } |
| | 7000 | continue; |
| | 7001 | } |
| 6966 | const index = context.reasoning.snapshotBlockIndex.get(message.message.id) ?? 0; | 7002 | const index = context.reasoning.snapshotBlockIndex.get(message.message.id) ?? 0; |
| 6967 | context.reasoning.snapshotBlockIndex.set(message.message.id, index + 1); | 7003 | context.reasoning.snapshotBlockIndex.set(message.message.id, index + 1); |
| 6968 | const itemId = `${message.message.id}:thinking:${index}`; | 7004 | const itemId = `${message.message.id}:thinking:${index}`; |
| 6969 | yield* ensureReasoningBlock(context, itemId); | 7005 | yield* ensureReasoningBlock(context, itemId); |
| 6970 | const finalText = block.thinking || context.reasoning.blocks.get(itemId)?.text; | 7006 | const finalText = block.thinking || context.reasoning.blocks.get(itemId)?.text; |
| 6971 | yield* reasoningDeltas.complete({ | 7007 | const completedText = yield* reasoningDeltas.complete({ |
| 6972 | turnId: context.nativeTurnId, | 7008 | turnId: context.nativeTurnId, |
| 6973 | itemId, | 7009 | itemId, |
| 6974 | ...(finalText ? { finalText } : {}), | 7010 | ...(finalText ? { finalText } : {}), |
| 6975 | emitEmpty: false, | 7011 | emitEmpty: false, |
| 6976 | }); | 7012 | }); |
| | 7013 | const previous = context.reasoning.lastThinking; |
| | 7014 | context.reasoning.lastThinking = completedText.trim() |
| | 7015 | ? { |
| | 7016 | messageId: message.message.id, |
| | 7017 | itemId, |
| | 7018 | text: completedText, |
| | 7019 | followsThinking: previous !== null && previous.messageId === message.message.id, |
| | 7020 | } |
| | 7021 | : null; |
| 6977 | } | 7022 | } |
| 6978 | } | 7023 | } |
| 6979 | | 7024 | |
| ... | @@ -8699,6 +8744,7 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* ( | ... | @@ -8699,6 +8744,7 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* ( |
| 8699 | streamBlocks: new Map(), | 8744 | streamBlocks: new Map(), |
| 8700 | nextBlockIndex: new Map(), | 8745 | nextBlockIndex: new Map(), |
| 8701 | snapshotBlockIndex: new Map(), | 8746 | snapshotBlockIndex: new Map(), |
| | 8747 | lastThinking: null, |
| 8702 | snapshots: new Set(), | 8748 | snapshots: new Set(), |
| 8703 | blocks: new Map(), | 8749 | blocks: new Map(), |
| 8704 | }, | 8750 | }, |