patch-cc 0.1.0__py3-none-any.whl
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.
- patch_cc/__init__.py +3 -0
- patch_cc/bun/__init__.py +6 -0
- patch_cc/bun/blob.py +256 -0
- patch_cc/bun/container.py +129 -0
- patch_cc/bun/elf.py +281 -0
- patch_cc/bun/errors.py +12 -0
- patch_cc/bun/macho.py +124 -0
- patch_cc/cache.py +86 -0
- patch_cc/cli.py +399 -0
- patch_cc/doctor.py +116 -0
- patch_cc/locate.py +117 -0
- patch_cc/menu.py +1135 -0
- patch_cc/patcher.py +244 -0
- patch_cc/patches/__init__.py +75 -0
- patch_cc/patches/agents.py +289 -0
- patch_cc/patches/base.py +200 -0
- patch_cc/patches/chrome.py +187 -0
- patch_cc/patches/output.py +173 -0
- patch_cc/patches/streaming.py +922 -0
- patch_cc/patches/thinking.py +88 -0
- patch_cc/ui.py +23 -0
- patch_cc-0.1.0.dist-info/METADATA +141 -0
- patch_cc-0.1.0.dist-info/RECORD +26 -0
- patch_cc-0.1.0.dist-info/WHEEL +4 -0
- patch_cc-0.1.0.dist-info/entry_points.txt +2 -0
- patch_cc-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,922 @@
|
|
|
1
|
+
"""Live (streaming) thinking.
|
|
2
|
+
|
|
3
|
+
This is the single most fragile patch in the set: upstream has reshaped the
|
|
4
|
+
stream reducer at least three times, and most of their commit traffic lands
|
|
5
|
+
here. Two structural choices follow from that:
|
|
6
|
+
|
|
7
|
+
1. It is built from ~11 **named steps**, each recording its own outcome. A
|
|
8
|
+
scalar hit count cannot tell "everything landed" from "half of it silently
|
|
9
|
+
drifted", which is precisely how this patch hides its own regressions.
|
|
10
|
+
2. Steps share discovered identifiers through :class:`Discovery` rather than
|
|
11
|
+
re-deriving them, and every step tolerates its own failure so one drifted
|
|
12
|
+
shape does not take the rest down with it.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import re
|
|
18
|
+
from dataclasses import dataclass
|
|
19
|
+
|
|
20
|
+
from .base import GROUP_LIVE, IDENT, Options, Outcome, Patch, compile_js, splice
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass(slots=True)
|
|
24
|
+
class Discovery:
|
|
25
|
+
"""Identifiers found in one step and consumed by later ones."""
|
|
26
|
+
|
|
27
|
+
streaming_var: str | None = None
|
|
28
|
+
create_message_helper: str | None = None
|
|
29
|
+
transcript_var: str | None = None
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# --------------------------------------------------------------- JS builders
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _reset(setter: str, ended_at: str) -> str:
|
|
36
|
+
"""The `mark the live block finished` state update, used by many cases."""
|
|
37
|
+
return (
|
|
38
|
+
f"{setter}?.((__cc_prevStreamingThinking)=>__cc_prevStreamingThinking?"
|
|
39
|
+
f"{{...__cc_prevStreamingThinking,isStreaming:!1,streamingEndedAt:{ended_at},"
|
|
40
|
+
f"currentIndex:null,currentMessage:null}}:__cc_prevStreamingThinking)"
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _block_start(event: str, setter: str, helper: str) -> str:
|
|
45
|
+
"""Create a virtual message when a thinking content block starts.
|
|
46
|
+
|
|
47
|
+
Keyed by content-block index, so a block-start handled twice replaces its
|
|
48
|
+
entry rather than appending a duplicate live block.
|
|
49
|
+
"""
|
|
50
|
+
return (
|
|
51
|
+
f"{setter}?.((__cc_prevStreamingThinking)=>{{"
|
|
52
|
+
f"let __cc_streamingThinkingMessage={helper}({{content:["
|
|
53
|
+
f'{event}.event.content_block.type==="redacted_thinking"'
|
|
54
|
+
f'?{{type:"redacted_thinking",data:{event}.event.content_block.data??""}}'
|
|
55
|
+
f':{{type:"thinking",thinking:""}}],isVirtual:!0}}),'
|
|
56
|
+
f"__cc_nextStreamingThinkingMessages=[...(__cc_prevStreamingThinking?.messages??[])"
|
|
57
|
+
f".filter((__cc_entry)=>__cc_entry.index!=={event}.event.index),"
|
|
58
|
+
f"{{index:{event}.event.index,message:__cc_streamingThinkingMessage}}];"
|
|
59
|
+
f'return{{thinking:{event}.event.content_block.type==="redacted_thinking"'
|
|
60
|
+
f'?{event}.event.content_block.data??"":"",isStreaming:!0,streamingEndedAt:void 0,'
|
|
61
|
+
f"currentIndex:{event}.event.index,currentMessage:__cc_streamingThinkingMessage,"
|
|
62
|
+
f"messages:__cc_nextStreamingThinkingMessages}}}})"
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _delta(event: str, setter: str, helper: str) -> str:
|
|
67
|
+
"""Append a thinking delta to the live block."""
|
|
68
|
+
return (
|
|
69
|
+
f"{setter}?.((__cc_prevStreamingThinking)=>{{"
|
|
70
|
+
f"let __cc_nextStreamingThinkingDelta=typeof {event}.event.delta.thinking==="
|
|
71
|
+
f'"string"?{event}.event.delta.thinking:"",'
|
|
72
|
+
f'__cc_nextStreamingThinkingText=(__cc_prevStreamingThinking?.thinking??"")'
|
|
73
|
+
f"+__cc_nextStreamingThinkingDelta,"
|
|
74
|
+
f"__cc_nextStreamingThinkingIndex=__cc_prevStreamingThinking?.currentIndex"
|
|
75
|
+
f"??{event}.event.index,"
|
|
76
|
+
f"__cc_nextStreamingThinkingMessage={helper}({{content:["
|
|
77
|
+
f'{{type:"thinking",thinking:__cc_nextStreamingThinkingText}}],isVirtual:!0}}),'
|
|
78
|
+
f"__cc_nextStreamingThinkingMessages=[...(__cc_prevStreamingThinking?.messages??[])"
|
|
79
|
+
f".filter((__cc_entry)=>__cc_entry.index!==__cc_nextStreamingThinkingIndex),"
|
|
80
|
+
f"{{index:__cc_nextStreamingThinkingIndex,message:__cc_nextStreamingThinkingMessage}}];"
|
|
81
|
+
f"return __cc_prevStreamingThinking?{{...__cc_prevStreamingThinking,"
|
|
82
|
+
f"thinking:__cc_nextStreamingThinkingText,isStreaming:!0,streamingEndedAt:void 0,"
|
|
83
|
+
f"currentIndex:__cc_nextStreamingThinkingIndex,"
|
|
84
|
+
f"currentMessage:__cc_nextStreamingThinkingMessage,"
|
|
85
|
+
f"messages:__cc_nextStreamingThinkingMessages}}"
|
|
86
|
+
f":{{thinking:__cc_nextStreamingThinkingText,isStreaming:!0,streamingEndedAt:void 0,"
|
|
87
|
+
f"currentIndex:{event}.event.index,"
|
|
88
|
+
f"currentMessage:__cc_nextStreamingThinkingMessage,"
|
|
89
|
+
f"messages:[{{index:{event}.event.index,"
|
|
90
|
+
f"message:__cc_nextStreamingThinkingMessage}}]}}}})"
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
# ------------------------------------------------------------------- step 1
|
|
95
|
+
|
|
96
|
+
_MEMO_CACHE = compile_js(
|
|
97
|
+
rf"if\(({IDENT})\[(\d+)\]!==({IDENT})\|\|\1\[(\d+)\]!==({IDENT})\|\|\1\[(\d+)\]!==({IDENT})\)"
|
|
98
|
+
rf"([\s\S]{{0,700}}?thinking:\5\.thinking[\s\S]{{0,700}}?)"
|
|
99
|
+
rf"\1\[\2\]=\3,\1\[\4\]=\5,\1\[\6\]=\7,(\1\[\d+\]={IDENT};)"
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _step_memo_cache(content: str, outcome: Outcome) -> str:
|
|
104
|
+
"""Key the memo cache on `thinking?.thinking`, not the wrapper object.
|
|
105
|
+
|
|
106
|
+
Without this the comparator sees the same object identity across deltas and
|
|
107
|
+
never re-renders while text is still streaming in.
|
|
108
|
+
"""
|
|
109
|
+
step = outcome.step("memo-cache")
|
|
110
|
+
|
|
111
|
+
def rewrite(match: re.Match[str]) -> str:
|
|
112
|
+
cache, i1, v1, i2, v2, i3, v3, middle, tail = match.groups()
|
|
113
|
+
step.candidates += 1
|
|
114
|
+
if f"{v2}?.thinking" in match.group(0):
|
|
115
|
+
return match.group(0)
|
|
116
|
+
step.applied += 1
|
|
117
|
+
return (
|
|
118
|
+
f"if({cache}[{i1}]!=={v1}||{cache}[{i2}]!=={v2}?.thinking||{cache}[{i3}]!=={v3})"
|
|
119
|
+
f"{middle}{cache}[{i1}]={v1},{cache}[{i2}]={v2}?.thinking,{cache}[{i3}]={v3},{tail}"
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
return _MEMO_CACHE.sub(rewrite, content)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
# ------------------------------------------------------------------- step 2
|
|
126
|
+
|
|
127
|
+
_HIDE_PAST = compile_js(rf"hidePastThinking:!0,streamingThinking:({IDENT})")
|
|
128
|
+
_ON_STREAMING = compile_js(rf"onStreamingThinking:({IDENT})")
|
|
129
|
+
_CREATE_ELEMENT_CALL = compile_js(rf"createElement\(({IDENT}),\{{([^{{}}]*?)\}}\)")
|
|
130
|
+
_PROMPT_RENDERER = compile_js(
|
|
131
|
+
rf"createElement\(({IDENT}),\{{([\s\S]{{0,2000}}?placeholderElement:[\s\S]{{0,2000}}?"
|
|
132
|
+
rf"agentDefinitions:[^}}]*?onOpenRateLimitOptions:[^}}]*?isLoading:)([^,}}]+)"
|
|
133
|
+
rf"(,streamingText:[^}}]*?(?:showThinkingHint:[^}}]*?)?isBriefOnly:[^}}]*?)\}}\)"
|
|
134
|
+
)
|
|
135
|
+
_JSX_MAIN_PROPS = compile_js(
|
|
136
|
+
r"(screen:[^,}]+,streamingToolUses:[^,}]+,)"
|
|
137
|
+
r"(showAllInTranscript:[^,}]+,agentDefinitions:[^,}]+,onOpenRateLimitOptions:[^,}]+,isLoading:[^,}]+)"
|
|
138
|
+
)
|
|
139
|
+
_JSX_TRANSCRIPT_PROPS = compile_js(
|
|
140
|
+
r"(screen:[^,}]+,agentDefinitions:[^,}]+,streamingToolUses:[^,}]+,)"
|
|
141
|
+
r"(showAllInTranscript:[^,}]+,onOpenRateLimitOptions:[^,}]+,isLoading:[^,}]+)"
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _discover_streaming_var(content: str, found: Discovery, outcome: Outcome) -> None:
|
|
146
|
+
"""Find the state variable holding live thinking.
|
|
147
|
+
|
|
148
|
+
2.1.216 no longer ships `hidePastThinking`, so the primary anchor is already
|
|
149
|
+
dead and we rely on the `onStreamingThinking` -> `useState(null)` back-scan.
|
|
150
|
+
Losing that fallback too would leave this patch with nothing.
|
|
151
|
+
"""
|
|
152
|
+
primary = _HIDE_PAST.search(content)
|
|
153
|
+
if primary:
|
|
154
|
+
found.streaming_var = primary.group(1)
|
|
155
|
+
return
|
|
156
|
+
|
|
157
|
+
outcome.step("discover").note(
|
|
158
|
+
"hidePastThinking anchor gone; using useState back-scan"
|
|
159
|
+
)
|
|
160
|
+
for match in _ON_STREAMING.finditer(content):
|
|
161
|
+
setter = match.group(1)
|
|
162
|
+
start = max(0, match.start() - 50_000)
|
|
163
|
+
window = content[start : match.start()]
|
|
164
|
+
state = compile_js(
|
|
165
|
+
rf"\[({IDENT}),{re.escape(setter)}\]={IDENT}\.useState\(null\)"
|
|
166
|
+
)
|
|
167
|
+
candidates = list(state.finditer(window))
|
|
168
|
+
if candidates:
|
|
169
|
+
found.streaming_var = candidates[-1].group(1)
|
|
170
|
+
return
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def _step_prop_threading(content: str, found: Discovery, outcome: Outcome) -> str:
|
|
174
|
+
"""Pass the live-thinking state into the renderers that need it."""
|
|
175
|
+
step = outcome.step("prop-threading")
|
|
176
|
+
if found.streaming_var is None:
|
|
177
|
+
step.note("no streaming state variable found; skipped")
|
|
178
|
+
return content
|
|
179
|
+
var = found.streaming_var
|
|
180
|
+
|
|
181
|
+
def rewrite_create_element(match: re.Match[str]) -> str:
|
|
182
|
+
component, props = match.group(1), match.group(2)
|
|
183
|
+
required = (
|
|
184
|
+
"streamingToolUses:",
|
|
185
|
+
"toolJSX:",
|
|
186
|
+
"agentDefinitions:",
|
|
187
|
+
"onOpenRateLimitOptions:",
|
|
188
|
+
"conversationId:",
|
|
189
|
+
"isLoading:",
|
|
190
|
+
)
|
|
191
|
+
forbidden = ("streamingThinking:", "hidePastThinking:")
|
|
192
|
+
if any(tok not in props for tok in required) or any(
|
|
193
|
+
t in props for t in forbidden
|
|
194
|
+
):
|
|
195
|
+
return match.group(0)
|
|
196
|
+
step.candidates += 1
|
|
197
|
+
step.applied += 1
|
|
198
|
+
return f"createElement({component},{{{props},streamingThinking:{var}}})"
|
|
199
|
+
|
|
200
|
+
output = _CREATE_ELEMENT_CALL.sub(rewrite_create_element, content)
|
|
201
|
+
|
|
202
|
+
def rewrite_prompt(match: re.Match[str]) -> str:
|
|
203
|
+
if "streamingThinking:" in match.group(0):
|
|
204
|
+
return match.group(0)
|
|
205
|
+
component, before, is_loading, after = match.groups()
|
|
206
|
+
step.candidates += 1
|
|
207
|
+
step.applied += 1
|
|
208
|
+
return (
|
|
209
|
+
f"createElement({component},{{{before}{is_loading},"
|
|
210
|
+
f"streamingThinking:{var}{after}}})"
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
output = _PROMPT_RENDERER.sub(rewrite_prompt, output)
|
|
214
|
+
|
|
215
|
+
def inject(match: re.Match[str]) -> str:
|
|
216
|
+
if "streamingThinking:" in match.group(0):
|
|
217
|
+
return match.group(0)
|
|
218
|
+
step.candidates += 1
|
|
219
|
+
step.applied += 1
|
|
220
|
+
return f"{match.group(1)}streamingThinking:{var},{match.group(2)}"
|
|
221
|
+
|
|
222
|
+
output = _JSX_MAIN_PROPS.sub(inject, output)
|
|
223
|
+
return _JSX_TRANSCRIPT_PROPS.sub(inject, output)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
# ------------------------------------------------------------------- step 3
|
|
227
|
+
|
|
228
|
+
_THINKING_DISPLAY = compile_js(
|
|
229
|
+
rf"({IDENT})=({IDENT})\.type!==\"disabled\"&&!({IDENT})"
|
|
230
|
+
rf"\(process\.env\.CLAUDE_CODE_DISABLE_THINKING\),({IDENT})=\1"
|
|
231
|
+
rf"(?:&&{IDENT}\(\)&&{IDENT}\({IDENT}\))?\?\2\.display(?:\?\?void 0)?:void 0,({IDENT})=void 0;"
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
# 2.1.216 hoists the env check into its own variable and gates the display
|
|
235
|
+
# value behind extra feature/model helpers. The helper chain is kept verbatim;
|
|
236
|
+
# only the display expression gains the `??"summarized"` default.
|
|
237
|
+
_THINKING_DISPLAY_2 = compile_js(
|
|
238
|
+
rf"({IDENT})=({IDENT})\(process\.env\.CLAUDE_CODE_DISABLE_THINKING\),"
|
|
239
|
+
rf"({IDENT})=({IDENT})\.type!==\"disabled\"&&!\1,"
|
|
240
|
+
rf"({IDENT})=\3((?:&&{IDENT}\((?:{IDENT})?\))*)\?\4\.display:void 0,"
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def _step_display_mode(content: str, outcome: Outcome) -> str:
|
|
245
|
+
"""Default the thinking request to `summarized`.
|
|
246
|
+
|
|
247
|
+
Without a display mode in the request the API streams signature-only (or
|
|
248
|
+
late) thinking, so the live row starves -- worst on short thinks. Upstream
|
|
249
|
+
only requests summaries when the `showThinkingSummaries` setting is on;
|
|
250
|
+
default it on instead.
|
|
251
|
+
"""
|
|
252
|
+
step = outcome.step("display-mode")
|
|
253
|
+
|
|
254
|
+
def rewrite(match: re.Match[str]) -> str:
|
|
255
|
+
enabled, config, env_helper, display, request = match.groups()
|
|
256
|
+
step.candidates += 1
|
|
257
|
+
if 'display??"summarized"' in match.group(0):
|
|
258
|
+
return match.group(0)
|
|
259
|
+
step.applied += 1
|
|
260
|
+
return (
|
|
261
|
+
f'{enabled}={config}.type!=="disabled"&&!{env_helper}'
|
|
262
|
+
f"(process.env.CLAUDE_CODE_DISABLE_THINKING),"
|
|
263
|
+
f'{display}={enabled}?{config}.display??"summarized":void 0,{request}=void 0;'
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
output = _THINKING_DISPLAY.sub(rewrite, content)
|
|
267
|
+
|
|
268
|
+
def rewrite_hoisted(match: re.Match[str]) -> str:
|
|
269
|
+
env_var, env_helper, enabled, config, display, guards = match.groups()
|
|
270
|
+
step.candidates += 1
|
|
271
|
+
step.applied += 1
|
|
272
|
+
return (
|
|
273
|
+
f"{env_var}={env_helper}(process.env.CLAUDE_CODE_DISABLE_THINKING),"
|
|
274
|
+
f'{enabled}={config}.type!=="disabled"&&!{env_var},'
|
|
275
|
+
f'{display}={enabled}{guards}?{config}.display??"summarized":void 0,'
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
return _THINKING_DISPLAY_2.sub(rewrite_hoisted, output, count=1)
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
# ------------------------------------------------------------------- step 4
|
|
282
|
+
|
|
283
|
+
_ASSISTANT_THINKING = compile_js(
|
|
284
|
+
rf"let ({IDENT})=({IDENT})\.message\.content\.find\(\(({IDENT})\)=>"
|
|
285
|
+
rf'\3\.type==="thinking"\);if\(\1&&\1\.type==="thinking"\)({IDENT})'
|
|
286
|
+
rf"\?\.\(\(\)=>\(\{{thinking:\1\.thinking,isStreaming:!1,"
|
|
287
|
+
rf"streamingEndedAt:Date\.now\(\)\}}\)\)"
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def _step_final_summary(content: str, outcome: Outcome) -> str:
|
|
292
|
+
"""Include redacted thinking in the final assistant-message summary."""
|
|
293
|
+
step = outcome.step("final-summary")
|
|
294
|
+
|
|
295
|
+
def rewrite(match: re.Match[str]) -> str:
|
|
296
|
+
block, message, item, setter = match.groups()
|
|
297
|
+
step.candidates += 1
|
|
298
|
+
step.applied += 1
|
|
299
|
+
return (
|
|
300
|
+
f"let {block}={message}.message.content.find(({item})=>"
|
|
301
|
+
f'{item}.type==="thinking"||{item}.type==="redacted_thinking");'
|
|
302
|
+
f'if({block}&&({block}.type==="thinking"||{block}.type==="redacted_thinking"))'
|
|
303
|
+
f'{setter}?.(()=>({{thinking:{block}.type==="thinking"'
|
|
304
|
+
f'?{block}.thinking:{block}.data??"",isStreaming:!1,'
|
|
305
|
+
f"streamingEndedAt:Date.now()}}))"
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
return _ASSISTANT_THINKING.sub(rewrite, content)
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
# ------------------------------------------------------------------- step 5
|
|
312
|
+
|
|
313
|
+
_MEMO_ASSIGN = compile_js(rf"({IDENT})=({IDENT})\.memo\(({IDENT}),({IDENT})\)")
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def _step_memo_removal(content: str, outcome: Outcome) -> str:
|
|
317
|
+
"""Unwrap the message-row memo whose comparator suppresses live updates."""
|
|
318
|
+
step = outcome.step("memo-removal")
|
|
319
|
+
output = content
|
|
320
|
+
pos = 0
|
|
321
|
+
while True:
|
|
322
|
+
match = _MEMO_ASSIGN.search(output, pos)
|
|
323
|
+
if not match:
|
|
324
|
+
break
|
|
325
|
+
lhs, _ns, render_fn, comparator = match.groups()
|
|
326
|
+
pos = match.end()
|
|
327
|
+
|
|
328
|
+
start = output.find(f"function {comparator}(")
|
|
329
|
+
if start == -1:
|
|
330
|
+
continue
|
|
331
|
+
body = output[start : start + 2200]
|
|
332
|
+
if not all(
|
|
333
|
+
tok in body
|
|
334
|
+
for tok in (
|
|
335
|
+
".screen!==",
|
|
336
|
+
".columns!==",
|
|
337
|
+
".lastThinkingBlockId",
|
|
338
|
+
".streamingToolUseIDs",
|
|
339
|
+
)
|
|
340
|
+
):
|
|
341
|
+
continue
|
|
342
|
+
|
|
343
|
+
step.candidates += 1
|
|
344
|
+
replacement = f"{lhs}={render_fn}"
|
|
345
|
+
if replacement != match.group(0):
|
|
346
|
+
output = splice(output, match.start(), match.end(), replacement)
|
|
347
|
+
step.applied += 1
|
|
348
|
+
pos = match.start() + len(replacement)
|
|
349
|
+
return output
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
# ------------------------------------------------------------------- step 6
|
|
353
|
+
|
|
354
|
+
_LINGER_LABEL = compile_js(
|
|
355
|
+
rf"({IDENT}):\{{if\(!({IDENT})\)\{{({IDENT})=!1;break \1\}}"
|
|
356
|
+
rf"if\(\2\.isStreaming\)\{{\3=!0;break \1\}}"
|
|
357
|
+
rf"if\(\2\.streamingEndedAt\)\{{\3=Date\.now\(\)-\2\.streamingEndedAt<30000;break \1\}}"
|
|
358
|
+
rf"\3=!1\}}let ({IDENT})=\3"
|
|
359
|
+
)
|
|
360
|
+
_LINGER_MEMO = compile_js(
|
|
361
|
+
rf"({IDENT})=({IDENT})\.useMemo\(\(\)=>\{{if\(!({IDENT})\)return!1;"
|
|
362
|
+
rf"if\(\3\.isStreaming\)return!0;"
|
|
363
|
+
rf"if\(\3\.streamingEndedAt\)return Date\.now\(\)-\3\.streamingEndedAt<30000;"
|
|
364
|
+
rf"return!1\}},\[\3\]\)"
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
def _step_linger(content: str, outcome: Outcome) -> str:
|
|
369
|
+
"""Drop the 30-second post-stream linger; show only while streaming."""
|
|
370
|
+
step = outcome.step("linger")
|
|
371
|
+
|
|
372
|
+
def rewrite_label(match: re.Match[str]) -> str:
|
|
373
|
+
step.candidates += 1
|
|
374
|
+
step.applied += 1
|
|
375
|
+
return (
|
|
376
|
+
f"let {match.group(4)}=!!({match.group(2)}&&{match.group(2)}.isStreaming)"
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
def rewrite_memo(match: re.Match[str]) -> str:
|
|
380
|
+
visible, ns, stream = match.groups()
|
|
381
|
+
step.candidates += 1
|
|
382
|
+
step.applied += 1
|
|
383
|
+
return (
|
|
384
|
+
f"{visible}={ns}.useMemo(()=>!!({stream}&&{stream}.isStreaming),[{stream}])"
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
output = _LINGER_LABEL.sub(rewrite_label, content)
|
|
388
|
+
return _LINGER_MEMO.sub(rewrite_memo, output)
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
# ------------------------------------------------------------------- step 7
|
|
392
|
+
|
|
393
|
+
_TOOLUSE_HELPERS = compile_js(
|
|
394
|
+
rf"let {IDENT}=({IDENT})\(\{{content:\[{IDENT}\.contentBlock\]\}}\);"
|
|
395
|
+
rf"return {IDENT}\.uuid=({IDENT})\({IDENT}\.contentBlock\.id,0\),({IDENT})\(\[{IDENT}\]\)"
|
|
396
|
+
)
|
|
397
|
+
_RENDERER_HAS_VAR = compile_js(
|
|
398
|
+
rf"\(\{{messages:[^}}]*?streamingToolUses:{IDENT},streamingThinking:({IDENT}),showAllInTranscript:"
|
|
399
|
+
)
|
|
400
|
+
_RENDERER_SIGNATURE = compile_js(
|
|
401
|
+
rf"(\(\{{messages:[^}}]*?streamingToolUses:{IDENT},)(showAllInTranscript:)"
|
|
402
|
+
)
|
|
403
|
+
_TRANSCRIPT_VAR = compile_js(
|
|
404
|
+
rf"streamingToolUses:{IDENT},[^}}]*streamingThinking:({IDENT}),streamingText:"
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
def _step_transcript_signature(content: str, found: Discovery, outcome: Outcome) -> str:
|
|
409
|
+
"""Make sure the transcript renderer actually receives the live state."""
|
|
410
|
+
step = outcome.step("transcript-signature")
|
|
411
|
+
|
|
412
|
+
helpers = _TOOLUSE_HELPERS.search(content)
|
|
413
|
+
if helpers:
|
|
414
|
+
found.create_message_helper = helpers.group(1)
|
|
415
|
+
|
|
416
|
+
existing = _RENDERER_HAS_VAR.search(content)
|
|
417
|
+
if existing:
|
|
418
|
+
found.transcript_var = existing.group(1)
|
|
419
|
+
step.candidates += 1
|
|
420
|
+
step.applied += 1 # nothing to do; upstream already threads it
|
|
421
|
+
return content
|
|
422
|
+
|
|
423
|
+
output = content
|
|
424
|
+
if found.streaming_var is not None:
|
|
425
|
+
|
|
426
|
+
def inject(match: re.Match[str]) -> str:
|
|
427
|
+
if "streamingThinking:" in match.group(0):
|
|
428
|
+
return match.group(0)
|
|
429
|
+
step.candidates += 1
|
|
430
|
+
step.applied += 1
|
|
431
|
+
found.transcript_var = "__cc_streamingThinking"
|
|
432
|
+
return f"{match.group(1)}streamingThinking:__cc_streamingThinking,{match.group(2)}"
|
|
433
|
+
|
|
434
|
+
output = _RENDERER_SIGNATURE.sub(inject, output, count=1)
|
|
435
|
+
|
|
436
|
+
if found.transcript_var is None:
|
|
437
|
+
fallback = _TRANSCRIPT_VAR.search(output)
|
|
438
|
+
if fallback:
|
|
439
|
+
found.transcript_var = fallback.group(1)
|
|
440
|
+
return output
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
# ------------------------------------------------------------------- step 8
|
|
444
|
+
|
|
445
|
+
_INLINE_EXTRAS = compile_js(
|
|
446
|
+
rf"({IDENT})=({IDENT})\.useMemo\(\(\)=>({IDENT})\.flatMap\(\(({IDENT})\)=>\{{"
|
|
447
|
+
rf"let ({IDENT})=({IDENT})\(\{{content:\[\4\.contentBlock\]\}}\);"
|
|
448
|
+
rf"return \5\.uuid=({IDENT})\(\4\.contentBlock\.id,0\),({IDENT})\(\[\5\]\)\}}\),\[\3\]\)"
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
def _step_inline_extras(content: str, found: Discovery, outcome: Outcome) -> str:
|
|
453
|
+
"""Render live thinking inline, ordered with streaming tool-use blocks."""
|
|
454
|
+
step = outcome.step("inline-extras")
|
|
455
|
+
if not found.transcript_var:
|
|
456
|
+
step.note("no transcript streaming variable; skipped")
|
|
457
|
+
return content
|
|
458
|
+
var = found.transcript_var
|
|
459
|
+
|
|
460
|
+
def rewrite(match: re.Match[str]) -> str:
|
|
461
|
+
extras, ns, tool_uses, entry, message, helper, uuid_helper, normalize = (
|
|
462
|
+
match.groups()
|
|
463
|
+
)
|
|
464
|
+
step.candidates += 1
|
|
465
|
+
step.applied += 1
|
|
466
|
+
found.create_message_helper = helper
|
|
467
|
+
return (
|
|
468
|
+
f"{extras}={ns}.useMemo(()=>{{"
|
|
469
|
+
f"let __cc_streamingToolUseExtras={tool_uses}.map(({entry})=>{{"
|
|
470
|
+
f"let {message}={helper}({{content:[{entry}.contentBlock]}});"
|
|
471
|
+
f"return {message}.uuid={uuid_helper}({entry}.contentBlock.id,0),"
|
|
472
|
+
f"{{index:{entry}.index??9007199254740991,"
|
|
473
|
+
f"messages:{normalize}([{message}])}}}}),"
|
|
474
|
+
f"__cc_streamingThinkingExtras=({var}?.messages??[])"
|
|
475
|
+
f".map((__cc_entry,__cc_index)=>({{"
|
|
476
|
+
f"index:__cc_entry.index??9007199254740991+__cc_index,"
|
|
477
|
+
f"messages:{normalize}([__cc_entry.message??__cc_entry])}}));"
|
|
478
|
+
f"return[...__cc_streamingToolUseExtras,...__cc_streamingThinkingExtras]"
|
|
479
|
+
f".sort((__cc_a,__cc_b)=>__cc_a.index===__cc_b.index?0:__cc_a.index-__cc_b.index)"
|
|
480
|
+
f".flatMap((__cc_entry)=>__cc_entry.messages)}},[{tool_uses},{var}])"
|
|
481
|
+
)
|
|
482
|
+
|
|
483
|
+
return _INLINE_EXTRAS.sub(rewrite, content)
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
# ------------------------------------------------------------------- step 9
|
|
487
|
+
|
|
488
|
+
_LIVE_ROW = compile_js(
|
|
489
|
+
rf"({IDENT})&{{2}}({IDENT})&{{2}}!({IDENT})&{{2}}({IDENT})\.createElement\(({IDENT}),"
|
|
490
|
+
rf"\{{marginTop:1\}},\4\.createElement\(({IDENT}),\{{param:\{{type:\"thinking\","
|
|
491
|
+
rf"thinking:\2\.thinking\}},addMargin:!1,isTranscriptMode:!0,verbose:({IDENT}),"
|
|
492
|
+
rf"hideInTranscript:!1\}}\)\)"
|
|
493
|
+
)
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
def _step_bottom_row(content: str, outcome: Outcome) -> str:
|
|
497
|
+
"""Remove the separate bottom-pinned live row now that it renders inline."""
|
|
498
|
+
step = outcome.step("bottom-row")
|
|
499
|
+
|
|
500
|
+
def rewrite(_match: re.Match[str]) -> str:
|
|
501
|
+
step.candidates += 1
|
|
502
|
+
step.applied += 1
|
|
503
|
+
return "null"
|
|
504
|
+
|
|
505
|
+
return _LIVE_ROW.sub(rewrite, content)
|
|
506
|
+
|
|
507
|
+
|
|
508
|
+
# ------------------------------------------------------ steps 10-11: reducer
|
|
509
|
+
|
|
510
|
+
_PROGRESS_ONLY = (
|
|
511
|
+
r'case"thinking_delta":\{{let\{{delta:({ident})\}}={event}\.event;'
|
|
512
|
+
r'if\("estimated_tokens"in \1&&typeof \1\.estimated_tokens==="number"\)'
|
|
513
|
+
r'({ident})\?\.\(\{{type:"thinking_progress",'
|
|
514
|
+
r"estimatedTokensDelta:\1\.estimated_tokens\}}\);return\}}"
|
|
515
|
+
)
|
|
516
|
+
_PROGRESS_WITH_TEXT = (
|
|
517
|
+
r'case"thinking_delta":\{{let\{{delta:({ident})\}}={event}\.event;'
|
|
518
|
+
r'if\("estimated_tokens"in \1&&typeof \1\.estimated_tokens==="number"\)'
|
|
519
|
+
r'({ident})\?\.\(\{{type:"thinking_progress",'
|
|
520
|
+
r"estimatedTokensDelta:\1\.estimated_tokens\}}\);"
|
|
521
|
+
r'else if\("thinking"in \1&&typeof \1\.thinking==="string"&&\1\.thinking\.length>0\)'
|
|
522
|
+
r'\2\?\.\(\{{type:"thinking_progress",'
|
|
523
|
+
r"estimatedTokensDelta:({ident})\(\1\.thinking\)\}}\);return\}}"
|
|
524
|
+
)
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
def _apply_pairs(segment: str, pairs: list[tuple[str, str]], step: Outcome) -> str:
|
|
528
|
+
"""Apply literal before/after rewrites, counting each independently."""
|
|
529
|
+
for before, after in pairs:
|
|
530
|
+
if before and before in segment:
|
|
531
|
+
step.candidates += 1
|
|
532
|
+
segment = segment.replace(before, after, 1)
|
|
533
|
+
if after in segment:
|
|
534
|
+
step.applied += 1
|
|
535
|
+
return segment
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
def _apply_progress_variants(
|
|
539
|
+
segment: str, event: str, delta_body: str, step: Outcome
|
|
540
|
+
) -> str:
|
|
541
|
+
"""Keep upstream's thinking-progress metrics while adding our state update."""
|
|
542
|
+
for template in (_PROGRESS_ONLY, _PROGRESS_WITH_TEXT):
|
|
543
|
+
pattern = compile_js(template.format(ident=IDENT, event=re.escape(event)))
|
|
544
|
+
|
|
545
|
+
def rewrite(match: re.Match[str]) -> str:
|
|
546
|
+
groups = match.groups()
|
|
547
|
+
delta_var, metrics = groups[0], groups[1]
|
|
548
|
+
tail = (
|
|
549
|
+
f"let{{delta:{delta_var}}}={event}.event;"
|
|
550
|
+
f'if("estimated_tokens"in {delta_var}&&'
|
|
551
|
+
f'typeof {delta_var}.estimated_tokens==="number")'
|
|
552
|
+
f'{metrics}?.({{type:"thinking_progress",'
|
|
553
|
+
f"estimatedTokensDelta:{delta_var}.estimated_tokens}});"
|
|
554
|
+
)
|
|
555
|
+
if len(groups) > 2:
|
|
556
|
+
estimator = groups[2]
|
|
557
|
+
tail += (
|
|
558
|
+
f'else if("thinking"in {delta_var}&&'
|
|
559
|
+
f'typeof {delta_var}.thinking==="string"&&'
|
|
560
|
+
f"{delta_var}.thinking.length>0)"
|
|
561
|
+
f'{metrics}?.({{type:"thinking_progress",'
|
|
562
|
+
f"estimatedTokensDelta:{estimator}({delta_var}.thinking)}});"
|
|
563
|
+
)
|
|
564
|
+
return f'case"thinking_delta":{{{delta_body}{tail}return}}'
|
|
565
|
+
|
|
566
|
+
updated = pattern.sub(rewrite, segment, count=1)
|
|
567
|
+
if updated != segment:
|
|
568
|
+
step.candidates += 1
|
|
569
|
+
step.applied += 1
|
|
570
|
+
segment = updated
|
|
571
|
+
return segment
|
|
572
|
+
|
|
573
|
+
|
|
574
|
+
def _reducer_pairs(
|
|
575
|
+
event: str,
|
|
576
|
+
setter: str,
|
|
577
|
+
mode: str,
|
|
578
|
+
tools: str,
|
|
579
|
+
helper: str,
|
|
580
|
+
*,
|
|
581
|
+
optional: bool,
|
|
582
|
+
options_param: str | None = None,
|
|
583
|
+
display_transform: str | None = None,
|
|
584
|
+
) -> list[tuple[str, str]]:
|
|
585
|
+
"""Before/after rewrites for one stream-reducer shape.
|
|
586
|
+
|
|
587
|
+
``optional`` selects whether upstream calls the setters directly or through
|
|
588
|
+
``?.`` -- both spellings exist in the wild.
|
|
589
|
+
"""
|
|
590
|
+
call = "?." if optional else ""
|
|
591
|
+
ended = _reset(setter, "Date.now()")
|
|
592
|
+
cleared = _reset(setter, "void 0")
|
|
593
|
+
pairs: list[tuple[str, str]] = [
|
|
594
|
+
(
|
|
595
|
+
f'if({event}.type==="stream_request_start"){{{mode}("requesting");return}}',
|
|
596
|
+
f'if({event}.type==="stream_request_start"){{{setter}?.(null),{mode}{call}("requesting");return}}',
|
|
597
|
+
),
|
|
598
|
+
(
|
|
599
|
+
f'if({event}.type==="stream_request_start"){{{mode}?.("requesting");return}}',
|
|
600
|
+
f'if({event}.type==="stream_request_start"){{{setter}?.(null),{mode}?.("requesting");return}}',
|
|
601
|
+
),
|
|
602
|
+
]
|
|
603
|
+
|
|
604
|
+
if options_param:
|
|
605
|
+
pairs.append(
|
|
606
|
+
(
|
|
607
|
+
f'if({event}.event.type==="message_stop"){{{options_param}.displayTransform?.finalize(),'
|
|
608
|
+
f'{mode}("tool-use"),{tools}(()=>[]);return}}',
|
|
609
|
+
f'if({event}.event.type==="message_stop"){{{options_param}.displayTransform?.finalize(),'
|
|
610
|
+
f'{ended},{mode}("tool-use"),{tools}(()=>[]);return}}',
|
|
611
|
+
)
|
|
612
|
+
)
|
|
613
|
+
if display_transform:
|
|
614
|
+
for prefix in (
|
|
615
|
+
f"{display_transform}.finalize()",
|
|
616
|
+
f"{display_transform}?.finalize()",
|
|
617
|
+
):
|
|
618
|
+
for mo, to in ((mode, tools), (f"{mode}?.", f"{tools}?.")):
|
|
619
|
+
pairs.append(
|
|
620
|
+
(
|
|
621
|
+
f'if({event}.event.type==="message_stop"){{{prefix},'
|
|
622
|
+
f'{mo}("tool-use"),{to}(()=>[]);return}}',
|
|
623
|
+
f'if({event}.event.type==="message_stop"){{{display_transform}?.finalize(),'
|
|
624
|
+
f'{ended},{mode}?.("tool-use"),{tools}?.(()=>[]);return}}',
|
|
625
|
+
)
|
|
626
|
+
)
|
|
627
|
+
|
|
628
|
+
pairs += [
|
|
629
|
+
(
|
|
630
|
+
f'if({event}.event.type==="message_stop"){{{mode}("tool-use"),{tools}(()=>[]);return}}',
|
|
631
|
+
f'if({event}.event.type==="message_stop"){{{ended},{mode}{call}("tool-use"),'
|
|
632
|
+
f"{tools}{call}(()=>[]);return}}",
|
|
633
|
+
),
|
|
634
|
+
(
|
|
635
|
+
f'if({event}.event.type==="message_stop"){{{mode}?.("tool-use"),{tools}?.(()=>[]);return}}',
|
|
636
|
+
f'if({event}.event.type==="message_stop"){{{ended},{mode}?.("tool-use"),'
|
|
637
|
+
f"{tools}?.(()=>[]);return}}",
|
|
638
|
+
),
|
|
639
|
+
(
|
|
640
|
+
f'case"thinking":case"redacted_thinking":{mode}("thinking");return;',
|
|
641
|
+
f'case"thinking":case"redacted_thinking":{_block_start(event, setter, helper)},'
|
|
642
|
+
f'{mode}{call}("thinking");return;',
|
|
643
|
+
),
|
|
644
|
+
(
|
|
645
|
+
f'case"thinking":case"redacted_thinking":{mode}?.("thinking");return;',
|
|
646
|
+
f'case"thinking":case"redacted_thinking":{_block_start(event, setter, helper)},'
|
|
647
|
+
f'{mode}?.("thinking");return;',
|
|
648
|
+
),
|
|
649
|
+
(
|
|
650
|
+
f'case"text":{mode}("responding");return;',
|
|
651
|
+
f'case"text":{cleared},{mode}{call}("responding");return;',
|
|
652
|
+
),
|
|
653
|
+
(
|
|
654
|
+
f'case"text":{mode}?.("responding");return;',
|
|
655
|
+
f'case"text":{cleared},{mode}?.("responding");return;',
|
|
656
|
+
),
|
|
657
|
+
(
|
|
658
|
+
f'case"message_delta":if({mode}("responding"),{event}.event.usage.output_tokens!=null)',
|
|
659
|
+
f'case"message_delta":if({cleared},{mode}("responding"),'
|
|
660
|
+
f"{event}.event.usage.output_tokens!=null)",
|
|
661
|
+
),
|
|
662
|
+
(
|
|
663
|
+
f'case"message_delta":{mode}("responding");return;',
|
|
664
|
+
f'case"message_delta":{cleared},{mode}{call}("responding");return;',
|
|
665
|
+
),
|
|
666
|
+
(
|
|
667
|
+
f'case"message_delta":{mode}?.("responding");return;',
|
|
668
|
+
f'case"message_delta":{cleared},{mode}?.("responding");return;',
|
|
669
|
+
),
|
|
670
|
+
(
|
|
671
|
+
f'case"message_delta":{{{mode}("responding");',
|
|
672
|
+
f'case"message_delta":{{{cleared},{mode}{call}("responding");',
|
|
673
|
+
),
|
|
674
|
+
(
|
|
675
|
+
f'case"message_delta":{{{mode}?.("responding");',
|
|
676
|
+
f'case"message_delta":{{{cleared},{mode}?.("responding");',
|
|
677
|
+
),
|
|
678
|
+
]
|
|
679
|
+
return pairs
|
|
680
|
+
|
|
681
|
+
|
|
682
|
+
_DESTRUCTURED_HANDLER = compile_js(
|
|
683
|
+
rf"function {IDENT}\(({IDENT}),({IDENT})\)\{{let\{{([^}}]*onStreamingThinking:{IDENT}[^}}]*)\}}=\2;"
|
|
684
|
+
)
|
|
685
|
+
_MISSING_HANDLER = compile_js(
|
|
686
|
+
rf"function {IDENT}\(({IDENT}),({IDENT})(?:,{IDENT})?\)\{{let\{{([^}}]*)\}}=\2;"
|
|
687
|
+
)
|
|
688
|
+
_LEGACY_ANCHOR = 'type!=="stream_event"&&'
|
|
689
|
+
_FN_SIG = compile_js(rf"^function {IDENT}\(([^)]*)\)\{{")
|
|
690
|
+
|
|
691
|
+
|
|
692
|
+
def _prop_var(props: str, name: str, *, shorthand: bool = False) -> str | None:
|
|
693
|
+
alias = compile_js(rf"{re.escape(name)}:({IDENT})").search(props)
|
|
694
|
+
if alias:
|
|
695
|
+
return alias.group(1)
|
|
696
|
+
if shorthand and compile_js(rf"(?:^|,){re.escape(name)}(?:,|$)").search(props):
|
|
697
|
+
return name
|
|
698
|
+
return None
|
|
699
|
+
|
|
700
|
+
|
|
701
|
+
def _handler_is_stream_reducer(segment: str) -> bool:
|
|
702
|
+
return all(
|
|
703
|
+
tok in segment
|
|
704
|
+
for tok in (
|
|
705
|
+
'type==="stream_request_start"',
|
|
706
|
+
'case"thinking_delta"',
|
|
707
|
+
"content_block_start",
|
|
708
|
+
)
|
|
709
|
+
)
|
|
710
|
+
|
|
711
|
+
|
|
712
|
+
def _step_reducer_destructured(content: str, found: Discovery, outcome: Outcome) -> str:
|
|
713
|
+
"""2.1.138+ shape: options bag that already destructures onStreamingThinking."""
|
|
714
|
+
step = outcome.step("reducer-destructured")
|
|
715
|
+
helper = found.create_message_helper
|
|
716
|
+
if helper is None:
|
|
717
|
+
step.note("no virtual-message helper discovered; skipped")
|
|
718
|
+
return content
|
|
719
|
+
|
|
720
|
+
output, pos = content, 0
|
|
721
|
+
while True:
|
|
722
|
+
match = _DESTRUCTURED_HANDLER.search(output, pos)
|
|
723
|
+
if not match:
|
|
724
|
+
break
|
|
725
|
+
event, options_param, props = match.groups()
|
|
726
|
+
pos = match.end()
|
|
727
|
+
|
|
728
|
+
mode = _prop_var(props, "onSetStreamMode")
|
|
729
|
+
tools = _prop_var(props, "onStreamingToolUses")
|
|
730
|
+
setter = _prop_var(props, "onStreamingThinking")
|
|
731
|
+
if not (mode and tools and setter):
|
|
732
|
+
continue
|
|
733
|
+
|
|
734
|
+
end = output.find("function ", match.end())
|
|
735
|
+
if end == -1:
|
|
736
|
+
continue
|
|
737
|
+
segment = output[match.start() : end]
|
|
738
|
+
if not _handler_is_stream_reducer(segment):
|
|
739
|
+
continue
|
|
740
|
+
|
|
741
|
+
delta_body = _delta(event, setter, helper) + ";"
|
|
742
|
+
pairs = _reducer_pairs(
|
|
743
|
+
event,
|
|
744
|
+
setter,
|
|
745
|
+
mode,
|
|
746
|
+
tools,
|
|
747
|
+
helper,
|
|
748
|
+
optional=False,
|
|
749
|
+
options_param=options_param,
|
|
750
|
+
)
|
|
751
|
+
pairs.append(
|
|
752
|
+
(
|
|
753
|
+
'case"thinking_delta":return;',
|
|
754
|
+
f'case"thinking_delta":{{{delta_body}return;}}',
|
|
755
|
+
)
|
|
756
|
+
)
|
|
757
|
+
|
|
758
|
+
updated = _apply_pairs(segment, pairs, step)
|
|
759
|
+
updated = _apply_progress_variants(updated, event, delta_body, step)
|
|
760
|
+
if updated != segment:
|
|
761
|
+
output = splice(output, match.start(), end, updated)
|
|
762
|
+
pos = match.start() + len(updated)
|
|
763
|
+
return output
|
|
764
|
+
|
|
765
|
+
|
|
766
|
+
def _step_reducer_inner(content: str, found: Discovery, outcome: Outcome) -> str:
|
|
767
|
+
"""2.1.183+ shape: inner handler that dropped onStreamingThinking."""
|
|
768
|
+
step = outcome.step("reducer-inner")
|
|
769
|
+
helper = found.create_message_helper
|
|
770
|
+
if helper is None:
|
|
771
|
+
step.note("no virtual-message helper discovered; skipped")
|
|
772
|
+
return content
|
|
773
|
+
|
|
774
|
+
setter = "__cc_onStreamingThinking"
|
|
775
|
+
output, pos = content, 0
|
|
776
|
+
while True:
|
|
777
|
+
match = _MISSING_HANDLER.search(output, pos)
|
|
778
|
+
if not match:
|
|
779
|
+
break
|
|
780
|
+
event, options_param, props = match.groups()
|
|
781
|
+
pos = match.end()
|
|
782
|
+
if "onStreamingThinking:" in props:
|
|
783
|
+
continue
|
|
784
|
+
|
|
785
|
+
mode = _prop_var(props, "onSetStreamMode", shorthand=True)
|
|
786
|
+
tools = _prop_var(props, "onStreamingToolUses", shorthand=True)
|
|
787
|
+
display = _prop_var(props, "displayTransform", shorthand=True)
|
|
788
|
+
if not (mode and tools):
|
|
789
|
+
continue
|
|
790
|
+
|
|
791
|
+
end = output.find("function ", match.end())
|
|
792
|
+
if end == -1:
|
|
793
|
+
continue
|
|
794
|
+
segment = output[match.start() : end]
|
|
795
|
+
if not _handler_is_stream_reducer(segment):
|
|
796
|
+
continue
|
|
797
|
+
|
|
798
|
+
delta_body = _delta(event, setter, helper) + ";"
|
|
799
|
+
pairs = [
|
|
800
|
+
(
|
|
801
|
+
f"let{{{props}}}={options_param};",
|
|
802
|
+
f"let{{{props},onStreamingThinking:{setter}}}={options_param};",
|
|
803
|
+
)
|
|
804
|
+
]
|
|
805
|
+
pairs += _reducer_pairs(
|
|
806
|
+
event, setter, mode, tools, helper, optional=True, display_transform=display
|
|
807
|
+
)
|
|
808
|
+
pairs.append(
|
|
809
|
+
(
|
|
810
|
+
'case"thinking_delta":return;',
|
|
811
|
+
f'case"thinking_delta":{{{delta_body}return;}}',
|
|
812
|
+
)
|
|
813
|
+
)
|
|
814
|
+
|
|
815
|
+
updated = _apply_pairs(segment, pairs, step)
|
|
816
|
+
updated = _apply_progress_variants(updated, event, delta_body, step)
|
|
817
|
+
if updated != segment:
|
|
818
|
+
output = splice(output, match.start(), end, updated)
|
|
819
|
+
pos = match.start() + len(updated)
|
|
820
|
+
return output
|
|
821
|
+
|
|
822
|
+
|
|
823
|
+
def _step_reducer_legacy(content: str, found: Discovery, outcome: Outcome) -> str:
|
|
824
|
+
"""Pre-2.1.138 shape: positional parameters, no options bag."""
|
|
825
|
+
step = outcome.step("reducer-legacy")
|
|
826
|
+
anchor = content.find(_LEGACY_ANCHOR)
|
|
827
|
+
if anchor == -1:
|
|
828
|
+
return content
|
|
829
|
+
if content.find('type==="stream_request_start"', anchor) == -1:
|
|
830
|
+
return content
|
|
831
|
+
if content.find('case"thinking_delta"', anchor) == -1:
|
|
832
|
+
return content
|
|
833
|
+
|
|
834
|
+
start = content.rfind("function ", 0, anchor)
|
|
835
|
+
end = content.find("function ", anchor + len(_LEGACY_ANCHOR))
|
|
836
|
+
if start == -1 or end == -1:
|
|
837
|
+
return content
|
|
838
|
+
|
|
839
|
+
segment = content[start:end]
|
|
840
|
+
signature = _FN_SIG.search(segment)
|
|
841
|
+
if not signature:
|
|
842
|
+
return content
|
|
843
|
+
params = [p.strip() for p in signature.group(1).split(",")]
|
|
844
|
+
if len(params) < 7:
|
|
845
|
+
return content
|
|
846
|
+
|
|
847
|
+
event, append_output, mode, tools, setter = (
|
|
848
|
+
params[0],
|
|
849
|
+
params[2],
|
|
850
|
+
params[3],
|
|
851
|
+
params[4],
|
|
852
|
+
params[6],
|
|
853
|
+
)
|
|
854
|
+
helper = found.create_message_helper
|
|
855
|
+
|
|
856
|
+
pairs = _reducer_pairs(event, setter, mode, tools, helper or "", optional=False)
|
|
857
|
+
if helper is None:
|
|
858
|
+
# Without the helper we cannot synthesise virtual messages; keep only
|
|
859
|
+
# the rewrites that do not need it.
|
|
860
|
+
pairs = [p for p in pairs if "__cc_streamingThinkingMessage" not in p[1]]
|
|
861
|
+
else:
|
|
862
|
+
delta_body = _delta(event, setter, helper) + ";"
|
|
863
|
+
pairs += [
|
|
864
|
+
(
|
|
865
|
+
f'case"thinking_delta":{append_output}({event}.event.delta.thinking);return;',
|
|
866
|
+
f'case"thinking_delta":{{{append_output}({event}.event.delta.thinking);'
|
|
867
|
+
f"{delta_body}return;}}",
|
|
868
|
+
),
|
|
869
|
+
(
|
|
870
|
+
'case"thinking_delta":return;',
|
|
871
|
+
f'case"thinking_delta":{{{delta_body}return;}}',
|
|
872
|
+
),
|
|
873
|
+
]
|
|
874
|
+
|
|
875
|
+
updated = _apply_pairs(segment, pairs, step)
|
|
876
|
+
if helper is not None:
|
|
877
|
+
updated = _apply_progress_variants(
|
|
878
|
+
updated, event, _delta(event, setter, helper) + ";", step
|
|
879
|
+
)
|
|
880
|
+
return splice(content, start, end, updated) if updated != segment else content
|
|
881
|
+
|
|
882
|
+
|
|
883
|
+
# ------------------------------------------------------------------ assembly
|
|
884
|
+
|
|
885
|
+
|
|
886
|
+
def _live_thinking(content: str, _options: Options, outcome: Outcome) -> str:
|
|
887
|
+
found = Discovery()
|
|
888
|
+
output = _step_memo_cache(content, outcome)
|
|
889
|
+
_discover_streaming_var(output, found, outcome)
|
|
890
|
+
output = _step_prop_threading(output, found, outcome)
|
|
891
|
+
output = _step_display_mode(output, outcome)
|
|
892
|
+
output = _step_final_summary(output, outcome)
|
|
893
|
+
output = _step_memo_removal(output, outcome)
|
|
894
|
+
output = _step_linger(output, outcome)
|
|
895
|
+
output = _step_transcript_signature(output, found, outcome)
|
|
896
|
+
output = _step_inline_extras(output, found, outcome)
|
|
897
|
+
output = _step_bottom_row(output, outcome)
|
|
898
|
+
output = _step_reducer_destructured(output, found, outcome)
|
|
899
|
+
output = _step_reducer_inner(output, found, outcome)
|
|
900
|
+
output = _step_reducer_legacy(output, found, outcome)
|
|
901
|
+
|
|
902
|
+
if found.streaming_var is None:
|
|
903
|
+
outcome.note("live-thinking state variable was never found")
|
|
904
|
+
return output
|
|
905
|
+
|
|
906
|
+
|
|
907
|
+
PATCHES = [
|
|
908
|
+
Patch(
|
|
909
|
+
id="live-thinking",
|
|
910
|
+
title="Stream thinking live",
|
|
911
|
+
summary="Show thinking as it is generated, inline and in order, instead of "
|
|
912
|
+
"only after the turn finishes.",
|
|
913
|
+
group=GROUP_LIVE,
|
|
914
|
+
fn=_live_thinking,
|
|
915
|
+
anchors=(
|
|
916
|
+
"onStreamingThinking:",
|
|
917
|
+
'case"thinking_delta"',
|
|
918
|
+
'type==="stream_request_start"',
|
|
919
|
+
"content_block_start",
|
|
920
|
+
),
|
|
921
|
+
),
|
|
922
|
+
]
|