@oh-my-pi/pi-coding-agent 17.1.0 → 17.1.2

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.
Files changed (196) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/dist/cli.js +6325 -4420
  3. package/dist/types/cli/bench-cli.d.ts +1 -0
  4. package/dist/types/config/model-resolver.d.ts +6 -3
  5. package/dist/types/config/settings-schema.d.ts +62 -0
  6. package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +3 -0
  7. package/dist/types/launch/broker-list-order.test.d.ts +1 -0
  8. package/dist/types/live/attestation.d.ts +2 -0
  9. package/dist/types/live/controller.d.ts +10 -2
  10. package/dist/types/live/protocol.d.ts +1 -1
  11. package/dist/types/live/transport.d.ts +6 -19
  12. package/dist/types/live/visualizer.d.ts +8 -11
  13. package/dist/types/modes/components/assistant-message.d.ts +1 -0
  14. package/dist/types/modes/components/custom-message.d.ts +1 -1
  15. package/dist/types/modes/components/message-frame.d.ts +8 -4
  16. package/dist/types/modes/components/session-account-selector.d.ts +11 -0
  17. package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
  18. package/dist/types/modes/interactive-mode.d.ts +3 -6
  19. package/dist/types/modes/types.d.ts +9 -5
  20. package/dist/types/modes/utils/ui-helpers.d.ts +1 -6
  21. package/dist/types/session/agent-session-types.d.ts +8 -1
  22. package/dist/types/session/agent-session.d.ts +20 -1
  23. package/dist/types/session/auth-storage.d.ts +1 -1
  24. package/dist/types/session/eval-runner.d.ts +2 -0
  25. package/dist/types/session/messages.d.ts +2 -0
  26. package/dist/types/session/session-tools.d.ts +15 -0
  27. package/dist/types/session/streaming-output.d.ts +8 -0
  28. package/dist/types/slash-commands/helpers/session-pin.d.ts +9 -0
  29. package/dist/types/stt/index.d.ts +0 -2
  30. package/dist/types/stt/stt-controller.d.ts +7 -0
  31. package/dist/types/task/executor.d.ts +3 -1
  32. package/dist/types/task/structured-subagent.d.ts +3 -0
  33. package/dist/types/task/types.d.ts +11 -11
  34. package/dist/types/thinking.d.ts +13 -0
  35. package/dist/types/tiny/title-client.d.ts +10 -0
  36. package/dist/types/tools/builtin-names.d.ts +1 -1
  37. package/dist/types/tools/computer/protocol.d.ts +43 -0
  38. package/dist/types/tools/computer/supervisor.d.ts +32 -0
  39. package/dist/types/tools/computer/worker-entry.d.ts +1 -0
  40. package/dist/types/tools/computer/worker.d.ts +15 -0
  41. package/dist/types/tools/computer-renderer.d.ts +22 -0
  42. package/dist/types/tools/computer.d.ts +71 -0
  43. package/dist/types/tools/context.d.ts +2 -0
  44. package/dist/types/tools/default-renderer.d.ts +21 -0
  45. package/dist/types/tools/eval-format/index.d.ts +7 -0
  46. package/dist/types/tools/eval-format/javascript.d.ts +2 -0
  47. package/dist/types/tools/eval-format/julia.d.ts +2 -0
  48. package/dist/types/tools/eval-format/python.d.ts +2 -0
  49. package/dist/types/tools/eval-format/ruby.d.ts +2 -0
  50. package/dist/types/tools/index.d.ts +2 -0
  51. package/dist/types/tools/resolve.d.ts +7 -0
  52. package/dist/types/tools/todo.d.ts +4 -1
  53. package/dist/types/tts/streaming-player.d.ts +10 -43
  54. package/dist/types/utils/tools-manager.d.ts +1 -2
  55. package/dist/types/web/search/providers/base.d.ts +16 -0
  56. package/dist/types/web/search/providers/brave.d.ts +2 -0
  57. package/dist/types/web/search/providers/firecrawl.d.ts +2 -0
  58. package/dist/types/web/search/providers/gemini.d.ts +3 -0
  59. package/dist/types/web/search/providers/jina.d.ts +2 -0
  60. package/dist/types/web/search/providers/kagi.d.ts +2 -0
  61. package/dist/types/web/search/providers/kimi.d.ts +2 -0
  62. package/dist/types/web/search/providers/parallel.d.ts +2 -0
  63. package/dist/types/web/search/providers/perplexity.d.ts +3 -0
  64. package/dist/types/web/search/providers/searxng.d.ts +10 -0
  65. package/dist/types/web/search/providers/tavily.d.ts +8 -0
  66. package/dist/types/web/search/query.d.ts +190 -0
  67. package/package.json +12 -12
  68. package/src/cli/args.ts +1 -0
  69. package/src/cli/bench-cli.ts +12 -1
  70. package/src/cli/setup-cli.ts +2 -14
  71. package/src/cli/web-search-cli.ts +7 -0
  72. package/src/cli.ts +8 -0
  73. package/src/config/model-registry.ts +6 -0
  74. package/src/config/model-resolver.ts +17 -6
  75. package/src/config/settings-schema.ts +66 -0
  76. package/src/debug/raw-sse-buffer.ts +157 -30
  77. package/src/eval/executor-base.ts +1 -0
  78. package/src/eval/js/executor.ts +2 -0
  79. package/src/exec/bash-executor.ts +1 -0
  80. package/src/export/share.ts +4 -3
  81. package/src/extensibility/extensions/wrapper.ts +68 -12
  82. package/src/extensibility/legacy-pi-coding-agent-shim.ts +7 -1
  83. package/src/launch/broker-list-order.test.ts +89 -0
  84. package/src/launch/broker.ts +49 -8
  85. package/src/live/attestation.ts +91 -0
  86. package/src/live/controller.ts +76 -23
  87. package/src/live/protocol.test.ts +3 -3
  88. package/src/live/protocol.ts +1 -1
  89. package/src/live/transport.ts +72 -140
  90. package/src/live/visualizer.ts +114 -134
  91. package/src/modes/components/assistant-message.ts +7 -2
  92. package/src/modes/components/custom-message.ts +4 -1
  93. package/src/modes/components/message-frame.ts +14 -8
  94. package/src/modes/components/session-account-selector.ts +62 -0
  95. package/src/modes/components/tool-execution.ts +17 -110
  96. package/src/modes/controllers/input-controller.ts +55 -47
  97. package/src/modes/controllers/live-command-controller.ts +82 -5
  98. package/src/modes/controllers/selector-controller.ts +62 -0
  99. package/src/modes/interactive-mode.ts +79 -5
  100. package/src/modes/types.ts +10 -4
  101. package/src/modes/utils/ui-helpers.ts +7 -8
  102. package/src/prompts/system/computer-safety.md +14 -0
  103. package/src/prompts/system/resolve-device-reminder.md +1 -1
  104. package/src/prompts/system/workflow-notice.md +1 -1
  105. package/src/prompts/tools/ast-edit.md +1 -0
  106. package/src/prompts/tools/bash.md +1 -0
  107. package/src/prompts/tools/computer.md +26 -0
  108. package/src/prompts/tools/eval.md +2 -2
  109. package/src/prompts/tools/task.md +5 -2
  110. package/src/prompts/tools/web-search.md +2 -0
  111. package/src/sdk.ts +13 -4
  112. package/src/session/agent-session-types.ts +9 -0
  113. package/src/session/agent-session.ts +70 -0
  114. package/src/session/auth-storage.ts +1 -0
  115. package/src/session/eval-runner.ts +5 -0
  116. package/src/session/messages.ts +3 -0
  117. package/src/session/queued-messages.ts +7 -1
  118. package/src/session/session-tools.ts +37 -0
  119. package/src/session/streaming-output.ts +52 -5
  120. package/src/slash-commands/builtin-registry.ts +165 -9
  121. package/src/slash-commands/helpers/session-pin.ts +44 -0
  122. package/src/stt/downloader.ts +0 -2
  123. package/src/stt/index.ts +0 -2
  124. package/src/stt/stt-controller.ts +57 -146
  125. package/src/system-prompt.ts +4 -0
  126. package/src/task/executor.ts +17 -9
  127. package/src/task/index.ts +14 -34
  128. package/src/task/structured-subagent.ts +4 -0
  129. package/src/task/types.ts +15 -13
  130. package/src/thinking.ts +27 -0
  131. package/src/tiny/title-client.ts +22 -0
  132. package/src/tools/ast-edit.ts +4 -1
  133. package/src/tools/bash-interactive.ts +90 -86
  134. package/src/tools/bash.ts +13 -0
  135. package/src/tools/browser/render.ts +2 -1
  136. package/src/tools/builtin-names.ts +1 -0
  137. package/src/tools/computer/protocol.ts +28 -0
  138. package/src/tools/computer/supervisor.ts +258 -0
  139. package/src/tools/computer/worker-entry.ts +25 -0
  140. package/src/tools/computer/worker.ts +135 -0
  141. package/src/tools/computer-renderer.ts +108 -0
  142. package/src/tools/computer.ts +433 -0
  143. package/src/tools/context.ts +2 -0
  144. package/src/tools/default-renderer.ts +139 -0
  145. package/src/tools/essential-tools.ts +1 -0
  146. package/src/tools/eval-format/index.ts +24 -0
  147. package/src/tools/eval-format/javascript.ts +952 -0
  148. package/src/tools/eval-format/julia.ts +446 -0
  149. package/src/tools/eval-format/python.ts +544 -0
  150. package/src/tools/eval-format/ruby.ts +380 -0
  151. package/src/tools/eval-render.ts +12 -6
  152. package/src/tools/index.ts +5 -0
  153. package/src/tools/renderers.ts +2 -0
  154. package/src/tools/resolve.ts +10 -1
  155. package/src/tools/todo.ts +58 -6
  156. package/src/tools/xdev.ts +54 -26
  157. package/src/tts/streaming-player.ts +81 -340
  158. package/src/utils/clipboard.ts +1 -30
  159. package/src/utils/mac-file-urls.applescript +37 -0
  160. package/src/utils/tool-choice.ts +14 -0
  161. package/src/utils/tools-manager.ts +1 -19
  162. package/src/web/search/index.ts +28 -5
  163. package/src/web/search/providers/anthropic.ts +62 -4
  164. package/src/web/search/providers/base.ts +16 -0
  165. package/src/web/search/providers/brave.ts +30 -3
  166. package/src/web/search/providers/codex.ts +14 -1
  167. package/src/web/search/providers/duckduckgo.ts +23 -1
  168. package/src/web/search/providers/ecosia.ts +5 -1
  169. package/src/web/search/providers/exa.ts +28 -1
  170. package/src/web/search/providers/firecrawl.ts +37 -3
  171. package/src/web/search/providers/gemini.ts +12 -2
  172. package/src/web/search/providers/google.ts +2 -1
  173. package/src/web/search/providers/jina.ts +31 -8
  174. package/src/web/search/providers/kagi.ts +10 -1
  175. package/src/web/search/providers/kimi.ts +16 -1
  176. package/src/web/search/providers/mojeek.ts +14 -1
  177. package/src/web/search/providers/parallel.ts +48 -2
  178. package/src/web/search/providers/perplexity.ts +94 -6
  179. package/src/web/search/providers/searxng.ts +145 -9
  180. package/src/web/search/providers/startpage.ts +8 -2
  181. package/src/web/search/providers/synthetic.ts +7 -1
  182. package/src/web/search/providers/tavily.ts +52 -3
  183. package/src/web/search/providers/tinyfish.ts +6 -1
  184. package/src/web/search/providers/xai.ts +49 -2
  185. package/src/web/search/providers/zai.ts +19 -1
  186. package/src/web/search/query.ts +850 -0
  187. package/dist/types/stt/recorder.d.ts +0 -30
  188. package/dist/types/stt/transcriber.d.ts +0 -14
  189. package/dist/types/stt/wav.d.ts +0 -29
  190. package/dist/types/tts/player.d.ts +0 -32
  191. package/src/live/audio-worklet.txt +0 -59
  192. package/src/live/browser-runtime.txt +0 -221
  193. package/src/stt/recorder.ts +0 -551
  194. package/src/stt/transcriber.ts +0 -60
  195. package/src/stt/wav.ts +0 -173
  196. package/src/tts/player.ts +0 -137
