@kitn.ai/ui 0.20.1 → 0.22.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 +44 -45
- package/dist/components/artifact-card.d.ts +51 -0
- package/dist/components/artifact.d.ts +1 -1
- package/dist/components/attachments.d.ts +2 -2
- package/dist/components/audio-visualizer/index.d.ts +3 -3
- package/dist/components/audio-visualizer/labs/lab-choreography.d.ts +59 -0
- package/dist/components/audio-visualizer/labs/lab-shaders.d.ts +22 -0
- package/dist/components/audio-visualizer/labs/lab-visualizer.d.ts +18 -0
- package/dist/components/audio-visualizer/variant-aurora.d.ts +2 -2
- package/dist/components/audio-visualizer/variant-bar.d.ts +2 -2
- package/dist/components/audio-visualizer/variant-custom.d.ts +2 -2
- package/dist/components/audio-visualizer/variant-grid.d.ts +1 -1
- package/dist/components/audio-visualizer/variant-radial.d.ts +1 -1
- package/dist/components/audio-visualizer/variant-wave.d.ts +1 -1
- package/dist/components/card-fallback.d.ts +12 -2
- package/dist/components/card-renderer.d.ts +63 -2
- package/dist/components/chat-container.d.ts +3 -0
- package/dist/components/chat-scope-picker.d.ts +2 -2
- package/dist/components/chat-thread.d.ts +77 -18
- package/dist/components/choice-card.d.ts +3 -28
- package/dist/components/composer-dom.d.ts +1 -1
- package/dist/components/composer-highlight.d.ts +1 -1
- package/dist/components/composer-history.d.ts +1 -1
- package/dist/components/composer.d.ts +1 -1
- package/dist/components/confirm-card.d.ts +3 -18
- package/dist/components/context.d.ts +8 -4
- package/dist/components/conversation-item.d.ts +1 -1
- package/dist/components/conversation-list.d.ts +4 -3
- package/dist/components/embed.d.ts +3 -3
- package/dist/components/file-tree.d.ts +11 -22
- package/dist/components/form-widgets.d.ts +1 -1
- package/dist/components/form.d.ts +3 -44
- package/dist/components/link-preview.d.ts +3 -3
- package/dist/components/loader.d.ts +30 -48
- package/dist/components/message-skills.d.ts +4 -0
- package/dist/components/message.d.ts +84 -21
- package/dist/components/model-switcher.d.ts +2 -2
- package/dist/components/prompt-input.d.ts +2 -2
- package/dist/components/response-compare-types.d.ts +11 -2
- package/dist/components/response-compare.d.ts +3 -3
- package/dist/components/source.d.ts +12 -3
- package/dist/components/tasks-card.d.ts +3 -31
- package/dist/components/thread.d.ts +17 -5
- package/dist/components/toast.d.ts +2 -2
- package/dist/components/tool-classify.d.ts +8 -0
- package/dist/components/tool-types.d.ts +29 -1
- package/dist/components/tool.d.ts +3 -3
- package/dist/components/use-card-resolution.d.ts +1 -1
- package/dist/create-tween-COnPwAQN.js +338 -0
- package/dist/create-tween-DmTUaXWE.js +341 -0
- package/dist/create-tween-qs-jjvNA.js +7 -0
- package/dist/custom-elements.json +1726 -463
- 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-types.d.ts +70 -10
- package/dist/elements/chat-workspace.js +1 -1
- package/dist/elements/chat.js +1 -1
- package/dist/elements/checkpoint.js +1 -1
- package/dist/elements/choice.js +1 -1
- package/dist/elements/chunks/{Icon-Tj58ES_d.js → Icon-CHG5z5L7.js} +1 -1
- package/dist/elements/chunks/{action-icons-ns1Fnedj.js → action-icons-BAtCEEgg.js} +1 -1
- package/dist/elements/chunks/arrow-left-CFPTeL1Y.js +1 -0
- package/dist/elements/chunks/artifact-fBZOE4SO.js +1 -0
- package/dist/elements/chunks/{attachments-mCB5F1Fq.js → attachments-DpYMe5VQ.js} +1 -1
- package/dist/elements/chunks/audio-visualizer-DFwzAcuL.js +1 -0
- package/dist/elements/chunks/{badge-B4Zuajl2.js → badge-D6zodm6E.js} +1 -1
- package/dist/elements/chunks/{button-ZWNif3--.js → button-C42vrGvO.js} +1 -1
- package/dist/elements/chunks/card-renderer-DMZsSuvU.js +1 -0
- package/dist/elements/chunks/card-tags-D8lZ-C_U.js +1 -0
- package/dist/elements/chunks/chat-thread-CNNEhol-.js +1 -0
- package/dist/elements/chunks/check-7Q3K-iXE.js +1 -0
- package/dist/elements/chunks/chevron-down-CpNYLn7E.js +1 -0
- package/dist/elements/chunks/chevron-right-DrH85Fha.js +1 -0
- package/dist/elements/chunks/choice-card-YoWjpsFV.js +1 -0
- package/dist/elements/chunks/circle-C9KRjKYv.js +1 -0
- package/dist/elements/chunks/circle-check-Dmi7Y4zw.js +1 -0
- package/dist/elements/chunks/{circle-x-B7faJFlY.js → circle-x-QIxXda6k.js} +1 -1
- package/dist/elements/chunks/code-block-CqeugReY.js +1 -0
- package/dist/elements/chunks/{collapsible-DM1Ckd4e.js → collapsible-sTv-_5lF.js} +1 -1
- package/dist/elements/chunks/{composer-BguS0QL_.js → composer-ldQyeVEB.js} +2 -2
- package/dist/elements/chunks/confirm-card-C0RNOwfn.js +1 -0
- package/dist/elements/chunks/context-5qgypNHC.js +1 -0
- package/dist/elements/chunks/conversation-list-DLzOacXz.js +1 -0
- package/dist/elements/chunks/create-tween-V4TXZ93X.js +7 -0
- package/dist/elements/chunks/default-input-DeeRSDHc.js +1 -0
- package/dist/elements/chunks/define-lE-QvQFn.js +1 -0
- package/dist/elements/chunks/{disclosure-CIZ5c8nO.js → disclosure-DZSa9jog.js} +1 -1
- package/dist/elements/chunks/{download-Dsbg3ste.js → download-B2ZmnGqW.js} +1 -1
- package/dist/elements/chunks/{dropdown-BDd1-Zgi.js → dropdown-R3B7cSRE.js} +1 -1
- package/dist/elements/chunks/{ellipsis-Dw6qrss-.js → ellipsis-BmxceY4l.js} +1 -1
- package/dist/elements/chunks/embed-BVrtTZPo.js +1 -0
- package/dist/elements/chunks/{external-link-Vt74zAoL.js → external-link-BbdR7YO7.js} +1 -1
- package/dist/elements/chunks/{file-text-CZrZF6_v.js → file-text-D7dijo1A.js} +1 -1
- package/dist/elements/chunks/{file-tree-DF4okKyo.js → file-tree-0upJ5jeb.js} +1 -1
- package/dist/elements/chunks/{folder-glP5rha4.js → folder-D8z58t7B.js} +1 -1
- package/dist/elements/chunks/form-p5BKJNw5.js +1 -0
- package/dist/elements/chunks/hover-card-CN9s2md5.js +1 -0
- package/dist/elements/chunks/{icon-BLb8beoi.js → icon-CNiKP37E.js} +1 -1
- package/dist/elements/chunks/{info-CIsv92JK.js → info-BSnuJiHI.js} +1 -1
- package/dist/elements/chunks/{input-Dds1kFyt.js → input-BauDmzxX.js} +1 -1
- package/dist/elements/chunks/{kbd-De_xbgi9.js → kbd-BlHJaP9b.js} +1 -1
- package/dist/elements/chunks/{link-i4r_k7z4.js → link-DEi5iUWC.js} +1 -1
- package/dist/elements/chunks/link-preview-BaHyM6W4.js +1 -0
- package/dist/elements/chunks/{loader-BmQH4s3V.js → loader-CmJG_Fkq.js} +1 -1
- package/dist/elements/chunks/{markdown-Zi21WhUz.js → markdown-D2jaMpCN.js} +1 -1
- package/dist/elements/chunks/message-C5UfXZ2Z.js +1 -0
- package/dist/elements/chunks/message-CXWLmmLn.js +1 -0
- package/dist/elements/chunks/message-square-hXylwsy0.js +1 -0
- package/dist/elements/chunks/{minimize-2--ifeCGrl.js → minimize-2-C9ytZFMU.js} +1 -1
- package/dist/elements/chunks/model-switcher-CivGfD7-.js +1 -0
- package/dist/elements/chunks/{overlay-kD8dwRv_.js → overlay-DJL_sbOm.js} +1 -1
- package/dist/elements/chunks/{paperclip-q21dYOSK.js → paperclip-CFaB8naM.js} +1 -1
- package/dist/elements/chunks/{progress-bar-BrAIIaaJ.js → progress-bar-Br5SksfR.js} +1 -1
- package/dist/elements/chunks/{prompt-suggestion-Bjn2K-fR.js → prompt-suggestion-CMIvtAhU.js} +1 -1
- package/dist/elements/chunks/reasoning-DpZcNzLq.js +1 -0
- package/dist/elements/chunks/{resizable-Dp4JExql.js → resizable-Q8i3T9a_.js} +1 -1
- package/dist/elements/chunks/{rotate-cw-CFWWkpj2.js → rotate-cw-B5IHCBFV.js} +1 -1
- package/dist/elements/chunks/{scroll-area-Bb0kHH1j.js → scroll-area-eLeydBje.js} +1 -1
- package/dist/elements/chunks/scroll-button-DWbz0yA1.js +1 -0
- package/dist/elements/chunks/{separator-9kRgr1X9.js → separator-Xic78AD_.js} +1 -1
- package/dist/elements/chunks/{settings-B8lmec4D.js → settings-B8H51pAx.js} +1 -1
- package/dist/elements/chunks/{settings-group-I0hEuXiN.js → settings-group-CsG9eYLe.js} +1 -1
- package/dist/elements/chunks/{share-C3lTiYGt.js → share-CNTx0z__.js} +1 -1
- package/dist/elements/chunks/skeleton-CLlHTcMI.js +1 -0
- package/dist/elements/chunks/slots-CIw9RlAe.js +1 -0
- package/dist/elements/chunks/source-BoVcU5ok.js +1 -0
- package/dist/elements/chunks/{star-CSJtGxgv.js → star-CxQVTVhI.js} +1 -1
- package/dist/elements/chunks/{store-CHyW95TP.js → store-CLtS5CUG.js} +1 -1
- package/dist/elements/chunks/tasks-card-CzlkLmUt.js +1 -0
- package/dist/elements/chunks/{text-shimmer-Cn9TmNpI.js → text-shimmer-D5NhpIQ1.js} +1 -1
- package/dist/elements/chunks/textarea-x5Uxsmtm.js +1 -0
- package/dist/elements/chunks/{thumbs-up-PNVVThEF.js → thumbs-up-UobcJnx9.js} +1 -1
- package/dist/elements/chunks/{toast-store-DKbQn3Wn.js → toast-store-BOfs9eXq.js} +1 -1
- package/dist/elements/chunks/{tool-DjTRpIpl.js → tool-Be6Dh_dt.js} +1 -1
- package/dist/elements/chunks/tooltip-RxMxyCLF.js +1 -0
- package/dist/elements/chunks/{triangle-alert-DsHTHCPm.js → triangle-alert-C4yxH2XN.js} +1 -1
- package/dist/elements/chunks/use-card-resolution-jva9ehH6.js +1 -0
- package/dist/elements/chunks/{variant-aurora-Dc4nQUia.js → variant-aurora-Dx4VRcjS.js} +2 -2
- package/dist/elements/chunks/{variant-custom-y_3kVQhS.js → variant-custom-Y1ZTWgEa.js} +1 -1
- package/dist/elements/chunks/{variant-wave-D5OR1-9Z.js → variant-wave-B9gml9Cg.js} +2 -2
- package/dist/elements/chunks/{video-B7vmenoC.js → video-CrPHsytT.js} +1 -1
- package/dist/elements/chunks/x-_-OPHept.js +1 -0
- 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-list.js +1 -1
- package/dist/elements/default-input.d.ts +34 -7
- package/dist/elements/define.d.ts +1 -0
- package/dist/elements/dialog.js +1 -1
- package/dist/elements/disclosure.d.ts +1 -1
- package/dist/elements/editable-label.js +1 -1
- package/dist/elements/element-data-types.d.ts +107 -0
- 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-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/reasoning.js +1 -1
- package/dist/elements/register.d.ts +8 -6
- 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/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/slot-text.d.ts +51 -0
- package/dist/elements/slots.d.ts +21 -0
- 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/validate-messages.d.ts +25 -0
- package/dist/elements/voice-input.js +1 -1
- package/dist/elements/voice-output.js +1 -1
- package/dist/elements.d.ts +2734 -217
- package/dist/index.d.ts +163 -125
- package/dist/index.js +6572 -5990
- package/dist/index.server.d.ts +5 -0
- package/dist/index.server.js +5403 -4860
- package/dist/kai-provider.es.d.ts +5 -0
- package/dist/kai-provider.es.js +79 -77
- package/dist/kai.es.d.ts +5 -0
- package/dist/kai.es.js +1 -1
- package/dist/llms/llms-full.txt +774 -151
- package/dist/llms/llms.txt +57 -5
- package/dist/mcp.es.js +4756 -782
- package/dist/primitives/card-component-types.d.ts +11 -0
- package/dist/primitives/card-data-types.d.ts +196 -0
- package/dist/primitives/card-host.d.ts +1 -1
- package/dist/primitives/card-recovery.d.ts +3 -3
- package/dist/primitives/card-registry.d.ts +4 -14
- package/dist/primitives/card-resolution.d.ts +1 -1
- package/dist/primitives/card-routing.d.ts +1 -1
- package/dist/primitives/card-tags.d.ts +11 -0
- package/dist/primitives/card-validate-cards.d.ts +57 -0
- package/dist/primitives/card-validate-generator.testlib.d.ts +20 -0
- package/dist/primitives/card-validate-schemas.d.ts +17 -0
- package/dist/primitives/card-validate.d.ts +24 -0
- package/dist/primitives/create-kai-chat.d.ts +3 -3
- package/dist/primitives/embed-providers.d.ts +1 -1
- package/dist/primitives/link-preview.d.ts +1 -1
- package/dist/primitives/message-feedback.d.ts +2 -2
- package/dist/primitives/toast-store.d.ts +1 -1
- package/dist/react/index.d.ts +730 -302
- package/dist/react/use-kai-chat.d.ts +8 -4
- package/dist/react.d.ts +5 -0
- package/dist/react.js +418 -317
- package/dist/register-impl-AxIlqjo7.js +145 -0
- package/dist/remote/host-embed.d.ts +1 -1
- package/dist/remote/index.d.ts +2 -2
- package/dist/remote/provider-runtime.d.ts +2 -2
- package/dist/remote/provider.d.ts +2 -2
- package/dist/remote/validate.d.ts +1 -1
- package/dist/remote/wire.d.ts +1 -1
- package/dist/schemas/artifact.schema.json +92 -0
- package/dist/schemas/embed.schema.json +0 -2
- package/dist/schemas/from-tool-call.d.ts +48 -0
- package/dist/schemas/index.d.ts +48 -0
- package/dist/schemas/link.schema.json +0 -2
- package/dist/schemas/provider-subsets.d.ts +136 -0
- package/dist/schemas/registry.d.ts +140 -0
- package/dist/schemas/tool-defs.d.ts +121 -0
- package/dist/schemas.d.ts +5 -0
- package/dist/schemas.js +717 -0
- package/dist/solid-BGYSJyHG.js +22960 -0
- package/dist/solid-Dt2nEwfl.js +20194 -0
- package/dist/solid.d.ts +66 -0
- package/dist/solid.js +261 -0
- package/dist/solid.server.d.ts +5 -0
- package/dist/solid.server.js +261 -0
- package/dist/state/index.d.ts +14 -4
- package/dist/state/messages.d.ts +8 -3
- package/dist/state/mock.d.ts +46 -0
- package/dist/state/parts.d.ts +85 -0
- package/dist/state/stream.d.ts +15 -9
- package/dist/state.d.ts +5 -0
- package/dist/state.js +253 -84
- package/dist/ui/action-icons.d.ts +1 -1
- package/dist/ui/collapsible.d.ts +11 -6
- package/dist/ui/dropdown.d.ts +47 -42
- package/dist/ui/pane-group.d.ts +1 -1
- package/dist/{variant-aurora-DVtGqVH6.js → variant-aurora-B44X69AG.js} +1 -1
- package/dist/variant-aurora-BRhV8Flk.js +391 -0
- package/dist/variant-aurora-Ds5ou9BH.js +381 -0
- package/dist/variant-custom-B20FhtYj.js +170 -0
- package/dist/{variant-custom-Dif-IV4u.js → variant-custom-BvA1Schl.js} +1 -1
- package/dist/variant-custom-D6Rtvneb.js +200 -0
- package/dist/{variant-wave-DbfNdvSi.js → variant-wave-BUQtkryq.js} +1 -1
- package/dist/variant-wave-BVYnqy9L.js +218 -0
- package/dist/variant-wave-DXC6_LcH.js +208 -0
- package/dist/wire/chunk.d.ts +171 -0
- package/dist/wire/consume.d.ts +26 -0
- package/dist/wire/encode.d.ts +221 -0
- package/dist/wire/files.d.ts +116 -0
- package/dist/wire/formats/anthropic.d.ts +2 -0
- package/dist/wire/formats/openai.d.ts +5 -0
- package/dist/wire/index.d.ts +16 -0
- package/dist/wire/media-types.d.ts +106 -0
- package/dist/wire/read.d.ts +26 -0
- package/dist/wire/sink-helpers.d.ts +21 -0
- package/dist/wire/sse.d.ts +33 -0
- package/dist/wire.d.ts +5 -53
- package/dist/wire.js +1126 -0
- package/frameworks/react/index.tsx +191 -158
- package/frameworks/react/runtime.tsx +21 -1
- package/frameworks/react/use-kai-chat.tsx +11 -1
- package/llms-full.txt +774 -151
- package/llms.txt +57 -5
- package/package.json +62 -8
- package/src/agent-tooling/archetypes.ts +127 -0
- package/src/agent-tooling/integrations/anthropic.ts +436 -0
- package/src/agent-tooling/integrations/cloudflare.ts +141 -52
- package/src/agent-tooling/integrations/langgraph.ts +80 -13
- package/src/agent-tooling/integrations/mastra.ts +161 -12
- package/src/agent-tooling/integrations/mock.ts +47 -8
- package/src/agent-tooling/integrations/ollama.ts +68 -24
- package/src/agent-tooling/integrations/openai.ts +89 -0
- package/src/agent-tooling/integrations/openrouter.ts +58 -8
- package/src/agent-tooling/integrations/pi.ts +104 -23
- package/src/agent-tooling/integrations/pydantic-ai.ts +61 -5
- package/src/agent-tooling/integrations/vercel-ai-sdk.ts +120 -13
- package/src/agent-tooling/mcp/manifest.ts +206 -25
- package/src/agent-tooling/mcp/tools/debug.ts +13 -7
- package/src/agent-tooling/mcp/tools/reference.ts +285 -6
- package/src/agent-tooling/mcp/tools/scaffold.ts +4152 -661
- package/src/agent-tooling/registry.ts +74 -0
- package/src/agent-tooling/types.ts +361 -1
- package/src/components/artifact-card.tsx +120 -0
- package/src/components/audio-visualizer/index.tsx +9 -0
- package/src/components/audio-visualizer/labs/lab-choreography.ts +187 -0
- package/src/components/audio-visualizer/labs/lab-shaders.ts +363 -0
- package/src/components/audio-visualizer/labs/lab-visualizer.tsx +102 -0
- package/src/components/audio-visualizer/shader-canvas.tsx +31 -1
- package/src/components/card-fallback.tsx +42 -10
- package/src/components/card-renderer.tsx +146 -9
- package/src/components/chat-container.tsx +4 -0
- package/src/components/chat-thread.tsx +128 -52
- package/src/components/choice-card.tsx +17 -29
- package/src/components/composer.tsx +12 -2
- package/src/components/confirm-card.tsx +24 -20
- package/src/components/context.tsx +30 -16
- package/src/components/conversation-item.tsx +6 -0
- package/src/components/conversation-list.tsx +18 -2
- package/src/components/file-tree.tsx +11 -22
- package/src/components/file-upload.tsx +14 -8
- package/src/components/form.tsx +14 -47
- package/src/components/loader.tsx +39 -12
- package/src/components/message-skills.tsx +4 -0
- package/src/components/message.tsx +294 -46
- package/src/components/model-switcher.tsx +38 -8
- package/src/components/reasoning.tsx +92 -5
- package/src/components/response-compare-types.ts +25 -0
- package/src/components/response-compare.tsx +4 -9
- package/src/components/source.tsx +50 -24
- package/src/components/tasks-card.tsx +10 -33
- package/src/components/thread.tsx +84 -42
- package/src/components/toast.tsx +17 -6
- package/src/components/tool-classify.ts +26 -0
- package/src/components/tool-types.ts +31 -1
- package/src/elements/agent-card.tsx +11 -11
- package/src/elements/artifact.tsx +15 -7
- package/src/elements/attachments.tsx +6 -4
- package/src/elements/avatar.tsx +1 -1
- package/src/elements/button.tsx +29 -6
- package/src/elements/card.tsx +2 -2
- package/src/elements/cards.tsx +109 -14
- package/src/elements/chain-of-thought.tsx +14 -10
- package/src/elements/chat-scope-picker.tsx +9 -7
- package/src/elements/chat-types.ts +59 -6
- package/src/elements/chat-workspace.tsx +50 -9
- package/src/elements/chat.tsx +90 -3
- package/src/elements/choice.tsx +8 -8
- package/src/elements/coachmark.tsx +1 -1
- package/src/elements/command.tsx +8 -23
- package/src/elements/compare.tsx +7 -6
- package/src/elements/compiled.css +1 -1
- package/src/elements/composer.tsx +8 -8
- package/src/elements/confirm-card.tsx +9 -9
- package/src/elements/context-meter.tsx +4 -18
- package/src/elements/conversation-list.tsx +14 -9
- package/src/elements/default-input.tsx +131 -8
- package/src/elements/define.tsx +178 -43
- package/src/elements/editable-label.tsx +17 -2
- package/src/elements/element-data-types.ts +112 -0
- package/src/elements/element-meta.json +1624 -306
- package/src/elements/element-types.d.ts +2645 -214
- package/src/elements/feedback-bar.tsx +1 -1
- package/src/elements/file-tree.tsx +1 -1
- package/src/elements/file-upload.tsx +1 -1
- package/src/elements/form.tsx +14 -6
- package/src/elements/input.tsx +1 -1
- package/src/elements/kbd.tsx +1 -1
- package/src/elements/menu.tsx +40 -32
- package/src/elements/message-skills.tsx +7 -9
- package/src/elements/message.tsx +200 -32
- package/src/elements/model-switcher.tsx +5 -3
- package/src/elements/pane.tsx +1 -1
- package/src/elements/prompt-dock.tsx +8 -8
- package/src/elements/prompt-input.tsx +10 -10
- package/src/elements/prompt-suggestions.tsx +7 -4
- package/src/elements/reasoning.tsx +1 -1
- package/src/elements/register.ts +9 -0
- package/src/elements/resizable.globals.d.ts +36 -0
- package/src/elements/resizable.tsx +24 -13
- package/src/elements/response-stream.tsx +1 -1
- package/src/elements/scroll-button.tsx +1 -1
- package/src/elements/search.tsx +6 -1
- package/src/elements/segmented.tsx +5 -11
- package/src/elements/separator.tsx +1 -1
- package/src/elements/slot-text.ts +73 -0
- package/src/elements/slots.ts +67 -24
- package/src/elements/source.tsx +21 -22
- package/src/elements/switch.tsx +3 -3
- package/src/elements/tasks.tsx +5 -4
- package/src/elements/thread.tsx +36 -4
- package/src/elements/toast.tsx +3 -2
- package/src/elements/tool.tsx +1 -1
- package/src/elements/validate-messages.ts +76 -0
- package/src/elements/voice-input.tsx +7 -7
- package/src/elements/voice-output.tsx +4 -4
- package/src/index.ts +145 -6
- package/src/primitives/card-component-types.ts +63 -0
- package/src/primitives/card-data-types.ts +312 -0
- package/src/primitives/card-recovery.ts +13 -2
- package/src/primitives/card-registry.tsx +34 -23
- package/src/primitives/card-schemas/artifact.schema.json +92 -0
- package/src/primitives/card-schemas/embed.schema.json +0 -2
- package/src/primitives/card-schemas/link.schema.json +0 -2
- package/src/primitives/card-tags.ts +61 -0
- package/src/primitives/card-validate-cards.ts +255 -0
- package/src/primitives/card-validate-generator.testlib.ts +38 -0
- package/src/primitives/card-validate-schemas.ts +58 -0
- package/src/primitives/card-validate.ts +48 -20
- package/src/primitives/create-tween.ts +54 -2
- package/src/primitives/message-feedback.ts +3 -2
- package/src/primitives/use-audio-analysis.ts +20 -1
- package/src/primitives/use-sequencer.ts +20 -1
- package/src/schemas/from-tool-call.ts +151 -0
- package/src/schemas/index.ts +340 -0
- package/src/schemas/provider-subsets.ts +538 -0
- package/src/schemas/registry.ts +355 -0
- package/src/schemas/tool-defs.ts +492 -0
- package/src/solid.ts +150 -0
- package/src/state/index.ts +27 -1
- package/src/state/messages.ts +21 -4
- package/src/state/mock.ts +211 -0
- package/src/state/parts.ts +295 -0
- package/src/state/stream.ts +56 -58
- package/src/ui/collapsible.tsx +17 -5
- package/src/ui/dropdown.tsx +46 -12
- package/src/ui/overlay.tsx +10 -4
- package/src/wire/chunk.ts +201 -0
- package/src/wire/consume.ts +440 -0
- package/src/wire/encode.ts +689 -0
- package/src/wire/files.ts +342 -0
- package/src/wire/formats/anthropic.ts +267 -0
- package/src/wire/formats/openai.ts +187 -0
- package/src/wire/index.ts +73 -0
- package/src/wire/media-types.ts +344 -0
- package/src/wire/read.ts +152 -0
- package/src/wire/sink-helpers.ts +50 -0
- package/src/wire/sse.ts +140 -0
- package/dist/create-tween-hvpmSuwW.js +0 -7
- package/dist/elements/chunks/arrow-left-DQocUFvy.js +0 -1
- package/dist/elements/chunks/audio-visualizer-BqNy7dfm.js +0 -1
- package/dist/elements/chunks/card-BkHbp4BA.js +0 -1
- package/dist/elements/chunks/chat-thread-CkRN6pZ7.js +0 -1
- package/dist/elements/chunks/check-B9-gn6SS.js +0 -1
- package/dist/elements/chunks/chevron-down-BNsxc2hk.js +0 -1
- package/dist/elements/chunks/chevron-right-Hv00StBO.js +0 -1
- package/dist/elements/chunks/circle-0e3KJTDr.js +0 -1
- package/dist/elements/chunks/circle-check-BNAS0JEy.js +0 -1
- package/dist/elements/chunks/code-block-BFUHn6Va.js +0 -1
- package/dist/elements/chunks/context-Cb7k4F-v.js +0 -1
- package/dist/elements/chunks/conversation-list-CvoAkWhN.js +0 -1
- package/dist/elements/chunks/create-tween-oVvAIG8r.js +0 -7
- package/dist/elements/chunks/default-input-Cqqilg9t.js +0 -1
- package/dist/elements/chunks/define-D1din7Ld.js +0 -1
- package/dist/elements/chunks/hover-card-Cmm6Q53m.js +0 -1
- package/dist/elements/chunks/message-BqyXDN3-.js +0 -1
- package/dist/elements/chunks/message-feedback-Djr3z6Yb.js +0 -1
- package/dist/elements/chunks/message-square-vFAgbDaX.js +0 -1
- package/dist/elements/chunks/model-switcher-B94YAKsM.js +0 -1
- package/dist/elements/chunks/reasoning-DTpCGtnY.js +0 -1
- package/dist/elements/chunks/scroll-button-B0cH5fXf.js +0 -1
- package/dist/elements/chunks/skeleton-DSXW5Yya.js +0 -1
- package/dist/elements/chunks/slots--V19HwUb.js +0 -1
- package/dist/elements/chunks/textarea-CUwqAX0G.js +0 -1
- package/dist/elements/chunks/tooltip-CgBZCVKp.js +0 -1
- package/dist/elements/chunks/use-card-resolution-BSu_XHjH.js +0 -1
- package/dist/elements/chunks/x-GRUeeQFT.js +0 -1
- package/dist/host-embed.d.ts +0 -33
- package/dist/kai-provider.d.ts +0 -2
- package/dist/origin.d.ts +0 -5
- package/dist/provider-runtime.d.ts +0 -27
- package/dist/register-impl-DQyejdfb.js +0 -145
- package/dist/validate.d.ts +0 -4
- package/dist/version.d.ts +0 -4
- package/src/components/component-meta.json +0 -4550
- package/src/elements/card-media.jpg +0 -0
- package/src/elements/resizable.d.ts +0 -27
- package/src/elements/video-poster.jpg +0 -0
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { RawOrigin, ToolPart } from '../components/tool-types.js';
|
|
2
|
+
import { MessagePart, MessageSource } from '../elements/chat-types.js';
|
|
3
|
+
import { ReasoningOpts } from '../state/parts.js';
|
|
4
|
+
/** One fragment of a tool call. */
|
|
5
|
+
export interface ModelToolCallDelta {
|
|
6
|
+
/**
|
|
7
|
+
* The ONLY thing correlating fragments, and its NAMESPACE IS FORMAT-DEFINED.
|
|
8
|
+
* `openaiChatFormat` uses the position in `delta.tool_calls`;
|
|
9
|
+
* `anthropicMessagesFormat` uses the content-block index. Both are correct and
|
|
10
|
+
* both are stable within one stream, but they are not the same number, so a
|
|
11
|
+
* third-party format must pick one and stay consistent with itself.
|
|
12
|
+
*/
|
|
13
|
+
index: number;
|
|
14
|
+
id?: string;
|
|
15
|
+
/** Usually whole on the first fragment; a few providers split it. */
|
|
16
|
+
name?: string;
|
|
17
|
+
/** A FRAGMENT of the JSON arguments string, not valid JSON on its own. */
|
|
18
|
+
arguments?: string;
|
|
19
|
+
/** A result the PROVIDER executed (Anthropic web_search_tool_result, an OpenAI
|
|
20
|
+
* built-in). Completes the panel with no host work. */
|
|
21
|
+
output?: Record<string, unknown>;
|
|
22
|
+
/** A provider-executed tool that failed. */
|
|
23
|
+
outputError?: string;
|
|
24
|
+
}
|
|
25
|
+
/** Field names are deliberately provider-neutral. OpenAI says prompt/completion,
|
|
26
|
+
* Anthropic says input/output; input/output is the one that reads correctly for
|
|
27
|
+
* both. */
|
|
28
|
+
export interface ModelUsage {
|
|
29
|
+
inputTokens?: number;
|
|
30
|
+
outputTokens?: number;
|
|
31
|
+
totalTokens?: number;
|
|
32
|
+
/** Non-zero proves the model reasoned even when no reasoning text streamed. */
|
|
33
|
+
reasoningTokens?: number;
|
|
34
|
+
cachedInputTokens?: number;
|
|
35
|
+
costUsd?: number;
|
|
36
|
+
}
|
|
37
|
+
export interface ModelStreamChunk {
|
|
38
|
+
text?: string;
|
|
39
|
+
/**
|
|
40
|
+
* Reasoning delta. `''` is MEANINGFUL, not a no-op: a redacted block has no
|
|
41
|
+
* readable text but still carries a payload that must round-trip, and a format
|
|
42
|
+
* uses an empty delta to OPEN a reasoning part at the right position in the
|
|
43
|
+
* stream so block order survives into `parts`.
|
|
44
|
+
*/
|
|
45
|
+
reasoning?: string;
|
|
46
|
+
/** The provider's BLOCK index. Keeps parallel reasoning blocks distinct.
|
|
47
|
+
* Omitted means block 0, the single-block case every provider degrades to. */
|
|
48
|
+
reasoningIndex?: number;
|
|
49
|
+
/**
|
|
50
|
+
* The UNTRANSLATED provider payload for this reasoning block. Valid on a chunk
|
|
51
|
+
* with NO reasoning text at all, which is the whole point: Anthropic returns
|
|
52
|
+
* 400 if a `thinking` block is modified, reordered or RECONSTRUCTED, so an
|
|
53
|
+
* encoder has to echo the original block rather than rebuild one from `text`
|
|
54
|
+
* plus `signature`.
|
|
55
|
+
*/
|
|
56
|
+
reasoningRaw?: RawOrigin;
|
|
57
|
+
/** Informational. `reasoningRaw` is the round-trip channel, not this. */
|
|
58
|
+
reasoningSignature?: string;
|
|
59
|
+
toolCalls?: ModelToolCallDelta[];
|
|
60
|
+
/** Citations the model produced. A run of consecutive `source` parts renders
|
|
61
|
+
* as one citation row (`part="citations"`), outside the message bubble. */
|
|
62
|
+
sources?: MessageSource[];
|
|
63
|
+
/** Provider VERBATIM: 'stop' | 'tool_calls' | 'end_turn' | 'max_tokens' | ...
|
|
64
|
+
* Normalizing in place would destroy information consumers branch on. */
|
|
65
|
+
finishReason?: string | null;
|
|
66
|
+
usage?: ModelUsage;
|
|
67
|
+
/** An in-band provider error (the HTTP response was already 200). */
|
|
68
|
+
error?: {
|
|
69
|
+
code?: string | number;
|
|
70
|
+
message: string;
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
/** One vocabulary across formats, for code that has to BRANCH. `finishReason`
|
|
74
|
+
* stays beside it, verbatim, for code that has to REPORT. */
|
|
75
|
+
export type StopReason = 'stop' | 'length' | 'tool-calls' | 'content-filter' | 'error' | 'other';
|
|
76
|
+
/** Unknown reasons degrade to 'other' rather than throwing: providers add stop
|
|
77
|
+
* reasons without warning and a new one must not take a turn down. */
|
|
78
|
+
export declare function normalizeStopReason(finishReason: string | null | undefined): StopReason | undefined;
|
|
79
|
+
/**
|
|
80
|
+
* The subset of the kit's `AssistantStream` the adapter drives. Declared
|
|
81
|
+
* STRUCTURALLY so the adapter has no runtime dependency on a stream
|
|
82
|
+
* implementation and can be tested against a recorder. The kit's real
|
|
83
|
+
* `AssistantStream` satisfies it as-is: same method names, same arities, and its
|
|
84
|
+
* `AssistantStream` returns are assignable to `unknown`.
|
|
85
|
+
*
|
|
86
|
+
* `addSource` is optional so a hand-rolled three-method sink still compiles.
|
|
87
|
+
*/
|
|
88
|
+
export interface AssistantStreamSink {
|
|
89
|
+
appendText(delta: string): unknown;
|
|
90
|
+
appendReasoning(delta: string, opts?: ReasoningOpts): unknown;
|
|
91
|
+
/** Create-or-merge. There is no separate "announce" call: handing a patch for
|
|
92
|
+
* an unknown `toolCallId` creates the ToolPart, and every later patch merges. */
|
|
93
|
+
upsertTool(toolCallId: string, patch: Partial<ToolPart>): unknown;
|
|
94
|
+
addSource?(source: MessageSource): unknown;
|
|
95
|
+
}
|
|
96
|
+
/** One tool call reassembled out of the stream's fragments. */
|
|
97
|
+
export interface ModelToolCall {
|
|
98
|
+
/** The delta index that correlated this call's fragments. */
|
|
99
|
+
index: number;
|
|
100
|
+
/** Provider call id (synthesised as `call_<index>` if the provider omits it). */
|
|
101
|
+
id: string;
|
|
102
|
+
name: string;
|
|
103
|
+
/** The RAW accumulated argument fragments. Echo THIS back on the next turn,
|
|
104
|
+
* not a re-stringified parse. */
|
|
105
|
+
argumentsText: string;
|
|
106
|
+
/** Parsed arguments: present only when `argumentsText` was a valid JSON object. */
|
|
107
|
+
input?: Record<string, unknown>;
|
|
108
|
+
/** Present only for a call the PROVIDER executed. */
|
|
109
|
+
output?: Record<string, unknown>;
|
|
110
|
+
/** True when the provider ran the tool and returned its result in-stream. The
|
|
111
|
+
* host must NOT execute these. */
|
|
112
|
+
providerExecuted?: boolean;
|
|
113
|
+
/** Why this call is unusable (malformed or truncated args, missing name). */
|
|
114
|
+
error?: string;
|
|
115
|
+
}
|
|
116
|
+
/** Everything one assistant turn produced. */
|
|
117
|
+
export interface ModelTurn {
|
|
118
|
+
/** The turn as ORDERED MESSAGE PARTS, built with the kit's own part builders,
|
|
119
|
+
* so it is exactly what the sink was driven with. Covers this turn only. */
|
|
120
|
+
parts: MessagePart[];
|
|
121
|
+
/** Flat concatenation of the text deltas. The provider wire format is a flat
|
|
122
|
+
* string, so this is kept for encoders. Not the content model. */
|
|
123
|
+
text: string;
|
|
124
|
+
/** Flat concatenation of the reasoning deltas, for the same reason. */
|
|
125
|
+
reasoning: string;
|
|
126
|
+
toolCalls: ModelToolCall[];
|
|
127
|
+
sources: MessageSource[];
|
|
128
|
+
/** The provider's own word for why it stopped. Never normalized. */
|
|
129
|
+
finishReason: string | null;
|
|
130
|
+
/** The same fact in one vocabulary. Branch on this. */
|
|
131
|
+
stopReason?: StopReason;
|
|
132
|
+
error?: {
|
|
133
|
+
code?: string | number;
|
|
134
|
+
message: string;
|
|
135
|
+
};
|
|
136
|
+
usage?: ModelUsage;
|
|
137
|
+
/** How many chunks carried a NON-EMPTY reasoning delta. Zero with a non-zero
|
|
138
|
+
* `usage.reasoningTokens` means the provider hid the thinking text. */
|
|
139
|
+
reasoningChunks: number;
|
|
140
|
+
chunks: number;
|
|
141
|
+
}
|
|
142
|
+
export interface ConsumeOptions {
|
|
143
|
+
/** Label for the reasoning disclosure. Defaults to 'Thinking'. */
|
|
144
|
+
reasoningLabel?: string;
|
|
145
|
+
/** Fires once per tool call the moment its arguments parse cleanly. This is
|
|
146
|
+
* the hook a host's tool loop waits on. There is deliberately no
|
|
147
|
+
* per-fragment callback: `ToolPart.rawInput` is written on every fragment,
|
|
148
|
+
* so the streaming text is already on the part. */
|
|
149
|
+
onToolCallReady?: (call: ModelToolCall) => void;
|
|
150
|
+
}
|
|
151
|
+
/** Per-stream state for one format. */
|
|
152
|
+
export interface WireFormatReader {
|
|
153
|
+
/**
|
|
154
|
+
* Map one decoded frame onto zero or more neutral chunks. Returns an ARRAY
|
|
155
|
+
* because the mapping is not one-to-one: an Anthropic `message_start` yields
|
|
156
|
+
* usage, a `content_block_start` for `tool_use` yields an id-plus-name delta,
|
|
157
|
+
* a `ping` yields nothing.
|
|
158
|
+
*
|
|
159
|
+
* MUST NOT throw on an unrecognized frame. Return `[]` instead: providers add
|
|
160
|
+
* event types without warning.
|
|
161
|
+
*/
|
|
162
|
+
push(frame: unknown): ModelStreamChunk[];
|
|
163
|
+
}
|
|
164
|
+
/** A pluggable wire format. Values, not a flag, so a third party can add one
|
|
165
|
+
* without a PR to this repo. */
|
|
166
|
+
export interface WireFormat {
|
|
167
|
+
readonly id: string;
|
|
168
|
+
/** Called once per stream so a format can hold per-stream state. Two calls
|
|
169
|
+
* must share NOTHING. */
|
|
170
|
+
open(): WireFormatReader;
|
|
171
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { AssistantStreamSink, ConsumeOptions, ModelStreamChunk, ModelToolCall, ModelToolCallDelta, ModelTurn, StopReason } from './chunk.js';
|
|
2
|
+
/**
|
|
3
|
+
* Correlate tool-call fragments by `index` and drive the ToolPart lifecycle.
|
|
4
|
+
* THE HARD PART: `id`, `name` and each slice of `arguments` arrive across
|
|
5
|
+
* arbitrarily many chunks in provider-dependent order.
|
|
6
|
+
*
|
|
7
|
+
* A ToolPart is pushed (`input-streaming`) the first time a call has BOTH an id
|
|
8
|
+
* and a non-empty name: announcing earlier would create a panel whose `type` is
|
|
9
|
+
* the empty string. Everything before that is buffered and flushed at announce.
|
|
10
|
+
*/
|
|
11
|
+
export declare function createToolCallAccumulator(sink: AssistantStreamSink, opts?: ConsumeOptions): {
|
|
12
|
+
apply: (raw: ModelToolCallDelta) => void;
|
|
13
|
+
settle: (stopReason: StopReason | undefined, streamError?: string) => ModelToolCall[];
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Read one turn's worth of `ModelStreamChunk`s and drive `sink` with them.
|
|
17
|
+
*
|
|
18
|
+
* The sink is expected to be the kit's `AssistantStream`, which produces a NEW
|
|
19
|
+
* message object (and a new `parts` array) on every real mutation: that is what
|
|
20
|
+
* makes `<kai-thread>` re-render. This adapter calls the sink once per delta and
|
|
21
|
+
* never batches; batching is a host concern.
|
|
22
|
+
*
|
|
23
|
+
* This is also the escape hatch for a consumer who already has neutral chunks
|
|
24
|
+
* from somewhere else and does not need a WireFormat at all.
|
|
25
|
+
*/
|
|
26
|
+
export declare function consumeModelStream(chunks: AsyncIterable<ModelStreamChunk>, sink: AssistantStreamSink, opts?: ConsumeOptions): Promise<ModelTurn>;
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { ChatMessage } from '../elements/chat-types.js';
|
|
2
|
+
import { MediaTypeFilter } from './media-types.js';
|
|
3
|
+
export interface OpenAIToolCall {
|
|
4
|
+
id: string;
|
|
5
|
+
type: 'function';
|
|
6
|
+
function: {
|
|
7
|
+
name: string;
|
|
8
|
+
arguments: string;
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
/** One `reasoning_details` entry. Provider-owned open shape, kept as a record
|
|
12
|
+
* for the same reason `AnthropicContentBlock` is: an opaque entry has to pass
|
|
13
|
+
* through UNTOUCHED, and a closed type would be a list of the fields we happen
|
|
14
|
+
* to have seen. */
|
|
15
|
+
export type OpenAIReasoningDetail = Record<string, unknown>;
|
|
16
|
+
/** A multimodal user message's content entries. `image_url` takes an https URL
|
|
17
|
+
* or a `data:` URI in the same field; `file` takes `file_data`, which is a DATA
|
|
18
|
+
* URI on this wire (`data:application/pdf;base64,...`) and not bare base64. */
|
|
19
|
+
export type OpenAIContentPart = {
|
|
20
|
+
type: 'text';
|
|
21
|
+
text: string;
|
|
22
|
+
} | {
|
|
23
|
+
type: 'image_url';
|
|
24
|
+
image_url: {
|
|
25
|
+
url: string;
|
|
26
|
+
};
|
|
27
|
+
} | {
|
|
28
|
+
type: 'file';
|
|
29
|
+
file: {
|
|
30
|
+
filename?: string;
|
|
31
|
+
file_data: string;
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
export interface OpenAIWireMessage {
|
|
35
|
+
role: 'system' | 'user' | 'assistant' | 'tool';
|
|
36
|
+
/** An ARRAY only when the turn carries an encodable `file` part. A text-only
|
|
37
|
+
* turn stays a plain string, so adding attachment support changed nothing
|
|
38
|
+
* about what an existing thread puts on the wire. */
|
|
39
|
+
content: string | OpenAIContentPart[] | null;
|
|
40
|
+
tool_calls?: OpenAIToolCall[];
|
|
41
|
+
tool_call_id?: string;
|
|
42
|
+
name?: string;
|
|
43
|
+
/** Only ever present when `toOpenAIMessages` was asked for it. See
|
|
44
|
+
* `OpenAIEncodeOptions.reasoning`. */
|
|
45
|
+
reasoning_details?: OpenAIReasoningDetail[];
|
|
46
|
+
}
|
|
47
|
+
/** EXTENDS rather than restates the file options: `onUnencodableFile` and
|
|
48
|
+
* `accept` mean the same thing on both wires, and a second declaration of them
|
|
49
|
+
* here is a second place to forget to update. */
|
|
50
|
+
export interface OpenAIEncodeOptions extends FileEncodeOptions {
|
|
51
|
+
/**
|
|
52
|
+
* Whether to send the assistant's own reasoning back with the thread.
|
|
53
|
+
*
|
|
54
|
+
* DEFAULT `'omit'`, and that default is a measurement, not caution. Omitting
|
|
55
|
+
* reasoning is accepted by every configuration tested -- five live omission
|
|
56
|
+
* trials plus 28 recorded live requests per configuration across the spike's
|
|
57
|
+
* conformance sweep, zero 400s -- so the path that ships today demonstrably
|
|
58
|
+
* works, while including reasoning cost about 25% more prompt tokens per round
|
|
59
|
+
* when measured (665 -> 834 on a two-round loop). A library does not get to
|
|
60
|
+
* raise every consumer's bill and add a new provider-validation surface as a
|
|
61
|
+
* side effect of a bug fix.
|
|
62
|
+
*
|
|
63
|
+
* `'include'` is for a multi-round TOOL loop, which is where OpenRouter says it
|
|
64
|
+
* pays: "when you post tool results, including the original reasoning ensures
|
|
65
|
+
* the model can continue its reasoning from where it left off". Measured
|
|
66
|
+
* accepted (HTTP 200) for a signed Anthropic block and for an OpenAI encrypted
|
|
67
|
+
* block, over the OpenAI-compatible wire.
|
|
68
|
+
*
|
|
69
|
+
* The Anthropic wire has no such knob because it has no such choice: a filtered
|
|
70
|
+
* or rebuilt thinking block there is a hard 400.
|
|
71
|
+
*/
|
|
72
|
+
reasoning?: 'omit' | 'include';
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* What to do with a `file` part this wire cannot carry.
|
|
76
|
+
*
|
|
77
|
+
* DEFAULT `'throw'`, and the default is the whole point. Skipping is how
|
|
78
|
+
* attachments came to render perfectly in the thread and reach the model as
|
|
79
|
+
* nothing: the developer wires up upload, watches it work, and ships a model
|
|
80
|
+
* that cannot see the file. A throw here names the message, the part and the
|
|
81
|
+
* reason, which is strictly more than a 400 at request time would tell you.
|
|
82
|
+
*
|
|
83
|
+
* `'skip'` restores the lenient behaviour for a host that would rather send a
|
|
84
|
+
* degraded turn than fail one. It is silent, but it is silence the developer
|
|
85
|
+
* asked for by name, which is the difference that matters.
|
|
86
|
+
*/
|
|
87
|
+
export type UnencodableFilePolicy = 'throw' | 'skip';
|
|
88
|
+
export interface FileEncodeOptions {
|
|
89
|
+
onUnencodableFile?: UnencodableFilePolicy;
|
|
90
|
+
/**
|
|
91
|
+
* Narrow which attachment media types reach the wire, as HTML `accept` syntax
|
|
92
|
+
* (`'image/*,application/pdf'`) or an array of the same.
|
|
93
|
+
*
|
|
94
|
+
* THE SAME STRING the composer takes as `<kai-chat accept="...">`, resolved by
|
|
95
|
+
* the same function against the same declaration -- so a developer writes the
|
|
96
|
+
* set once as a constant and hands it to both ends. Omitted means the kit's
|
|
97
|
+
* full capability set, which is `encodableMediaTypes()`.
|
|
98
|
+
*
|
|
99
|
+
* It can only NARROW. Naming a type the encoders cannot represent does not
|
|
100
|
+
* enable it; that would just move the failure to a provider 400.
|
|
101
|
+
*/
|
|
102
|
+
accept?: MediaTypeFilter;
|
|
103
|
+
}
|
|
104
|
+
export type AnthropicEncodeOptions = FileEncodeOptions;
|
|
105
|
+
/** Anthropic content blocks are an open, provider-owned union. Keeping them as
|
|
106
|
+
* records is what lets a verbatim `thinking` payload pass through UNTOUCHED,
|
|
107
|
+
* which is the entire point of this encoder. */
|
|
108
|
+
export type AnthropicContentBlock = Record<string, unknown>;
|
|
109
|
+
export interface AnthropicWireMessage {
|
|
110
|
+
role: 'user' | 'assistant';
|
|
111
|
+
content: AnthropicContentBlock[];
|
|
112
|
+
}
|
|
113
|
+
/** A message cannot be encoded without losing something the provider will reject.
|
|
114
|
+
* Thrown at encode time, on purpose: a throw here beats a 400 at request time,
|
|
115
|
+
* because here you still know which message and which part caused it. */
|
|
116
|
+
export declare class WireEncodeError extends Error {
|
|
117
|
+
readonly messageId: string;
|
|
118
|
+
readonly partIndex: number;
|
|
119
|
+
constructor(message: string, messageId: string, partIndex: number);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* ChatMessage[] to an OpenAI chat-completions `messages` array.
|
|
123
|
+
*
|
|
124
|
+
* ONE ChatMessage CAN BECOME SEVERAL WIRE MESSAGES. The kit streams a whole
|
|
125
|
+
* assistant turn into a single message, so text, a tool call and the model's
|
|
126
|
+
* answer to that call all live in one `parts` array. The OpenAI wire has no such
|
|
127
|
+
* shape: a `role:'tool'` result must sit between the assistant message that
|
|
128
|
+
* announced the call and whatever the model said afterwards. So the turn is
|
|
129
|
+
* SPLIT at each tool boundary, into
|
|
130
|
+
*
|
|
131
|
+
* assistant(pre-tool text + tool_calls) -> tool(result)... -> assistant(answer)
|
|
132
|
+
*
|
|
133
|
+
* Flattening instead would put the model's answer BEFORE the result it was based
|
|
134
|
+
* on. No endpoint rejects that, which is exactly why it is worth spelling out:
|
|
135
|
+
* it quietly degrades every later round of a tool loop.
|
|
136
|
+
*
|
|
137
|
+
* Consecutive tool parts stay in ONE assistant message, because parallel calls
|
|
138
|
+
* are announced together and their results follow together.
|
|
139
|
+
*
|
|
140
|
+
* A turn that encodes to nothing is SKIPPED, never sent as `{ content: null }`
|
|
141
|
+
* with no `tool_calls`: OpenAI treats `content` as required unless `tool_calls`
|
|
142
|
+
* is present, and strict-compatible endpoints reject it.
|
|
143
|
+
*
|
|
144
|
+
* REASONING IS OPT-IN, and off by default. OpenRouter's OpenAI-compatible
|
|
145
|
+
* endpoint does have a channel on the way back in -- `reasoning_details` on the
|
|
146
|
+
* assistant message -- and `{ reasoning: 'include' }` uses it, one entry per
|
|
147
|
+
* reasoning part, in part order, reassembled by `reasoningDetailOf` rather than
|
|
148
|
+
* echoed out of `part.raw`. Read that function for which blocks make it and why.
|
|
149
|
+
* The default omits, because omitting is measured-accepted everywhere and costs
|
|
150
|
+
* about 25% fewer prompt tokens per round; see `OpenAIEncodeOptions.reasoning`.
|
|
151
|
+
*
|
|
152
|
+
* Reasoning alone still encodes to NOTHING. A block is content the model already
|
|
153
|
+
* produced, not a reason to send a turn, so a message carrying reasoning and no
|
|
154
|
+
* text and no settled tool is skipped exactly as before, rather than becoming
|
|
155
|
+
* `{ content: null }` with no `tool_calls`.
|
|
156
|
+
*
|
|
157
|
+
* `card` and `source` parts are never encoded; they are kit-side.
|
|
158
|
+
*
|
|
159
|
+
* `file` parts ARE encoded, on a USER turn, and a turn carrying nothing but an
|
|
160
|
+
* attachment is now a real message rather than nothing. Images become
|
|
161
|
+
* `image_url` (https URL or `data:` URI alike); a base64 PDF becomes a `file`
|
|
162
|
+
* part whose `file_data` is the data URI. Two cases have no form here and THROW
|
|
163
|
+
* by default: a remote PDF, because this wire's `file` part has no URL variant,
|
|
164
|
+
* and anything that is neither -- see `UnencodableFilePolicy` for why the
|
|
165
|
+
* default is a throw and not a skip.
|
|
166
|
+
*
|
|
167
|
+
* A `file` part on an ASSISTANT turn is still dropped. Neither API accepts image
|
|
168
|
+
* or document content in an assistant message, so there is nothing to encode it
|
|
169
|
+
* to; attachments belong to the user turn that sent them.
|
|
170
|
+
*/
|
|
171
|
+
export declare function toOpenAIMessages(messages: ChatMessage[], options?: OpenAIEncodeOptions): OpenAIWireMessage[];
|
|
172
|
+
/**
|
|
173
|
+
* ChatMessage[] to an Anthropic Messages `messages` array. THE ROUND-TRIP
|
|
174
|
+
* ENCODER.
|
|
175
|
+
*
|
|
176
|
+
* A reasoning block is emitted as `part.raw.payload` verbatim and is NEVER
|
|
177
|
+
* rebuilt from `text` plus `signature`: Anthropic returns 400 if a thinking
|
|
178
|
+
* block in the most recent assistant message is modified, reordered, filtered or
|
|
179
|
+
* reconstructed. A reasoning part with no `raw`, or with a `raw` captured from
|
|
180
|
+
* some other format, therefore THROWS rather than silently producing a request
|
|
181
|
+
* that will fail.
|
|
182
|
+
*
|
|
183
|
+
* Block order follows part order, which follows stream order, with no filtering,
|
|
184
|
+
* because the API validates order too. An empty-text reasoning part (an omitted
|
|
185
|
+
* or redacted block) is still emitted: the docs require sending back every block
|
|
186
|
+
* "including any blocks with empty thinking fields".
|
|
187
|
+
*
|
|
188
|
+
* ONE ChatMessage CAN BECOME SEVERAL WIRE MESSAGES, for the same reason as
|
|
189
|
+
* `toOpenAIMessages`: the kit streams a whole assistant turn into one message, so
|
|
190
|
+
* the tool call and the model's answer to it share a `parts` array, but Anthropic
|
|
191
|
+
* carries the result in a SEPARATE user message that has to sit between them. So
|
|
192
|
+
* the turn is SPLIT at each tool boundary, into
|
|
193
|
+
*
|
|
194
|
+
* assistant(pre-tool blocks + tool_use) -> user(tool_result)... -> assistant(answer)
|
|
195
|
+
*
|
|
196
|
+
* Flattening instead puts the model's answer BEFORE the result it was based on,
|
|
197
|
+
* and strands every later round's thinking block in the first assistant message.
|
|
198
|
+
* Consecutive tool parts stay in ONE assistant message, because parallel calls are
|
|
199
|
+
* announced together and their results come back together.
|
|
200
|
+
*
|
|
201
|
+
* Adjacent user messages are MERGED. The API combines consecutive same-role turns
|
|
202
|
+
* itself rather than rejecting them, so this is not what stands between you and a
|
|
203
|
+
* 400; it is emitted anyway because the tool-result turn and a following user turn
|
|
204
|
+
* are one turn, several OpenAI-compatible Anthropic proxies do enforce strict
|
|
205
|
+
* alternation, and the merged form is what the models are trained on. Ordering is
|
|
206
|
+
* safe by construction: `results` is only non-empty when `blocks` is, so a
|
|
207
|
+
* tool_result message always follows its assistant message and can never be
|
|
208
|
+
* appended after a plain user turn.
|
|
209
|
+
*
|
|
210
|
+
* `file` parts on a USER turn become `image` and `document` blocks, in part
|
|
211
|
+
* order. Both take `source: {type:'base64'}` and `source: {type:'url'}`, so this
|
|
212
|
+
* wire can carry a remote PDF that `toOpenAIMessages` has to refuse. Anything
|
|
213
|
+
* neither API accepts as message content THROWS by default; see
|
|
214
|
+
* `UnencodableFilePolicy`. A `file` part on an ASSISTANT turn is dropped, because
|
|
215
|
+
* an assistant message here carries only text, thinking and tool_use.
|
|
216
|
+
*
|
|
217
|
+
* Asymmetry worth knowing: `tool_use.input` is a parsed OBJECT on this wire, not
|
|
218
|
+
* a string, so it uses `input` and not `rawInput`. Only thinking blocks carry a
|
|
219
|
+
* verbatim requirement.
|
|
220
|
+
*/
|
|
221
|
+
export declare function toAnthropicMessages(messages: ChatMessage[], options?: AnthropicEncodeOptions): AnthropicWireMessage[];
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { AttachmentData } from '../components/attachment-types.js';
|
|
2
|
+
import { MediaPolicy } from './media-types.js';
|
|
3
|
+
/** Where the bytes live. `base64` carries them inline; `remote` is an address
|
|
4
|
+
* the PROVIDER dereferences, never us. */
|
|
5
|
+
export type FileSource = {
|
|
6
|
+
type: 'base64';
|
|
7
|
+
mediaType: string;
|
|
8
|
+
data: string;
|
|
9
|
+
dataUri: string;
|
|
10
|
+
} | {
|
|
11
|
+
type: 'remote';
|
|
12
|
+
url: string;
|
|
13
|
+
};
|
|
14
|
+
/** Bytes already in hand, shaped for the provider. A union rather than an
|
|
15
|
+
* optional `text` field so neither encoder can reach for text that is not
|
|
16
|
+
* there, and so adding a kind is a compile error at both wires rather than a
|
|
17
|
+
* silently unhandled branch. */
|
|
18
|
+
export type ClassifiedFile =
|
|
19
|
+
/** Named for the Anthropic blocks they become. `document` is PDF-only. */
|
|
20
|
+
{
|
|
21
|
+
kind: 'image';
|
|
22
|
+
mediaType: string;
|
|
23
|
+
source: FileSource;
|
|
24
|
+
filename?: string;
|
|
25
|
+
} | {
|
|
26
|
+
kind: 'document';
|
|
27
|
+
mediaType: string;
|
|
28
|
+
source: FileSource;
|
|
29
|
+
filename?: string;
|
|
30
|
+
}
|
|
31
|
+
/** The bytes, decoded. A text file rides as TEXT CONTENT on both wires --
|
|
32
|
+
* neither API has an arbitrary-file block, so this is the only representation
|
|
33
|
+
* the wire can express, not a preference. `source` is narrowed to `base64`
|
|
34
|
+
* because the text has to be in hand to inline it, and fetching a remote one
|
|
35
|
+
* would put I/O in the encoder. */
|
|
36
|
+
| {
|
|
37
|
+
kind: 'text';
|
|
38
|
+
mediaType: string;
|
|
39
|
+
source: Extract<FileSource, {
|
|
40
|
+
type: 'base64';
|
|
41
|
+
}>;
|
|
42
|
+
filename?: string;
|
|
43
|
+
text: string;
|
|
44
|
+
};
|
|
45
|
+
export type FileClassification =
|
|
46
|
+
/** Ready for either wire to shape. A wire may still refuse it -- see
|
|
47
|
+
* OpenAI and remote PDFs -- but the attachment itself is sound. */
|
|
48
|
+
{
|
|
49
|
+
status: 'encodable';
|
|
50
|
+
file: ClassifiedFile;
|
|
51
|
+
}
|
|
52
|
+
/** Not an upload at all. A `source-document` is the citation chip an app
|
|
53
|
+
* renders next to a RAG answer, not something the user staged to send, and
|
|
54
|
+
* its content is already in the prompt that produced the answer. Encoding it
|
|
55
|
+
* would send the same text twice. */
|
|
56
|
+
| {
|
|
57
|
+
status: 'kit-side';
|
|
58
|
+
}
|
|
59
|
+
/** Cannot reach a model. `reason` completes the sentence "Cannot encode file
|
|
60
|
+
* part N of message X: ...", so it explains AND says what to do. */
|
|
61
|
+
| {
|
|
62
|
+
status: 'unencodable';
|
|
63
|
+
reason: string;
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* The text content a text attachment contributes to the prompt.
|
|
67
|
+
*
|
|
68
|
+
* ★ THE SWAP POINT for how a text file appears in a message, and the shape is
|
|
69
|
+
* SETTLED: `<file name="..." type="...">…</file>`. Two alternatives were weighed
|
|
70
|
+
* and rejected. A bare `filename:\n` prefix marks where the file BEGINS and not
|
|
71
|
+
* where it ends, so the next part of the turn reads as more file. An Anthropic
|
|
72
|
+
* `document` block would buy citations, but it is Anthropic-specific -- the
|
|
73
|
+
* OpenAI wire has no equivalent, so the two wires would disagree about one
|
|
74
|
+
* attachment -- and it needs a Files API upload round trip, which is I/O, in a
|
|
75
|
+
* layer that by design has none. Changing the envelope is still a change to this
|
|
76
|
+
* function and nothing else; it just is not an open question.
|
|
77
|
+
*
|
|
78
|
+
* ★ WHY THE BODY IS BARELY TOUCHED. Both attributes are escaped in full, as
|
|
79
|
+
* attributes always are. The body is not, and that is deliberate: these are
|
|
80
|
+
* source files, a reader will diff what they attached against what arrived, and
|
|
81
|
+
* XML-escaping every `<` in a TSX file would mangle the exact thing this feature
|
|
82
|
+
* exists to send and pay tokens to do it. So the body transform is the smallest
|
|
83
|
+
* one that makes the delimiter unforgeable -- the end tag, and nothing else.
|
|
84
|
+
*
|
|
85
|
+
* Left alone, that end tag is a way out of the block. A file containing `</file>`
|
|
86
|
+
* closes it early, and everything after that point reads to the model as the turn
|
|
87
|
+
* AROUND the file rather than as file content: user-supplied bytes in instruction
|
|
88
|
+
* position, reachable by anyone who can get a file in front of the composer.
|
|
89
|
+
* Escaped, it stays where it belongs. `</file>` ends nothing, and it is
|
|
90
|
+
* visible in the output and reversible by eye, which a deletion or a silent
|
|
91
|
+
* substitution would not be -- a reader who spots it can say exactly what the
|
|
92
|
+
* original byte was. The OPENING tag is left untouched on purpose: it cannot end
|
|
93
|
+
* the block, and escaping it would corrupt every HTML file anyone ever attaches.
|
|
94
|
+
*/
|
|
95
|
+
export declare function textFileContent(file: Extract<ClassifiedFile, {
|
|
96
|
+
kind: 'text';
|
|
97
|
+
}>): string;
|
|
98
|
+
/**
|
|
99
|
+
* One attachment to the provider-neutral facts both encoders need.
|
|
100
|
+
*
|
|
101
|
+
* A `data:` URI's OWN media type wins over `attachment.mediaType` when they
|
|
102
|
+
* disagree: the URI describes the bytes actually present, while the field is a
|
|
103
|
+
* label the host set, and it is the bytes the provider will decode. The one
|
|
104
|
+
* exception is a URI whose media type names nothing, where there is no answer to
|
|
105
|
+
* prefer -- see below.
|
|
106
|
+
*
|
|
107
|
+
* When NOTHING names the file, it is settled by DECODING it rather than by
|
|
108
|
+
* reading its filename. That path can only ever produce text, and only when the
|
|
109
|
+
* policy still allows text, so it cannot be used to get around `accept`.
|
|
110
|
+
*
|
|
111
|
+
* `policy` narrows what counts as encodable. It defaults to the kit's full
|
|
112
|
+
* capability set, so an omitted policy behaves exactly as before for images and
|
|
113
|
+
* PDFs. It is the SAME object the composer resolves for its picker -- see
|
|
114
|
+
* `media-types.ts` for why that sharing is the point rather than a convenience.
|
|
115
|
+
*/
|
|
116
|
+
export declare function classifyAttachment(attachment: AttachmentData, policy?: MediaPolicy): FileClassification;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { WireFormat } from '../chunk.js';
|
|
2
|
+
/** Stateless: every frame is self-describing, so `open()` returns a reader with
|
|
3
|
+
* no closure state. The interface still calls it per stream, which is what lets
|
|
4
|
+
* the stateful Anthropic format use the same seam. */
|
|
5
|
+
export declare const openaiChatFormat: WireFormat;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export { readModelStream, readOpenAIStream, readAnthropicStream, WireError } from './read.js';
|
|
2
|
+
export type { StreamSource, ReadOptions } from './read.js';
|
|
3
|
+
export { consumeModelStream, createToolCallAccumulator } from './consume.js';
|
|
4
|
+
export { applyToolOutput, applyToolFailure, bufferText } from './sink-helpers.js';
|
|
5
|
+
export { toOpenAIMessages, toAnthropicMessages, WireEncodeError } from './encode.js';
|
|
6
|
+
export type { AnthropicContentBlock, AnthropicEncodeOptions, AnthropicWireMessage, FileEncodeOptions, OpenAIContentPart, OpenAIEncodeOptions, OpenAIReasoningDetail, OpenAIToolCall, OpenAIWireMessage, UnencodableFilePolicy, } from './encode.js';
|
|
7
|
+
export { encodableMediaTypes, resolveMediaPolicy } from './media-types.js';
|
|
8
|
+
export type { EncodableKind, MediaDecision, MediaPolicy, MediaPolicyOptions, MediaTypeFilter, } from './media-types.js';
|
|
9
|
+
export { openaiChatFormat } from './formats/openai.js';
|
|
10
|
+
export { anthropicMessagesFormat } from './formats/anthropic.js';
|
|
11
|
+
export { sseDataFrames, sseJson, readableToAsyncIterable } from './sse.js';
|
|
12
|
+
export type { ByteSource } from './sse.js';
|
|
13
|
+
export { normalizeStopReason } from './chunk.js';
|
|
14
|
+
export type { AssistantStreamSink, ConsumeOptions, ModelStreamChunk, ModelToolCall, ModelToolCallDelta, ModelTurn, ModelUsage, StopReason, WireFormat, WireFormatReader, } from './chunk.js';
|
|
15
|
+
export type { ChatMessage, MessagePart, MessageSource, RawOrigin } from '../elements/chat-types.js';
|
|
16
|
+
export type { ToolPart } from '../components/tool-types.js';
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/** What a media type BECOMES on the wire. Named for the Anthropic block set,
|
|
2
|
+
* which is the narrower of the two: `text` / `image` / `document` and nothing
|
|
3
|
+
* else. There is no arbitrary-file block on either API, which is why a text
|
|
4
|
+
* file has to ride as text content and a `.zip` has no representation at all. */
|
|
5
|
+
export type EncodableKind = 'image' | 'document' | 'text';
|
|
6
|
+
/**
|
|
7
|
+
* The media type an unnamed file gets once its BYTES have proven to be UTF-8
|
|
8
|
+
* text, and the type whose presence in the effective policy is what permits that
|
|
9
|
+
* proof to be attempted at all.
|
|
10
|
+
*
|
|
11
|
+
* `text/plain` is not a guess dressed up as a fact. It is the media type for
|
|
12
|
+
* "text, no further structure claimed", which is exactly what a successful
|
|
13
|
+
* decode establishes and the most any decode could ever establish. A `.rs` file
|
|
14
|
+
* arriving as `text/plain` is true; arriving as `text/x-rust` would be the
|
|
15
|
+
* filename talking.
|
|
16
|
+
*/
|
|
17
|
+
export declare const UNNAMED_TEXT_MEDIA_TYPE = "text/plain";
|
|
18
|
+
/** A developer's narrowing filter. A comma-separated string (so it works as an
|
|
19
|
+
* HTML attribute under the `kai-` contract) or an array (so it composes in JS).
|
|
20
|
+
* Both spell the same thing; the string form is what `<kai-chat accept="...">`
|
|
21
|
+
* takes and what the `kai` MCP scaffolder can emit as data. */
|
|
22
|
+
export type MediaTypeFilter = string | readonly string[];
|
|
23
|
+
/** One media type, decided against the effective set.
|
|
24
|
+
*
|
|
25
|
+
* `unsupported` and `filtered` are split because the developer needs different
|
|
26
|
+
* things from them: `unsupported` means fix your expectations (no API takes
|
|
27
|
+
* this), `filtered` means fix your `accept` (the kit could have sent it). */
|
|
28
|
+
export type MediaDecision = {
|
|
29
|
+
status: 'allowed';
|
|
30
|
+
kind: EncodableKind;
|
|
31
|
+
} | {
|
|
32
|
+
status: 'unsupported';
|
|
33
|
+
} | {
|
|
34
|
+
status: 'filtered';
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Nobody named this file, so its media type cannot decide it. LOOK AT THE
|
|
38
|
+
* BYTES: decode them as UTF-8 (`classifyAttachment` does), and it is `text`
|
|
39
|
+
* if they decode and unencodable if they do not. Do not look at the filename.
|
|
40
|
+
*
|
|
41
|
+
* ★ THIS VARIANT IS WHERE THE DEVELOPER'S FILTER IS ENFORCED, so the caller
|
|
42
|
+
* cannot get it wrong by forgetting to check. It is returned ONLY when the
|
|
43
|
+
* effective policy still admits `text/plain` -- the one thing a decode can
|
|
44
|
+
* establish. Narrow `accept` to `image/png` and an unnamed file comes back
|
|
45
|
+
* `unsupported` instead, so the decode path is unreachable and a typeless
|
|
46
|
+
* `.rs` cannot slip past an images-only filter. That is the whole reason the
|
|
47
|
+
* check lives here rather than at each call site: there is one place to get
|
|
48
|
+
* right, and the callers physically cannot reach the bytes without being told
|
|
49
|
+
* to.
|
|
50
|
+
*/
|
|
51
|
+
| {
|
|
52
|
+
status: 'undetermined';
|
|
53
|
+
};
|
|
54
|
+
export interface MediaPolicy {
|
|
55
|
+
/** The effective patterns: the kit's capability set narrowed by the
|
|
56
|
+
* developer's filter. Never wider than `encodableMediaTypes()`. */
|
|
57
|
+
readonly types: readonly string[];
|
|
58
|
+
/** The same set as an HTML `accept` attribute value, ready to put on an
|
|
59
|
+
* `<input type="file">`. This is what makes the picker and the encoder
|
|
60
|
+
* provably one thing rather than two lists that agree today. */
|
|
61
|
+
readonly accept: string;
|
|
62
|
+
/**
|
|
63
|
+
* What this policy makes of one media type, including "I cannot tell from
|
|
64
|
+
* this, go and read the bytes" -- see `undetermined`.
|
|
65
|
+
*
|
|
66
|
+
* The primitive behind "expose information, do not make decisions": it answers
|
|
67
|
+
* a question and returns a fact, so a consumer can build their own picker,
|
|
68
|
+
* their own validation and their own error copy on top of it without the kit
|
|
69
|
+
* deciding anything on their behalf.
|
|
70
|
+
*/
|
|
71
|
+
decide(mediaType: string | undefined): MediaDecision;
|
|
72
|
+
}
|
|
73
|
+
export interface MediaPolicyOptions {
|
|
74
|
+
/** Narrow the kit's capability set. Omitted means the full set. Anything here
|
|
75
|
+
* that the kit cannot encode is dropped rather than honoured, because
|
|
76
|
+
* allowing it would only relocate the failure to the provider. */
|
|
77
|
+
accept?: MediaTypeFilter;
|
|
78
|
+
}
|
|
79
|
+
/** Does this media type say anything at all about the bytes it labels? Exported
|
|
80
|
+
* so `files.ts` decides "nameless" by the same rule `decide()` does, rather
|
|
81
|
+
* than growing a second opinion about which spellings mean "I don't know". */
|
|
82
|
+
export declare const namesNothing: (mediaType: string | undefined) => boolean;
|
|
83
|
+
/**
|
|
84
|
+
* Every media type this kit's encoders can represent, as HTML `accept` patterns.
|
|
85
|
+
*
|
|
86
|
+
* PUBLIC ON PURPOSE, and not merely for introspection: a consumer who wants
|
|
87
|
+
* their own picker, their own validation and their own error copy can build all
|
|
88
|
+
* three against this without touching `<kai-chat>` at all. That demotes the
|
|
89
|
+
* `accept` prop from "the only door" to a convenience over a published fact,
|
|
90
|
+
* which is the composition-first shape this project already committed to.
|
|
91
|
+
*/
|
|
92
|
+
export declare function encodableMediaTypes(): readonly string[];
|
|
93
|
+
/**
|
|
94
|
+
* The effective policy: the kit's capability set narrowed by the developer's
|
|
95
|
+
* filter, plus the text ceiling.
|
|
96
|
+
*
|
|
97
|
+
* BOTH LAYERS CALL THIS. The composer calls it to build its picker `accept`
|
|
98
|
+
* attribute and to filter what actually gets staged; the encoders call it to
|
|
99
|
+
* decide what reaches the wire. That shared call is the whole design -- it is
|
|
100
|
+
* why changing `ENCODABLE` above moves the picker and the encoder together
|
|
101
|
+
* rather than moving one and leaving the other to be noticed in production.
|
|
102
|
+
*/
|
|
103
|
+
export declare function resolveMediaPolicy(options?: MediaPolicyOptions): MediaPolicy;
|
|
104
|
+
/** The policy every caller gets when nobody narrowed anything: the kit's full
|
|
105
|
+
* capability set. Built once -- it has no inputs. */
|
|
106
|
+
export declare const DEFAULT_MEDIA_POLICY: MediaPolicy;
|