@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,446 @@
1
+ const INDENT = " ";
2
+
3
+ const BLOCK_OPENERS: Record<string, true> = {
4
+ function: true,
5
+ macro: true,
6
+ struct: true,
7
+ if: true,
8
+ for: true,
9
+ while: true,
10
+ let: true,
11
+ begin: true,
12
+ quote: true,
13
+ try: true,
14
+ module: true,
15
+ baremodule: true,
16
+ do: true,
17
+ };
18
+
19
+ const BRANCH_CLAUSES: Record<string, true> = {
20
+ else: true,
21
+ elseif: true,
22
+ catch: true,
23
+ finally: true,
24
+ };
25
+
26
+ const EXPRESSION_PREFIX_WORDS: Record<string, true> = {
27
+ baremodule: true,
28
+ begin: true,
29
+ catch: true,
30
+ const: true,
31
+ do: true,
32
+ else: true,
33
+ elseif: true,
34
+ finally: true,
35
+ for: true,
36
+ function: true,
37
+ global: true,
38
+ if: true,
39
+ in: true,
40
+ isa: true,
41
+ let: true,
42
+ local: true,
43
+ macro: true,
44
+ module: true,
45
+ mutable: true,
46
+ quote: true,
47
+ return: true,
48
+ struct: true,
49
+ throw: true,
50
+ try: true,
51
+ where: true,
52
+ while: true,
53
+ };
54
+
55
+ type QuoteKind = "double" | "triple" | "command" | "char";
56
+
57
+ interface QuoteFrame {
58
+ type: "quote";
59
+ kind: QuoteKind;
60
+ escaped: boolean;
61
+ }
62
+
63
+ interface InterpolationFrame {
64
+ type: "interpolation";
65
+ closers: string[];
66
+ canEndExpression: boolean;
67
+ }
68
+
69
+ type LiteralFrame = QuoteFrame | InterpolationFrame;
70
+
71
+ type PrefixToken = "none" | "dot" | "colon" | "at" | "other";
72
+
73
+ function isIdentifierStart(character: string): boolean {
74
+ const code = character.charCodeAt(0);
75
+ return character === "_" || (code >= 65 && code <= 90) || (code >= 97 && code <= 122) || code >= 0x80;
76
+ }
77
+
78
+ function isIdentifierContinue(character: string): boolean {
79
+ const code = character.charCodeAt(0);
80
+ return isIdentifierStart(character) || (code >= 48 && code <= 57) || character === "!" || character === "?";
81
+ }
82
+
83
+ function isHorizontalWhitespace(character: string): boolean {
84
+ return character === " " || character === "\t" || character === "\v" || character === "\f";
85
+ }
86
+
87
+ function isExpressionSeparator(character: string): boolean {
88
+ return (
89
+ character === "=" ||
90
+ character === "," ||
91
+ character === ";" ||
92
+ character === ":" ||
93
+ character === "." ||
94
+ character === "@" ||
95
+ character === "+" ||
96
+ character === "-" ||
97
+ character === "*" ||
98
+ character === "/" ||
99
+ character === "\\" ||
100
+ character === "%" ||
101
+ character === "^" ||
102
+ character === "&" ||
103
+ character === "|" ||
104
+ character === "<" ||
105
+ character === ">" ||
106
+ character === "~"
107
+ );
108
+ }
109
+
110
+ function consumeLineComment(source: string, start: number): number {
111
+ let index = start + 1;
112
+ while (index < source.length && source[index] !== "\n" && source[index] !== "\r") index++;
113
+ return index;
114
+ }
115
+
116
+ function consumeBlockComment(source: string, start: number): number {
117
+ let depth = 1;
118
+ let index = start + 2;
119
+
120
+ while (index < source.length) {
121
+ if (source[index] === "#" && source[index + 1] === "=") {
122
+ depth++;
123
+ index += 2;
124
+ continue;
125
+ }
126
+ if (source[index] === "=" && source[index + 1] === "#") {
127
+ depth--;
128
+ index += 2;
129
+ if (depth === 0) return index;
130
+ continue;
131
+ }
132
+ index++;
133
+ }
134
+
135
+ return source.length;
136
+ }
137
+
138
+ function quoteWidth(kind: QuoteKind): number {
139
+ return kind === "triple" ? 3 : 1;
140
+ }
141
+
142
+ function quoteCloses(source: string, index: number, kind: QuoteKind): boolean {
143
+ if (kind === "triple") {
144
+ return source[index] === '"' && source[index + 1] === '"' && source[index + 2] === '"';
145
+ }
146
+ if (kind === "double") return source[index] === '"';
147
+ if (kind === "command") return source[index] === "`";
148
+ return source[index] === "'";
149
+ }
150
+
151
+ function pushQuote(frames: LiteralFrame[], kind: QuoteKind): void {
152
+ frames.push({ type: "quote", kind, escaped: false });
153
+ }
154
+
155
+ /**
156
+ * Finds the end of a quoted literal while treating interpolation as opaque code.
157
+ * The small lexer is deliberately independent from display layout: its only job
158
+ * is to keep separators and block words inside a literal out of the formatter.
159
+ */
160
+ function consumeQuotedLiteral(source: string, start: number, kind: QuoteKind): number {
161
+ const frames: LiteralFrame[] = [];
162
+ pushQuote(frames, kind);
163
+ let index = start + quoteWidth(kind);
164
+
165
+ while (index < source.length) {
166
+ const frame = frames.at(-1);
167
+ if (!frame) return index;
168
+
169
+ if (frame.type === "quote") {
170
+ const character = source[index];
171
+ if (frame.escaped) {
172
+ frame.escaped = false;
173
+ index++;
174
+ continue;
175
+ }
176
+ if (character === "\\") {
177
+ frame.escaped = true;
178
+ index++;
179
+ continue;
180
+ }
181
+ if (frame.kind !== "char" && character === "$" && source[index + 1] === "(") {
182
+ frames.push({ type: "interpolation", closers: [")"], canEndExpression: false });
183
+ index += 2;
184
+ continue;
185
+ }
186
+ if (quoteCloses(source, index, frame.kind)) {
187
+ index += quoteWidth(frame.kind);
188
+ frames.pop();
189
+ const parent = frames.at(-1);
190
+ if (!parent) return index;
191
+ if (parent.type === "interpolation") parent.canEndExpression = true;
192
+ continue;
193
+ }
194
+ index++;
195
+ continue;
196
+ }
197
+
198
+ const character = source[index];
199
+ if (character === "#") {
200
+ index = source[index + 1] === "=" ? consumeBlockComment(source, index) : consumeLineComment(source, index);
201
+ continue;
202
+ }
203
+ if (character === '"') {
204
+ const nestedKind = source.startsWith('"""', index) ? "triple" : "double";
205
+ pushQuote(frames, nestedKind);
206
+ index += quoteWidth(nestedKind);
207
+ continue;
208
+ }
209
+ if (character === "`") {
210
+ pushQuote(frames, "command");
211
+ index++;
212
+ continue;
213
+ }
214
+ if (character === "'") {
215
+ if (frame.canEndExpression) {
216
+ index++;
217
+ continue;
218
+ }
219
+ pushQuote(frames, "char");
220
+ index++;
221
+ continue;
222
+ }
223
+ if (isIdentifierStart(character)) {
224
+ const wordStart = index;
225
+ index++;
226
+ while (index < source.length && isIdentifierContinue(source[index])) index++;
227
+ frame.canEndExpression = EXPRESSION_PREFIX_WORDS[source.slice(wordStart, index)] !== true;
228
+ continue;
229
+ }
230
+ if (character === "(" || character === "[" || character === "{") {
231
+ frame.closers.push(character === "(" ? ")" : character === "[" ? "]" : "}");
232
+ frame.canEndExpression = false;
233
+ index++;
234
+ continue;
235
+ }
236
+ if (character === ")" || character === "]" || character === "}") {
237
+ if (frame.closers.at(-1) === character) {
238
+ frame.closers.pop();
239
+ index++;
240
+ if (frame.closers.length === 0) frames.pop();
241
+ else frame.canEndExpression = true;
242
+ continue;
243
+ }
244
+ frame.canEndExpression = true;
245
+ index++;
246
+ continue;
247
+ }
248
+ if (character === "\n" || character === "\r" || isExpressionSeparator(character)) {
249
+ frame.canEndExpression = false;
250
+ index++;
251
+ continue;
252
+ }
253
+ if (!isHorizontalWhitespace(character)) frame.canEndExpression = true;
254
+ index++;
255
+ }
256
+
257
+ return source.length;
258
+ }
259
+
260
+ /** Formats an arbitrary Julia source prefix for stable, readable display. */
261
+ export function formatJuliaForDisplay(source: string): string {
262
+ const output: string[] = [];
263
+ const delimiterClosers: string[] = [];
264
+ let index = 0;
265
+ let blockDepth = 0;
266
+ let atLineStart = true;
267
+ let pendingWhitespace = "";
268
+ let suppressSourceNewline = false;
269
+ let canEndExpression = false;
270
+ let previousToken: PrefixToken = "none";
271
+
272
+ function beginContent(dedentBlock: boolean, dedentDelimiter: boolean): void {
273
+ if (atLineStart) {
274
+ pendingWhitespace = "";
275
+ const indentation = Math.max(
276
+ 0,
277
+ blockDepth - (dedentBlock ? 1 : 0) + delimiterClosers.length - (dedentDelimiter ? 1 : 0),
278
+ );
279
+ if (indentation > 0) output.push(INDENT.repeat(indentation));
280
+ atLineStart = false;
281
+ } else if (pendingWhitespace.length > 0) {
282
+ output.push(pendingWhitespace);
283
+ pendingWhitespace = "";
284
+ }
285
+ suppressSourceNewline = false;
286
+ }
287
+
288
+ function appendOpaque(start: number, end: number): boolean {
289
+ output.push(source.slice(start, end));
290
+ let containsNewline = false;
291
+ for (let cursor = start; cursor < end; cursor++) {
292
+ if (source[cursor] === "\n" || source[cursor] === "\r") {
293
+ containsNewline = true;
294
+ atLineStart = true;
295
+ } else {
296
+ atLineStart = false;
297
+ }
298
+ }
299
+ return containsNewline;
300
+ }
301
+
302
+ while (index < source.length) {
303
+ const character = source[index];
304
+
305
+ if (isHorizontalWhitespace(character)) {
306
+ const whitespaceStart = index;
307
+ index++;
308
+ while (index < source.length && isHorizontalWhitespace(source[index])) index++;
309
+ if (!atLineStart) pendingWhitespace = source.slice(whitespaceStart, index);
310
+ continue;
311
+ }
312
+
313
+ if (character === "\n" || character === "\r") {
314
+ pendingWhitespace = "";
315
+ const newlineWidth = character === "\r" && source[index + 1] === "\n" ? 2 : 1;
316
+ index += newlineWidth;
317
+ if (suppressSourceNewline && atLineStart) {
318
+ suppressSourceNewline = false;
319
+ continue;
320
+ }
321
+ output.push("\n");
322
+ atLineStart = true;
323
+ suppressSourceNewline = false;
324
+ canEndExpression = false;
325
+ previousToken = "none";
326
+ continue;
327
+ }
328
+
329
+ if (character === "#") {
330
+ beginContent(false, false);
331
+ const commentEnd =
332
+ source[index + 1] === "=" ? consumeBlockComment(source, index) : consumeLineComment(source, index);
333
+ const containsNewline = appendOpaque(index, commentEnd);
334
+ if (containsNewline) {
335
+ canEndExpression = false;
336
+ previousToken = "none";
337
+ }
338
+ index = commentEnd;
339
+ continue;
340
+ }
341
+
342
+ if (character === '"') {
343
+ beginContent(false, false);
344
+ const literalKind = source.startsWith('"""', index) ? "triple" : "double";
345
+ const literalEnd = consumeQuotedLiteral(source, index, literalKind);
346
+ appendOpaque(index, literalEnd);
347
+ index = literalEnd;
348
+ canEndExpression = true;
349
+ previousToken = "other";
350
+ continue;
351
+ }
352
+
353
+ if (character === "`") {
354
+ beginContent(false, false);
355
+ const literalEnd = consumeQuotedLiteral(source, index, "command");
356
+ appendOpaque(index, literalEnd);
357
+ index = literalEnd;
358
+ canEndExpression = true;
359
+ previousToken = "other";
360
+ continue;
361
+ }
362
+
363
+ if (character === "'") {
364
+ beginContent(false, false);
365
+ if (canEndExpression) {
366
+ output.push(character);
367
+ index++;
368
+ previousToken = "other";
369
+ continue;
370
+ }
371
+ const literalEnd = consumeQuotedLiteral(source, index, "char");
372
+ appendOpaque(index, literalEnd);
373
+ index = literalEnd;
374
+ canEndExpression = true;
375
+ previousToken = "other";
376
+ continue;
377
+ }
378
+
379
+ if (isIdentifierStart(character)) {
380
+ const wordStart = index;
381
+ index++;
382
+ while (index < source.length && isIdentifierContinue(source[index])) index++;
383
+ const word = source.slice(wordStart, index);
384
+ const isStructural =
385
+ delimiterClosers.length === 0 &&
386
+ previousToken !== "dot" &&
387
+ previousToken !== "colon" &&
388
+ previousToken !== "at";
389
+ const isEnd = isStructural && word === "end";
390
+ const isBranch = isStructural && BRANCH_CLAUSES[word] === true;
391
+ beginContent(isEnd || isBranch, false);
392
+ output.push(word);
393
+
394
+ if (isEnd) blockDepth = Math.max(0, blockDepth - 1);
395
+ else if (isStructural && BLOCK_OPENERS[word] === true) blockDepth++;
396
+
397
+ canEndExpression = EXPRESSION_PREFIX_WORDS[word] !== true;
398
+ previousToken = "other";
399
+ continue;
400
+ }
401
+
402
+ if (character === "(" || character === "[" || character === "{") {
403
+ beginContent(false, false);
404
+ output.push(character);
405
+ delimiterClosers.push(character === "(" ? ")" : character === "[" ? "]" : "}");
406
+ canEndExpression = false;
407
+ previousToken = "other";
408
+ index++;
409
+ continue;
410
+ }
411
+
412
+ if (character === ")" || character === "]" || character === "}") {
413
+ const matchesDelimiter = delimiterClosers.at(-1) === character;
414
+ beginContent(false, matchesDelimiter);
415
+ output.push(character);
416
+ if (matchesDelimiter) delimiterClosers.pop();
417
+ canEndExpression = true;
418
+ previousToken = "other";
419
+ index++;
420
+ continue;
421
+ }
422
+
423
+ if (character === ";" && delimiterClosers.length === 0) {
424
+ beginContent(false, false);
425
+ output.push(";\n");
426
+ atLineStart = true;
427
+ pendingWhitespace = "";
428
+ suppressSourceNewline = true;
429
+ canEndExpression = false;
430
+ previousToken = "none";
431
+ index++;
432
+ continue;
433
+ }
434
+
435
+ beginContent(false, false);
436
+ output.push(character);
437
+ if (character === ".") previousToken = "dot";
438
+ else if (character === ":") previousToken = "colon";
439
+ else if (character === "@") previousToken = "at";
440
+ else previousToken = "other";
441
+ canEndExpression = !isExpressionSeparator(character);
442
+ index++;
443
+ }
444
+
445
+ return output.join("");
446
+ }