@@ -0,0 +1,544 @@
1
+ type HeaderKind =
2
+ | "def"
3
+ | "class"
4
+ | "if"
5
+ | "elif"
6
+ | "else"
7
+ | "for"
8
+ | "while"
9
+ | "try"
10
+ | "except"
11
+ | "finally"
12
+ | "with"
13
+ | "match"
14
+ | "case"
15
+ | "async def"
16
+ | "async for"
17
+ | "async with";
18
+
19
+ type ChainKind = "if" | "loop" | "try" | "match";
20
+
21
+ interface Word {
22
+ text: string;
23
+ end: number;
24
+ }
25
+
26
+ interface Header {
27
+ kind: HeaderKind;
28
+ end: number;
29
+ }
30
+
31
+ interface BlockFrame {
32
+ kind: HeaderKind;
33
+ chain: ChainKind | null;
34
+ headerIndent: number;
35
+ sourceIndent: number;
36
+ previousChain: number;
37
+ }
38
+
39
+ interface DelimiterFrame {
40
+ opener: string;
41
+ outputIndent: number;
42
+ }
43
+
44
+ interface PendingSuiteColon {
45
+ kind: HeaderKind;
46
+ chain: ChainKind | null;
47
+ headerIndent: number;
48
+ sourceIndent: number;
49
+ afterColon: string[];
50
+ }
51
+
52
+ interface StringState {
53
+ quote: string;
54
+ triple: boolean;
55
+ escaped: boolean;
56
+ }
57
+
58
+ interface ClauseAlignment {
59
+ indent: number;
60
+ chain: ChainKind | null;
61
+ }
62
+
63
+ function isWordStart(char: string | undefined): boolean {
64
+ if (!char) return false;
65
+ const code = char.charCodeAt(0);
66
+ return char === "_" || (code >= 65 && code <= 90) || (code >= 97 && code <= 122);
67
+ }
68
+
69
+ function isWordPart(char: string): boolean {
70
+ const code = char.charCodeAt(0);
71
+ return isWordStart(char) || (code >= 48 && code <= 57);
72
+ }
73
+
74
+ function readWord(source: string, start: number): Word {
75
+ let end = start;
76
+ while (end < source.length && isWordPart(source[end])) end++;
77
+ return { text: source.slice(start, end), end };
78
+ }
79
+
80
+ function simpleHeader(word: Word): Header | null {
81
+ switch (word.text) {
82
+ case "def":
83
+ case "class":
84
+ case "if":
85
+ case "elif":
86
+ case "else":
87
+ case "for":
88
+ case "while":
89
+ case "try":
90
+ case "except":
91
+ case "finally":
92
+ case "with":
93
+ case "match":
94
+ case "case":
95
+ return { kind: word.text, end: word.end };
96
+ default:
97
+ return null;
98
+ }
99
+ }
100
+
101
+ function readHeader(source: string, start: number): Header | null {
102
+ if (!isWordStart(source[start])) return null;
103
+ const first = readWord(source, start);
104
+ if (first.text !== "async") return simpleHeader(first);
105
+
106
+ let next = first.end;
107
+ while (source[next] === " " || source[next] === "\t") next++;
108
+ if (!isWordStart(source[next])) return null;
109
+ const second = readWord(source, next);
110
+ switch (second.text) {
111
+ case "def":
112
+ return { kind: "async def", end: second.end };
113
+ case "for":
114
+ return { kind: "async for", end: second.end };
115
+ case "with":
116
+ return { kind: "async with", end: second.end };
117
+ default:
118
+ return null;
119
+ }
120
+ }
121
+
122
+ function defaultChain(kind: HeaderKind): ChainKind | null {
123
+ switch (kind) {
124
+ case "if":
125
+ case "elif":
126
+ case "else":
127
+ return "if";
128
+ case "for":
129
+ case "while":
130
+ case "async for":
131
+ return "loop";
132
+ case "try":
133
+ case "except":
134
+ case "finally":
135
+ return "try";
136
+ case "match":
137
+ case "case":
138
+ return "match";
139
+ default:
140
+ return null;
141
+ }
142
+ }
143
+
144
+ function canOpenSuite(kind: HeaderKind, hasPayload: boolean): boolean {
145
+ switch (kind) {
146
+ case "else":
147
+ case "try":
148
+ case "finally":
149
+ return !hasPayload;
150
+ case "except":
151
+ return true;
152
+ default:
153
+ return hasPayload;
154
+ }
155
+ }
156
+
157
+ function matchingCloser(opener: string, closer: string): boolean {
158
+ return (
159
+ (opener === "(" && closer === ")") || (opener === "[" && closer === "]") || (opener === "{" && closer === "}")
160
+ );
161
+ }
162
+
163
+ function formatPythonPrefix(source: string): string {
164
+ const chunks: string[] = [];
165
+ const blocks: BlockFrame[] = [];
166
+ const delimiters: DelimiterFrame[] = [];
167
+ const chainTops: Record<ChainKind, number> = { if: -1, loop: -1, try: -1, match: -1 };
168
+ const pendingHorizontal: string[] = [];
169
+
170
+ let outputLineStart = true;
171
+ let lineIndent: number | null = null;
172
+ let currentOutputIndent = 0;
173
+ let sourceLineStart = true;
174
+ let sourceIndent = 0;
175
+ let currentSourceIndent = 0;
176
+ let skipGeneratedNewline = false;
177
+
178
+ let statementPrepared = false;
179
+ let statementKind: HeaderKind | null = null;
180
+ let statementHeaderEnd = -1;
181
+ let statementHasPayload = false;
182
+ let statementIndent = 0;
183
+ let statementSourceIndent = 0;
184
+ let statementChain: ChainKind | null = null;
185
+
186
+ let pendingColon: PendingSuiteColon | null = null;
187
+ let stringState: StringState | null = null;
188
+ let inComment = false;
189
+
190
+ function currentBlockIndent(): number {
191
+ const top = blocks[blocks.length - 1];
192
+ return top ? top.headerIndent + 1 : 0;
193
+ }
194
+
195
+ function popBlock(): void {
196
+ const index = blocks.length - 1;
197
+ const frame = blocks.pop();
198
+ if (frame?.chain && chainTops[frame.chain] === index) chainTops[frame.chain] = frame.previousChain;
199
+ }
200
+
201
+ function popThrough(index: number): void {
202
+ while (blocks.length > index) popBlock();
203
+ }
204
+
205
+ function pushBlock(frame: PendingSuiteColon): void {
206
+ const previousChain = frame.chain ? chainTops[frame.chain] : -1;
207
+ blocks.push({
208
+ kind: frame.kind,
209
+ chain: frame.chain,
210
+ headerIndent: frame.headerIndent,
211
+ sourceIndent: frame.sourceIndent,
212
+ previousChain,
213
+ });
214
+ if (frame.chain) chainTops[frame.chain] = blocks.length - 1;
215
+ }
216
+
217
+ function resetStatement(): void {
218
+ statementPrepared = false;
219
+ statementKind = null;
220
+ statementHeaderEnd = -1;
221
+ statementHasPayload = false;
222
+ statementIndent = currentBlockIndent();
223
+ statementSourceIndent = currentSourceIndent;
224
+ statementChain = null;
225
+ }
226
+
227
+ function flushHorizontal(): void {
228
+ if (pendingHorizontal.length === 0) return;
229
+ chunks.push(pendingHorizontal.join("").replaceAll("\t", " "));
230
+ pendingHorizontal.length = 0;
231
+ }
232
+
233
+ function appendNormal(text: string): void {
234
+ if (outputLineStart) {
235
+ const indent = lineIndent ?? currentBlockIndent();
236
+ if (indent > 0) chunks.push(" ".repeat(indent));
237
+ currentOutputIndent = indent;
238
+ outputLineStart = false;
239
+ }
240
+ flushHorizontal();
241
+ chunks.push(text);
242
+ }
243
+
244
+ function appendRaw(text: string): void {
245
+ chunks.push(text);
246
+ if (outputLineStart) {
247
+ outputLineStart = false;
248
+ currentOutputIndent = 0;
249
+ }
250
+ }
251
+
252
+ function finishOutputLine(): void {
253
+ pendingHorizontal.length = 0;
254
+ chunks.push("\n");
255
+ outputLineStart = true;
256
+ lineIndent = null;
257
+ currentOutputIndent = 0;
258
+ }
259
+
260
+ function consumeSourceNewline(): void {
261
+ sourceLineStart = true;
262
+ sourceIndent = 0;
263
+ currentSourceIndent = 0;
264
+ skipGeneratedNewline = false;
265
+ if (delimiters.length === 0) resetStatement();
266
+ }
267
+
268
+ function popSourceDedents(indent: number): void {
269
+ let top = blocks[blocks.length - 1];
270
+ while (top && indent <= top.sourceIndent) {
271
+ popBlock();
272
+ top = blocks[blocks.length - 1];
273
+ }
274
+ }
275
+
276
+ function alignTo(index: number, fallback: ChainKind): ClauseAlignment {
277
+ if (index < 0) return { indent: currentBlockIndent(), chain: fallback };
278
+ const frame = blocks[index];
279
+ const alignment = { indent: frame.headerIndent, chain: frame.chain ?? fallback };
280
+ popThrough(index);
281
+ return alignment;
282
+ }
283
+
284
+ function alignClause(kind: HeaderKind): ClauseAlignment | null {
285
+ switch (kind) {
286
+ case "elif":
287
+ return alignTo(chainTops.if, "if");
288
+ case "except":
289
+ case "finally":
290
+ return alignTo(chainTops.try, "try");
291
+ case "else": {
292
+ const target = Math.max(chainTops.if, chainTops.loop, chainTops.try);
293
+ return alignTo(target, "if");
294
+ }
295
+ case "case": {
296
+ const target = chainTops.match;
297
+ if (target < 0) return { indent: currentBlockIndent(), chain: "match" };
298
+ const frame = blocks[target];
299
+ if (frame.kind === "match") {
300
+ while (blocks.length > target + 1) popBlock();
301
+ return { indent: frame.headerIndent + 1, chain: "match" };
302
+ }
303
+ const indent = frame.headerIndent;
304
+ popThrough(target);
305
+ return { indent, chain: "match" };
306
+ }
307
+ default:
308
+ return null;
309
+ }
310
+ }
311
+
312
+ function prepareStatement(index: number, physicalLineStart: boolean): void {
313
+ if (physicalLineStart) popSourceDedents(currentSourceIndent);
314
+ const header = readHeader(source, index);
315
+ const alignment = header ? alignClause(header.kind) : null;
316
+ statementPrepared = true;
317
+ statementKind = header?.kind ?? null;
318
+ statementHeaderEnd = header?.end ?? -1;
319
+ statementHasPayload = false;
320
+ statementIndent = alignment?.indent ?? currentBlockIndent();
321
+ statementSourceIndent = currentSourceIndent;
322
+ statementChain = alignment?.chain ?? (header ? defaultChain(header.kind) : null);
323
+ lineIndent = statementIndent;
324
+ }
325
+
326
+ function prepareToken(index: number, char: string, comment: boolean): void {
327
+ const physicalLineStart = sourceLineStart;
328
+ if (physicalLineStart) {
329
+ currentSourceIndent = sourceIndent;
330
+ sourceLineStart = false;
331
+ }
332
+ if (skipGeneratedNewline) skipGeneratedNewline = false;
333
+ if (!outputLineStart) return;
334
+
335
+ const delimiter = delimiters[delimiters.length - 1];
336
+ if (delimiter && statementPrepared) {
337
+ const closes = matchingCloser(delimiter.opener, char);
338
+ const structuralIndent = delimiter.outputIndent + (closes ? 0 : 1);
339
+ lineIndent = Math.max(structuralIndent, Math.ceil(currentSourceIndent / 4));
340
+ return;
341
+ }
342
+
343
+ if (comment) {
344
+ lineIndent = physicalLineStart
345
+ ? Math.min(currentBlockIndent(), Math.ceil(currentSourceIndent / 4))
346
+ : currentBlockIndent();
347
+ return;
348
+ }
349
+ if (!statementPrepared) prepareStatement(index, physicalLineStart);
350
+ else lineIndent = statementIndent;
351
+ }
352
+
353
+ function notePayload(index: number): void {
354
+ if (statementKind && index >= statementHeaderEnd) statementHasPayload = true;
355
+ }
356
+
357
+ function openPendingSuite(frame: PendingSuiteColon): void {
358
+ pushBlock(frame);
359
+ resetStatement();
360
+ }
361
+
362
+ let index = 0;
363
+ while (index < source.length) {
364
+ let char = source[index];
365
+
366
+ if (stringState) {
367
+ const newline = char === "\n" || char === "\r";
368
+ if (newline) {
369
+ const width = char === "\r" && source[index + 1] === "\n" ? 2 : 1;
370
+ appendRaw(source.slice(index, index + width));
371
+ outputLineStart = true;
372
+ lineIndent = null;
373
+ currentOutputIndent = 0;
374
+ sourceLineStart = true;
375
+ sourceIndent = 0;
376
+ stringState.escaped = false;
377
+ index += width;
378
+ continue;
379
+ }
380
+
381
+ if (
382
+ stringState.triple &&
383
+ !stringState.escaped &&
384
+ char === stringState.quote &&
385
+ source[index + 1] === char &&
386
+ source[index + 2] === char
387
+ ) {
388
+ appendRaw(source.slice(index, index + 3));
389
+ sourceLineStart = false;
390
+ stringState = null;
391
+ index += 3;
392
+ continue;
393
+ }
394
+
395
+ appendRaw(char);
396
+ sourceLineStart = false;
397
+ if (stringState.escaped) stringState.escaped = false;
398
+ else if (char === "\\") stringState.escaped = true;
399
+ else if (!stringState.triple && char === stringState.quote) stringState = null;
400
+ index++;
401
+ continue;
402
+ }
403
+
404
+ if (inComment) {
405
+ if (char === "\n" || char === "\r") {
406
+ const width = char === "\r" && source[index + 1] === "\n" ? 2 : 1;
407
+ finishOutputLine();
408
+ inComment = false;
409
+ consumeSourceNewline();
410
+ index += width;
411
+ continue;
412
+ }
413
+ appendRaw(char);
414
+ index++;
415
+ continue;
416
+ }
417
+
418
+ if (pendingColon) {
419
+ if (char === " " || char === "\t") {
420
+ pendingColon.afterColon.push(char);
421
+ index++;
422
+ continue;
423
+ }
424
+ if (char === "=" && pendingColon.afterColon.length === 0) {
425
+ appendNormal(":");
426
+ pendingColon = null;
427
+ } else if (char === "#") {
428
+ const frame = pendingColon;
429
+ appendNormal(":");
430
+ if (frame.afterColon.length > 0) chunks.push(frame.afterColon.join("").replaceAll("\t", " "));
431
+ openPendingSuite(frame);
432
+ pendingColon = null;
433
+ } else if (char === "\n" || char === "\r") {
434
+ const frame = pendingColon;
435
+ const width = char === "\r" && source[index + 1] === "\n" ? 2 : 1;
436
+ appendNormal(":");
437
+ openPendingSuite(frame);
438
+ pendingColon = null;
439
+ finishOutputLine();
440
+ consumeSourceNewline();
441
+ index += width;
442
+ continue;
443
+ } else {
444
+ const frame = pendingColon;
445
+ appendNormal(":");
446
+ openPendingSuite(frame);
447
+ pendingColon = null;
448
+ finishOutputLine();
449
+ skipGeneratedNewline = true;
450
+ continue;
451
+ }
452
+ char = source[index];
453
+ }
454
+
455
+ if (sourceLineStart && (char === " " || char === "\t")) {
456
+ if (char === "\t") sourceIndent += 4 - (sourceIndent % 4);
457
+ else sourceIndent++;
458
+ index++;
459
+ continue;
460
+ }
461
+
462
+ if (char === "\n" || char === "\r") {
463
+ const width = char === "\r" && source[index + 1] === "\n" ? 2 : 1;
464
+ pendingHorizontal.length = 0;
465
+ if (!skipGeneratedNewline) finishOutputLine();
466
+ consumeSourceNewline();
467
+ index += width;
468
+ continue;
469
+ }
470
+
471
+ if (char === " " || char === "\t") {
472
+ if (!skipGeneratedNewline && (!outputLineStart || statementPrepared)) pendingHorizontal.push(char);
473
+ index++;
474
+ continue;
475
+ }
476
+
477
+ prepareToken(index, char, char === "#");
478
+
479
+ if (char === "#") {
480
+ appendNormal(char);
481
+ inComment = true;
482
+ index++;
483
+ continue;
484
+ }
485
+
486
+ if (char === "'" || char === '"') {
487
+ notePayload(index);
488
+ const triple = source[index + 1] === char && source[index + 2] === char;
489
+ appendNormal(triple ? source.slice(index, index + 3) : char);
490
+ stringState = { quote: char, triple, escaped: false };
491
+ index += triple ? 3 : 1;
492
+ continue;
493
+ }
494
+
495
+ if (
496
+ char === ":" &&
497
+ delimiters.length === 0 &&
498
+ statementKind &&
499
+ canOpenSuite(statementKind, statementHasPayload)
500
+ ) {
501
+ flushHorizontal();
502
+ pendingColon = {
503
+ kind: statementKind,
504
+ chain: statementChain,
505
+ headerIndent: statementIndent,
506
+ sourceIndent: statementSourceIndent,
507
+ afterColon: [],
508
+ };
509
+ index++;
510
+ continue;
511
+ }
512
+
513
+ if (char === ";" && delimiters.length === 0) {
514
+ appendNormal(char);
515
+ finishOutputLine();
516
+ resetStatement();
517
+ skipGeneratedNewline = true;
518
+ index++;
519
+ continue;
520
+ }
521
+
522
+ notePayload(index);
523
+ appendNormal(char);
524
+ if (char === "(" || char === "[" || char === "{") {
525
+ delimiters.push({ opener: char, outputIndent: currentOutputIndent });
526
+ } else {
527
+ const delimiter = delimiters[delimiters.length - 1];
528
+ if (delimiter && matchingCloser(delimiter.opener, char)) delimiters.pop();
529
+ }
530
+ index++;
531
+ }
532
+
533
+ if (pendingColon) appendNormal(":");
534
+ return chunks.join("");
535
+ }
536
+
537
+ /** Formats an arbitrary Python source prefix for stable, readable display. */
538
+ export function formatPythonForDisplay(source: string): string {
539
+ try {
540
+ return formatPythonPrefix(source);
541
+ } catch {
542
+ return source;
543
+ }
544
+ }