@kitn.ai/ui 0.28.0 → 0.29.0

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 (376) hide show
  1. package/README.md +1 -1
  2. package/dist/agent-tooling/construct/public.d.ts +15 -0
  3. package/dist/agent-tooling/construct/schema.d.ts +111 -0
  4. package/dist/construct-cli.es.js +3 -3
  5. package/dist/construct.d.ts +5 -0
  6. package/dist/construct.js +327 -0
  7. package/dist/core-C8fzo39E.js +1 -12
  8. package/dist/engine-javascript-C1x7zo1_.js +1 -141
  9. package/dist/mcp.es.js +1 -1
  10. package/package.json +16 -16
  11. package/bin/route.test.js +0 -58
  12. package/src/agent-tooling/README.md +0 -192
  13. package/src/agent-tooling/archetypes.ts +0 -205
  14. package/src/agent-tooling/catalog/README.md +0 -676
  15. package/src/agent-tooling/catalog/catalog-types.ts +0 -231
  16. package/src/agent-tooling/catalog/fabrications.ts +0 -96
  17. package/src/agent-tooling/catalog/invariants.ts +0 -284
  18. package/src/agent-tooling/catalog/labs-titles.ts +0 -114
  19. package/src/agent-tooling/catalog/scenarios.ts +0 -87
  20. package/src/agent-tooling/catalog/surfaces.ts +0 -347
  21. package/src/agent-tooling/construct/cli-entry.ts +0 -8
  22. package/src/agent-tooling/construct/cli.ts +0 -147
  23. package/src/agent-tooling/construct/codegen.ts +0 -1635
  24. package/src/agent-tooling/construct/construct.v1.schema.json +0 -319
  25. package/src/agent-tooling/construct/dev.ts +0 -148
  26. package/src/agent-tooling/construct/fixtures/demo-widget.construct.json +0 -7
  27. package/src/agent-tooling/construct/fixtures/ops-console.construct.json +0 -44
  28. package/src/agent-tooling/construct/fixtures/owner-widget.construct.json +0 -32
  29. package/src/agent-tooling/construct/schema.ts +0 -452
  30. package/src/agent-tooling/integrations/anthropic.ts +0 -442
  31. package/src/agent-tooling/integrations/cloudflare.ts +0 -218
  32. package/src/agent-tooling/integrations/langgraph.ts +0 -133
  33. package/src/agent-tooling/integrations/mastra.ts +0 -182
  34. package/src/agent-tooling/integrations/mock.ts +0 -148
  35. package/src/agent-tooling/integrations/ollama.ts +0 -97
  36. package/src/agent-tooling/integrations/openai.ts +0 -95
  37. package/src/agent-tooling/integrations/openrouter.ts +0 -82
  38. package/src/agent-tooling/integrations/pi.ts +0 -176
  39. package/src/agent-tooling/integrations/pydantic-ai.ts +0 -108
  40. package/src/agent-tooling/integrations/vercel-ai-sdk.ts +0 -398
  41. package/src/agent-tooling/mcp/css-raw.d.ts +0 -12
  42. package/src/agent-tooling/mcp/manifest.ts +0 -422
  43. package/src/agent-tooling/mcp/server.ts +0 -116
  44. package/src/agent-tooling/mcp/stdio.ts +0 -18
  45. package/src/agent-tooling/mcp/tools/construct.ts +0 -130
  46. package/src/agent-tooling/mcp/tools/debug.ts +0 -621
  47. package/src/agent-tooling/mcp/tools/reference.ts +0 -980
  48. package/src/agent-tooling/mcp/tools/scaffold.ts +0 -6708
  49. package/src/agent-tooling/mcp/tools/theme.ts +0 -456
  50. package/src/agent-tooling/mcp/tools/types.ts +0 -14
  51. package/src/agent-tooling/mcp/validate-args.ts +0 -141
  52. package/src/agent-tooling/recipes/composed-thread.ts +0 -714
  53. package/src/agent-tooling/recipes/index.ts +0 -22
  54. package/src/agent-tooling/recipes/types.ts +0 -32
  55. package/src/agent-tooling/registry.ts +0 -132
  56. package/src/agent-tooling/route-emit.ts +0 -305
  57. package/src/agent-tooling/types.ts +0 -390
  58. package/src/components/artifact-card.tsx +0 -120
  59. package/src/components/artifact.tsx +0 -914
  60. package/src/components/attachment-types.ts +0 -66
  61. package/src/components/attachments.tsx +0 -536
  62. package/src/components/audio-visualizer/audio-visualizer.voice-fixture.ts +0 -257
  63. package/src/components/audio-visualizer/aurora.glsl.ts +0 -330
  64. package/src/components/audio-visualizer/fit-scale.ts +0 -112
  65. package/src/components/audio-visualizer/index.tsx +0 -528
  66. package/src/components/audio-visualizer/labs/lab-choreography.ts +0 -187
  67. package/src/components/audio-visualizer/labs/lab-shaders.ts +0 -363
  68. package/src/components/audio-visualizer/labs/lab-visualizer.tsx +0 -102
  69. package/src/components/audio-visualizer/shader-canvas.tsx +0 -876
  70. package/src/components/audio-visualizer/sizes.ts +0 -54
  71. package/src/components/audio-visualizer/variant-aurora.tsx +0 -305
  72. package/src/components/audio-visualizer/variant-bar.tsx +0 -175
  73. package/src/components/audio-visualizer/variant-custom.tsx +0 -344
  74. package/src/components/audio-visualizer/variant-grid.tsx +0 -175
  75. package/src/components/audio-visualizer/variant-radial.tsx +0 -163
  76. package/src/components/audio-visualizer/variant-wave.tsx +0 -130
  77. package/src/components/audio-visualizer/wave.glsl.ts +0 -130
  78. package/src/components/card-fallback.tsx +0 -60
  79. package/src/components/card-renderer.tsx +0 -220
  80. package/src/components/card.tsx +0 -212
  81. package/src/components/chain-of-thought.tsx +0 -348
  82. package/src/components/chat-container.tsx +0 -89
  83. package/src/components/chat-scope-picker.tsx +0 -57
  84. package/src/components/chat-thread.tsx +0 -1014
  85. package/src/components/checkpoint.tsx +0 -88
  86. package/src/components/choice-card.tsx +0 -743
  87. package/src/components/coachmark.tsx +0 -249
  88. package/src/components/code-block.tsx +0 -181
  89. package/src/components/composer-dom.ts +0 -159
  90. package/src/components/composer-highlight.ts +0 -242
  91. package/src/components/composer-history.ts +0 -82
  92. package/src/components/composer.tsx +0 -1075
  93. package/src/components/confirm-card.tsx +0 -402
  94. package/src/components/context.tsx +0 -377
  95. package/src/components/conversation-item.tsx +0 -241
  96. package/src/components/conversation-list.tsx +0 -429
  97. package/src/components/conversation-panel.tsx +0 -118
  98. package/src/components/dismissed-stub.tsx +0 -80
  99. package/src/components/embed.tsx +0 -200
  100. package/src/components/empty.tsx +0 -177
  101. package/src/components/feedback-bar.tsx +0 -166
  102. package/src/components/file-tree.tsx +0 -530
  103. package/src/components/file-upload.tsx +0 -167
  104. package/src/components/form-widgets.tsx +0 -525
  105. package/src/components/form.tsx +0 -1296
  106. package/src/components/home-panel.tsx +0 -159
  107. package/src/components/image.tsx +0 -65
  108. package/src/components/link-preview.tsx +0 -195
  109. package/src/components/loader.tsx +0 -367
  110. package/src/components/markdown.tsx +0 -162
  111. package/src/components/message-skills.tsx +0 -40
  112. package/src/components/message.tsx +0 -765
  113. package/src/components/model-switcher.tsx +0 -132
  114. package/src/components/prompt-input.tsx +0 -188
  115. package/src/components/prompt-suggestion.tsx +0 -150
  116. package/src/components/reasoning.tsx +0 -293
  117. package/src/components/response-compare-types.ts +0 -152
  118. package/src/components/response-compare.tsx +0 -455
  119. package/src/components/response-stream.tsx +0 -109
  120. package/src/components/screen.tsx +0 -181
  121. package/src/components/scroll-button.tsx +0 -93
  122. package/src/components/source.tsx +0 -192
  123. package/src/components/tasks-card.tsx +0 -801
  124. package/src/components/text-shimmer.tsx +0 -37
  125. package/src/components/thinking-bar.tsx +0 -50
  126. package/src/components/thread.tsx +0 -228
  127. package/src/components/toast.tsx +0 -503
  128. package/src/components/tool-classify.ts +0 -26
  129. package/src/components/tool-types.ts +0 -49
  130. package/src/components/tool.tsx +0 -190
  131. package/src/components/use-card-resolution.ts +0 -44
  132. package/src/components/voice-input.tsx +0 -222
  133. package/src/components/voice-output.tsx +0 -242
  134. package/src/components/widget-tab-bar.tsx +0 -73
  135. package/src/components/workspace-shell.tsx +0 -285
  136. package/src/diagnostics/hook.ts +0 -352
  137. package/src/diagnostics/index.ts +0 -82
  138. package/src/diagnostics/report-request.ts +0 -296
  139. package/src/elements/agent-card.tsx +0 -67
  140. package/src/elements/artifact.tsx +0 -194
  141. package/src/elements/attachments.tsx +0 -140
  142. package/src/elements/audio-visualizer.tsx +0 -227
  143. package/src/elements/autoloader.ts +0 -89
  144. package/src/elements/avatar.tsx +0 -33
  145. package/src/elements/badge.tsx +0 -32
  146. package/src/elements/button.tsx +0 -149
  147. package/src/elements/card.tsx +0 -133
  148. package/src/elements/cards.tsx +0 -266
  149. package/src/elements/chain-of-thought.tsx +0 -141
  150. package/src/elements/chat-scope-picker.tsx +0 -65
  151. package/src/elements/chat-types.ts +0 -102
  152. package/src/elements/chat-workspace.tsx +0 -188
  153. package/src/elements/chat.tsx +0 -304
  154. package/src/elements/checkbox-group.tsx +0 -164
  155. package/src/elements/checkbox.tsx +0 -134
  156. package/src/elements/checkpoint.tsx +0 -47
  157. package/src/elements/choice.tsx +0 -100
  158. package/src/elements/coachmark.tsx +0 -90
  159. package/src/elements/code-block.tsx +0 -60
  160. package/src/elements/command.tsx +0 -197
  161. package/src/elements/compare.tsx +0 -111
  162. package/src/elements/compiled.css +0 -2
  163. package/src/elements/composer.tsx +0 -162
  164. package/src/elements/confirm-card.tsx +0 -76
  165. package/src/elements/context-meter.tsx +0 -107
  166. package/src/elements/conversation-item.tsx +0 -120
  167. package/src/elements/conversation-list.tsx +0 -203
  168. package/src/elements/css.ts +0 -5
  169. package/src/elements/default-input.tsx +0 -392
  170. package/src/elements/define-entry.ts +0 -12
  171. package/src/elements/define.tsx +0 -563
  172. package/src/elements/diagnostic-events.ts +0 -114
  173. package/src/elements/dialog.tsx +0 -145
  174. package/src/elements/disclosure.ts +0 -95
  175. package/src/elements/dock.tsx +0 -215
  176. package/src/elements/dropdown.tsx +0 -123
  177. package/src/elements/editable-label.tsx +0 -140
  178. package/src/elements/element-data-types.ts +0 -152
  179. package/src/elements/element-diagnostics.ts +0 -392
  180. package/src/elements/element-manifest.json +0 -354
  181. package/src/elements/element-nonscalar.json +0 -187
  182. package/src/elements/element-types.d.ts +0 -4353
  183. package/src/elements/embed.tsx +0 -35
  184. package/src/elements/empty.tsx +0 -29
  185. package/src/elements/feedback-bar.tsx +0 -66
  186. package/src/elements/file-tree.tsx +0 -62
  187. package/src/elements/file-upload.tsx +0 -44
  188. package/src/elements/form.tsx +0 -94
  189. package/src/elements/hover-card.tsx +0 -80
  190. package/src/elements/icon.tsx +0 -41
  191. package/src/elements/image.tsx +0 -32
  192. package/src/elements/input.tsx +0 -492
  193. package/src/elements/kbd.tsx +0 -45
  194. package/src/elements/link-preview.tsx +0 -34
  195. package/src/elements/loader.tsx +0 -25
  196. package/src/elements/markdown.tsx +0 -38
  197. package/src/elements/menu.tsx +0 -241
  198. package/src/elements/message-skills.tsx +0 -83
  199. package/src/elements/message.tsx +0 -361
  200. package/src/elements/model-switcher.tsx +0 -121
  201. package/src/elements/nav.tsx +0 -87
  202. package/src/elements/notice.tsx +0 -53
  203. package/src/elements/pane-grid.tsx +0 -111
  204. package/src/elements/pane-group.tsx +0 -119
  205. package/src/elements/pane.tsx +0 -129
  206. package/src/elements/popover.tsx +0 -81
  207. package/src/elements/progress-bar.tsx +0 -39
  208. package/src/elements/prompt-dock.tsx +0 -82
  209. package/src/elements/prompt-input.tsx +0 -240
  210. package/src/elements/prompt-suggestions.tsx +0 -113
  211. package/src/elements/radio-group.tsx +0 -129
  212. package/src/elements/reasoning.tsx +0 -71
  213. package/src/elements/register-impl.ts +0 -148
  214. package/src/elements/register.ts +0 -76
  215. package/src/elements/remote.tsx +0 -225
  216. package/src/elements/resizable.globals.d.ts +0 -36
  217. package/src/elements/resizable.tsx +0 -704
  218. package/src/elements/response-stream.tsx +0 -40
  219. package/src/elements/screen.tsx +0 -90
  220. package/src/elements/scroll-area.tsx +0 -33
  221. package/src/elements/scroll-button.tsx +0 -179
  222. package/src/elements/search.tsx +0 -185
  223. package/src/elements/segmented.tsx +0 -129
  224. package/src/elements/select.tsx +0 -165
  225. package/src/elements/separator.tsx +0 -36
  226. package/src/elements/setting-item.tsx +0 -50
  227. package/src/elements/settings-group.tsx +0 -37
  228. package/src/elements/skeleton.tsx +0 -45
  229. package/src/elements/slider.tsx +0 -171
  230. package/src/elements/slot-text.ts +0 -73
  231. package/src/elements/slots.ts +0 -787
  232. package/src/elements/source.tsx +0 -126
  233. package/src/elements/status.tsx +0 -42
  234. package/src/elements/styles.css +0 -328
  235. package/src/elements/switch.tsx +0 -118
  236. package/src/elements/tabs.tsx +0 -95
  237. package/src/elements/tasks.tsx +0 -103
  238. package/src/elements/text-shimmer.tsx +0 -28
  239. package/src/elements/thinking-bar.tsx +0 -43
  240. package/src/elements/thread.tsx +0 -139
  241. package/src/elements/toast.tsx +0 -106
  242. package/src/elements/tool.tsx +0 -78
  243. package/src/elements/tooltip.tsx +0 -85
  244. package/src/elements/validate-messages.ts +0 -76
  245. package/src/elements/voice-input.tsx +0 -100
  246. package/src/elements/voice-output.tsx +0 -82
  247. package/src/index.ts +0 -430
  248. package/src/primitives/audio-bands.ts +0 -174
  249. package/src/primitives/card-component-types.ts +0 -63
  250. package/src/primitives/card-contract.ts +0 -80
  251. package/src/primitives/card-data-types.ts +0 -361
  252. package/src/primitives/card-host.tsx +0 -37
  253. package/src/primitives/card-recovery.ts +0 -160
  254. package/src/primitives/card-registry.tsx +0 -78
  255. package/src/primitives/card-resolution.ts +0 -36
  256. package/src/primitives/card-routing.ts +0 -85
  257. package/src/primitives/card-schemas/artifact.schema.json +0 -92
  258. package/src/primitives/card-schemas/card-envelope.schema.json +0 -14
  259. package/src/primitives/card-schemas/card-event.schema.json +0 -12
  260. package/src/primitives/card-schemas/choice.schema.json +0 -75
  261. package/src/primitives/card-schemas/confirm.schema.json +0 -65
  262. package/src/primitives/card-schemas/embed.schema.json +0 -63
  263. package/src/primitives/card-schemas/form.result.schema.json +0 -7
  264. package/src/primitives/card-schemas/form.schema.json +0 -53
  265. package/src/primitives/card-schemas/link.schema.json +0 -54
  266. package/src/primitives/card-schemas/tasks.result.schema.json +0 -16
  267. package/src/primitives/card-schemas/tasks.schema.json +0 -83
  268. package/src/primitives/card-tags.ts +0 -61
  269. package/src/primitives/card-validate-cards.ts +0 -255
  270. package/src/primitives/card-validate-schemas.ts +0 -58
  271. package/src/primitives/card-validate.ts +0 -131
  272. package/src/primitives/chat-config.tsx +0 -103
  273. package/src/primitives/composer-model.ts +0 -49
  274. package/src/primitives/composer-triggers.ts +0 -35
  275. package/src/primitives/controllable.ts +0 -21
  276. package/src/primitives/conversation-store.ts +0 -248
  277. package/src/primitives/create-kai-chat.ts +0 -66
  278. package/src/primitives/create-tween.ts +0 -266
  279. package/src/primitives/embed-providers.ts +0 -254
  280. package/src/primitives/field-mask.ts +0 -256
  281. package/src/primitives/field-semantics.ts +0 -115
  282. package/src/primitives/highlighter.ts +0 -157
  283. package/src/primitives/input-mask.ts +0 -853
  284. package/src/primitives/link-preview.ts +0 -87
  285. package/src/primitives/message-feedback.ts +0 -124
  286. package/src/primitives/pdf-preview.ts +0 -121
  287. package/src/primitives/toast-store.ts +0 -338
  288. package/src/primitives/url-scheme-policy.ts +0 -70
  289. package/src/primitives/use-audio-analysis.ts +0 -340
  290. package/src/primitives/use-auto-resize.ts +0 -31
  291. package/src/primitives/use-resize-observer.ts +0 -45
  292. package/src/primitives/use-sequencer.ts +0 -72
  293. package/src/primitives/use-speech-recognition.ts +0 -146
  294. package/src/primitives/use-stick-to-bottom.ts +0 -75
  295. package/src/primitives/use-text-stream.ts +0 -143
  296. package/src/primitives/use-voice-recorder.ts +0 -62
  297. package/src/primitives/visualizer-sequences.ts +0 -350
  298. package/src/remote/host-embed.ts +0 -345
  299. package/src/remote/index.ts +0 -2
  300. package/src/remote/origin.ts +0 -30
  301. package/src/remote/provider-runtime.ts +0 -262
  302. package/src/remote/provider.ts +0 -2
  303. package/src/remote/validate.ts +0 -22
  304. package/src/remote/version.ts +0 -12
  305. package/src/remote/wire.ts +0 -48
  306. package/src/schemas/from-tool-call.ts +0 -151
  307. package/src/schemas/index.ts +0 -341
  308. package/src/schemas/provider-subsets.ts +0 -538
  309. package/src/schemas/registry.ts +0 -355
  310. package/src/schemas/tool-defs.ts +0 -793
  311. package/src/solid.ts +0 -173
  312. package/src/state/index.ts +0 -44
  313. package/src/state/messages.ts +0 -53
  314. package/src/state/mock.ts +0 -297
  315. package/src/state/parts.ts +0 -295
  316. package/src/state/persistence.ts +0 -180
  317. package/src/state/stream.ts +0 -246
  318. package/src/state/suggestions.ts +0 -9
  319. package/src/state/threads.ts +0 -142
  320. package/src/test-utils/fake-clock.ts +0 -88
  321. package/src/types.ts +0 -95
  322. package/src/ui/action-icons.ts +0 -53
  323. package/src/ui/agent-card.tsx +0 -218
  324. package/src/ui/avatar.tsx +0 -23
  325. package/src/ui/badge.tsx +0 -21
  326. package/src/ui/button.tsx +0 -49
  327. package/src/ui/card.tsx +0 -245
  328. package/src/ui/checkbox-group.tsx +0 -153
  329. package/src/ui/checkbox.tsx +0 -63
  330. package/src/ui/collapsible.tsx +0 -163
  331. package/src/ui/command.tsx +0 -103
  332. package/src/ui/dialog.tsx +0 -223
  333. package/src/ui/dock.tsx +0 -630
  334. package/src/ui/dropdown.tsx +0 -636
  335. package/src/ui/editable-label.tsx +0 -112
  336. package/src/ui/hover-card.tsx +0 -345
  337. package/src/ui/icon.tsx +0 -167
  338. package/src/ui/input.tsx +0 -391
  339. package/src/ui/kbd.tsx +0 -118
  340. package/src/ui/nav.tsx +0 -282
  341. package/src/ui/notice.tsx +0 -84
  342. package/src/ui/overlay.tsx +0 -277
  343. package/src/ui/pane-grid.tsx +0 -117
  344. package/src/ui/pane-group.tsx +0 -291
  345. package/src/ui/pane.tsx +0 -193
  346. package/src/ui/popover.tsx +0 -106
  347. package/src/ui/progress-bar.tsx +0 -83
  348. package/src/ui/prompt-dock.tsx +0 -145
  349. package/src/ui/radio.tsx +0 -150
  350. package/src/ui/resizable.tsx +0 -693
  351. package/src/ui/scroll-area.tsx +0 -26
  352. package/src/ui/segmented.tsx +0 -109
  353. package/src/ui/select.tsx +0 -168
  354. package/src/ui/separator.tsx +0 -10
  355. package/src/ui/settings-group.tsx +0 -67
  356. package/src/ui/skeleton.tsx +0 -73
  357. package/src/ui/slider.tsx +0 -178
  358. package/src/ui/status.tsx +0 -48
  359. package/src/ui/switch.tsx +0 -119
  360. package/src/ui/tabs.tsx +0 -168
  361. package/src/ui/textarea.tsx +0 -21
  362. package/src/ui/tooltip.tsx +0 -118
  363. package/src/utils/cn.ts +0 -30
  364. package/src/wire/chunk.ts +0 -248
  365. package/src/wire/consume.ts +0 -599
  366. package/src/wire/diagnostics.ts +0 -727
  367. package/src/wire/encode-probe.ts +0 -214
  368. package/src/wire/encode.ts +0 -947
  369. package/src/wire/files.ts +0 -342
  370. package/src/wire/formats/anthropic.ts +0 -274
  371. package/src/wire/formats/openai.ts +0 -228
  372. package/src/wire/index.ts +0 -95
  373. package/src/wire/media-types.ts +0 -344
  374. package/src/wire/read.ts +0 -396
  375. package/src/wire/sink-helpers.ts +0 -50
  376. package/src/wire/sse.ts +0 -153
