@gaunt-sloth/core 2.0.0-alpha.3 → 2.0.0-alpha.31

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 (271) hide show
  1. package/README.md +52 -20
  2. package/dist/config/colour.d.ts +38 -0
  3. package/dist/config/colour.js +36 -0
  4. package/dist/config/colour.js.map +1 -0
  5. package/dist/config/defaults.d.ts +84 -0
  6. package/dist/config/defaults.js +99 -0
  7. package/dist/config/defaults.js.map +1 -0
  8. package/dist/config/filesystem-tools.d.ts +41 -0
  9. package/dist/config/filesystem-tools.js +56 -0
  10. package/dist/config/filesystem-tools.js.map +1 -0
  11. package/dist/config/jsonc.d.ts +12 -0
  12. package/dist/config/jsonc.js +41 -0
  13. package/dist/config/jsonc.js.map +1 -0
  14. package/dist/config/loader.d.ts +246 -0
  15. package/dist/config/loader.js +1321 -0
  16. package/dist/config/loader.js.map +1 -0
  17. package/dist/config/mouse.d.ts +50 -0
  18. package/dist/config/mouse.js +44 -0
  19. package/dist/config/mouse.js.map +1 -0
  20. package/dist/config/profiles.d.ts +68 -0
  21. package/dist/config/profiles.js +93 -0
  22. package/dist/config/profiles.js.map +1 -0
  23. package/dist/config/providerKeys.d.ts +69 -0
  24. package/dist/config/providerKeys.js +69 -0
  25. package/dist/config/providerKeys.js.map +1 -0
  26. package/dist/config/schema.d.ts +2999 -0
  27. package/dist/config/schema.js +1519 -0
  28. package/dist/config/schema.js.map +1 -0
  29. package/dist/config/shell-policy.d.ts +896 -0
  30. package/dist/config/shell-policy.js +750 -0
  31. package/dist/config/shell-policy.js.map +1 -0
  32. package/dist/config/tool-descriptions.d.ts +211 -0
  33. package/dist/config/tool-descriptions.js +272 -0
  34. package/dist/config/tool-descriptions.js.map +1 -0
  35. package/dist/config/types.d.ts +760 -0
  36. package/dist/config/types.js +13 -0
  37. package/dist/config/types.js.map +1 -0
  38. package/dist/config.d.ts +34 -827
  39. package/dist/config.js +25 -657
  40. package/dist/config.js.map +1 -1
  41. package/dist/constants.d.ts +46 -0
  42. package/dist/constants.js +46 -0
  43. package/dist/constants.js.map +1 -1
  44. package/dist/core/GthAbstractAgent.d.ts +135 -2
  45. package/dist/core/GthAbstractAgent.js +496 -30
  46. package/dist/core/GthAbstractAgent.js.map +1 -1
  47. package/dist/core/GthAgentRunner.d.ts +568 -54
  48. package/dist/core/GthAgentRunner.js +1537 -136
  49. package/dist/core/GthAgentRunner.js.map +1 -1
  50. package/dist/core/GthLangChainAgent.d.ts +115 -0
  51. package/dist/core/GthLangChainAgent.js +644 -17
  52. package/dist/core/GthLangChainAgent.js.map +1 -1
  53. package/dist/core/approvals/annotations.d.ts +122 -0
  54. package/dist/core/approvals/annotations.js +137 -0
  55. package/dist/core/approvals/annotations.js.map +1 -0
  56. package/dist/core/approvals/grants.d.ts +216 -0
  57. package/dist/core/approvals/grants.js +469 -0
  58. package/dist/core/approvals/grants.js.map +1 -0
  59. package/dist/core/approvals/matcher.d.ts +202 -0
  60. package/dist/core/approvals/matcher.js +267 -0
  61. package/dist/core/approvals/matcher.js.map +1 -0
  62. package/dist/core/approvals/mcpSubjects.d.ts +40 -0
  63. package/dist/core/approvals/mcpSubjects.js +99 -0
  64. package/dist/core/approvals/mcpSubjects.js.map +1 -0
  65. package/dist/core/approvals/toolAnnotationSources.d.ts +105 -0
  66. package/dist/core/approvals/toolAnnotationSources.js +277 -0
  67. package/dist/core/approvals/toolAnnotationSources.js.map +1 -0
  68. package/dist/core/approvals/toolHost.d.ts +46 -0
  69. package/dist/core/approvals/toolHost.js +108 -0
  70. package/dist/core/approvals/toolHost.js.map +1 -0
  71. package/dist/core/debugCapture.d.ts +75 -0
  72. package/dist/core/debugCapture.js +101 -0
  73. package/dist/core/debugCapture.js.map +1 -0
  74. package/dist/core/gthLeanAgentFactory.d.ts +9 -0
  75. package/dist/core/gthLeanAgentFactory.js +10 -0
  76. package/dist/core/gthLeanAgentFactory.js.map +1 -0
  77. package/dist/core/launchBanner.d.ts +120 -0
  78. package/dist/core/launchBanner.js +418 -0
  79. package/dist/core/launchBanner.js.map +1 -0
  80. package/dist/core/plainToolIndication.d.ts +15 -0
  81. package/dist/core/plainToolIndication.js +174 -0
  82. package/dist/core/plainToolIndication.js.map +1 -0
  83. package/dist/core/reasoningBlocks.d.ts +60 -0
  84. package/dist/core/reasoningBlocks.js +98 -0
  85. package/dist/core/reasoningBlocks.js.map +1 -0
  86. package/dist/core/refusal.d.ts +53 -0
  87. package/dist/core/refusal.js +133 -0
  88. package/dist/core/refusal.js.map +1 -0
  89. package/dist/core/runStats.d.ts +52 -0
  90. package/dist/core/runStats.js +118 -0
  91. package/dist/core/runStats.js.map +1 -0
  92. package/dist/core/shell/ShellCommandFailedError.d.ts +54 -0
  93. package/dist/core/shell/ShellCommandFailedError.js +68 -0
  94. package/dist/core/shell/ShellCommandFailedError.js.map +1 -0
  95. package/dist/core/shell/abstention.d.ts +88 -0
  96. package/dist/core/shell/abstention.js +184 -0
  97. package/dist/core/shell/abstention.js.map +1 -0
  98. package/dist/core/shell/approvalCapture.d.ts +271 -0
  99. package/dist/core/shell/approvalCapture.js +108 -0
  100. package/dist/core/shell/approvalCapture.js.map +1 -0
  101. package/dist/core/shell/approvalStop.d.ts +84 -0
  102. package/dist/core/shell/approvalStop.js +119 -0
  103. package/dist/core/shell/approvalStop.js.map +1 -0
  104. package/dist/core/shell/arity.d.ts +6 -0
  105. package/dist/core/shell/arity.js +20 -6
  106. package/dist/core/shell/arity.js.map +1 -1
  107. package/dist/core/shell/denylist.d.ts +11 -0
  108. package/dist/core/shell/denylist.js +37 -0
  109. package/dist/core/shell/denylist.js.map +1 -0
  110. package/dist/core/shell/escalationSeverity.d.ts +141 -0
  111. package/dist/core/shell/escalationSeverity.js +89 -0
  112. package/dist/core/shell/escalationSeverity.js.map +1 -0
  113. package/dist/core/shell/framing.d.ts +190 -0
  114. package/dist/core/shell/framing.js +633 -0
  115. package/dist/core/shell/framing.js.map +1 -0
  116. package/dist/core/shell/hardline.d.ts +81 -0
  117. package/dist/core/shell/hardline.js +607 -0
  118. package/dist/core/shell/hardline.js.map +1 -0
  119. package/dist/core/shell/negotiation.d.ts +249 -0
  120. package/dist/core/shell/negotiation.js +355 -0
  121. package/dist/core/shell/negotiation.js.map +1 -0
  122. package/dist/core/shell/normalize.d.ts +44 -4
  123. package/dist/core/shell/normalize.js +61 -7
  124. package/dist/core/shell/normalize.js.map +1 -1
  125. package/dist/core/shell/openWorld.d.ts +263 -0
  126. package/dist/core/shell/openWorld.js +1188 -0
  127. package/dist/core/shell/openWorld.js.map +1 -0
  128. package/dist/core/shell/rater.d.ts +935 -0
  129. package/dist/core/shell/rater.js +1473 -0
  130. package/dist/core/shell/rater.js.map +1 -0
  131. package/dist/core/shell/raterModel.d.ts +41 -0
  132. package/dist/core/shell/raterModel.js +51 -0
  133. package/dist/core/shell/raterModel.js.map +1 -0
  134. package/dist/core/shell/rejection.d.ts +69 -0
  135. package/dist/core/shell/rejection.js +38 -0
  136. package/dist/core/shell/rejection.js.map +1 -0
  137. package/dist/core/toolCallRepair/grammar.d.ts +41 -0
  138. package/dist/core/toolCallRepair/grammar.js +116 -0
  139. package/dist/core/toolCallRepair/grammar.js.map +1 -0
  140. package/dist/core/toolCallRepair/index.d.ts +2 -0
  141. package/dist/core/toolCallRepair/index.js +7 -0
  142. package/dist/core/toolCallRepair/index.js.map +1 -0
  143. package/dist/core/toolCallRepair/payload.d.ts +36 -0
  144. package/dist/core/toolCallRepair/payload.js +341 -0
  145. package/dist/core/toolCallRepair/payload.js.map +1 -0
  146. package/dist/core/toolCallRepair/promote.d.ts +45 -0
  147. package/dist/core/toolCallRepair/promote.js +90 -0
  148. package/dist/core/toolCallRepair/promote.js.map +1 -0
  149. package/dist/core/toolDisplay.d.ts +123 -0
  150. package/dist/core/toolDisplay.js +451 -0
  151. package/dist/core/toolDisplay.js.map +1 -0
  152. package/dist/core/toolOutputChannel.d.ts +95 -0
  153. package/dist/core/toolOutputChannel.js +165 -0
  154. package/dist/core/toolOutputChannel.js.map +1 -0
  155. package/dist/core/types.d.ts +294 -12
  156. package/dist/core/types.js.map +1 -1
  157. package/dist/history/historyFormat.d.ts +28 -0
  158. package/dist/history/historyFormat.js +127 -0
  159. package/dist/history/historyFormat.js.map +1 -0
  160. package/dist/history/historyStore.d.ts +198 -0
  161. package/dist/history/historyStore.js +482 -0
  162. package/dist/history/historyStore.js.map +1 -0
  163. package/dist/history/recordSession.d.ts +37 -0
  164. package/dist/history/recordSession.js +56 -0
  165. package/dist/history/recordSession.js.map +1 -0
  166. package/dist/index.d.ts +4 -0
  167. package/dist/index.js +4 -0
  168. package/dist/index.js.map +1 -1
  169. package/dist/providers/anthropic.d.ts +1 -1
  170. package/dist/providers/anthropic.js +5 -10
  171. package/dist/providers/anthropic.js.map +1 -1
  172. package/dist/providers/deepseek.d.ts +1 -1
  173. package/dist/providers/deepseek.js +5 -10
  174. package/dist/providers/deepseek.js.map +1 -1
  175. package/dist/providers/geminiSchemaSanitizer.d.ts +55 -0
  176. package/dist/providers/geminiSchemaSanitizer.js +347 -0
  177. package/dist/providers/geminiSchemaSanitizer.js.map +1 -0
  178. package/dist/providers/geminiThinking.d.ts +52 -0
  179. package/dist/providers/geminiThinking.js +72 -0
  180. package/dist/providers/geminiThinking.js.map +1 -0
  181. package/dist/providers/google-genai.d.ts +1 -1
  182. package/dist/providers/google-genai.js +12 -11
  183. package/dist/providers/google-genai.js.map +1 -1
  184. package/dist/providers/groq.d.ts +1 -1
  185. package/dist/providers/groq.js +5 -10
  186. package/dist/providers/groq.js.map +1 -1
  187. package/dist/providers/huggingface.d.ts +25 -0
  188. package/dist/providers/huggingface.js +69 -0
  189. package/dist/providers/huggingface.js.map +1 -0
  190. package/dist/providers/modelCatalog.d.ts +109 -0
  191. package/dist/providers/modelCatalog.js +245 -0
  192. package/dist/providers/modelCatalog.js.map +1 -0
  193. package/dist/providers/modelDiscovery.d.ts +153 -6
  194. package/dist/providers/modelDiscovery.js +295 -41
  195. package/dist/providers/modelDiscovery.js.map +1 -1
  196. package/dist/providers/ollama.d.ts +19 -5
  197. package/dist/providers/ollama.js +60 -52
  198. package/dist/providers/ollama.js.map +1 -1
  199. package/dist/providers/openai.d.ts +1 -1
  200. package/dist/providers/openai.js +39 -10
  201. package/dist/providers/openai.js.map +1 -1
  202. package/dist/providers/openrouter.d.ts +4 -5
  203. package/dist/providers/openrouter.js +20 -35
  204. package/dist/providers/openrouter.js.map +1 -1
  205. package/dist/providers/vertexai.d.ts +1 -1
  206. package/dist/providers/vertexai.js +12 -11
  207. package/dist/providers/vertexai.js.map +1 -1
  208. package/dist/providers/xai.d.ts +1 -1
  209. package/dist/providers/xai.js +5 -10
  210. package/dist/providers/xai.js.map +1 -1
  211. package/dist/runtime/askStructured.d.ts +105 -0
  212. package/dist/runtime/askStructured.js +120 -0
  213. package/dist/runtime/askStructured.js.map +1 -0
  214. package/dist/runtime/conversation.d.ts +60 -0
  215. package/dist/runtime/conversation.js +151 -0
  216. package/dist/runtime/conversation.js.map +1 -0
  217. package/dist/runtime/singleShot.d.ts +25 -5
  218. package/dist/runtime/singleShot.js +95 -36
  219. package/dist/runtime/singleShot.js.map +1 -1
  220. package/dist/runtime/structuredOutput.d.ts +104 -0
  221. package/dist/runtime/structuredOutput.js +393 -0
  222. package/dist/runtime/structuredOutput.js.map +1 -0
  223. package/dist/utils/ProgressIndicator.d.ts +21 -0
  224. package/dist/utils/ProgressIndicator.js +30 -3
  225. package/dist/utils/ProgressIndicator.js.map +1 -1
  226. package/dist/utils/aiignoreUtils.js.map +1 -1
  227. package/dist/utils/binaryOutputUtils.js.map +1 -1
  228. package/dist/utils/consoleUtils.d.ts +52 -0
  229. package/dist/utils/consoleUtils.js +72 -2
  230. package/dist/utils/consoleUtils.js.map +1 -1
  231. package/dist/utils/crashHandler.d.ts +87 -0
  232. package/dist/utils/crashHandler.js +128 -0
  233. package/dist/utils/crashHandler.js.map +1 -0
  234. package/dist/utils/debugDump.d.ts +134 -0
  235. package/dist/utils/debugDump.js +381 -0
  236. package/dist/utils/debugDump.js.map +1 -0
  237. package/dist/utils/debugUtils.d.ts +13 -4
  238. package/dist/utils/debugUtils.js +36 -13
  239. package/dist/utils/debugUtils.js.map +1 -1
  240. package/dist/utils/displayWidth.d.ts +53 -0
  241. package/dist/utils/displayWidth.js +195 -0
  242. package/dist/utils/displayWidth.js.map +1 -0
  243. package/dist/utils/fileUtils.d.ts +24 -2
  244. package/dist/utils/fileUtils.js +54 -12
  245. package/dist/utils/fileUtils.js.map +1 -1
  246. package/dist/utils/llmUtils.d.ts +39 -8
  247. package/dist/utils/llmUtils.js +76 -8
  248. package/dist/utils/llmUtils.js.map +1 -1
  249. package/dist/utils/redactSecrets.d.ts +63 -0
  250. package/dist/utils/redactSecrets.js +286 -0
  251. package/dist/utils/redactSecrets.js.map +1 -0
  252. package/dist/utils/systemPromptNotes.d.ts +225 -0
  253. package/dist/utils/systemPromptNotes.js +340 -0
  254. package/dist/utils/systemPromptNotes.js.map +1 -0
  255. package/dist/utils/systemUtils.d.ts +49 -1
  256. package/dist/utils/systemUtils.js +67 -3
  257. package/dist/utils/systemUtils.js.map +1 -1
  258. package/dist/utils/toolMatching.d.ts +30 -0
  259. package/dist/utils/toolMatching.js +44 -0
  260. package/dist/utils/toolMatching.js.map +1 -0
  261. package/dist/utils/untrustedText.d.ts +66 -0
  262. package/dist/utils/untrustedText.js +80 -0
  263. package/dist/utils/untrustedText.js.map +1 -0
  264. package/package.json +22 -6
  265. package/schema/gsloth-config.schema.json +3134 -0
  266. package/dist/core/shell/allowlist.d.ts +0 -75
  267. package/dist/core/shell/allowlist.js +0 -187
  268. package/dist/core/shell/allowlist.js.map +0 -1
  269. package/dist/core/shell/judge.d.ts +0 -161
  270. package/dist/core/shell/judge.js +0 -261
  271. package/dist/core/shell/judge.js.map +0 -1
