@kitn.ai/ui 0.28.0 → 0.30.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.
- package/README.md +11 -7
- package/dist/{Icon-DObhsRtm.js → Icon-BzBET3E-.js} +1 -1
- package/dist/{action-icons-CcgNN2oT.js → action-icons-DaW2pU6m.js} +1 -1
- package/dist/agent-tooling/construct/public.d.ts +15 -0
- package/dist/agent-tooling/construct/schema.d.ts +111 -0
- package/dist/arrow-down-D783iMJj.js +1 -0
- package/dist/arrow-left-0VLM5kyg.js +1 -0
- package/dist/artifact-DGjzHYUT.js +1 -0
- package/dist/{attachments-Qr4kg-2b.js → attachments-YvXHlg88.js} +1 -1
- package/dist/{audio-visualizer-DToYdfSc.js → audio-visualizer-9lz5IHsL.js} +1 -1
- package/dist/{badge-CdEu_jn2.js → badge-Dh_vp8A5.js} +1 -1
- package/dist/{bell-DQAAOpDz.js → bell-CT7_WUwS.js} +1 -1
- package/dist/{button-DkuOnGLy.js → button-C_3te6fm.js} +1 -1
- package/dist/{card-renderer-Jx70m6_l.js → card-renderer-DZVaGH2e.js} +1 -1
- package/dist/check-CiEOVnEN.js +1 -0
- package/dist/{checkbox-D7iRB3nL.js → checkbox-Bu2T2e0U.js} +1 -1
- package/dist/{checkbox-group-B2_rpoPM.js → checkbox-group-BiryEXPQ.js} +1 -1
- package/dist/chevron-down-CSobuZRG.js +1 -0
- package/dist/chevron-right-DR9ZAP-2.js +1 -0
- package/dist/{choice-card-m0BYqqOK.js → choice-card-CM6IjkmL.js} +1 -1
- package/dist/circle-BC66EYF7.js +1 -0
- package/dist/circle-check-BqmahW0O.js +1 -0
- package/dist/{circle-x-BPG0iPst.js → circle-x-Bk-dA5Wf.js} +1 -1
- package/dist/{code-block-ClMLKb7C.js → code-block-B26t-Q7q.js} +1 -1
- package/dist/{collapsible-B4DlWicL.js → collapsible-Bhc636Cf.js} +1 -1
- package/dist/components/builder-composer-triggers.d.ts +62 -0
- package/dist/components/builder-layout.d.ts +54 -0
- package/dist/components/builder-message-actions.d.ts +72 -0
- package/dist/components/builder-panel.d.ts +189 -0
- package/dist/components/builder-preview.d.ts +11 -0
- package/dist/components/builder-shell-controls.d.ts +69 -0
- package/dist/components/builder-skeleton.d.ts +58 -0
- package/dist/components/builder-start.d.ts +75 -0
- package/dist/components/builder-workspace-variants.d.ts +23 -0
- package/dist/components/captions.d.ts +66 -0
- package/dist/{composer-sQ0HcvyG.js → composer-DMI85B6j.js} +1 -1
- package/dist/{confirm-card-rQWDQsnC.js → confirm-card-B0YIAwXp.js} +1 -1
- package/dist/construct-cli.es.js +3 -3
- package/dist/construct.d.ts +5 -0
- package/dist/construct.js +327 -0
- package/dist/{context-0kg4ZX1J.js → context-DWgfhqWp.js} +1 -1
- package/dist/controllable-BagRpyHv.js +1 -0
- package/dist/{conversation-item-BxwQLaFJ.js → conversation-item-BsOsVGmT.js} +1 -1
- package/dist/{conversation-list-BCfGN1JU.js → conversation-list-aX3Vtc7L.js} +1 -1
- package/dist/{copy-n9u9rqpi.js → copy-BmEwsNhJ.js} +1 -1
- package/dist/core-C8fzo39E.js +1 -12
- package/dist/{create-tween-uPkArjIG.js → create-tween-CNOAU3Fx.js} +1 -1
- package/dist/{create-tween-C-q-JCM-.js → create-tween-WfqiENls.js} +1 -1
- package/dist/{create-tween-CRrJXxBd.js → create-tween-wlFtW9Kh.js} +1 -1
- package/dist/{create-tween-DZ3RRDHD.js → create-tween-zt79ba32.js} +1 -1
- package/dist/{default-input-CaMrJkgH.js → default-input-DMV5Eaze.js} +1 -1
- package/dist/define-CI0y3eE4.js +1 -0
- package/dist/define.js +40 -40
- package/dist/define.server.js +45 -45
- package/dist/{disclosure-WUImBbDv.js → disclosure-NnACBxsC.js} +1 -1
- package/dist/{dropdown-wZVeJOMd.js → dropdown-DT7G0RGR.js} +1 -1
- package/dist/elements/agent-card.js +1 -1
- package/dist/elements/artifact.js +1 -1
- package/dist/elements/attachments.js +1 -1
- package/dist/elements/audio-visualizer.js +1 -1
- package/dist/elements/avatar.js +1 -1
- package/dist/elements/badge.js +1 -1
- package/dist/elements/button.js +1 -1
- package/dist/elements/card.js +1 -1
- package/dist/elements/cards.js +1 -1
- package/dist/elements/chain-of-thought.js +1 -1
- package/dist/elements/chat-scope-picker.js +1 -1
- package/dist/elements/chat-workspace.js +1 -1
- package/dist/elements/chat.js +1 -1
- package/dist/elements/checkbox-group.js +1 -1
- package/dist/elements/checkbox.js +1 -1
- package/dist/elements/checkpoint.js +1 -1
- package/dist/elements/choice.js +1 -1
- package/dist/elements/coachmark.js +1 -1
- package/dist/elements/code-block.js +1 -1
- package/dist/elements/command.js +1 -1
- package/dist/elements/compare.js +1 -1
- package/dist/elements/composer.js +1 -1
- package/dist/elements/confirm-card.js +1 -1
- package/dist/elements/context-meter.js +1 -1
- package/dist/elements/conversation-item.js +1 -1
- package/dist/elements/conversation-list.js +1 -1
- package/dist/elements/dialog.js +1 -1
- package/dist/elements/dock.js +1 -1
- package/dist/elements/dropdown.js +1 -1
- package/dist/elements/editable-label.js +1 -1
- package/dist/elements/embed.js +1 -1
- package/dist/elements/empty.js +1 -1
- package/dist/elements/feedback-bar.js +1 -1
- package/dist/elements/file-tree.js +1 -1
- package/dist/elements/file-upload.js +1 -1
- package/dist/elements/form.js +1 -1
- package/dist/elements/hover-card.js +1 -1
- package/dist/elements/icon.js +1 -1
- package/dist/elements/image.js +1 -1
- package/dist/elements/input.js +1 -1
- package/dist/elements/kbd.js +1 -1
- package/dist/elements/link-preview.js +1 -1
- package/dist/elements/loader.js +1 -1
- package/dist/elements/markdown.js +1 -1
- package/dist/elements/menu.js +1 -1
- package/dist/elements/message-skills.js +1 -1
- package/dist/elements/message.js +1 -1
- package/dist/elements/model-switcher.js +1 -1
- package/dist/elements/nav.js +1 -1
- package/dist/elements/notice.js +1 -1
- package/dist/elements/pane-grid.js +1 -1
- package/dist/elements/pane-group.js +1 -1
- package/dist/elements/pane.js +1 -1
- package/dist/elements/popover.js +1 -1
- package/dist/elements/progress-bar.js +1 -1
- package/dist/elements/prompt-dock.js +1 -1
- package/dist/elements/prompt-input.js +1 -1
- package/dist/elements/prompt-suggestions.js +1 -1
- package/dist/elements/radio-group.js +1 -1
- package/dist/elements/reasoning.js +1 -1
- package/dist/elements/remote.js +1 -1
- package/dist/elements/resizable.js +1 -1
- package/dist/elements/response-stream.js +1 -1
- package/dist/elements/screen.js +1 -1
- package/dist/elements/scroll-area.js +1 -1
- package/dist/elements/scroll-button.js +1 -1
- package/dist/elements/search.js +1 -1
- package/dist/elements/segmented.js +1 -1
- package/dist/elements/select.js +1 -1
- package/dist/elements/separator.js +1 -1
- package/dist/elements/setting-item.js +1 -1
- package/dist/elements/settings-group.js +1 -1
- package/dist/elements/skeleton.js +1 -1
- package/dist/elements/slider.js +1 -1
- package/dist/elements/source.js +1 -1
- package/dist/elements/status.js +1 -1
- package/dist/elements/switch.js +1 -1
- package/dist/elements/tabs.js +1 -1
- package/dist/elements/tasks.js +1 -1
- package/dist/elements/text-shimmer.js +1 -1
- package/dist/elements/thinking-bar.js +1 -1
- package/dist/elements/thread.js +1 -1
- package/dist/elements/toast.js +1 -1
- package/dist/elements/tool.js +1 -1
- package/dist/elements/tooltip.js +1 -1
- package/dist/elements/voice-input.js +1 -1
- package/dist/elements/voice-output.js +1 -1
- package/dist/{ellipsis-CbOpJnU3.js → ellipsis-6pJ7SxYT.js} +1 -1
- package/dist/{embed-CrfjkbUG.js → embed-BMje-nxj.js} +1 -1
- package/dist/engine-javascript-C1x7zo1_.js +1 -141
- package/dist/{external-link-ByXAwBBQ.js → external-link-Beh9DKeR.js} +1 -1
- package/dist/{file-text-DMib2qwt.js → file-text-D1GPfZ8Q.js} +1 -1
- package/dist/{file-tree-CJqM2aP7.js → file-tree-Dwj2lqg-.js} +1 -1
- package/dist/{folder-_BAb929C.js → folder-b6D8x1DY.js} +1 -1
- package/dist/{form-DSZgZQZk.js → form-DWgdxDXu.js} +1 -1
- package/dist/{hover-card-BQbul9eq.js → hover-card-CWRkqm40.js} +1 -1
- package/dist/{icon-CdcfRhYI.js → icon-CL0jdhsf.js} +1 -1
- package/dist/index.js +1741 -1725
- package/dist/index.server.js +1582 -1566
- package/dist/{info-DsFjGuI_.js → info-0cOUherQ.js} +1 -1
- package/dist/{input-DKbmkMos.js → input-BH6Ru91Q.js} +1 -1
- package/dist/kai.es.js +1 -1
- package/dist/{kbd-PvdGhX4s.js → kbd-DRAnVtNX.js} +1 -1
- package/dist/{link-zF8PzWfF.js → link-Dlr0vssB.js} +1 -1
- package/dist/{link-preview-DbM1866N.js → link-preview-DODgRyV9.js} +1 -1
- package/dist/loader-KvJB6NrT.js +1 -0
- package/dist/{markdown-LMvmAyBk.js → markdown-DX9tuJVj.js} +1 -1
- package/dist/mcp.es.js +9 -5
- package/dist/{message-BYZECyek.js → message-BqQ7Ot5q.js} +1 -1
- package/dist/{message-BSsgvHvf.js → message-DkaGfBd8.js} +1 -1
- package/dist/message-circle-BPpp5QKv.js +1 -0
- package/dist/message-square-BLbsd9BJ.js +1 -0
- package/dist/{minimize-2-BQRoBV1V.js → minimize-2-CyOHJwcN.js} +1 -1
- package/dist/{model-switcher-CMpCOYcB.js → model-switcher-0hrKDiK2.js} +1 -1
- package/dist/{overlay-Cb-MkyDa.js → overlay-DBMFXie6.js} +1 -1
- package/dist/{panel-right-i8warFxT.js → panel-right-t8SWJL6e.js} +1 -1
- package/dist/{paperclip-ByY3UVO6.js → paperclip-CdzuOjbU.js} +1 -1
- package/dist/play-BXy1u7rW.js +1 -0
- package/dist/primitives/use-auto-resize.d.ts +11 -0
- package/dist/{progress-bar-jVAHvMUf.js → progress-bar-CiBtxMWT.js} +1 -1
- package/dist/{prompt-suggestion-CD-qtNgC.js → prompt-suggestion-I_DHP21i.js} +1 -1
- package/dist/{radio-BsoBMhkF.js → radio-C_p3n39l.js} +1 -1
- package/dist/{reasoning-4kBPtlTx.js → reasoning-BDwy_tIe.js} +1 -1
- package/dist/register-impl-DvEnhIxx.js +424 -0
- package/dist/{resizable-uPmY9VY-.js → resizable-SYC-fStM.js} +1 -1
- package/dist/{rotate-ccw-B2xn93py.js → rotate-ccw-B1nwmwuY.js} +1 -1
- package/dist/{rotate-cw-CMVLoCyH.js → rotate-cw-CLAnBcm1.js} +1 -1
- package/dist/{scroll-area-BqLUo8ZZ.js → scroll-area-zRyXoLhI.js} +1 -1
- package/dist/{scroll-button-DslXiJyL.js → scroll-button-jzrbYCG9.js} +1 -1
- package/dist/{select-2cLRtPGT.js → select-DM-0nEe0.js} +1 -1
- package/dist/{separator-BWQtNJcM.js → separator-BToOOoOt.js} +1 -1
- package/dist/{settings-BBR63jLg.js → settings-PaGo_S4U.js} +1 -1
- package/dist/{settings-group-B2ciy-Iw.js → settings-group-BFXGIfXq.js} +1 -1
- package/dist/{skeleton-CkHI-HgC.js → skeleton-DYpmFnnm.js} +1 -1
- package/dist/{slider-DDR9U-i0.js → slider-ebutAmkQ.js} +1 -1
- package/dist/{solid-DruVOXnf.js → solid-15v_3WVL.js} +3563 -3412
- package/dist/{solid-BzCFdL25.js → solid-5P-otgdi.js} +4502 -4296
- package/dist/solid.d.ts +6 -0
- package/dist/solid.js +259 -254
- package/dist/solid.server.js +259 -254
- package/dist/{source-GEnFF528.js → source-c1gpRl9h.js} +1 -1
- package/dist/{star-CilHq8GP.js → star-pcV_3LqF.js} +1 -1
- package/dist/{store-Ciyk6eZG.js → store-CSgbFFCg.js} +1 -1
- package/dist/{switch-KkeQIbuH.js → switch-Bh2OdLV2.js} +1 -1
- package/dist/{tasks-card-BrN4bggJ.js → tasks-card-CbqQAn2S.js} +1 -1
- package/dist/text-shimmer-BSzgEnDH.js +1 -0
- package/dist/textarea-DrYNt_0z.js +1 -0
- package/dist/theme.tokens.css +22 -21
- package/dist/{thumbs-up-CsT9Wzb9.js → thumbs-up-BL3-NenK.js} +1 -1
- package/dist/{toast-store-tgK6MuMv.js → toast-store-DkOlYczt.js} +1 -1
- package/dist/{tool-DzUjIBT8.js → tool-CeIWUVUe.js} +1 -1
- package/dist/{tooltip-Bkkg_4t0.js → tooltip-GA-nmg06.js} +1 -1
- package/dist/{trash-2-jBKxjGGQ.js → trash-2-CT66QWlm.js} +1 -1
- package/dist/{triangle-alert-DlQ68ppf.js → triangle-alert-CeHLWxhD.js} +1 -1
- package/dist/ui/color-field.d.ts +48 -0
- package/dist/ui/textarea.d.ts +4 -0
- package/dist/ui/toggle-chip.d.ts +40 -0
- package/dist/{upload-TcAHXv9W.js → upload-9Csb0F0J.js} +1 -1
- package/dist/{use-card-resolution-DaZqAdWf.js → use-card-resolution-Zg0a08Z-.js} +1 -1
- package/dist/{variant-aurora-DbW_OWL2.js → variant-aurora-4VBQHIKV.js} +1 -1
- package/dist/{variant-aurora-CNmfHhcx.js → variant-aurora-DBeCAQNk.js} +2 -2
- package/dist/{variant-aurora-D6GsdTPA.js → variant-aurora-FzHVXHby.js} +2 -2
- package/dist/{variant-aurora-BhLuYxJR.js → variant-aurora-LE3fqxgw.js} +1 -1
- package/dist/{variant-custom-BvYHR62b.js → variant-custom-AFRGcOBS.js} +1 -1
- package/dist/{variant-custom-DV2x254s.js → variant-custom-BQkNuPpE.js} +2 -2
- package/dist/{variant-custom-BRvaNHSi.js → variant-custom-DI5R0rln.js} +1 -1
- package/dist/{variant-custom-DMTrEO5w.js → variant-custom-WWnrrRHS.js} +2 -2
- package/dist/{variant-wave-CyBueXj9.js → variant-wave-DUCbH5oI.js} +2 -2
- package/dist/{variant-wave-BNMGrDfj.js → variant-wave-DuQV7DhQ.js} +2 -2
- package/dist/{variant-wave-BpDd8t3Z.js → variant-wave-nSI2sfII.js} +1 -1
- package/dist/{variant-wave-sp-qwsdI.js → variant-wave-wTP5v_jU.js} +1 -1
- package/dist/x-eiO9V6J6.js +1 -0
- package/{src/elements/styles.css → kit-base.css} +27 -44
- package/package.json +32 -17
- package/solid.css +33 -0
- package/theme.css +39 -23
- package/bin/route.test.js +0 -58
- package/dist/arrow-down-CjRU3JgV.js +0 -1
- package/dist/arrow-left-Bnpb1UEW.js +0 -1
- package/dist/artifact-Bwt7OgcR.js +0 -1
- package/dist/check-njkVdZu2.js +0 -1
- package/dist/chevron-down-B72s6PXR.js +0 -1
- package/dist/chevron-right-BatuHzUm.js +0 -1
- package/dist/circle-check-B62pODB3.js +0 -1
- package/dist/circle-zx0ByzY9.js +0 -1
- package/dist/controllable-CnGpMF6r.js +0 -1
- package/dist/define-By-uL588.js +0 -1
- package/dist/loader-BM4CEz4G.js +0 -1
- package/dist/message-circle-DruwObfX.js +0 -1
- package/dist/message-square-BG6q6mlm.js +0 -1
- package/dist/play-BvKKq3fX.js +0 -1
- package/dist/register-impl-B1hLkrI8.js +0 -424
- package/dist/text-shimmer-Dcj1MHuq.js +0 -1
- package/dist/textarea-Dpvbngbg.js +0 -1
- package/dist/x-Z5UntkIJ.js +0 -1
- package/src/agent-tooling/README.md +0 -192
- package/src/agent-tooling/archetypes.ts +0 -205
- package/src/agent-tooling/catalog/README.md +0 -676
- package/src/agent-tooling/catalog/catalog-types.ts +0 -231
- package/src/agent-tooling/catalog/fabrications.ts +0 -96
- package/src/agent-tooling/catalog/invariants.ts +0 -284
- package/src/agent-tooling/catalog/labs-titles.ts +0 -114
- package/src/agent-tooling/catalog/scenarios.ts +0 -87
- package/src/agent-tooling/catalog/surfaces.ts +0 -347
- package/src/agent-tooling/construct/cli-entry.ts +0 -8
- package/src/agent-tooling/construct/cli.ts +0 -147
- package/src/agent-tooling/construct/codegen.ts +0 -1635
- package/src/agent-tooling/construct/construct.v1.schema.json +0 -319
- package/src/agent-tooling/construct/dev.ts +0 -148
- package/src/agent-tooling/construct/fixtures/demo-widget.construct.json +0 -7
- package/src/agent-tooling/construct/fixtures/ops-console.construct.json +0 -44
- package/src/agent-tooling/construct/fixtures/owner-widget.construct.json +0 -32
- package/src/agent-tooling/construct/schema.ts +0 -452
- package/src/agent-tooling/integrations/anthropic.ts +0 -442
- package/src/agent-tooling/integrations/cloudflare.ts +0 -218
- package/src/agent-tooling/integrations/langgraph.ts +0 -133
- package/src/agent-tooling/integrations/mastra.ts +0 -182
- package/src/agent-tooling/integrations/mock.ts +0 -148
- package/src/agent-tooling/integrations/ollama.ts +0 -97
- package/src/agent-tooling/integrations/openai.ts +0 -95
- package/src/agent-tooling/integrations/openrouter.ts +0 -82
- package/src/agent-tooling/integrations/pi.ts +0 -176
- package/src/agent-tooling/integrations/pydantic-ai.ts +0 -108
- package/src/agent-tooling/integrations/vercel-ai-sdk.ts +0 -398
- package/src/agent-tooling/mcp/css-raw.d.ts +0 -12
- package/src/agent-tooling/mcp/manifest.ts +0 -422
- package/src/agent-tooling/mcp/server.ts +0 -116
- package/src/agent-tooling/mcp/stdio.ts +0 -18
- package/src/agent-tooling/mcp/tools/construct.ts +0 -130
- package/src/agent-tooling/mcp/tools/debug.ts +0 -621
- package/src/agent-tooling/mcp/tools/reference.ts +0 -980
- package/src/agent-tooling/mcp/tools/scaffold.ts +0 -6708
- package/src/agent-tooling/mcp/tools/theme.ts +0 -456
- package/src/agent-tooling/mcp/tools/types.ts +0 -14
- package/src/agent-tooling/mcp/validate-args.ts +0 -141
- package/src/agent-tooling/recipes/composed-thread.ts +0 -714
- package/src/agent-tooling/recipes/index.ts +0 -22
- package/src/agent-tooling/recipes/types.ts +0 -32
- package/src/agent-tooling/registry.ts +0 -132
- package/src/agent-tooling/route-emit.ts +0 -305
- package/src/agent-tooling/types.ts +0 -390
- package/src/components/artifact-card.tsx +0 -120
- package/src/components/artifact.tsx +0 -914
- package/src/components/attachment-types.ts +0 -66
- package/src/components/attachments.tsx +0 -536
- package/src/components/audio-visualizer/audio-visualizer.voice-fixture.ts +0 -257
- package/src/components/audio-visualizer/aurora.glsl.ts +0 -330
- package/src/components/audio-visualizer/fit-scale.ts +0 -112
- package/src/components/audio-visualizer/index.tsx +0 -528
- package/src/components/audio-visualizer/labs/lab-choreography.ts +0 -187
- package/src/components/audio-visualizer/labs/lab-shaders.ts +0 -363
- package/src/components/audio-visualizer/labs/lab-visualizer.tsx +0 -102
- package/src/components/audio-visualizer/shader-canvas.tsx +0 -876
- package/src/components/audio-visualizer/sizes.ts +0 -54
- package/src/components/audio-visualizer/variant-aurora.tsx +0 -305
- package/src/components/audio-visualizer/variant-bar.tsx +0 -175
- package/src/components/audio-visualizer/variant-custom.tsx +0 -344
- package/src/components/audio-visualizer/variant-grid.tsx +0 -175
- package/src/components/audio-visualizer/variant-radial.tsx +0 -163
- package/src/components/audio-visualizer/variant-wave.tsx +0 -130
- package/src/components/audio-visualizer/wave.glsl.ts +0 -130
- package/src/components/card-fallback.tsx +0 -60
- package/src/components/card-renderer.tsx +0 -220
- package/src/components/card.tsx +0 -212
- package/src/components/chain-of-thought.tsx +0 -348
- package/src/components/chat-container.tsx +0 -89
- package/src/components/chat-scope-picker.tsx +0 -57
- package/src/components/chat-thread.tsx +0 -1014
- package/src/components/checkpoint.tsx +0 -88
- package/src/components/choice-card.tsx +0 -743
- package/src/components/coachmark.tsx +0 -249
- package/src/components/code-block.tsx +0 -181
- package/src/components/composer-dom.ts +0 -159
- package/src/components/composer-highlight.ts +0 -242
- package/src/components/composer-history.ts +0 -82
- package/src/components/composer.tsx +0 -1075
- package/src/components/confirm-card.tsx +0 -402
- package/src/components/context.tsx +0 -377
- package/src/components/conversation-item.tsx +0 -241
- package/src/components/conversation-list.tsx +0 -429
- package/src/components/conversation-panel.tsx +0 -118
- package/src/components/dismissed-stub.tsx +0 -80
- package/src/components/embed.tsx +0 -200
- package/src/components/empty.tsx +0 -177
- package/src/components/feedback-bar.tsx +0 -166
- package/src/components/file-tree.tsx +0 -530
- package/src/components/file-upload.tsx +0 -167
- package/src/components/form-widgets.tsx +0 -525
- package/src/components/form.tsx +0 -1296
- package/src/components/home-panel.tsx +0 -159
- package/src/components/image.tsx +0 -65
- package/src/components/link-preview.tsx +0 -195
- package/src/components/loader.tsx +0 -367
- package/src/components/markdown.tsx +0 -162
- package/src/components/message-skills.tsx +0 -40
- package/src/components/message.tsx +0 -765
- package/src/components/model-switcher.tsx +0 -132
- package/src/components/prompt-input.tsx +0 -188
- package/src/components/prompt-suggestion.tsx +0 -150
- package/src/components/reasoning.tsx +0 -293
- package/src/components/response-compare-types.ts +0 -152
- package/src/components/response-compare.tsx +0 -455
- package/src/components/response-stream.tsx +0 -109
- package/src/components/screen.tsx +0 -181
- package/src/components/scroll-button.tsx +0 -93
- package/src/components/source.tsx +0 -192
- package/src/components/tasks-card.tsx +0 -801
- package/src/components/text-shimmer.tsx +0 -37
- package/src/components/thinking-bar.tsx +0 -50
- package/src/components/thread.tsx +0 -228
- package/src/components/toast.tsx +0 -503
- package/src/components/tool-classify.ts +0 -26
- package/src/components/tool-types.ts +0 -49
- package/src/components/tool.tsx +0 -190
- package/src/components/use-card-resolution.ts +0 -44
- package/src/components/voice-input.tsx +0 -222
- package/src/components/voice-output.tsx +0 -242
- package/src/components/widget-tab-bar.tsx +0 -73
- package/src/components/workspace-shell.tsx +0 -285
- package/src/diagnostics/hook.ts +0 -352
- package/src/diagnostics/index.ts +0 -82
- package/src/diagnostics/report-request.ts +0 -296
- package/src/elements/agent-card.tsx +0 -67
- package/src/elements/artifact.tsx +0 -194
- package/src/elements/attachments.tsx +0 -140
- package/src/elements/audio-visualizer.tsx +0 -227
- package/src/elements/autoloader.ts +0 -89
- package/src/elements/avatar.tsx +0 -33
- package/src/elements/badge.tsx +0 -32
- package/src/elements/button.tsx +0 -149
- package/src/elements/card.tsx +0 -133
- package/src/elements/cards.tsx +0 -266
- package/src/elements/chain-of-thought.tsx +0 -141
- package/src/elements/chat-scope-picker.tsx +0 -65
- package/src/elements/chat-types.ts +0 -102
- package/src/elements/chat-workspace.tsx +0 -188
- package/src/elements/chat.tsx +0 -304
- package/src/elements/checkbox-group.tsx +0 -164
- package/src/elements/checkbox.tsx +0 -134
- package/src/elements/checkpoint.tsx +0 -47
- package/src/elements/choice.tsx +0 -100
- package/src/elements/coachmark.tsx +0 -90
- package/src/elements/code-block.tsx +0 -60
- package/src/elements/command.tsx +0 -197
- package/src/elements/compare.tsx +0 -111
- package/src/elements/compiled.css +0 -2
- package/src/elements/composer.tsx +0 -162
- package/src/elements/confirm-card.tsx +0 -76
- package/src/elements/context-meter.tsx +0 -107
- package/src/elements/conversation-item.tsx +0 -120
- package/src/elements/conversation-list.tsx +0 -203
- package/src/elements/css.ts +0 -5
- package/src/elements/default-input.tsx +0 -392
- package/src/elements/define-entry.ts +0 -12
- package/src/elements/define.tsx +0 -563
- package/src/elements/diagnostic-events.ts +0 -114
- package/src/elements/dialog.tsx +0 -145
- package/src/elements/disclosure.ts +0 -95
- package/src/elements/dock.tsx +0 -215
- package/src/elements/dropdown.tsx +0 -123
- package/src/elements/editable-label.tsx +0 -140
- package/src/elements/element-data-types.ts +0 -152
- package/src/elements/element-diagnostics.ts +0 -392
- package/src/elements/element-manifest.json +0 -354
- package/src/elements/element-nonscalar.json +0 -187
- package/src/elements/element-types.d.ts +0 -4353
- package/src/elements/embed.tsx +0 -35
- package/src/elements/empty.tsx +0 -29
- package/src/elements/feedback-bar.tsx +0 -66
- package/src/elements/file-tree.tsx +0 -62
- package/src/elements/file-upload.tsx +0 -44
- package/src/elements/form.tsx +0 -94
- package/src/elements/hover-card.tsx +0 -80
- package/src/elements/icon.tsx +0 -41
- package/src/elements/image.tsx +0 -32
- package/src/elements/input.tsx +0 -492
- package/src/elements/kbd.tsx +0 -45
- package/src/elements/link-preview.tsx +0 -34
- package/src/elements/loader.tsx +0 -25
- package/src/elements/markdown.tsx +0 -38
- package/src/elements/menu.tsx +0 -241
- package/src/elements/message-skills.tsx +0 -83
- package/src/elements/message.tsx +0 -361
- package/src/elements/model-switcher.tsx +0 -121
- package/src/elements/nav.tsx +0 -87
- package/src/elements/notice.tsx +0 -53
- package/src/elements/pane-grid.tsx +0 -111
- package/src/elements/pane-group.tsx +0 -119
- package/src/elements/pane.tsx +0 -129
- package/src/elements/popover.tsx +0 -81
- package/src/elements/progress-bar.tsx +0 -39
- package/src/elements/prompt-dock.tsx +0 -82
- package/src/elements/prompt-input.tsx +0 -240
- package/src/elements/prompt-suggestions.tsx +0 -113
- package/src/elements/radio-group.tsx +0 -129
- package/src/elements/reasoning.tsx +0 -71
- package/src/elements/register-impl.ts +0 -148
- package/src/elements/register.ts +0 -76
- package/src/elements/remote.tsx +0 -225
- package/src/elements/resizable.globals.d.ts +0 -36
- package/src/elements/resizable.tsx +0 -704
- package/src/elements/response-stream.tsx +0 -40
- package/src/elements/screen.tsx +0 -90
- package/src/elements/scroll-area.tsx +0 -33
- package/src/elements/scroll-button.tsx +0 -179
- package/src/elements/search.tsx +0 -185
- package/src/elements/segmented.tsx +0 -129
- package/src/elements/select.tsx +0 -165
- package/src/elements/separator.tsx +0 -36
- package/src/elements/setting-item.tsx +0 -50
- package/src/elements/settings-group.tsx +0 -37
- package/src/elements/skeleton.tsx +0 -45
- package/src/elements/slider.tsx +0 -171
- package/src/elements/slot-text.ts +0 -73
- package/src/elements/slots.ts +0 -787
- package/src/elements/source.tsx +0 -126
- package/src/elements/status.tsx +0 -42
- package/src/elements/switch.tsx +0 -118
- package/src/elements/tabs.tsx +0 -95
- package/src/elements/tasks.tsx +0 -103
- package/src/elements/text-shimmer.tsx +0 -28
- package/src/elements/thinking-bar.tsx +0 -43
- package/src/elements/thread.tsx +0 -139
- package/src/elements/toast.tsx +0 -106
- package/src/elements/tool.tsx +0 -78
- package/src/elements/tooltip.tsx +0 -85
- package/src/elements/validate-messages.ts +0 -76
- package/src/elements/voice-input.tsx +0 -100
- package/src/elements/voice-output.tsx +0 -82
- package/src/index.ts +0 -430
- package/src/primitives/audio-bands.ts +0 -174
- package/src/primitives/card-component-types.ts +0 -63
- package/src/primitives/card-contract.ts +0 -80
- package/src/primitives/card-data-types.ts +0 -361
- package/src/primitives/card-host.tsx +0 -37
- package/src/primitives/card-recovery.ts +0 -160
- package/src/primitives/card-registry.tsx +0 -78
- package/src/primitives/card-resolution.ts +0 -36
- package/src/primitives/card-routing.ts +0 -85
- package/src/primitives/card-schemas/artifact.schema.json +0 -92
- package/src/primitives/card-schemas/card-envelope.schema.json +0 -14
- package/src/primitives/card-schemas/card-event.schema.json +0 -12
- package/src/primitives/card-schemas/choice.schema.json +0 -75
- package/src/primitives/card-schemas/confirm.schema.json +0 -65
- package/src/primitives/card-schemas/embed.schema.json +0 -63
- package/src/primitives/card-schemas/form.result.schema.json +0 -7
- package/src/primitives/card-schemas/form.schema.json +0 -53
- package/src/primitives/card-schemas/link.schema.json +0 -54
- package/src/primitives/card-schemas/tasks.result.schema.json +0 -16
- package/src/primitives/card-schemas/tasks.schema.json +0 -83
- package/src/primitives/card-tags.ts +0 -61
- package/src/primitives/card-validate-cards.ts +0 -255
- package/src/primitives/card-validate-schemas.ts +0 -58
- package/src/primitives/card-validate.ts +0 -131
- package/src/primitives/chat-config.tsx +0 -103
- package/src/primitives/composer-model.ts +0 -49
- package/src/primitives/composer-triggers.ts +0 -35
- package/src/primitives/controllable.ts +0 -21
- package/src/primitives/conversation-store.ts +0 -248
- package/src/primitives/create-kai-chat.ts +0 -66
- package/src/primitives/create-tween.ts +0 -266
- package/src/primitives/embed-providers.ts +0 -254
- package/src/primitives/field-mask.ts +0 -256
- package/src/primitives/field-semantics.ts +0 -115
- package/src/primitives/highlighter.ts +0 -157
- package/src/primitives/input-mask.ts +0 -853
- package/src/primitives/link-preview.ts +0 -87
- package/src/primitives/message-feedback.ts +0 -124
- package/src/primitives/pdf-preview.ts +0 -121
- package/src/primitives/toast-store.ts +0 -338
- package/src/primitives/url-scheme-policy.ts +0 -70
- package/src/primitives/use-audio-analysis.ts +0 -340
- package/src/primitives/use-auto-resize.ts +0 -31
- package/src/primitives/use-resize-observer.ts +0 -45
- package/src/primitives/use-sequencer.ts +0 -72
- package/src/primitives/use-speech-recognition.ts +0 -146
- package/src/primitives/use-stick-to-bottom.ts +0 -75
- package/src/primitives/use-text-stream.ts +0 -143
- package/src/primitives/use-voice-recorder.ts +0 -62
- package/src/primitives/visualizer-sequences.ts +0 -350
- package/src/remote/host-embed.ts +0 -345
- package/src/remote/index.ts +0 -2
- package/src/remote/origin.ts +0 -30
- package/src/remote/provider-runtime.ts +0 -262
- package/src/remote/provider.ts +0 -2
- package/src/remote/validate.ts +0 -22
- package/src/remote/version.ts +0 -12
- package/src/remote/wire.ts +0 -48
- package/src/schemas/from-tool-call.ts +0 -151
- package/src/schemas/index.ts +0 -341
- package/src/schemas/provider-subsets.ts +0 -538
- package/src/schemas/registry.ts +0 -355
- package/src/schemas/tool-defs.ts +0 -793
- package/src/solid.ts +0 -173
- package/src/state/index.ts +0 -44
- package/src/state/messages.ts +0 -53
- package/src/state/mock.ts +0 -297
- package/src/state/parts.ts +0 -295
- package/src/state/persistence.ts +0 -180
- package/src/state/stream.ts +0 -246
- package/src/state/suggestions.ts +0 -9
- package/src/state/threads.ts +0 -142
- package/src/test-utils/fake-clock.ts +0 -88
- package/src/types.ts +0 -95
- package/src/ui/action-icons.ts +0 -53
- package/src/ui/agent-card.tsx +0 -218
- package/src/ui/avatar.tsx +0 -23
- package/src/ui/badge.tsx +0 -21
- package/src/ui/button.tsx +0 -49
- package/src/ui/card.tsx +0 -245
- package/src/ui/checkbox-group.tsx +0 -153
- package/src/ui/checkbox.tsx +0 -63
- package/src/ui/collapsible.tsx +0 -163
- package/src/ui/command.tsx +0 -103
- package/src/ui/dialog.tsx +0 -223
- package/src/ui/dock.tsx +0 -630
- package/src/ui/dropdown.tsx +0 -636
- package/src/ui/editable-label.tsx +0 -112
- package/src/ui/hover-card.tsx +0 -345
- package/src/ui/icon.tsx +0 -167
- package/src/ui/input.tsx +0 -391
- package/src/ui/kbd.tsx +0 -118
- package/src/ui/nav.tsx +0 -282
- package/src/ui/notice.tsx +0 -84
- package/src/ui/overlay.tsx +0 -277
- package/src/ui/pane-grid.tsx +0 -117
- package/src/ui/pane-group.tsx +0 -291
- package/src/ui/pane.tsx +0 -193
- package/src/ui/popover.tsx +0 -106
- package/src/ui/progress-bar.tsx +0 -83
- package/src/ui/prompt-dock.tsx +0 -145
- package/src/ui/radio.tsx +0 -150
- package/src/ui/resizable.tsx +0 -693
- package/src/ui/scroll-area.tsx +0 -26
- package/src/ui/segmented.tsx +0 -109
- package/src/ui/select.tsx +0 -168
- package/src/ui/separator.tsx +0 -10
- package/src/ui/settings-group.tsx +0 -67
- package/src/ui/skeleton.tsx +0 -73
- package/src/ui/slider.tsx +0 -178
- package/src/ui/status.tsx +0 -48
- package/src/ui/switch.tsx +0 -119
- package/src/ui/tabs.tsx +0 -168
- package/src/ui/textarea.tsx +0 -21
- package/src/ui/tooltip.tsx +0 -118
- package/src/utils/cn.ts +0 -30
- package/src/wire/chunk.ts +0 -248
- package/src/wire/consume.ts +0 -599
- package/src/wire/diagnostics.ts +0 -727
- package/src/wire/encode-probe.ts +0 -214
- package/src/wire/encode.ts +0 -947
- package/src/wire/files.ts +0 -342
- package/src/wire/formats/anthropic.ts +0 -274
- package/src/wire/formats/openai.ts +0 -228
- package/src/wire/index.ts +0 -95
- package/src/wire/media-types.ts +0 -344
- package/src/wire/read.ts +0 -396
- package/src/wire/sink-helpers.ts +0 -50
- package/src/wire/sse.ts +0 -153
|
@@ -1,228 +0,0 @@
|
|
|
1
|
-
// OpenAI chat-completions SSE. Also the shape every non-mock integration in the
|
|
2
|
-
// kit's catalog re-frames to server-side, so this one decoder covers all nine.
|
|
3
|
-
//
|
|
4
|
-
// Input is an ALREADY DECODED JSON frame typed `unknown`. Nothing here imports a
|
|
5
|
-
// provider SDK, and nothing here throws: a frame this format does not recognise
|
|
6
|
-
// yields [] so a provider adding a field cannot take a turn down.
|
|
7
|
-
import type { MessageSource } from '../../elements/chat-types';
|
|
8
|
-
import type {
|
|
9
|
-
ModelStreamChunk,
|
|
10
|
-
ModelToolCallDelta,
|
|
11
|
-
ModelUsage,
|
|
12
|
-
WireFormat,
|
|
13
|
-
WireFormatReader,
|
|
14
|
-
} from '../chunk';
|
|
15
|
-
|
|
16
|
-
function isRecord(v: unknown): v is Record<string, unknown> {
|
|
17
|
-
return typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function str(v: unknown): string | undefined {
|
|
21
|
-
return typeof v === 'string' ? v : undefined;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function num(v: unknown): number | undefined {
|
|
25
|
-
return typeof v === 'number' ? v : undefined;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function usageOf(raw: unknown): ModelUsage | undefined {
|
|
29
|
-
if (!isRecord(raw)) return undefined;
|
|
30
|
-
const out: ModelUsage = {};
|
|
31
|
-
const input = num(raw.prompt_tokens);
|
|
32
|
-
if (input !== undefined) out.inputTokens = input;
|
|
33
|
-
const output = num(raw.completion_tokens);
|
|
34
|
-
if (output !== undefined) out.outputTokens = output;
|
|
35
|
-
const total = num(raw.total_tokens);
|
|
36
|
-
if (total !== undefined) out.totalTokens = total;
|
|
37
|
-
const details = raw.completion_tokens_details;
|
|
38
|
-
if (isRecord(details)) {
|
|
39
|
-
// The one number that proves reasoning happened even when no reasoning text
|
|
40
|
-
// was streamed back.
|
|
41
|
-
const reasoning = num(details.reasoning_tokens);
|
|
42
|
-
if (reasoning !== undefined) out.reasoningTokens = reasoning;
|
|
43
|
-
}
|
|
44
|
-
const promptDetails = raw.prompt_tokens_details;
|
|
45
|
-
if (isRecord(promptDetails)) {
|
|
46
|
-
const cached = num(promptDetails.cached_tokens);
|
|
47
|
-
if (cached !== undefined) out.cachedInputTokens = cached;
|
|
48
|
-
}
|
|
49
|
-
const cost = num(raw.cost);
|
|
50
|
-
if (cost !== undefined) out.costUsd = cost;
|
|
51
|
-
return Object.keys(out).length > 0 ? out : undefined;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function toolCallDelta(raw: unknown, position: number): ModelToolCallDelta | undefined {
|
|
55
|
-
if (!isRecord(raw)) return undefined;
|
|
56
|
-
const fn = isRecord(raw.function) ? raw.function : undefined;
|
|
57
|
-
const out: ModelToolCallDelta = { index: num(raw.index) ?? position };
|
|
58
|
-
const id = str(raw.id);
|
|
59
|
-
if (id) out.id = id;
|
|
60
|
-
const name = str(fn?.name);
|
|
61
|
-
if (name) out.name = name;
|
|
62
|
-
const args = str(fn?.arguments);
|
|
63
|
-
if (args !== undefined) out.arguments = args;
|
|
64
|
-
return out;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/** Sum the READABLE text across reasoning_details entries. Entries with no
|
|
68
|
-
* readable text (`reasoning.encrypted`, opaque signed blobs) contribute nothing
|
|
69
|
-
* here; they still ride along whole in `reasoningRaw`. */
|
|
70
|
-
function detailText(details: unknown[]): string {
|
|
71
|
-
let out = '';
|
|
72
|
-
for (const d of details) {
|
|
73
|
-
if (!isRecord(d)) continue;
|
|
74
|
-
const text = str(d.text) ?? str(d.summary);
|
|
75
|
-
if (text) out += text;
|
|
76
|
-
}
|
|
77
|
-
return out;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function detailField(details: unknown[], key: 'index' | 'signature'): unknown {
|
|
81
|
-
for (const d of details) {
|
|
82
|
-
if (isRecord(d) && d[key] !== undefined) return d[key];
|
|
83
|
-
}
|
|
84
|
-
return undefined;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/** The sibling reasoning STRINGS, in precedence order. See `applyReasoning`. */
|
|
88
|
-
const REASONING_KEYS = ['reasoning', 'reasoning_content'] as const;
|
|
89
|
-
|
|
90
|
-
/** First non-empty sibling string wins. These are alternative SPELLINGS of the
|
|
91
|
-
* same text, never two halves of it, so this coalesces and never concatenates. */
|
|
92
|
-
function siblingReasoning(delta: Record<string, unknown>): string | undefined {
|
|
93
|
-
for (const key of REASONING_KEYS) {
|
|
94
|
-
const raw = str(delta[key]);
|
|
95
|
-
if (raw !== undefined && raw !== '') return raw;
|
|
96
|
-
}
|
|
97
|
-
return undefined;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* FINDINGS: OpenRouter frequently puts the SAME text in `reasoning` AND in
|
|
102
|
-
* `reasoning_details` on the same delta. Concatenating both doubles every
|
|
103
|
-
* reasoning token, so `reasoning` wins and details are only a text FALLBACK.
|
|
104
|
-
*
|
|
105
|
-
* `reasoning_details` is still read in BOTH cases, for `reasoningRaw`, the block
|
|
106
|
-
* index and the signature. It is the provider's own block list, and dropping it
|
|
107
|
-
* is exactly the Anthropic 400 this entry exists to avoid.
|
|
108
|
-
*
|
|
109
|
-
* SIBLING NAMES. Chat-completions says nothing about reasoning, so every vendor
|
|
110
|
-
* extended it on its own and each parser in the wild reads a different subset:
|
|
111
|
-
* `reasoning` is what OpenRouter normalises to, `reasoning_content` is what
|
|
112
|
-
* DeepSeek's own API emits and what LiteLLM forwards. Nobody is ahead here and
|
|
113
|
-
* there is no canonical spelling. A stream that only ever went through
|
|
114
|
-
* OpenRouter never shows the other name, which is the whole reason this one
|
|
115
|
-
* stayed invisible: reading `reasoning` alone loses a DeepSeek-direct stream's
|
|
116
|
-
* reasoning completely, and silently.
|
|
117
|
-
*
|
|
118
|
-
* PRECEDENCE. The siblings coalesce, first non-empty wins, and both outrank the
|
|
119
|
-
* details text. `reasoning` is checked first, which is what keeps this additive:
|
|
120
|
-
* any stream that already carried `reasoning` parses byte for byte as it did
|
|
121
|
-
* before, so the order is only observable on a provider that disagrees with
|
|
122
|
-
* itself. A gateway that aliases one spelling onto the other sends identical
|
|
123
|
-
* text in both, and summing them would double every token exactly the way
|
|
124
|
-
* summing `reasoning` and `reasoning_details` does.
|
|
125
|
-
*
|
|
126
|
-
* SCOPE, and it is NOT a closed class. This covers the NAMING axis only.
|
|
127
|
-
* Reasoning on this wire is fragmented three ways STRUCTURALLY: a sibling string
|
|
128
|
-
* (here), a block array (`reasoning_details`), and reasoning carried inside a
|
|
129
|
-
* polymorphic `content` array. Aliasing bridges names, not shapes. A fourth
|
|
130
|
-
* spelling is one entry in REASONING_KEYS; a fourth SHAPE is not, and needs its
|
|
131
|
-
* own branch plus its own fixture.
|
|
132
|
-
*/
|
|
133
|
-
function applyReasoning(delta: Record<string, unknown>, out: ModelStreamChunk): void {
|
|
134
|
-
const details = Array.isArray(delta.reasoning_details) ? delta.reasoning_details : undefined;
|
|
135
|
-
const primary = siblingReasoning(delta);
|
|
136
|
-
const fallback = details ? detailText(details) : undefined;
|
|
137
|
-
const hasDetails = details !== undefined && details.length > 0;
|
|
138
|
-
const text = primary ?? fallback;
|
|
139
|
-
if (text === undefined && !hasDetails) return;
|
|
140
|
-
|
|
141
|
-
// '' is deliberate and meaningful: an encrypted-only detail entry has no
|
|
142
|
-
// readable text but must still produce a reasoning part carrying its payload.
|
|
143
|
-
out.reasoning = text ?? '';
|
|
144
|
-
if (!hasDetails) return;
|
|
145
|
-
|
|
146
|
-
out.reasoningRaw = { source: 'openai.reasoning_details', payload: details };
|
|
147
|
-
const index = num(detailField(details, 'index'));
|
|
148
|
-
if (index !== undefined) out.reasoningIndex = index;
|
|
149
|
-
const signature = str(detailField(details, 'signature'));
|
|
150
|
-
if (signature !== undefined) out.reasoningSignature = signature;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
function sourcesOf(delta: Record<string, unknown>): MessageSource[] {
|
|
154
|
-
if (!Array.isArray(delta.annotations)) return [];
|
|
155
|
-
const out: MessageSource[] = [];
|
|
156
|
-
for (const a of delta.annotations) {
|
|
157
|
-
if (!isRecord(a)) continue;
|
|
158
|
-
const citation = isRecord(a.url_citation) ? a.url_citation : undefined;
|
|
159
|
-
const url = str(citation?.url);
|
|
160
|
-
if (!url) continue;
|
|
161
|
-
const source: MessageSource = { url };
|
|
162
|
-
const title = str(citation?.title);
|
|
163
|
-
if (title) source.title = title;
|
|
164
|
-
const snippet = str(citation?.content);
|
|
165
|
-
if (snippet) source.snippet = snippet;
|
|
166
|
-
out.push(source);
|
|
167
|
-
}
|
|
168
|
-
return out;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
function pushOpenAI(frame: unknown): ModelStreamChunk[] {
|
|
172
|
-
if (!isRecord(frame)) return [];
|
|
173
|
-
const out: ModelStreamChunk = {};
|
|
174
|
-
|
|
175
|
-
const err = frame.error;
|
|
176
|
-
if (isRecord(err)) {
|
|
177
|
-
const message = str(err.message);
|
|
178
|
-
if (message) {
|
|
179
|
-
out.error = { message };
|
|
180
|
-
const code = err.code;
|
|
181
|
-
if (typeof code === 'string' || typeof code === 'number') out.error.code = code;
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// Stated per frame and reported per frame, no dedupe: a stream that changes
|
|
186
|
-
// its mind mid-turn is a finding, and collapsing it would hide one.
|
|
187
|
-
const model = str(frame.model);
|
|
188
|
-
if (model) out.model = model;
|
|
189
|
-
|
|
190
|
-
const usage = usageOf(frame.usage);
|
|
191
|
-
if (usage) out.usage = usage;
|
|
192
|
-
|
|
193
|
-
const choices = Array.isArray(frame.choices) ? frame.choices : [];
|
|
194
|
-
const choice = isRecord(choices[0]) ? choices[0] : undefined;
|
|
195
|
-
if (choice) {
|
|
196
|
-
const delta = isRecord(choice.delta) ? choice.delta : undefined;
|
|
197
|
-
if (delta) {
|
|
198
|
-
const content = str(delta.content);
|
|
199
|
-
if (content) out.text = content;
|
|
200
|
-
|
|
201
|
-
applyReasoning(delta, out);
|
|
202
|
-
|
|
203
|
-
if (Array.isArray(delta.tool_calls) && delta.tool_calls.length > 0) {
|
|
204
|
-
const calls = delta.tool_calls
|
|
205
|
-
.map((raw, i) => toolCallDelta(raw, i))
|
|
206
|
-
.filter((c): c is ModelToolCallDelta => c !== undefined);
|
|
207
|
-
if (calls.length > 0) out.toolCalls = calls;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
const sources = sourcesOf(delta);
|
|
211
|
-
if (sources.length > 0) out.sources = sources;
|
|
212
|
-
}
|
|
213
|
-
const finish = str(choice.finish_reason);
|
|
214
|
-
if (finish) out.finishReason = finish;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
return Object.keys(out).length > 0 ? [out] : [];
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
/** Stateless: every frame is self-describing, so `open()` returns a reader with
|
|
221
|
-
* no closure state. The interface still calls it per stream, which is what lets
|
|
222
|
-
* the stateful Anthropic format use the same seam. */
|
|
223
|
-
export const openaiChatFormat: WireFormat = {
|
|
224
|
-
id: 'openai.chat-completions',
|
|
225
|
-
open(): WireFormatReader {
|
|
226
|
-
return { push: pushOpenAI };
|
|
227
|
-
},
|
|
228
|
-
};
|
package/src/wire/index.ts
DELETED
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
// @kitn.ai/ui/wire: the model-stream adapter.
|
|
2
|
-
//
|
|
3
|
-
// Separate from ./state on purpose. `state` is I/O-free pure functions over
|
|
4
|
-
// ChatMessage[]; `wire` touches Response, TextDecoder and byte streams. Keeping
|
|
5
|
-
// them apart leaves the bring-your-own-transport consumer at zero cost, and
|
|
6
|
-
// gives a future AG-UI format an obvious home. The cost is that importing both
|
|
7
|
-
// entries ships state/parts.ts twice, about 2 KB.
|
|
8
|
-
//
|
|
9
|
-
// The kit PARSES. The consumer FETCHES. There is no client, no key handling and
|
|
10
|
-
// no provider SDK anywhere below this file.
|
|
11
|
-
|
|
12
|
-
export { readModelStream, readOpenAIStream, readAnthropicStream, WireError } from './read';
|
|
13
|
-
export type { StreamSource, ReadOptions } from './read';
|
|
14
|
-
|
|
15
|
-
export { consumeModelStream, createToolCallAccumulator } from './consume';
|
|
16
|
-
export { applyToolOutput, applyToolFailure, bufferText } from './sink-helpers';
|
|
17
|
-
|
|
18
|
-
export { toOpenAIMessages, toAnthropicMessages, WireEncodeError } from './encode';
|
|
19
|
-
export type {
|
|
20
|
-
AnthropicContentBlock,
|
|
21
|
-
AnthropicEncodeOptions,
|
|
22
|
-
AnthropicWireMessage,
|
|
23
|
-
FileEncodeOptions,
|
|
24
|
-
OpenAIContentPart,
|
|
25
|
-
OpenAIEncodeOptions,
|
|
26
|
-
OpenAIReasoningDetail,
|
|
27
|
-
OpenAIToolCall,
|
|
28
|
-
OpenAIWireMessage,
|
|
29
|
-
UnencodableFilePolicy,
|
|
30
|
-
} from './encode';
|
|
31
|
-
|
|
32
|
-
// What the encoders CAN represent, published as a value.
|
|
33
|
-
//
|
|
34
|
-
// The point is not introspection. A consumer who wants their own file picker,
|
|
35
|
-
// their own validation and their own error copy can build all three off
|
|
36
|
-
// `encodableMediaTypes()` and `resolveMediaPolicy(...).decide(...)` without
|
|
37
|
-
// using our composer at all -- the kit states a fact about itself and the
|
|
38
|
-
// application decides what to do about it. `<kai-chat accept="...">` is a
|
|
39
|
-
// convenience over exactly this, not a separate mechanism.
|
|
40
|
-
export { encodableMediaTypes, resolveMediaPolicy } from './media-types';
|
|
41
|
-
export type {
|
|
42
|
-
EncodableKind,
|
|
43
|
-
MediaDecision,
|
|
44
|
-
MediaPolicy,
|
|
45
|
-
MediaPolicyOptions,
|
|
46
|
-
MediaTypeFilter,
|
|
47
|
-
} from './media-types';
|
|
48
|
-
|
|
49
|
-
export { openaiChatFormat } from './formats/openai';
|
|
50
|
-
export { anthropicMessagesFormat } from './formats/anthropic';
|
|
51
|
-
|
|
52
|
-
export { sseDataFrames, sseJson, readableToAsyncIterable } from './sse';
|
|
53
|
-
export type { ByteSource } from './sse';
|
|
54
|
-
|
|
55
|
-
// The diagnostic event stream: metadata about what the pipeline SAW.
|
|
56
|
-
//
|
|
57
|
-
// Only the subscription and the types are public. `emitWireDiagnostic`,
|
|
58
|
-
// `wireDiagnosticsActive` and `nextStreamId` stay internal on purpose -- they
|
|
59
|
-
// are the producer side, and a consumer that can forge events or reset the
|
|
60
|
-
// stream counter can make a panel lie about a stream that never happened.
|
|
61
|
-
export { subscribeWireDiagnostics } from './diagnostics';
|
|
62
|
-
export type {
|
|
63
|
-
AppRequestEvent,
|
|
64
|
-
EncodeAttachmentReport,
|
|
65
|
-
EncodeDroppedEvent,
|
|
66
|
-
EncodeRequestEvent,
|
|
67
|
-
WireCloseEvent,
|
|
68
|
-
WireDiagnosticBase,
|
|
69
|
-
WireDiagnosticEvent,
|
|
70
|
-
WireFailedEvent,
|
|
71
|
-
WireFrameEvent,
|
|
72
|
-
WireInterruptedEvent,
|
|
73
|
-
WireOpenEvent,
|
|
74
|
-
WirePartEvent,
|
|
75
|
-
} from './diagnostics';
|
|
76
|
-
|
|
77
|
-
export { normalizeStopReason } from './chunk';
|
|
78
|
-
export type {
|
|
79
|
-
AssistantStreamSink,
|
|
80
|
-
ConsumeOptions,
|
|
81
|
-
ModelStreamChunk,
|
|
82
|
-
ModelToolCall,
|
|
83
|
-
ModelToolCallDelta,
|
|
84
|
-
ModelTurn,
|
|
85
|
-
ModelUsage,
|
|
86
|
-
StopReason,
|
|
87
|
-
WireFormat,
|
|
88
|
-
WireFormatReader,
|
|
89
|
-
} from './chunk';
|
|
90
|
-
|
|
91
|
-
// The content-model types every signature above mentions, re-exported so a
|
|
92
|
-
// consumer importing only from '@kitn.ai/ui/wire' can annotate the values these
|
|
93
|
-
// functions take and return without a second import.
|
|
94
|
-
export type { ChatMessage, MessagePart, MessageSource, RawOrigin } from '../elements/chat-types';
|
|
95
|
-
export type { ToolPart } from '../components/tool-types';
|
package/src/wire/media-types.ts
DELETED
|
@@ -1,344 +0,0 @@
|
|
|
1
|
-
// THE media-type declaration. One table, read by every layer that has an
|
|
2
|
-
// opinion about attachments.
|
|
3
|
-
//
|
|
4
|
-
// WHY THIS FILE EXISTS AT ALL. Two features want the same knowledge: the
|
|
5
|
-
// composer needs to know what a user is allowed to stage, and the encoders need
|
|
6
|
-
// to know what they can turn into provider content. Those are the same set. When
|
|
7
|
-
// each grows its own copy they drift, and the drift is not theoretical -- it is
|
|
8
|
-
// the exact defect #186 fixed one layer down, where a composer happily staged a
|
|
9
|
-
// `blob:` URL that the encoder could not represent. So the set is declared ONCE,
|
|
10
|
-
// here, and both layers derive from it rather than restating it.
|
|
11
|
-
//
|
|
12
|
-
// THE BOUNDARY THIS DRAWS. The kit owns "what CAN be encoded" -- a fact about
|
|
13
|
-
// `encode.ts`, not a policy. The developer owns "what I want to ALLOW", and can
|
|
14
|
-
// only ever NARROW the kit's set, never widen it: widening would just move a
|
|
15
|
-
// provider 400 later in the request. That asymmetry is why the filter is a
|
|
16
|
-
// declarative list rather than a hook, and why `encodableMediaTypes()` is public.
|
|
17
|
-
//
|
|
18
|
-
// No I/O, like the rest of `wire/`. Pattern matching and, for text, decoding
|
|
19
|
-
// base64 that is already inline -- computation over bytes already in hand, never
|
|
20
|
-
// a fetch.
|
|
21
|
-
|
|
22
|
-
/** What a media type BECOMES on the wire. Named for the Anthropic block set,
|
|
23
|
-
* which is the narrower of the two: `text` / `image` / `document` and nothing
|
|
24
|
-
* else. There is no arbitrary-file block on either API, which is why a text
|
|
25
|
-
* file has to ride as text content and a `.zip` has no representation at all. */
|
|
26
|
-
export type EncodableKind = 'image' | 'document' | 'text';
|
|
27
|
-
|
|
28
|
-
// WHAT A BROWSER ACTUALLY CALLS A FILE -- ONE OBSERVATION, NOT A STANDING FACT.
|
|
29
|
-
//
|
|
30
|
-
// Observed 2026-08-13 (UTC), Chrome 151.0.7922.137, macOS 26.5 arm64, with the
|
|
31
|
-
// files handed to a real `<input type="file">` so the browser built every `File`
|
|
32
|
-
// itself. Re-run it rather than trusting it:
|
|
33
|
-
// node scripts/probe-file-media-types.mjs
|
|
34
|
-
//
|
|
35
|
-
// .txt text/plain .js text/javascript .png image/png
|
|
36
|
-
// .md text/markdown .css text/css .zip application/zip
|
|
37
|
-
// .csv text/csv .html text/html .pdf application/pdf
|
|
38
|
-
// .json application/json .sh text/x-sh
|
|
39
|
-
// .xml text/xml .py text/x-python-script
|
|
40
|
-
// .yaml application/x-yaml .yml application/x-yaml
|
|
41
|
-
// .ts .tsx .rs .go .sql .toml .log -> "" (the browser could not name them)
|
|
42
|
-
//
|
|
43
|
-
// THESE VALUES ARE PLATFORM-SPECIFIC. Nineteen of the twenty-two match macOS's
|
|
44
|
-
// own `preferredMIMEType`, so the browser is answering out of the OS type
|
|
45
|
-
// database and Linux and Windows will answer differently. Nothing returned
|
|
46
|
-
// `application/octet-stream` -- Chrome's failure mode here is the EMPTY STRING.
|
|
47
|
-
// (`.ts` is not a TypeScript file to macOS: it resolves to an MPEG-2
|
|
48
|
-
// transport-stream UTI, which carries no MIME string at all, which is why it
|
|
49
|
-
// comes back empty rather than as `video/mp2t`.)
|
|
50
|
-
//
|
|
51
|
-
// EXACTLY TWO THINGS BELOW REST ON THIS TABLE, and both survive it being wrong
|
|
52
|
-
// somewhere else:
|
|
53
|
-
// 1. `application/x-yaml` is a row because it was measured here. A platform
|
|
54
|
-
// that says something else does not make this row wrong, only incomplete.
|
|
55
|
-
// 2. Seven of the twenty-two extensions are the ones a developer is likeliest
|
|
56
|
-
// to attach to a coding chat, and the browser names NONE of them. That is
|
|
57
|
-
// why an unnamed file is decided by DECODING its bytes and never by reading
|
|
58
|
-
// its filename -- see `decide()`. Another OS moves WHICH rows come back
|
|
59
|
-
// empty; it cannot move the fact that some do.
|
|
60
|
-
// Anything else read off this table is being read too hard.
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* ★ THE DECLARATION. Everything else in this file, and both `accept` surfaces,
|
|
64
|
-
* are derived from this array. Adding a row teaches the encoder AND widens the
|
|
65
|
-
* composer's picker; deleting one narrows both. If you find yourself writing a
|
|
66
|
-
* second list of media types anywhere in this repo, delete it and read this.
|
|
67
|
-
*
|
|
68
|
-
* Patterns are HTML `accept` syntax: an exact type (`image/png`) or a subtype
|
|
69
|
-
* wildcard (`text/*`). File EXTENSIONS (`.md`) are deliberately not supported --
|
|
70
|
-
* they map to media types only by convention, and guessing is how a `.md` full
|
|
71
|
-
* of base64 becomes a 400. An extension in a developer's `accept` THROWS rather
|
|
72
|
-
* than matching nothing; a file the browser could not name is settled by
|
|
73
|
-
* decoding it. Neither path ever consults a filename.
|
|
74
|
-
*
|
|
75
|
-
* On the image list: JPEG, PNG, GIF and WebP are the four formats BOTH APIs
|
|
76
|
-
* document. SVG and BMP are the ones people actually try, and both are a 400 at
|
|
77
|
-
* request time, so they are absent on purpose.
|
|
78
|
-
*
|
|
79
|
-
* On the text list: `text/*` is the core, because a media type in that tree is
|
|
80
|
-
* text by definition of the tree. The `application/` rows are the judgement
|
|
81
|
-
* calls -- textual formats that IANA files outside `text/` for historical
|
|
82
|
-
* reasons, and that people genuinely attach. `application/json` and
|
|
83
|
-
* `application/xml` are the two obvious ones.
|
|
84
|
-
*
|
|
85
|
-
* On YAML, since it is two rows for one format: `application/x-yaml` is what
|
|
86
|
-
* Chrome hands back for a `.yaml` on macOS, MEASURED in the table above.
|
|
87
|
-
* `application/yaml` is the IANA-registered type (RFC 9512, 2024) and is what a
|
|
88
|
-
* correctly configured server sends, so it is here on REGISTRY grounds rather
|
|
89
|
-
* than on a measurement. `text/yaml` and `text/x-yaml` are in the wild too and
|
|
90
|
-
* need no row of their own: `text/*` already covers both.
|
|
91
|
-
*/
|
|
92
|
-
const ENCODABLE: readonly { readonly pattern: string; readonly kind: EncodableKind }[] = [
|
|
93
|
-
{ pattern: 'image/jpeg', kind: 'image' },
|
|
94
|
-
{ pattern: 'image/png', kind: 'image' },
|
|
95
|
-
{ pattern: 'image/gif', kind: 'image' },
|
|
96
|
-
{ pattern: 'image/webp', kind: 'image' },
|
|
97
|
-
{ pattern: 'application/pdf', kind: 'document' },
|
|
98
|
-
{ pattern: 'text/*', kind: 'text' },
|
|
99
|
-
{ pattern: 'application/json', kind: 'text' },
|
|
100
|
-
{ pattern: 'application/xml', kind: 'text' },
|
|
101
|
-
{ pattern: 'application/x-yaml', kind: 'text' },
|
|
102
|
-
{ pattern: 'application/yaml', kind: 'text' },
|
|
103
|
-
];
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* The media type an unnamed file gets once its BYTES have proven to be UTF-8
|
|
107
|
-
* text, and the type whose presence in the effective policy is what permits that
|
|
108
|
-
* proof to be attempted at all.
|
|
109
|
-
*
|
|
110
|
-
* `text/plain` is not a guess dressed up as a fact. It is the media type for
|
|
111
|
-
* "text, no further structure claimed", which is exactly what a successful
|
|
112
|
-
* decode establishes and the most any decode could ever establish. A `.rs` file
|
|
113
|
-
* arriving as `text/plain` is true; arriving as `text/x-rust` would be the
|
|
114
|
-
* filename talking.
|
|
115
|
-
*/
|
|
116
|
-
export const UNNAMED_TEXT_MEDIA_TYPE = 'text/plain';
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Media types that say nothing about the bytes they label.
|
|
120
|
-
*
|
|
121
|
-
* The empty string is what `File.type` is when the OS type database had no
|
|
122
|
-
* answer (seven of the twenty-two rows above). `application/octet-stream` is the
|
|
123
|
-
* same non-answer written down: RFC 2046 defines it as "arbitrary binary data",
|
|
124
|
-
* and it is what `FileReader.readAsDataURL` substitutes for an empty type --
|
|
125
|
-
* measured in Chrome 151 AND in the jsdom the unit suite runs on, both of which
|
|
126
|
-
* produce `data:application/octet-stream;base64,...` for a `File` whose `type`
|
|
127
|
-
* is `''`. So the same typeless file reaches the composer as `''` and reaches
|
|
128
|
-
* the encoder as `application/octet-stream`, and both spellings have to mean the
|
|
129
|
-
* same thing or the two layers disagree about one file.
|
|
130
|
-
*/
|
|
131
|
-
const NAMES_NOTHING: readonly string[] = ['', 'application/octet-stream'];
|
|
132
|
-
|
|
133
|
-
/** A developer's narrowing filter. A comma-separated string (so it works as an
|
|
134
|
-
* HTML attribute under the `kai-` contract) or an array (so it composes in JS).
|
|
135
|
-
* Both spell the same thing; the string form is what `<kai-chat accept="...">`
|
|
136
|
-
* takes and what the `kai` MCP scaffolder can emit as data. */
|
|
137
|
-
export type MediaTypeFilter = string | readonly string[];
|
|
138
|
-
|
|
139
|
-
/** One media type, decided against the effective set.
|
|
140
|
-
*
|
|
141
|
-
* `unsupported` and `filtered` are split because the developer needs different
|
|
142
|
-
* things from them: `unsupported` means fix your expectations (no API takes
|
|
143
|
-
* this), `filtered` means fix your `accept` (the kit could have sent it). */
|
|
144
|
-
export type MediaDecision =
|
|
145
|
-
| { status: 'allowed'; kind: EncodableKind }
|
|
146
|
-
| { status: 'unsupported' }
|
|
147
|
-
| { status: 'filtered' }
|
|
148
|
-
/**
|
|
149
|
-
* Nobody named this file, so its media type cannot decide it. LOOK AT THE
|
|
150
|
-
* BYTES: decode them as UTF-8 (`classifyAttachment` does), and it is `text`
|
|
151
|
-
* if they decode and unencodable if they do not. Do not look at the filename.
|
|
152
|
-
*
|
|
153
|
-
* ★ THIS VARIANT IS WHERE THE DEVELOPER'S FILTER IS ENFORCED, so the caller
|
|
154
|
-
* cannot get it wrong by forgetting to check. It is returned ONLY when the
|
|
155
|
-
* effective policy still admits `text/plain` -- the one thing a decode can
|
|
156
|
-
* establish. Narrow `accept` to `image/png` and an unnamed file comes back
|
|
157
|
-
* `unsupported` instead, so the decode path is unreachable and a typeless
|
|
158
|
-
* `.rs` cannot slip past an images-only filter. That is the whole reason the
|
|
159
|
-
* check lives here rather than at each call site: there is one place to get
|
|
160
|
-
* right, and the callers physically cannot reach the bytes without being told
|
|
161
|
-
* to.
|
|
162
|
-
*/
|
|
163
|
-
| { status: 'undetermined' };
|
|
164
|
-
|
|
165
|
-
export interface MediaPolicy {
|
|
166
|
-
/** The effective patterns: the kit's capability set narrowed by the
|
|
167
|
-
* developer's filter. Never wider than `encodableMediaTypes()`. */
|
|
168
|
-
readonly types: readonly string[];
|
|
169
|
-
/** The same set as an HTML `accept` attribute value, ready to put on an
|
|
170
|
-
* `<input type="file">`. This is what makes the picker and the encoder
|
|
171
|
-
* provably one thing rather than two lists that agree today. */
|
|
172
|
-
readonly accept: string;
|
|
173
|
-
/**
|
|
174
|
-
* What this policy makes of one media type, including "I cannot tell from
|
|
175
|
-
* this, go and read the bytes" -- see `undetermined`.
|
|
176
|
-
*
|
|
177
|
-
* The primitive behind "expose information, do not make decisions": it answers
|
|
178
|
-
* a question and returns a fact, so a consumer can build their own picker,
|
|
179
|
-
* their own validation and their own error copy on top of it without the kit
|
|
180
|
-
* deciding anything on their behalf.
|
|
181
|
-
*/
|
|
182
|
-
decide(mediaType: string | undefined): MediaDecision;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
export interface MediaPolicyOptions {
|
|
186
|
-
/** Narrow the kit's capability set. Omitted means the full set. Anything here
|
|
187
|
-
* that the kit cannot encode is dropped rather than honoured, because
|
|
188
|
-
* allowing it would only relocate the failure to the provider. */
|
|
189
|
-
accept?: MediaTypeFilter;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// NOTE ON WHAT IS DELIBERATELY ABSENT: there is no size cap, no file count, and
|
|
193
|
-
// no truncation path here, and their absence is a decision rather than an
|
|
194
|
-
// omission. How many bytes to send is a cost decision that lands on the
|
|
195
|
-
// consumer's invoice, and a component library that grows a limits engine starts
|
|
196
|
-
// making application choices on their behalf. The kit encodes what it is given.
|
|
197
|
-
// A developer who wants a ceiling has `File.size` and `decide()` and can enforce
|
|
198
|
-
// one in three lines of their own code, where it belongs.
|
|
199
|
-
|
|
200
|
-
const normalize = (value: string): string => value.trim().toLowerCase();
|
|
201
|
-
|
|
202
|
-
/** Does this media type say anything at all about the bytes it labels? Exported
|
|
203
|
-
* so `files.ts` decides "nameless" by the same rule `decide()` does, rather
|
|
204
|
-
* than growing a second opinion about which spellings mean "I don't know". */
|
|
205
|
-
export const namesNothing = (mediaType: string | undefined): boolean =>
|
|
206
|
-
NAMES_NOTHING.includes(mediaType === undefined ? '' : normalize(mediaType));
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* A file EXTENSION in `accept` is a hard error, and the loudness is the point.
|
|
210
|
-
*
|
|
211
|
-
* HTML's own `accept` takes extensions, the prop is called `accept`, and it is
|
|
212
|
-
* documented as HTML `accept` syntax -- so `accept=".py"` is a reasonable thing
|
|
213
|
-
* for a developer to write, and it is a trap. Measured on this branch before the
|
|
214
|
-
* throw existed: `.py` alone resolved to zero effective types and a picker with
|
|
215
|
-
* `accept=""`, a composer that silently accepted NOTHING. `".py,text/plain"` was
|
|
216
|
-
* worse, because the half that worked made it read as correct while the `.py`
|
|
217
|
-
* vanished without a word.
|
|
218
|
-
*
|
|
219
|
-
* A silent empty set is the one outcome nobody can debug, so this throws. It is
|
|
220
|
-
* safe to throw: no released version of this kit ships the prop, so no existing
|
|
221
|
-
* app can be relying on the old silence.
|
|
222
|
-
*/
|
|
223
|
-
const assertMediaType = (entry: string): string => {
|
|
224
|
-
if (!entry.startsWith('.')) return entry;
|
|
225
|
-
throw new Error(
|
|
226
|
-
`\`accept\` entry "${entry}" is a file extension, and this kit takes media types only. ` +
|
|
227
|
-
`HTML's own accept attribute does take extensions, which is exactly why this throws rather ` +
|
|
228
|
-
`than quietly matching nothing. Use a media type instead -- "text/*" covers source files, ` +
|
|
229
|
-
`"image/*" images, "application/pdf" PDFs, and \`encodableMediaTypes()\` is the full set. ` +
|
|
230
|
-
`You lose nothing by dropping the extension: a file the browser cannot name is decided by ` +
|
|
231
|
-
`decoding its bytes, which is stronger than matching its name.`,
|
|
232
|
-
);
|
|
233
|
-
};
|
|
234
|
-
|
|
235
|
-
const splitFilter = (filter: MediaTypeFilter): string[] =>
|
|
236
|
-
(typeof filter === 'string' ? filter.split(',') : filter)
|
|
237
|
-
.map(normalize)
|
|
238
|
-
.filter((v) => v !== '')
|
|
239
|
-
.map(assertMediaType);
|
|
240
|
-
|
|
241
|
-
/** `image/png` -> `['image','png']`; `text/*` -> `['text','*']`. */
|
|
242
|
-
const parts = (pattern: string): [string, string] => {
|
|
243
|
-
const slash = pattern.indexOf('/');
|
|
244
|
-
return slash === -1 ? [pattern, ''] : [pattern.slice(0, slash), pattern.slice(slash + 1)];
|
|
245
|
-
};
|
|
246
|
-
|
|
247
|
-
const matches = (pattern: string, mediaType: string): boolean => {
|
|
248
|
-
const [pType, pSub] = parts(pattern);
|
|
249
|
-
const [mType, mSub] = parts(mediaType);
|
|
250
|
-
if (pType !== '*' && pType !== mType) return false;
|
|
251
|
-
return pSub === '*' || pSub === mSub;
|
|
252
|
-
};
|
|
253
|
-
|
|
254
|
-
/**
|
|
255
|
-
* The narrower of two patterns, or `undefined` when they do not overlap.
|
|
256
|
-
*
|
|
257
|
-
* This is what "narrow, never widen" means mechanically. `accept: 'image/*'`
|
|
258
|
-
* against a capability of `image/png` yields `image/png`, not `image/*`: the
|
|
259
|
-
* developer asked for images, and the kit's answer is the images it can actually
|
|
260
|
-
* send. Conversely `accept: 'text/markdown'` against a capability of `text/*`
|
|
261
|
-
* yields `text/markdown`, because there the developer is the narrower one.
|
|
262
|
-
*/
|
|
263
|
-
const intersect = (capability: string, filter: string): string | undefined => {
|
|
264
|
-
if (matches(capability, filter)) return filter;
|
|
265
|
-
if (matches(filter, capability)) return capability;
|
|
266
|
-
return undefined;
|
|
267
|
-
};
|
|
268
|
-
|
|
269
|
-
/**
|
|
270
|
-
* Every media type this kit's encoders can represent, as HTML `accept` patterns.
|
|
271
|
-
*
|
|
272
|
-
* PUBLIC ON PURPOSE, and not merely for introspection: a consumer who wants
|
|
273
|
-
* their own picker, their own validation and their own error copy can build all
|
|
274
|
-
* three against this without touching `<kai-chat>` at all. That demotes the
|
|
275
|
-
* `accept` prop from "the only door" to a convenience over a published fact,
|
|
276
|
-
* which is the composition-first shape this project already committed to.
|
|
277
|
-
*/
|
|
278
|
-
export function encodableMediaTypes(): readonly string[] {
|
|
279
|
-
return ENCODABLE.map((entry) => entry.pattern);
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
/**
|
|
283
|
-
* The effective policy: the kit's capability set narrowed by the developer's
|
|
284
|
-
* filter, plus the text ceiling.
|
|
285
|
-
*
|
|
286
|
-
* BOTH LAYERS CALL THIS. The composer calls it to build its picker `accept`
|
|
287
|
-
* attribute and to filter what actually gets staged; the encoders call it to
|
|
288
|
-
* decide what reaches the wire. That shared call is the whole design -- it is
|
|
289
|
-
* why changing `ENCODABLE` above moves the picker and the encoder together
|
|
290
|
-
* rather than moving one and leaving the other to be noticed in production.
|
|
291
|
-
*/
|
|
292
|
-
export function resolveMediaPolicy(options: MediaPolicyOptions = {}): MediaPolicy {
|
|
293
|
-
const filter = options.accept === undefined ? undefined : splitFilter(options.accept);
|
|
294
|
-
|
|
295
|
-
const effective: { pattern: string; kind: EncodableKind }[] = [];
|
|
296
|
-
for (const capability of ENCODABLE) {
|
|
297
|
-
if (filter === undefined) {
|
|
298
|
-
effective.push({ ...capability });
|
|
299
|
-
continue;
|
|
300
|
-
}
|
|
301
|
-
for (const wanted of filter) {
|
|
302
|
-
const overlap = intersect(capability.pattern, wanted);
|
|
303
|
-
// A capability can overlap several filter entries (`text/*` against
|
|
304
|
-
// `text/plain,text/csv`), and each overlap is its own effective pattern.
|
|
305
|
-
if (overlap !== undefined && !effective.some((e) => e.pattern === overlap)) {
|
|
306
|
-
effective.push({ pattern: overlap, kind: capability.kind });
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
return {
|
|
312
|
-
types: effective.map((e) => e.pattern),
|
|
313
|
-
accept: effective.map((e) => e.pattern).join(','),
|
|
314
|
-
decide(mediaType) {
|
|
315
|
-
const type = mediaType === undefined ? '' : normalize(mediaType);
|
|
316
|
-
|
|
317
|
-
if (namesNothing(type)) {
|
|
318
|
-
// Nothing here names the bytes, and the filename is not evidence, so
|
|
319
|
-
// the answer is in the bytes themselves -- but only if the developer's
|
|
320
|
-
// filter would still take what a decode can prove. See the
|
|
321
|
-
// `undetermined` variant: this is the single place that gate exists.
|
|
322
|
-
return effective.some((e) => matches(e.pattern, UNNAMED_TEXT_MEDIA_TYPE))
|
|
323
|
-
? { status: 'undetermined' }
|
|
324
|
-
: // `unsupported` rather than `filtered`, deliberately: `filtered`
|
|
325
|
-
// claims the kit COULD have encoded this file, and nobody has
|
|
326
|
-
// established what it is. Unread and unnamed, it is not a text
|
|
327
|
-
// file the filter excluded -- it is a file with no known type.
|
|
328
|
-
{ status: 'unsupported' };
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
const hit = effective.find((e) => matches(e.pattern, type));
|
|
332
|
-
if (hit) return { status: 'allowed', kind: hit.kind };
|
|
333
|
-
// Distinguishing these two is the point: one is the kit's limit, the
|
|
334
|
-
// other is the developer's own filter looking back at them.
|
|
335
|
-
return ENCODABLE.some((e) => matches(e.pattern, type))
|
|
336
|
-
? { status: 'filtered' }
|
|
337
|
-
: { status: 'unsupported' };
|
|
338
|
-
},
|
|
339
|
-
};
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
/** The policy every caller gets when nobody narrowed anything: the kit's full
|
|
343
|
-
* capability set. Built once -- it has no inputs. */
|
|
344
|
-
export const DEFAULT_MEDIA_POLICY: MediaPolicy = resolveMediaPolicy();
|