@@ -1,599 +0,0 @@
1
- // The adapter core: read neutral chunks, drive a sink, and report the turn.
2
- //
3
- // PORTABILITY RULES: no React, no Solid, no DOM, no fetch, no SSE, and NO
4
- // PROVIDER SDK. The only kit values imported are the three part builders, which
5
- // are REUSED rather than reimplemented so that `ModelTurn.parts` is produced by
6
- // exactly the code that drove the sink.
7
- import type { MessagePart, MessageSource } from '../elements/chat-types';
8
- import { appendReasoningPart, appendTextPart, fingerprint, upsertToolPart } from '../state/parts';
9
- import {
10
- normalizeStopReason,
11
- type AssistantStreamSink,
12
- type ConsumeOptions,
13
- type ModelStreamChunk,
14
- type ModelToolCall,
15
- type ModelToolCallDelta,
16
- type ModelTurn,
17
- type ModelUsage,
18
- type StopReason,
19
- } from './chunk';
20
- import type { RawOrigin } from '../components/tool-types';
21
- import {
22
- emitWireDiagnostic,
23
- nextStreamId,
24
- wireCorrelation,
25
- wireDiagnosticsActive,
26
- withPayload,
27
- type WireCorrelation,
28
- } from './diagnostics';
29
-
30
- // ── Tool-call accumulator ────────────────────────────────────────────────────
31
-
32
- interface MutableCall {
33
- index: number;
34
- id: string | null;
35
- name: string;
36
- argumentsText: string;
37
- announced: boolean;
38
- /** Fingerprint of the last `input` written, so a re-parse of unchanged text
39
- * does not patch the part again. */
40
- lastInputFp: string | null;
41
- providerExecuted: boolean;
42
- output?: Record<string, unknown>;
43
- outputError?: string;
44
- }
45
-
46
- const snapshot = (c: MutableCall): ModelToolCall => ({
47
- index: c.index,
48
- id: c.id ?? `call_${c.index}`,
49
- name: c.name,
50
- argumentsText: c.argumentsText,
51
- });
52
-
53
- /**
54
- * The provider-shaped tool-call block, REASSEMBLED from the fragments, kept on
55
- * the part so an encoder can round-trip without re-deriving it.
56
- *
57
- * Tagged `custom.` on purpose: it is a reconstruction, not a payload the
58
- * provider handed over intact, so it must never be mistaken for one of the
59
- * verbatim blocks (Anthropic `thinking`) a provider refuses to accept rebuilt.
60
- */
61
- const rawOf = (c: MutableCall): RawOrigin => ({
62
- source: 'custom.wire.tool_call',
63
- payload: { id: c.id ?? `call_${c.index}`, name: c.name, arguments: c.argumentsText },
64
- });
65
-
66
- function clip(s: string, n: number): string {
67
- return s.length <= n ? s : `${s.slice(0, n)}...`;
68
- }
69
-
70
- /** Parse accumulated argument text into a JSON OBJECT, or undefined. A prefix of
71
- * a JSON object never parses, which is why `rawInput` exists. No tolerant
72
- * partial-JSON closer ships: guessing at a half-written object produces
73
- * confidently wrong tool inputs. */
74
- function parseArgumentObject(text: string): Record<string, unknown> | undefined {
75
- const trimmed = text.trim();
76
- if (trimmed === '') return undefined;
77
- try {
78
- const value: unknown = JSON.parse(trimmed);
79
- if (value === null || typeof value !== 'object' || Array.isArray(value)) return undefined;
80
- return value as Record<string, unknown>;
81
- } catch {
82
- return undefined;
83
- }
84
- }
85
-
86
- /**
87
- * Correlate tool-call fragments by `index` and drive the ToolPart lifecycle.
88
- * THE HARD PART: `id`, `name` and each slice of `arguments` arrive across
89
- * arbitrarily many chunks in provider-dependent order.
90
- *
91
- * A ToolPart is pushed (`input-streaming`) the first time a call has BOTH an id
92
- * and a non-empty name: announcing earlier would create a panel whose `type` is
93
- * the empty string. Everything before that is buffered and flushed at announce.
94
- */
95
- export function createToolCallAccumulator(sink: AssistantStreamSink, opts: ConsumeOptions = {}) {
96
- const calls = new Map<number, MutableCall>();
97
-
98
- /** Write the argument text that has arrived so far.
99
- *
100
- * `rawInput` is written on EVERY fragment, so a consumer watching the part
101
- * sees the arguments assemble character by character. `input` is written only
102
- * when the whole accumulated text parses to a JSON object. Honest caveat:
103
- * with plain `JSON.parse` a prefix never parses, so `input` in practice still
104
- * lands once, at the end. `rawInput` is what streams. */
105
- const writeArguments = (call: MutableCall) => {
106
- const id = call.id!;
107
- const text = call.argumentsText;
108
- const parsed = parseArgumentObject(text);
109
- if (parsed) {
110
- const fp = fingerprint(parsed);
111
- if (fp !== call.lastInputFp) {
112
- call.lastInputFp = fp;
113
- sink.upsertTool(id, { rawInput: text, input: parsed, state: 'input-available' });
114
- return;
115
- }
116
- }
117
- sink.upsertTool(id, { rawInput: text });
118
- };
119
-
120
- const announce = (call: MutableCall) => {
121
- if (call.announced) return;
122
- call.id ??= `call_${call.index}`;
123
- call.announced = true;
124
- sink.upsertTool(call.id, { type: call.name || 'unknown_tool', state: 'input-streaming' });
125
- // Fragments that arrived before the id did are flushed now.
126
- if (call.argumentsText) writeArguments(call);
127
- };
128
-
129
- const apply = (raw: ModelToolCallDelta) => {
130
- const index = typeof raw.index === 'number' ? raw.index : 0;
131
- let call = calls.get(index);
132
- if (!call) {
133
- call = {
134
- index,
135
- id: null,
136
- name: '',
137
- argumentsText: '',
138
- announced: false,
139
- lastInputFp: null,
140
- providerExecuted: false,
141
- };
142
- calls.set(index, call);
143
- }
144
- if (raw.id && !call.id) call.id = raw.id;
145
- if (raw.name) call.name += raw.name;
146
- if (call.id && call.name) announce(call);
147
-
148
- if (typeof raw.arguments === 'string' && raw.arguments !== '') {
149
- call.argumentsText += raw.arguments;
150
- if (call.announced) writeArguments(call);
151
- }
152
-
153
- if (raw.output !== undefined || raw.outputError !== undefined) {
154
- // A result the PROVIDER executed. Force the announce so the panel exists,
155
- // then complete it. `settle` must not touch it afterwards.
156
- announce(call);
157
- call.providerExecuted = true;
158
- if (raw.outputError !== undefined) {
159
- call.outputError = raw.outputError;
160
- sink.upsertTool(call.id!, { state: 'output-error', errorText: raw.outputError });
161
- } else {
162
- call.output = raw.output;
163
- sink.upsertTool(call.id!, { state: 'output-available', output: raw.output });
164
- }
165
- }
166
- };
167
-
168
- /** Settle every accumulated call once the stream ends. */
169
- const settle = (stopReason: StopReason | undefined, streamError?: string): ModelToolCall[] =>
170
- [...calls.values()]
171
- .sort((a, b) => a.index - b.index)
172
- .map((call) => {
173
- announce(call); // a call that only ever had arguments still gets a panel
174
- const id = call.id!;
175
- const base = snapshot(call);
176
-
177
- if (call.providerExecuted) {
178
- // Already at output-available/output-error. Re-settling would overwrite
179
- // the provider's own result with a parse of its arguments.
180
- return {
181
- ...base,
182
- providerExecuted: true,
183
- ...(call.output !== undefined ? { output: call.output } : {}),
184
- ...(call.outputError !== undefined ? { error: call.outputError } : {}),
185
- };
186
- }
187
-
188
- // Attached once, at settle: before that `argumentsText` is still growing
189
- // and a raw snapshot of half a JSON string is worse than none.
190
- const raw = rawOf(call);
191
- // `type` is re-sent because a provider may split the tool NAME across
192
- // fragments; the announce could have fired on a prefix.
193
- const name = call.name || 'unknown_tool';
194
-
195
- if (streamError) {
196
- const error = `Stream failed before the tool call completed: ${streamError}`;
197
- sink.upsertTool(id, {
198
- type: name,
199
- state: 'output-error',
200
- errorText: error,
201
- rawInput: call.argumentsText,
202
- raw,
203
- });
204
- return { ...base, error };
205
- }
206
-
207
- if (!call.name) {
208
- const error = 'Tool call arrived with no function name; cannot dispatch it.';
209
- sink.upsertTool(id, { state: 'output-error', errorText: error, raw });
210
- return { ...base, error };
211
- }
212
-
213
- const rawArgs = call.argumentsText.trim();
214
- try {
215
- // An argument-less tool legitimately streams '' or '{}'.
216
- const parsed: unknown = rawArgs === '' ? {} : JSON.parse(rawArgs);
217
- if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
218
- throw new Error('arguments must be a JSON object');
219
- }
220
- const input = parsed as Record<string, unknown>;
221
- sink.upsertTool(id, {
222
- type: name,
223
- state: 'input-available',
224
- input,
225
- rawInput: call.argumentsText,
226
- raw,
227
- });
228
- const ready: ModelToolCall = { ...base, input };
229
- opts.onToolCallReady?.(ready);
230
- return ready;
231
- } catch (e) {
232
- const truncated =
233
- stopReason === 'length' ? ' (the stream hit the token limit mid-call)' : '';
234
- const error =
235
- `Malformed tool arguments${truncated}: ${(e as Error).message}. ` +
236
- `Received ${rawArgs.length} chars: ${clip(rawArgs, 160)}`;
237
- sink.upsertTool(id, {
238
- type: name,
239
- state: 'output-error',
240
- errorText: error,
241
- rawInput: call.argumentsText,
242
- raw,
243
- });
244
- return { ...base, error };
245
- }
246
- });
247
-
248
- return { apply, settle };
249
- }
250
-
251
- // ── Part recording and sink tee ──────────────────────────────────────────────
252
-
253
- /** A sink that writes nowhere but into a `MessagePart[]`, using the kit's own
254
- * builders. Teed alongside the real sink so `ModelTurn.parts` cannot drift from
255
- * what the message actually received. */
256
- function createPartsRecorder(correlation: WireCorrelation): AssistantStreamSink & {
257
- parts(): MessagePart[];
258
- /** How many DISTINCT parts each variant produced, keyed by variant name.
259
- *
260
- * Counted from the sink method that was called plus the fact that the parts
261
- * array GREW, NOT by discriminating a `MessagePart` -- which is what keeps
262
- * this out of scope for `lint:silent-drops`: there is no new switch over
263
- * `MessagePart.type` in `wire/` to go stale when a seventh variant lands.
264
- *
265
- * Parts, not writes: five text deltas merge into ONE text part and report
266
- * `{ text: 1 }`. A consumer renders these as "N parts", so a write count
267
- * would lie there. Per-delta granularity is not lost -- one `wire.part`
268
- * event is still emitted per write, carrying that delta's own `chars`. */
269
- partCounts(): Record<string, number>;
270
- } {
271
- let parts: MessagePart[] = [];
272
- const counts: Record<string, number> = {};
273
-
274
- /** Count the part if this write created one, then report the write. `chars` is
275
- * the delta LENGTH and is passed only for the text-bearing variants; the
276
- * delta itself never travels.
277
- *
278
- * A write either APPENDS a part or MERGES into one that already exists, and
279
- * the array length is what separates them -- uniform across all four methods
280
- * and needing no knowledge of any variant's shape. The growth amount is used
281
- * rather than a flat +1 so a builder that ever appends two stays honest.
282
- *
283
- * `index` is the position in `parts` this write landed on, found by comparing
284
- * references before and after. The builders return a new array with a new
285
- * object for the item they changed, so the first slot that differs IS the
286
- * part that moved. Computed only when someone is listening. */
287
- const record = (
288
- variant: string,
289
- before: MessagePart[],
290
- chars?: number,
291
- /** Built ONLY when the payload switch is on -- a thunk, so with it off
292
- * nothing here is read or copied. */
293
- payload?: () => { delta?: string; patch?: unknown; source?: unknown },
294
- ) => {
295
- if (parts.length > before.length) {
296
- counts[variant] = (counts[variant] ?? 0) + (parts.length - before.length);
297
- }
298
- if (!wireDiagnosticsActive()) return;
299
- let index = parts.length - 1;
300
- for (let i = 0; i < parts.length; i++) {
301
- if (before[i] !== parts[i]) {
302
- index = i;
303
- break;
304
- }
305
- }
306
- emitWireDiagnostic({
307
- type: 'wire.part',
308
- t: Date.now(),
309
- ...correlation,
310
- variant,
311
- index,
312
- ...(chars !== undefined ? { chars } : {}),
313
- ...(payload ? withPayload(payload) : {}),
314
- });
315
- };
316
-
317
- return {
318
- appendText(delta) {
319
- const before = parts;
320
- parts = appendTextPart(parts, delta);
321
- record('text', before, delta.length, () => ({ delta }));
322
- },
323
- appendReasoning(delta, opts) {
324
- const before = parts;
325
- // `streamId` is DROPPED here on purpose. It namespaces block indices for a
326
- // sink that outlives one stream; this recorder starts a fresh array per
327
- // call, so within `ModelTurn.parts` there is nothing to disambiguate. Worse,
328
- // it is a new value on every call, so keeping it would make `parts`
329
- // non-reproducible for identical input and break the determinism the
330
- // adapter guarantees (same chunks in, same parts out, whatever the byte
331
- // boundaries). The host's own sink still receives it.
332
- parts = appendReasoningPart(parts, delta, { ...opts, streamId: undefined });
333
- record('reasoning', before, delta.length, () => ({ delta }));
334
- },
335
- upsertTool(toolCallId, patch) {
336
- const before = parts;
337
- parts = upsertToolPart(parts, toolCallId, patch);
338
- // The patch is where the tool's arguments and its output live, so the
339
- // whole thing is payload and none of it is metadata.
340
- record('tool', before, undefined, () => ({ patch }));
341
- },
342
- addSource(source) {
343
- const before = parts;
344
- parts = [...parts, { type: 'source', source }];
345
- record('source', before, undefined, () => ({ source }));
346
- },
347
- parts: () => parts,
348
- partCounts: () => counts,
349
- };
350
- }
351
-
352
- /** Drive two sinks from one call. `a` (the host's) goes first so the visible
353
- * message updates in stream order. */
354
- function teeSink(a: AssistantStreamSink, b: AssistantStreamSink): AssistantStreamSink {
355
- return {
356
- appendText(delta) {
357
- a.appendText(delta);
358
- b.appendText(delta);
359
- },
360
- appendReasoning(delta, opts) {
361
- a.appendReasoning(delta, opts);
362
- b.appendReasoning(delta, opts);
363
- },
364
- upsertTool(toolCallId, patch) {
365
- a.upsertTool(toolCallId, patch);
366
- b.upsertTool(toolCallId, patch);
367
- },
368
- addSource(source) {
369
- a.addSource?.(source);
370
- b.addSource?.(source);
371
- },
372
- };
373
- }
374
-
375
- // ── The main loop ────────────────────────────────────────────────────────────
376
-
377
- /**
378
- * Read one turn's worth of `ModelStreamChunk`s and drive `sink` with them.
379
- *
380
- * The sink is expected to be the kit's `AssistantStream`, which produces a NEW
381
- * message object (and a new `parts` array) on every real mutation: that is what
382
- * makes `<kai-thread>` re-render. This adapter calls the sink once per delta and
383
- * never batches; batching is a host concern.
384
- *
385
- * This is also the escape hatch for a consumer who already has neutral chunks
386
- * from somewhere else and does not need a WireFormat at all.
387
- */
388
- export async function consumeModelStream(
389
- chunks: AsyncIterable<ModelStreamChunk>,
390
- sink: AssistantStreamSink,
391
- opts: ConsumeOptions = {},
392
- ): Promise<ModelTurn> {
393
- const label = opts.reasoningLabel ?? 'Thinking';
394
- // `readModelStream` assigns the id one level up, because it opens the format
395
- // and counts frames before this function runs and every event from one read
396
- // has to carry the same id. A direct caller still gets a fresh one per call.
397
- const streamId = opts.streamId ?? nextStreamId();
398
- // `traceId`/`label` ride along unchanged from the caller. `readModelStream`
399
- // built the same object one level up and passes the parts of it through
400
- // `opts`, so a read and its consume report one identical correlation.
401
- const correlation = wireCorrelation(streamId, opts);
402
- const startedAt = Date.now();
403
- const recorder = createPartsRecorder(correlation);
404
- const out = teeSink(sink, recorder);
405
- const tools = createToolCallAccumulator(out, opts);
406
-
407
- let text = '';
408
- let reasoning = '';
409
- let reasoningChunks = 0;
410
- let chunkCount = 0;
411
- let finishReason: string | null = null;
412
- let error: ModelTurn['error'];
413
- let usage: ModelUsage | undefined;
414
- const sources: MessageSource[] = [];
415
-
416
- for await (const chunk of chunks) {
417
- chunkCount++;
418
-
419
- if (chunk.usage) usage = { ...usage, ...chunk.usage };
420
-
421
- if (chunk.error) {
422
- error = chunk.error;
423
- if (chunk.finishReason) finishReason = chunk.finishReason;
424
- break;
425
- }
426
-
427
- if (chunk.text) {
428
- text += chunk.text;
429
- out.appendText(chunk.text);
430
- }
431
-
432
- // REWORK 1. The spike gated this whole branch on `if (chunk.reasoning)`,
433
- // which is falsy for ''. That dropped exactly the payloads `raw` exists to
434
- // preserve: an Anthropic redacted_thinking block (opaque, no readable text,
435
- // and the docs require sending it back "including any blocks with empty
436
- // thinking fields") and the assembled block emitted at content_block_stop
437
- // after signature_delta. Both arrive with no text.
438
- if (
439
- chunk.reasoning !== undefined ||
440
- chunk.reasoningRaw !== undefined ||
441
- chunk.reasoningSignature !== undefined
442
- ) {
443
- const delta = chunk.reasoning ?? '';
444
- reasoning += delta;
445
- // Only NON-EMPTY text counts as "reasoning streamed".
446
- if (delta !== '') reasoningChunks++;
447
- // `raw` and `signature` are spread in only when present: passing undefined
448
- // would blank a value an earlier delta already established.
449
- out.appendReasoning(delta, {
450
- index: chunk.reasoningIndex ?? 0,
451
- // Namespaces the index to THIS stream, so a later round reading into the
452
- // same sink opens its own part instead of overwriting this one's `raw`.
453
- streamId,
454
- label,
455
- ...(chunk.reasoningRaw ? { raw: chunk.reasoningRaw } : {}),
456
- ...(chunk.reasoningSignature ? { signature: chunk.reasoningSignature } : {}),
457
- });
458
- }
459
-
460
- if (chunk.toolCalls) for (const tc of chunk.toolCalls) tools.apply(tc);
461
-
462
- if (chunk.sources) {
463
- for (const source of chunk.sources) {
464
- sources.push(source);
465
- out.addSource?.(source);
466
- }
467
- }
468
-
469
- if (chunk.finishReason) finishReason = chunk.finishReason;
470
- }
471
-
472
- // A turn that received NOTHING has failed, and used to say so nowhere.
473
- //
474
- // The response was 200 and the body carried no parsable frame at all, which is
475
- // exactly what a proxy route produces when it forwards a provider failure
476
- // WITHOUT its status: an upstream 401 (no key — the most common first run)
477
- // arrives as HTTP 200 whose body is a JSON error object mislabelled
478
- // text/event-stream. `readModelStream` cannot throw for it (the response is
479
- // ok) and the SSE decoder finds no `data:` line, so the turn resolved empty:
480
- // no text, no error, no console output, nothing to debug. The route templates
481
- // forward the status now; this is the second, independent guard, and it also
482
- // covers a truncated body, a non-streaming completion returned by mistake, and
483
- // an HTML proxy page.
484
- //
485
- // AND ITS SIBLING: a turn whose frames PARSED but produced no part.
486
- //
487
- // The guard judges on parts produced rather than on raw chunk count, which is
488
- // what makes it hold once a format reads a field that content-less frames also
489
- // carry (`model`, `finishReason`, `usage`). Judged on chunks alone, surfacing
490
- // `model` would have taken this whole guard quiet: frames yielding nothing but
491
- // an id would push `chunkCount` above zero and the turn would go back to
492
- // resolving empty and saying so nowhere.
493
- //
494
- // The split reports which of the two happened, because they have different
495
- // fixes: zero chunks means nothing parsed as a frame at all, while chunks with
496
- // no parts means the frames parsed and carried nothing THIS reader reads --
497
- // the wrong-dialect case, and the one this option makes common.
498
- // ORDER IS LOAD-BEARING: settle runs FIRST, because settle PRODUCES PARTS.
499
- //
500
- // A tool call that never held both an id and a non-empty name is announced
501
- // only inside `settle`, so a parts map read before it is not final. Judged
502
- // there, a turn whose only content is such a call looked empty, and the
503
- // damage went both ways: the guard's own prose was fed back in as
504
- // `streamError` and REPLACED the call's accurate "arrived with no function
505
- // name" diagnosis, while `wire.close` reported an emptiness code beside a
506
- // produced part. Both reads now happen after settle, so the parts map, the
507
- // error code and the tool's own error agree by construction.
508
- //
509
- // `settle` still receives only a GENUINE in-band stream error, exactly as it
510
- // did before the guard was widened -- an emptiness diagnosis must never flow
511
- // into it. `stopReason` is recomputed below because the guard can set the
512
- // error that makes it 'error'; settle only branches on 'length', which the
513
- // guard cannot produce, so the pre-guard value is the right one to pass.
514
- const settleStopReason = normalizeStopReason(finishReason) ?? (error ? 'error' : undefined);
515
- const toolCalls = tools.settle(settleStopReason, error?.message);
516
-
517
- const partsTotal = Object.values(recorder.partCounts()).reduce((a, b) => a + b, 0);
518
- if (!error && partsTotal === 0) {
519
- error =
520
- chunkCount === 0
521
- ? {
522
- code: 'empty-stream',
523
- message:
524
- 'The model stream produced no chunks. The response was 200 but nothing in its body parsed ' +
525
- 'as a stream frame — check the endpoint really sent Content-Type: text/event-stream and that ' +
526
- 'the request set stream: true. A route that forwards a provider error without its status ' +
527
- 'lands here: the body is a JSON error, not SSE.',
528
- }
529
- : {
530
- code: 'empty-turn',
531
- message:
532
- `The stream completed and ${chunkCount} chunk(s) were parsed, but none carried content ` +
533
- 'this reader reads, so no message part was produced. If the endpoint streams a different ' +
534
- 'dialect (or carries its payload in a field this format does not read), switch to the ' +
535
- 'matching reader. A model that returned no content at all also lands here.',
536
- };
537
- }
538
-
539
- // REWORK 3. `finishReason` is reported verbatim; `stopReason` is what the
540
- // adapter itself branches on, so no OpenAI literal leaks into adapter logic.
541
- //
542
- // Recomputed after the guard: an emptiness error is still an error, and a turn
543
- // that reports one must report `stopReason: 'error'` beside it as it always
544
- // has. Identical to `settleStopReason` whenever the guard stayed silent.
545
- const stopReason = normalizeStopReason(finishReason) ?? (error ? 'error' : undefined);
546
-
547
- if (wireDiagnosticsActive()) {
548
- // `_frames` is supplied by `readModelStream` only. A direct caller had no
549
- // frames, so the field is absent rather than zero -- a consumer must be able
550
- // to tell "not reported" from "none arrived".
551
- const frames = (opts as { _frames?: () => number })._frames?.();
552
- emitWireDiagnostic({
553
- type: 'wire.close',
554
- t: Date.now(),
555
- ...correlation,
556
- ...(frames !== undefined ? { frames } : {}),
557
- chunks: chunkCount,
558
- parts: recorder.partCounts(),
559
- finishReason,
560
- ...(stopReason ? { stopReason } : {}),
561
- // The CODE only. A provider's error message can echo request content back,
562
- // which is why it stays on `ModelTurn.error` and never on the event.
563
- ...(error?.code !== undefined ? { errorCode: error.code } : {}),
564
- ...(usage ? { usage } : {}),
565
- ms: Date.now() - startedAt,
566
- // The assembled turn: everything the sink was driven with, in one place,
567
- // which is what a panel renders when someone asks "what did it actually
568
- // say" -- plus the in-band error's own message.
569
- //
570
- // ALL THREE TERMINAL EVENTS TREAT PROVIDER MESSAGE TEXT IDENTICALLY:
571
- // `wire.close`, `wire.failed` and `wire.interrupted` each keep it out of
572
- // the metadata (where only `errorCode` travels, because a message can
573
- // echo request content back) and each publish it under `payload`, which
574
- // is the switch that accepts exactly that. A developer who armed payload
575
- // is most often chasing the in-band error that explains an empty turn.
576
- ...withPayload(() => ({
577
- text,
578
- reasoning,
579
- toolCalls,
580
- sources,
581
- ...(error?.message !== undefined ? { message: error.message } : {}),
582
- })),
583
- });
584
- }
585
-
586
- return {
587
- parts: recorder.parts(),
588
- text,
589
- reasoning,
590
- toolCalls,
591
- sources,
592
- finishReason,
593
- reasoningChunks,
594
- chunks: chunkCount,
595
- ...(stopReason ? { stopReason } : {}),
596
- ...(error ? { error } : {}),
597
- ...(usage ? { usage } : {}),
598
- };
599
- }