@@ -0,0 +1,418 @@
1
+ /**
2
+ * @module launchBanner
3
+ * TUI-C33 — the welcoming ASCII-art launch banner shown at the top of an INTERACTIVE session
4
+ * (`gth chat` / `gth code`): a sloth face on the left, the `GAUNT SLOTH` wordmark plus three live
5
+ * fields (version, model/provider, working directory) on the right.
6
+ *
7
+ *
8
+ * ▄█▀▀▀▀▀▀▀▀█▄ ┏┓ ┏┓┓ ┓
9
+ * ▄▀█▄█▀▀▀▀▀▀█▄█▀▄ ┃┓┏┓┓┏┏┓╋ ┗┓┃┏┓╋┣┓ v2.0.0-alpha.25
10
+ * █ ▀█▄▀ ▀▄█▀ █ ┗┛┗┻┗┻┛┗┗ ┗┛┗┗┛┗┛┗
11
+ * ▀▄▀▀ ██████ ▀▀▄▀ gemini-3.1-pro (google-genai)
12
+ * ▀██████████▀ ~/dev/takahe
13
+ *
14
+ * It is deliberately NOT emitted by the one-shot verbs (`ask`, `review`, `pr`, `get`, `eval`,
15
+ * `batch`, `api`, `init`): their stdout is piped into files, diffs and CI, where a banner is
16
+ * corruption rather than welcome.
17
+ *
18
+ * ## Why this module is pure, and split in two halves
19
+ *
20
+ * The banner has to render IDENTICALLY on two surfaces that share no rendering code — the Ink TUI
21
+ * (`packages/app/src/tui/components/LaunchBanner.tsx`) and the plain `--no-tui` readline session
22
+ * (`packages/agent/src/modules/interactiveSessionModule.ts`). So all the geometry lives here as
23
+ * pure functions of `{ version, model, provider, directory, homeDir, columns, colour }`
24
+ * ({@link launchBannerRows} / {@link launchBannerText}), and the surfaces differ only in how they
25
+ * EMIT: one string through `displayLaunchBanner`, or one `<Text>` per row. That is the same split
26
+ * `Rule.tsx` uses (pure `ruleWidth` + a thin React component), including its
27
+ * `columns === undefined → 80` convention for a non-TTY / test stdout.
28
+ *
29
+ * The one impure function is {@link launchBannerFields} at the bottom, which reads the live
30
+ * version / project dir / home dir. It is shared for the same reason: both surfaces must read the
31
+ * same sources rather than each inventing its own.
32
+ *
33
+ * ## Why the right column is a slice at a fixed column, not a glyph classifier
34
+ *
35
+ * Colouring is "columns 0–20 are magenta, columns 21+ are default foreground" — a per-line split at
36
+ * {@link RIGHT_COLUMN}. It is emphatically NOT "is this character a block element": the wordmark is
37
+ * box-drawing, the face is block elements, and the moment a field value contained a `▀` (or the art
38
+ * gained a non-block glyph) a classifier would mis-colour it. A fixed column cannot.
39
+ *
40
+ * The face is 16 columns wide and the right column starts at {@link RIGHT_COLUMN}, leaving a
41
+ * 5-column gutter. The version label sits three columns after the widest wordmark line, i.e. at
42
+ * {@link VERSION_COLUMN}.
43
+ *
44
+ * ## Why the padding is part of the layout rather than of either surface
45
+ *
46
+ * TUI-C36 — the block is a splash, so it gets breathing room: one blank row above, one blank row
47
+ * below, and a {@link LEFT_PAD}-column left margin on every art row. That padding lives HERE, with
48
+ * the rest of the geometry, for the same reason the columns do — the Ink surface and the readline
49
+ * surface must show the same block, and a margin added by one renderer is a margin the other one
50
+ * lacks. The blank rows are genuinely empty (no pad, no escape): a padding row that carried
51
+ * trailing spaces would be visible the moment anything selected or copied it.
52
+ *
53
+ * The pad column shifts every column constant by exactly one, which is why they are all derived
54
+ * from {@link LEFT_PAD} rather than written as literals. The field budgets are expressed against
55
+ * those constants (`columns - RIGHT_COLUMN`), so they absorb the shift without changing.
56
+ *
57
+ * ## Why every dynamic field is bounded, and why the version is the one that is DROPPED
58
+ *
59
+ * A wrapped line SHATTERS the art: the continuation starts back at column 0 and collides with the
60
+ * face, so the sloth grows an extra row of model id. Wrapping is therefore not allowed to happen at
61
+ * all, and each field is bounded by what is left of the terminal on its own line — `columns - 21`
62
+ * for the model/provider and directory lines, `columns - 43` for the version.
63
+ *
64
+ * Those budgets are TERMINAL COLUMNS, and every measurement and cut in this module goes through
65
+ * `#src/utils/displayWidth.js` for that reason. A CJK ideograph or an emoji is one code point in
66
+ * two columns, so a `.length`-style count would let a path or model id containing either measure
67
+ * short, slip past its budget and wrap — the very failure above.
68
+ *
69
+ * The model/provider and directory lines TRUNCATE to that budget: a clipped model id or path is
70
+ * still honest, it visibly ends in `…`. The directory is truncated from the LEFT
71
+ * (`…/dev/takahe`), because the leaf directory is the informative end; the model keeps its head and
72
+ * loses its tail.
73
+ *
74
+ * A VERSION that does not fit is dropped entirely instead ({@link fitOrDrop}) — never truncated.
75
+ * A clipped version number is not merely less informative, it is MISLEADING: `v2.0…` reads as a
76
+ * real, different version (2.0.1, say) rather than as a clipped `2.0.0-alpha.25`, and a wrong
77
+ * version in a bug report costs more than an absent one. The model/provider line already sets that
78
+ * drop-rather-than-mislead precedent by omitting itself when nothing resolves.
79
+ *
80
+ * Below {@link MIN_BANNER_COLUMNS} the right column cannot fit at all, so the face prints alone
81
+ * rather than wrapping the wordmark into rubble.
82
+ */
83
+ import { homedir } from 'node:os';
84
+ import { ELLIPSIS, ELLIPSIS_WIDTH } from '#src/core/toolDisplay.js';
85
+ import { ANSI_COLORS } from '#src/utils/consoleUtils.js';
86
+ import { displayWidth, sliceEndToWidth, sliceToWidth } from '#src/utils/displayWidth.js';
87
+ import { getProjectDir, getSlothVersion } from '#src/utils/systemUtils.js';
88
+ /**
89
+ * The sloth face, columns 0–15. Lines 1 and 5 are 14 columns, lines 2–4 are 16 — the face field is
90
+ * 16 wide and the short lines simply do not reach the edge. Do not "tidy" the leading spaces: they
91
+ * are part of the art.
92
+ */
93
+ const FACE_LINES = [
94
+ ' ▄█▀▀▀▀▀▀▀▀█▄',
95
+ '▄▀█▄█▀▀▀▀▀▀█▄█▀▄',
96
+ '█ ▀█▄▀ ▀▄█▀ █',
97
+ '▀▄▀▀ ██████ ▀▀▄▀',
98
+ ' ▀██████████▀',
99
+ ];
100
+ /**
101
+ * TUI-C40 — the resting face, under the name the animation layer refers to it by.
102
+ *
103
+ * Every frame below is the same five lines and the same 16-column field, so swapping one in changes
104
+ * nothing about the layout: the right column still starts at {@link RIGHT_COLUMN} and the
105
+ * truncation budgets are untouched. That invariant is what makes an animation a data change rather
106
+ * than a geometry change, and it is asserted per frame in the specs.
107
+ */
108
+ export const RESTING_FACE = FACE_LINES;
109
+ /**
110
+ * The extra faces, authored in takahē (`websites/ascii-art.md`) and transcribed here so the art has
111
+ * one home in this repo, exactly as the resting frame does. Names match the source file's blocks.
112
+ */
113
+ const BLINK_HALF = [
114
+ ' ▄█▀▀▀▀▀▀▀██▄',
115
+ '▄▀█▄█▀▀▀▀▀▀███▀▄',
116
+ '█ ▀█▄▀ ▀▄█▀ █',
117
+ '▀▄▀▀▀▐████▌▀▀▀▄▀',
118
+ ' ▀██████████▀',
119
+ ];
120
+ const BLINK_SHUT = [
121
+ ' ▄█▀▀▀▀▀▀███▄',
122
+ '▄▀█▄█▀▀▀▀▀▀███▀▄',
123
+ '█ ▀█▄▀ ▀▄█▀ █',
124
+ '▀▄▀▀▀▐████▌ ▄▀',
125
+ ' ▀██████████▀',
126
+ ];
127
+ const NOD_DOWN = [
128
+ ' ▄██████████▄',
129
+ '▄██▀▄▄▄▄▄▄▄▄▀██▄',
130
+ '█ ▀██ ▄ ▄ ██▀ █',
131
+ '█ ▄▄▀█▄▄▄▄█▀▄▄ █',
132
+ ' ▄▄▄██████▄▄▄',
133
+ ];
134
+ const LOOK_LEFT = [
135
+ ' ▄██████████▄',
136
+ '▄██▀▄▄▄▄▄▄▄████▄',
137
+ '█ ▀██ ▄ ▄ ██▀ █',
138
+ '█ ▄▀█▄▄▄▄█▀ ▄ █',
139
+ ' ▄▄▄██████▄▄▄',
140
+ ];
141
+ const LOOK_RIGHT = [
142
+ ' ▄██████████▄',
143
+ '▄████▄▄▄▄▄▄▄▀██▄',
144
+ '█ ▀██ ▄ ▄ ██▀ █',
145
+ '█ ▄ ▀█▄▄▄▄█▀▄ █',
146
+ ' ▄▄▄██████▄▄▄',
147
+ ];
148
+ const EYEROLL = [
149
+ ' ▄█▀▀▀▀▀▀▀▀█▄',
150
+ '▄▀█▄█▀▀▀▀▀▀█▄█▀▄',
151
+ '█ ▀██▄▀ ▀▄██▀ █',
152
+ '▀▌ ▄ ▐████▌ ▄ ▐▀',
153
+ ' ▀██████████▀',
154
+ ];
155
+ /** The animations a click can play. This list is the complete set. */
156
+ export const SLOTH_ANIMATIONS = ['blink', 'nod', 'look-around', 'eyeroll'];
157
+ /**
158
+ * The four sequences, as the frames shown AFTER the resting face.
159
+ *
160
+ * Playback always settles back on {@link RESTING_FACE} once the last step's hold elapses, so no
161
+ * sequence lists a trailing rest — but `nod` lists rest in the MIDDLE, because nodding twice means
162
+ * returning to level between the two dips. None of them loops, and a click plays exactly one.
163
+ */
164
+ export const SLOTH_ANIMATION_STEPS = {
165
+ // Quick, because a blink that lingers reads as a squint.
166
+ blink: [
167
+ { face: BLINK_HALF, holdMs: 90 },
168
+ { face: BLINK_SHUT, holdMs: 110 },
169
+ ],
170
+ nod: [
171
+ { face: NOD_DOWN, holdMs: 160 },
172
+ { face: RESTING_FACE, holdMs: 130 },
173
+ { face: NOD_DOWN, holdMs: 160 },
174
+ ],
175
+ 'look-around': [
176
+ { face: LOOK_LEFT, holdMs: 190 },
177
+ { face: LOOK_RIGHT, holdMs: 230 },
178
+ ],
179
+ // The longest hold: the joke only lands if you get to see it.
180
+ eyeroll: [{ face: EYEROLL, holdMs: 300 }],
181
+ };
182
+ /**
183
+ * Pick one animation at random. `random` is injectable so a test can pin the choice — the
184
+ * production call passes nothing and gets `Math.random`.
185
+ */
186
+ export function pickSlothAnimation(random = Math.random) {
187
+ const index = Math.floor(random() * SLOTH_ANIMATIONS.length);
188
+ // Guard the boundary: a `random()` of exactly 1 (or a stub returning it) would index past the end.
189
+ return SLOTH_ANIMATIONS[Math.min(index, SLOTH_ANIMATIONS.length - 1)];
190
+ }
191
+ /** The `GAUNT SLOTH` wordmark, occupying rows 1–3 of the right column. */
192
+ const WORDMARK_LINES = [
193
+ '┏┓ ┏┓┓ ┓',
194
+ '┃┓┏┓┓┏┏┓╋ ┗┓┃┏┓╋┣┓',
195
+ '┗┛┗┻┗┻┛┗┗ ┗┛┗┗┛┗┛┗',
196
+ ];
197
+ /** Widest wordmark line (19); rows 1 and 3 are padded to it so the version label lands square. */
198
+ const WORDMARK_WIDTH = Math.max(...WORDMARK_LINES.map((line) => displayWidth(line)));
199
+ /** Blank columns between the wordmark and the version label. */
200
+ const VERSION_GAP = 3;
201
+ /**
202
+ * Blank columns prepended to every art row (TUI-C36). One column of left margin, so the block does
203
+ * not sit flush against the edge of the terminal.
204
+ */
205
+ export const LEFT_PAD = 1;
206
+ /** The prepended margin itself, applied to art rows only — never to the blank padding rows. */
207
+ const PAD = ' '.repeat(LEFT_PAD);
208
+ /** Columns the face field occupies, after the margin. */
209
+ const FACE_WIDTH = 16;
210
+ /** Blank columns between the face field and the right column. */
211
+ const FACE_GAP = 5;
212
+ /**
213
+ * Column at which the right-hand column begins on EVERY row — the single number the whole layout
214
+ * and the colour split are expressed in terms of (margin + face + gutter = 22).
215
+ */
216
+ export const RIGHT_COLUMN = LEFT_PAD + FACE_WIDTH + FACE_GAP;
217
+ /** Column at which the version label begins on row 2 (right column + wordmark + gap = 44). */
218
+ export const VERSION_COLUMN = RIGHT_COLUMN + WORDMARK_WIDTH + VERSION_GAP;
219
+ /**
220
+ * Field budget the narrowest banner still affords the right column — the widest wordmark line plus
221
+ * a few columns of field, which is what the original threshold of 45 amounted to at
222
+ * {@link RIGHT_COLUMN} 21.
223
+ */
224
+ const MIN_FIELD_BUDGET = 24;
225
+ /**
226
+ * Narrowest terminal that still gets the right column. The wordmark itself ends at column 40, so
227
+ * anything narrower than this leaves no usable room for the fields beside it; below the threshold
228
+ * the face prints alone (see the module docs on why wrapping is not an option).
229
+ */
230
+ export const MIN_BANNER_COLUMNS = RIGHT_COLUMN + MIN_FIELD_BUDGET;
231
+ /** Width assumed when the terminal width is unknown (non-TTY / tests) — as in `ruleWidth`. */
232
+ const DEFAULT_COLUMNS = 80;
233
+ /**
234
+ * Right-pad `text` to `width` TERMINAL COLUMNS. `String.prototype.padEnd` counts UTF-16 units, so
235
+ * a field holding a wide glyph would be filled one column too far for each of them — and since the
236
+ * split column is the single number this whole layout is expressed in terms of, that shifts the
237
+ * right column and pushes the row past the terminal. Text already at or past the width is returned
238
+ * untouched, exactly as `padEnd` leaves it.
239
+ */
240
+ function padToWidth(text, width) {
241
+ return text + ' '.repeat(Math.max(0, width - displayWidth(text)));
242
+ }
243
+ /** Resolve the usable terminal width, mirroring `ruleWidth`'s unknown/non-finite handling. */
244
+ function resolveColumns(columns) {
245
+ const cols = typeof columns === 'number' && Number.isFinite(columns) ? columns : DEFAULT_COLUMNS;
246
+ return Math.floor(cols);
247
+ }
248
+ /**
249
+ * Truncate `text` to `budget` columns, losing the TAIL (`gemini-3.1-pro-experi…`). Returns
250
+ * `undefined` only when there is no budget at all; that guard keeps the function total (at
251
+ * `budget === 0` the slice below would otherwise return a bare marker) and is not reachable from
252
+ * {@link launchBannerRows}, whose narrowest field budget is `46 - 22 = 24`.
253
+ *
254
+ * `budget` is TERMINAL COLUMNS, and so is every measurement here: measuring and slicing both go
255
+ * through the shared width primitive, which counts a CJK ideograph or an emoji as the two columns
256
+ * it draws and cuts only between whole grapheme clusters. A code-point count would let such a
257
+ * value measure short, escape its budget and wrap into the face; a code-point slice would cut a
258
+ * cluster in half.
259
+ */
260
+ function truncateTail(text, budget) {
261
+ if (budget <= 0)
262
+ return undefined;
263
+ if (displayWidth(text) <= budget)
264
+ return text;
265
+ return sliceToWidth(text, budget - ELLIPSIS_WIDTH) + ELLIPSIS;
266
+ }
267
+ /**
268
+ * Truncate `text` to `budget` columns, losing the HEAD (`…/dev/takahe`). Used for the directory,
269
+ * where the leaf is what the user needs and the ancestors are noise. Columns, not code points —
270
+ * see {@link truncateTail}.
271
+ */
272
+ function truncateHead(text, budget) {
273
+ if (budget <= 0)
274
+ return undefined;
275
+ if (displayWidth(text) <= budget)
276
+ return text;
277
+ return ELLIPSIS + sliceEndToWidth(text, budget - ELLIPSIS_WIDTH);
278
+ }
279
+ /**
280
+ * All-or-nothing: `text` if it fits `budget` columns in full, otherwise `undefined`. Used for the
281
+ * VERSION label, which must never be truncated — see the module docs on why a clipped version
282
+ * number is misleading rather than merely terse. "Fits" is measured in columns, so a wide glyph
283
+ * cannot buy its way in on a short code-point count and push the row past the terminal.
284
+ */
285
+ function fitOrDrop(text, budget) {
286
+ return displayWidth(text) <= budget ? text : undefined;
287
+ }
288
+ /**
289
+ * Collapse the home-directory prefix to `~`, the way a shell prompt does — a deep path spends its
290
+ * whole budget on `/Users/<name>` otherwise.
291
+ *
292
+ * Both separators are accepted deliberately: on Windows `homedir()` reads `%USERPROFILE%` and the
293
+ * path arrives back-slashed, and hard-coding POSIX `/` here is exactly the win32-only bug this repo
294
+ * keeps re-learning. The boundary check matters too — `/home/mar` must not collapse inside
295
+ * `/home/maria/x`.
296
+ */
297
+ function collapseHomePrefix(directory, homeDir) {
298
+ if (!homeDir)
299
+ return directory;
300
+ if (directory === homeDir)
301
+ return '~';
302
+ if (!directory.startsWith(homeDir))
303
+ return directory;
304
+ const boundary = directory.charAt(homeDir.length);
305
+ if (boundary !== '/' && boundary !== '\\')
306
+ return directory;
307
+ return '~' + directory.slice(homeDir.length);
308
+ }
309
+ /**
310
+ * Build the model/provider line: `model (provider)` when both resolve, the one that resolved when
311
+ * only one does, and nothing at all when neither does — an empty `()` would look like a bug.
312
+ */
313
+ function modelProviderLabel(model, provider) {
314
+ const m = model?.trim();
315
+ const p = provider?.trim();
316
+ if (m && p)
317
+ return `${m} (${p})`;
318
+ return m || p || undefined;
319
+ }
320
+ /** A padding row: no margin and no content, so it is an empty line on every surface. */
321
+ function blankRow() {
322
+ return { face: '', right: '' };
323
+ }
324
+ /** Wrap the five art rows in the blank row above and below that make the block a splash. */
325
+ function withPaddingRows(art) {
326
+ return [blankRow(), ...art, blankRow()];
327
+ }
328
+ /**
329
+ * Assemble the banner as {@link LaunchBannerRow}s: a blank row, the five art rows each pre-split at
330
+ * {@link RIGHT_COLUMN}, and a blank row. Pure — every input arrives as an argument, so the geometry
331
+ * is testable without a terminal, a config or a process.
332
+ */
333
+ export function launchBannerRows(input) {
334
+ const columns = resolveColumns(input.columns);
335
+ // TUI-C40 — the face is the one part a caller may swap; everything below is unchanged geometry.
336
+ const face = input.face ?? RESTING_FACE;
337
+ // Too narrow for the right column: the face alone, with no fields. It keeps its left margin and
338
+ // its blank rows — the padding is what makes it a splash, and it costs one column, not the
339
+ // gutter's five.
340
+ if (columns < MIN_BANNER_COLUMNS) {
341
+ return withPaddingRows(face.map((line) => ({ face: PAD + line, right: '' })));
342
+ }
343
+ // Every field is budgeted against ITS OWN start column, because the version starts further right
344
+ // than the other two. The version is the only fit-or-drop field; the other two truncate.
345
+ const fieldBudget = columns - RIGHT_COLUMN;
346
+ const version = input.version?.trim()
347
+ ? fitOrDrop(`v${input.version.trim()}`, columns - VERSION_COLUMN)
348
+ : undefined;
349
+ const model = modelProviderLabel(input.model, input.provider);
350
+ const modelLine = model ? truncateTail(model, fieldBudget) : undefined;
351
+ const directory = input.directory?.trim()
352
+ ? truncateHead(collapseHomePrefix(input.directory.trim(), input.homeDir), fieldBudget)
353
+ : undefined;
354
+ const rightLines = [
355
+ WORDMARK_LINES[0],
356
+ version
357
+ ? padToWidth(WORDMARK_LINES[1], WORDMARK_WIDTH) + ' '.repeat(VERSION_GAP) + version
358
+ : WORDMARK_LINES[1],
359
+ WORDMARK_LINES[2],
360
+ modelLine ?? '',
361
+ directory ?? '',
362
+ ];
363
+ return withPaddingRows(face.map((line, index) => {
364
+ const right = rightLines[index] ?? '';
365
+ const padded = PAD + line;
366
+ // Pad the face out to the split column only when something follows it, so a row whose field
367
+ // was omitted (or truncated away) does not end in a run of spaces.
368
+ return { face: right ? padToWidth(padded, RIGHT_COLUMN) : padded, right };
369
+ }));
370
+ }
371
+ /**
372
+ * Render the banner as one multi-line string for the plain (readline) surface.
373
+ *
374
+ * Colour is the 16-colour `magenta` slot from {@link ANSI_COLORS} — the SAME slot the rest of the
375
+ * CLI uses — and never a 256-colour or 24-bit escape: the 16-colour slot picks up the violet from
376
+ * the user's own terminal theme, so the sloth looks native in both light and dark schemes. The
377
+ * right column carries no escape at all, not even dim.
378
+ *
379
+ * With `colour: false` the output contains ZERO escape sequences — not one dim wrapper, not one
380
+ * reset — so a monochrome session (`useColour: false`) stays genuinely clean. The caller decides;
381
+ * the plain surface passes `getUseColour()`, the TUI passes nothing and lets Ink's own
382
+ * colour-support detection make the same call.
383
+ */
384
+ export function launchBannerText(input) {
385
+ return launchBannerRows(input)
386
+ .map(({ face, right }) => {
387
+ // A padding row is an empty line and nothing else: wrapping it in the colour pair would put
388
+ // two escapes on a row that has no glyph to paint, which the colour-off guarantee and the
389
+ // one-pair-per-row assertion both read as a bug.
390
+ if (!face && !right)
391
+ return '';
392
+ return input.colour
393
+ ? `${ANSI_COLORS.magenta}${face}${ANSI_COLORS.reset}${right}`
394
+ : face + right;
395
+ })
396
+ .join('\n');
397
+ }
398
+ /**
399
+ * Read the live fields off the process, so both surfaces report the same three values from the same
400
+ * sources instead of each picking its own. `model`/`provider` come from the caller because only it
401
+ * has the resolved config (`config.modelDisplayName` / `config.modelProviderType`); the rest are
402
+ * process-global.
403
+ *
404
+ * Fail-soft, as in `debugDump`: `getSlothVersion()` reads the installed `package.json` through the
405
+ * install dir that the CLI entry point sets, and THROWS when nothing set it (an embedded caller, a
406
+ * unit test). A missing version drops one label — it must never stop a session from starting.
407
+ */
408
+ export function launchBannerFields(model, provider) {
409
+ let version;
410
+ try {
411
+ version = getSlothVersion();
412
+ }
413
+ catch {
414
+ /* no install dir (embedded/test) — the banner simply shows no version */
415
+ }
416
+ return { version, model, provider, directory: getProjectDir(), homeDir: homedir() };
417
+ }
418
+ //# sourceMappingURL=launchBanner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"launchBanner.js","sourceRoot":"","sources":["../../src/core/launchBanner.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ export interface PlainToolIndicationObserver {
2
+ /** Feed every streamed message/chunk (the string path's existing loop) through this. */
3
+ observe(chunk: unknown): void;
4
+ }
5
+ /**
6
+ * Create the per-stream observer. State is scoped to one stream (one `agent.stream()` call);
7
+ * tool_call deltas are accumulated from `tool_call_chunks` (keyed by the provider's chunk
8
+ * `index`, which restarts per LLM round — the map is flushed into the by-id map whenever a
9
+ * `ToolMessage` arrives, mirroring `processEventStream`'s reset-per-round). Deliberately does
10
+ * NOT `concat()` whole `AIMessageChunk`s: only the tool-call slices are needed, which also
11
+ * sidesteps the TUI-C29 `__raw_response` aggregation-growth trap entirely.
12
+ *
13
+ * `emit` is injectable for tests; production uses the INFO-level `displayToolIndication`.
14
+ */
15
+ export declare function createPlainToolIndication(emit?: (text: string) => void): PlainToolIndicationObserver;
@@ -0,0 +1,174 @@
1
+ /**
2
+ * @module plainToolIndication
3
+ * TUI-C30 — compact tool-call indication for the PLAIN surface (`--no-tui` readline sessions,
4
+ * piped/single-shot `ask`/`exec`/`review`/`pr`). The Ink TUI renders tool calls from the typed
5
+ * event stream; the plain surface streams strings, so until now a tool call only surfaced
6
+ * through the tools' own transient notices (`📁 Reading file: …`, `🔧 Executing …` + raw child
7
+ * output via the tool-output channel's default sink). This module watches the SAME LangGraph
8
+ * message stream the string path already iterates and, when each `ToolMessage` lands, prints
9
+ * one compact indication built from the shared {@link toolDisplay} registry:
10
+ *
11
+ * ✓ 📁 read_file(path=README.md)
12
+ * # Readme ← up to the canonical 10 preview lines, dim
13
+ * … (+42 more lines)
14
+ *
15
+ * Stream discipline (matches how the plain surface prints tool activity today): the block is
16
+ * emitted at INFO level through `displayToolIndication` — same stdout channel, same
17
+ * `consoleLevel` gate and session-log treatment as the existing tool notices — so scripted
18
+ * consumers that already silence INFO chatter silence this too. Colour is used exactly when the
19
+ * resolved `useColour` (the CFG-30 ladder in `config/colour.ts`) says so — TUI-C35 removed the
20
+ * local `&& stdout.isTTY` narrowing this module used to apply on top, which was redundant against
21
+ * the ladder's own rung-4 TTY auto-detection everywhere except `FORCE_COLOR` on a pipe, the one
22
+ * case that variable exists to serve. An ordinary piped run is therefore still clean monochrome
23
+ * (DL-7) — rung 4 decides that — with diff lines readable via their `+`/`-` prefixes.
24
+ *
25
+ * Live-output dedupe: shell-shaped results (`<COMMAND_OUTPUT>`) belong to tools whose child
26
+ * output ALREADY streamed raw via the channel's default sink, so those render with
27
+ * `liveOutputAlreadyShown` and show only the closing status line — never a repeat of output
28
+ * the user just watched.
29
+ */
30
+ import { AIMessage, AIMessageChunk, ToolMessage } from '@langchain/core/messages';
31
+ import { buildToolPreviewLines, getToolGlyph, isShellShapedResult, renderToolLineAnsi, summariseToolCall, } from '#src/core/toolDisplay.js';
32
+ import { displayToolIndication } from '#src/utils/consoleUtils.js';
33
+ import { getUseColour } from '#src/utils/systemUtils.js';
34
+ const INDENT = ' ';
35
+ /**
36
+ * Create the per-stream observer. State is scoped to one stream (one `agent.stream()` call);
37
+ * tool_call deltas are accumulated from `tool_call_chunks` (keyed by the provider's chunk
38
+ * `index`, which restarts per LLM round — the map is flushed into the by-id map whenever a
39
+ * `ToolMessage` arrives, mirroring `processEventStream`'s reset-per-round). Deliberately does
40
+ * NOT `concat()` whole `AIMessageChunk`s: only the tool-call slices are needed, which also
41
+ * sidesteps the TUI-C29 `__raw_response` aggregation-growth trap entirely.
42
+ *
43
+ * `emit` is injectable for tests; production uses the INFO-level `displayToolIndication`.
44
+ */
45
+ export function createPlainToolIndication(emit = displayToolIndication) {
46
+ /** Streaming tool-call deltas for the CURRENT round, keyed by tool_call_chunk index. */
47
+ const streaming = new Map();
48
+ /** Completed calls awaiting their ToolMessage, keyed by tool call id. */
49
+ const byId = new Map();
50
+ const flushStreamingIntoById = () => {
51
+ for (const call of streaming.values()) {
52
+ if (call.id)
53
+ byId.set(call.id, call);
54
+ }
55
+ streaming.clear();
56
+ };
57
+ const renderToolMessage = (message) => {
58
+ const id = typeof message.tool_call_id === 'string' ? message.tool_call_id : '';
59
+ const tracked = id ? byId.get(id) : undefined;
60
+ if (id)
61
+ byId.delete(id);
62
+ const name = tracked?.name || (typeof message.name === 'string' ? message.name : '') || '';
63
+ const result = typeof message.content === 'string' ? message.content : JSON.stringify(message.content);
64
+ const isError = message.status === 'error';
65
+ // TUI-C35 — colour is exactly what the resolved ladder says, with no local narrowing.
66
+ // This used to AND in `stdout.isTTY`, which was redundant in every case but one: rung 4 of
67
+ // `config/colour.ts` already auto-detects from stdout's TTY status, so an unconfigured piped
68
+ // run is monochrome either way. The one case it changed was `FORCE_COLOR` into a pipe — which
69
+ // it suppressed, defeating the only thing that variable is for.
70
+ const colour = getUseColour();
71
+ const statusGlyph = isError
72
+ ? colour
73
+ ? '\x1b[31m✗\x1b[0m'
74
+ : '✗'
75
+ : colour
76
+ ? '\x1b[32m✓\x1b[0m'
77
+ : '✓';
78
+ const summary = summariseToolCall(name, tracked?.argsText);
79
+ const summaryText = colour ? `\x1b[2m${summary}\x1b[0m` : summary;
80
+ const head = `${statusGlyph} ${getToolGlyph(name)} ${summaryText}`;
81
+ const preview = buildToolPreviewLines({
82
+ name,
83
+ argsText: tracked?.argsText,
84
+ result,
85
+ isError,
86
+ // Shell-shaped results stream their child output live through the channel's default
87
+ // sink on this surface — suppress the duplicated body, keep the status tail. TUI-C32
88
+ // residual c: gate on the tool NAME + shape (not shape alone), so a non-shell tool whose
89
+ // result merely quotes `<COMMAND_OUTPUT>` keeps its preview body instead of being suppressed.
90
+ liveOutputAlreadyShown: isShellShapedResult(name, result),
91
+ });
92
+ const body = preview.map((line) => INDENT + renderToolLineAnsi(line, colour));
93
+ // Leading newline mirrors the historical notice framing (the model text stream may have
94
+ // left the cursor mid-line).
95
+ emit(['', head, ...body].join('\n'));
96
+ };
97
+ return {
98
+ observe(chunk) {
99
+ // Order matters: AIMessageChunk extends AIMessage, so test the chunk shape first
100
+ // (mirrors processEventStream).
101
+ if (AIMessageChunk.isInstance(chunk)) {
102
+ // TUI-C32 residual e — fail-soft, matching the ToolMessage branch: accumulating tool-call
103
+ // deltas (`JSON.stringify(tc.args)` can throw on an unserialisable arg, e.g. a BigInt) must
104
+ // never break the run's stream loop. On any error we simply skip this chunk's tracking.
105
+ try {
106
+ const c = chunk;
107
+ const deltas = c.tool_call_chunks ?? [];
108
+ if (deltas.length > 0) {
109
+ for (const delta of deltas) {
110
+ const index = typeof delta.index === 'number' ? delta.index : 0;
111
+ const entry = streaming.get(index) ?? { name: '', argsText: '' };
112
+ if (delta.id)
113
+ entry.id = delta.id;
114
+ if (delta.name)
115
+ entry.name = entry.name || delta.name;
116
+ if (delta.args)
117
+ entry.argsText += delta.args;
118
+ streaming.set(index, entry);
119
+ }
120
+ }
121
+ else {
122
+ // Some providers surface COMPLETE tool_calls on a chunk instead of deltas.
123
+ for (const tc of c.tool_calls ?? []) {
124
+ if (tc.id) {
125
+ byId.set(tc.id, {
126
+ id: tc.id,
127
+ name: tc.name,
128
+ argsText: JSON.stringify(tc.args ?? {}),
129
+ });
130
+ }
131
+ }
132
+ }
133
+ }
134
+ catch {
135
+ /* indication is best-effort; the model-facing stream is untouched */
136
+ }
137
+ return;
138
+ }
139
+ if (AIMessage.isInstance(chunk)) {
140
+ // A non-chunk AIMessage (resumed/checkpoint-replayed runs) carries final tool_calls.
141
+ // TUI-C32 residual e — same fail-soft wrap as above/the ToolMessage branch.
142
+ try {
143
+ const m = chunk;
144
+ for (const tc of m.tool_calls ?? []) {
145
+ if (tc.id) {
146
+ byId.set(tc.id, {
147
+ id: tc.id,
148
+ name: tc.name,
149
+ argsText: JSON.stringify(tc.args ?? {}),
150
+ });
151
+ }
152
+ }
153
+ }
154
+ catch {
155
+ /* indication is best-effort; the model-facing stream is untouched */
156
+ }
157
+ return;
158
+ }
159
+ if (chunk instanceof ToolMessage) {
160
+ // The round is over: park any streamed calls under their ids (chunk indexes restart
161
+ // next round), then render the arrived result. Fail-soft — rendering must never break
162
+ // the run.
163
+ try {
164
+ flushStreamingIntoById();
165
+ renderToolMessage(chunk);
166
+ }
167
+ catch {
168
+ /* indication is best-effort; the model-facing stream is untouched */
169
+ }
170
+ }
171
+ },
172
+ };
173
+ }
174
+ //# sourceMappingURL=plainToolIndication.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plainToolIndication.js","sourceRoot":"","sources":["../../src/core/plainToolIndication.ts"],"names":[],"mappings":"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"}