@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,342 @@
|
|
|
1
|
+
// Attachment -> wire classification, shared by both encoders.
|
|
2
|
+
//
|
|
3
|
+
// Split out of encode.ts because the two wires disagree about attachments in a
|
|
4
|
+
// way that is worth stating once: WHAT a file is (an image, a PDF, or something
|
|
5
|
+
// neither API takes as message content) is provider-neutral and decided here;
|
|
6
|
+
// HOW it rides on the wire is provider-specific and decided in encode.ts. The
|
|
7
|
+
// one gap that falls out of that split is OpenAI's, and it is real: a remote PDF
|
|
8
|
+
// has no representation in chat-completions at all.
|
|
9
|
+
//
|
|
10
|
+
// No I/O, exactly like the rest of this layer. Nothing here fetches a URL,
|
|
11
|
+
// reads a File, or base64s anything: an attachment either already carries bytes
|
|
12
|
+
// (a `data:` URI) or already carries an address the provider can resolve
|
|
13
|
+
// itself. That constraint is what makes a `blob:` URL unencodable rather than
|
|
14
|
+
// merely inconvenient -- resolving one requires the browser tab that minted it.
|
|
15
|
+
import type { AttachmentData } from '../components/attachment-types';
|
|
16
|
+
import {
|
|
17
|
+
DEFAULT_MEDIA_POLICY,
|
|
18
|
+
UNNAMED_TEXT_MEDIA_TYPE,
|
|
19
|
+
namesNothing,
|
|
20
|
+
type MediaPolicy,
|
|
21
|
+
} from './media-types';
|
|
22
|
+
|
|
23
|
+
/** `data:<media type>;base64,<data>`. Base64 is required -- both APIs need it,
|
|
24
|
+
* and a `data:` URI without it cannot become a content block without guessing.
|
|
25
|
+
* The media type is NOT required, because a browser omits it for a file it
|
|
26
|
+
* could not name: the File API says to emit a `data:` URL with no media type,
|
|
27
|
+
* and Chrome and jsdom both write `application/octet-stream` there instead.
|
|
28
|
+
* Either way there is no type to read, which `decide()` answers with
|
|
29
|
+
* `undetermined` and this file settles by decoding. */
|
|
30
|
+
const BASE64_DATA_URI = /^data:([^;,]*);base64,([\s\S]*)$/;
|
|
31
|
+
|
|
32
|
+
const SCHEME = /^([a-z][a-z0-9+.-]*):/i;
|
|
33
|
+
|
|
34
|
+
/** Where the bytes live. `base64` carries them inline; `remote` is an address
|
|
35
|
+
* the PROVIDER dereferences, never us. */
|
|
36
|
+
export type FileSource =
|
|
37
|
+
| { type: 'base64'; mediaType: string; data: string; dataUri: string }
|
|
38
|
+
| { type: 'remote'; url: string };
|
|
39
|
+
|
|
40
|
+
/** Bytes already in hand, shaped for the provider. A union rather than an
|
|
41
|
+
* optional `text` field so neither encoder can reach for text that is not
|
|
42
|
+
* there, and so adding a kind is a compile error at both wires rather than a
|
|
43
|
+
* silently unhandled branch. */
|
|
44
|
+
export type ClassifiedFile =
|
|
45
|
+
/** Named for the Anthropic blocks they become. `document` is PDF-only. */
|
|
46
|
+
| { kind: 'image'; mediaType: string; source: FileSource; filename?: string }
|
|
47
|
+
| { kind: 'document'; mediaType: string; source: FileSource; filename?: string }
|
|
48
|
+
/** The bytes, decoded. A text file rides as TEXT CONTENT on both wires --
|
|
49
|
+
* neither API has an arbitrary-file block, so this is the only representation
|
|
50
|
+
* the wire can express, not a preference. `source` is narrowed to `base64`
|
|
51
|
+
* because the text has to be in hand to inline it, and fetching a remote one
|
|
52
|
+
* would put I/O in the encoder. */
|
|
53
|
+
| {
|
|
54
|
+
kind: 'text';
|
|
55
|
+
mediaType: string;
|
|
56
|
+
source: Extract<FileSource, { type: 'base64' }>;
|
|
57
|
+
filename?: string;
|
|
58
|
+
text: string;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export type FileClassification =
|
|
62
|
+
/** Ready for either wire to shape. A wire may still refuse it -- see
|
|
63
|
+
* OpenAI and remote PDFs -- but the attachment itself is sound. */
|
|
64
|
+
| { status: 'encodable'; file: ClassifiedFile }
|
|
65
|
+
/** Not an upload at all. A `source-document` is the citation chip an app
|
|
66
|
+
* renders next to a RAG answer, not something the user staged to send, and
|
|
67
|
+
* its content is already in the prompt that produced the answer. Encoding it
|
|
68
|
+
* would send the same text twice. */
|
|
69
|
+
| { status: 'kit-side' }
|
|
70
|
+
/** Cannot reach a model. `reason` completes the sentence "Cannot encode file
|
|
71
|
+
* part N of message X: ...", so it explains AND says what to do. */
|
|
72
|
+
| { status: 'unencodable'; reason: string };
|
|
73
|
+
|
|
74
|
+
const quotedList = (values: readonly string[]): string => values.map((v) => `"${v}"`).join(', ');
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Base64 to the bytes it stands for.
|
|
78
|
+
*
|
|
79
|
+
* NOT a violation of this file's no-I/O rule. The bytes are already inline in
|
|
80
|
+
* the `data:` URI the host staged; this decodes what is in hand and reaches for
|
|
81
|
+
* nothing. `atob` yields a binary string (one char per byte), which is then read
|
|
82
|
+
* as UTF-8 -- going straight from `atob` to text would mangle every non-ASCII
|
|
83
|
+
* character in the file.
|
|
84
|
+
*
|
|
85
|
+
* ★ WHAT A CLEAN DECODE PROVES, AND WHAT IT DOES NOT. It proves the bytes are
|
|
86
|
+
* well-formed UTF-8. It does not prove they are meaningful text, and the gap is
|
|
87
|
+
* not hypothetical: every byte below 0x80 is valid UTF-8 on its own, so a small
|
|
88
|
+
* binary made of low bytes decodes cleanly. A bare zip header -- `50 4b 03 04`
|
|
89
|
+
* followed by NULs -- is exactly that. This matters most on the path where
|
|
90
|
+
* nothing named the file, because there the decode is the ONLY evidence there is.
|
|
91
|
+
*
|
|
92
|
+
* A NUL-byte heuristic (git's rule for "is this binary") would catch most of
|
|
93
|
+
* them, and it is deliberately NOT here. This same function serves the LABELLED
|
|
94
|
+
* text path, where a host that said `text/plain` has asserted something the kit
|
|
95
|
+
* has no business overruling, and a text file that legitimately contains a NUL
|
|
96
|
+
* would start being refused on a rule nobody asked for. Weigh the two failures:
|
|
97
|
+
* being wrong the current way sends a block of gibberish the model can see and
|
|
98
|
+
* describe, and being wrong the other way silently refuses a real text file.
|
|
99
|
+
* Only the first is recoverable by the person it happens to.
|
|
100
|
+
*/
|
|
101
|
+
function decodeBase64Text(data: string): { ok: true; text: string; bytes: number } | { ok: false } {
|
|
102
|
+
try {
|
|
103
|
+
const binary = atob(data);
|
|
104
|
+
const bytes = new Uint8Array(binary.length);
|
|
105
|
+
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
106
|
+
// `fatal` so mislabelled binary (a .zip renamed .txt) is refused with a
|
|
107
|
+
// reason instead of silently becoming a screenful of replacement characters.
|
|
108
|
+
return { ok: true, text: new TextDecoder('utf-8', { fatal: true }).decode(bytes), bytes: binary.length };
|
|
109
|
+
} catch {
|
|
110
|
+
return { ok: false };
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* XML attribute escaping, for the two attributes the envelope carries.
|
|
116
|
+
*
|
|
117
|
+
* A filename is whatever the user called their file, and it lands between double
|
|
118
|
+
* quotes: `notes" x="` would otherwise rewrite the header into attributes of its
|
|
119
|
+
* own choosing. Newlines are escaped for the same reason and not for tidiness --
|
|
120
|
+
* a raw one splits the header across lines, which is how a filename gets to look
|
|
121
|
+
* like the start of the content.
|
|
122
|
+
*
|
|
123
|
+
* The media type goes through the same function, which is not symmetry for its
|
|
124
|
+
* own sake: it comes from a `data:` URI, where it is anything up to the first
|
|
125
|
+
* `;`, and `text/pl"ain` matches the `text/*` capability and arrives here whole.
|
|
126
|
+
*/
|
|
127
|
+
const escapeAttribute = (value: string): string =>
|
|
128
|
+
value
|
|
129
|
+
.replace(/&/g, '&')
|
|
130
|
+
.replace(/</g, '<')
|
|
131
|
+
.replace(/>/g, '>')
|
|
132
|
+
.replace(/"/g, '"')
|
|
133
|
+
.replace(/\r/g, ' ')
|
|
134
|
+
.replace(/\n/g, ' ');
|
|
135
|
+
|
|
136
|
+
/** The only sequence in the body that could end the block early, and so the only
|
|
137
|
+
* thing in the body that gets touched. Whitespace-tolerant and case-insensitive
|
|
138
|
+
* because the reader here is a model, and `</file >` reads as the end to one
|
|
139
|
+
* just as readily as `</file>` does. */
|
|
140
|
+
const CLOSING_DELIMITER = /<(\/file\s*)>/gi;
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* The text content a text attachment contributes to the prompt.
|
|
144
|
+
*
|
|
145
|
+
* ★ THE SWAP POINT for how a text file appears in a message, and the shape is
|
|
146
|
+
* SETTLED: `<file name="..." type="...">…</file>`. Two alternatives were weighed
|
|
147
|
+
* and rejected. A bare `filename:\n` prefix marks where the file BEGINS and not
|
|
148
|
+
* where it ends, so the next part of the turn reads as more file. An Anthropic
|
|
149
|
+
* `document` block would buy citations, but it is Anthropic-specific -- the
|
|
150
|
+
* OpenAI wire has no equivalent, so the two wires would disagree about one
|
|
151
|
+
* attachment -- and it needs a Files API upload round trip, which is I/O, in a
|
|
152
|
+
* layer that by design has none. Changing the envelope is still a change to this
|
|
153
|
+
* function and nothing else; it just is not an open question.
|
|
154
|
+
*
|
|
155
|
+
* ★ WHY THE BODY IS BARELY TOUCHED. Both attributes are escaped in full, as
|
|
156
|
+
* attributes always are. The body is not, and that is deliberate: these are
|
|
157
|
+
* source files, a reader will diff what they attached against what arrived, and
|
|
158
|
+
* XML-escaping every `<` in a TSX file would mangle the exact thing this feature
|
|
159
|
+
* exists to send and pay tokens to do it. So the body transform is the smallest
|
|
160
|
+
* one that makes the delimiter unforgeable -- the end tag, and nothing else.
|
|
161
|
+
*
|
|
162
|
+
* Left alone, that end tag is a way out of the block. A file containing `</file>`
|
|
163
|
+
* closes it early, and everything after that point reads to the model as the turn
|
|
164
|
+
* AROUND the file rather than as file content: user-supplied bytes in instruction
|
|
165
|
+
* position, reachable by anyone who can get a file in front of the composer.
|
|
166
|
+
* Escaped, it stays where it belongs. `</file>` ends nothing, and it is
|
|
167
|
+
* visible in the output and reversible by eye, which a deletion or a silent
|
|
168
|
+
* substitution would not be -- a reader who spots it can say exactly what the
|
|
169
|
+
* original byte was. The OPENING tag is left untouched on purpose: it cannot end
|
|
170
|
+
* the block, and escaping it would corrupt every HTML file anyone ever attaches.
|
|
171
|
+
*/
|
|
172
|
+
export function textFileContent(file: Extract<ClassifiedFile, { kind: 'text' }>): string {
|
|
173
|
+
const name = escapeAttribute(file.filename ?? 'attachment');
|
|
174
|
+
const type = escapeAttribute(file.mediaType);
|
|
175
|
+
const text = file.text.replace(CLOSING_DELIMITER, '<$1>');
|
|
176
|
+
return `<file name="${name}" type="${type}">\n${text}\n</file>`;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* One attachment to the provider-neutral facts both encoders need.
|
|
181
|
+
*
|
|
182
|
+
* A `data:` URI's OWN media type wins over `attachment.mediaType` when they
|
|
183
|
+
* disagree: the URI describes the bytes actually present, while the field is a
|
|
184
|
+
* label the host set, and it is the bytes the provider will decode. The one
|
|
185
|
+
* exception is a URI whose media type names nothing, where there is no answer to
|
|
186
|
+
* prefer -- see below.
|
|
187
|
+
*
|
|
188
|
+
* When NOTHING names the file, it is settled by DECODING it rather than by
|
|
189
|
+
* reading its filename. That path can only ever produce text, and only when the
|
|
190
|
+
* policy still allows text, so it cannot be used to get around `accept`.
|
|
191
|
+
*
|
|
192
|
+
* `policy` narrows what counts as encodable. It defaults to the kit's full
|
|
193
|
+
* capability set, so an omitted policy behaves exactly as before for images and
|
|
194
|
+
* PDFs. It is the SAME object the composer resolves for its picker -- see
|
|
195
|
+
* `media-types.ts` for why that sharing is the point rather than a convenience.
|
|
196
|
+
*/
|
|
197
|
+
export function classifyAttachment(
|
|
198
|
+
attachment: AttachmentData,
|
|
199
|
+
policy: MediaPolicy = DEFAULT_MEDIA_POLICY,
|
|
200
|
+
): FileClassification {
|
|
201
|
+
if (attachment.type === 'source-document') return { status: 'kit-side' };
|
|
202
|
+
|
|
203
|
+
const url = attachment.url;
|
|
204
|
+
if (url === undefined || url === '') {
|
|
205
|
+
return {
|
|
206
|
+
status: 'unencodable',
|
|
207
|
+
reason:
|
|
208
|
+
'it has no `url`, so there are no bytes and no address to send. Set `url` to a `data:` URI (read the File with FileReader.readAsDataURL before you stage it) or to an https URL the provider can fetch.',
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
let source: FileSource;
|
|
213
|
+
let mediaType: string;
|
|
214
|
+
|
|
215
|
+
const asData = BASE64_DATA_URI.exec(url);
|
|
216
|
+
if (asData) {
|
|
217
|
+
const inUri = asData[1].toLowerCase();
|
|
218
|
+
// The URI's own media type wins -- but only when it NAMES something. A
|
|
219
|
+
// browser writes `application/octet-stream` (or nothing) for a file it could
|
|
220
|
+
// not identify, and a non-answer describes the bytes no better than the
|
|
221
|
+
// host's `mediaType` field does. In that one case the field is the only
|
|
222
|
+
// label anybody actually wrote down, so it is read rather than overridden.
|
|
223
|
+
mediaType = namesNothing(inUri) ? (attachment.mediaType?.toLowerCase() ?? inUri) : inUri;
|
|
224
|
+
source = { type: 'base64', mediaType, data: asData[2], dataUri: url };
|
|
225
|
+
} else if (/^https?:\/\//i.test(url)) {
|
|
226
|
+
const declared = attachment.mediaType?.toLowerCase();
|
|
227
|
+
if (declared === undefined || declared === '') {
|
|
228
|
+
return {
|
|
229
|
+
status: 'unencodable',
|
|
230
|
+
reason: `its \`url\` is remote ("${url}") but it has no \`mediaType\`, so there is no way to tell an image from a document without fetching it -- and this layer does no I/O. Set \`mediaType\` when you stage the attachment.`,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
mediaType = declared;
|
|
234
|
+
source = { type: 'remote', url };
|
|
235
|
+
} else if (url.startsWith('data:')) {
|
|
236
|
+
return {
|
|
237
|
+
status: 'unencodable',
|
|
238
|
+
reason:
|
|
239
|
+
'its `url` is a `data:` URI that is not base64-encoded with an explicit media type. Both APIs need `data:<media type>;base64,<data>`.',
|
|
240
|
+
};
|
|
241
|
+
} else {
|
|
242
|
+
const scheme = SCHEME.exec(url)?.[1].toLowerCase();
|
|
243
|
+
// blob: is the one people hit, because it is what URL.createObjectURL and
|
|
244
|
+
// every drag-and-drop example hand you. It resolves only inside the tab that
|
|
245
|
+
// created it, so it looks perfect in the thread and is meaningless to a model.
|
|
246
|
+
const detail =
|
|
247
|
+
scheme === 'blob'
|
|
248
|
+
? 'a `blob:` URL resolves only inside the browser tab that created it, so a model can never fetch it. Read the File with FileReader.readAsDataURL and stage the `data:` URI instead, or upload it and stage the resulting https URL'
|
|
249
|
+
: `a "${scheme ?? url}" URL is not something a provider can fetch. Use a \`data:\` URI or an https URL`;
|
|
250
|
+
return { status: 'unencodable', reason: `its \`url\` is "${url}": ${detail}.` };
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const decision = policy.decide(mediaType);
|
|
254
|
+
|
|
255
|
+
if (decision.status === 'undetermined') {
|
|
256
|
+
// ★ NOBODY NAMED THIS FILE, SO READ IT. Seven of the file types a developer
|
|
257
|
+
// is likeliest to attach to a coding chat arrive from Chrome with no media
|
|
258
|
+
// type at all (the table in media-types.ts), and the tempting fix -- map
|
|
259
|
+
// `.rs` to text -- is a filename talking, which is the one thing this design
|
|
260
|
+
// refuses. The bytes are already in hand, so ask them instead: a clean UTF-8
|
|
261
|
+
// decode IS the evidence that this is text, and it is evidence about the
|
|
262
|
+
// actual file rather than about its name.
|
|
263
|
+
//
|
|
264
|
+
// The developer's filter is already enforced: `undetermined` comes back only
|
|
265
|
+
// when the effective policy still admits plain text, so reaching this line at
|
|
266
|
+
// all means a text file is something this policy would take. An
|
|
267
|
+
// images-only `accept` gets `unsupported` and never arrives here.
|
|
268
|
+
if (source.type !== 'base64') {
|
|
269
|
+
return {
|
|
270
|
+
status: 'unencodable',
|
|
271
|
+
reason: `it is at a remote URL ("${source.url}") and its media type "${mediaType}" says nothing about the bytes -- that is the label for "arbitrary binary", so it separates an image from a document not at all. Settling it by reading the bytes would need a fetch, and this layer does no I/O. Set \`mediaType\` to what the file actually is when you stage it.`,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
const decoded = decodeBase64Text(source.data);
|
|
275
|
+
if (!decoded.ok) {
|
|
276
|
+
return {
|
|
277
|
+
status: 'unencodable',
|
|
278
|
+
reason: `nothing names it -- ${mediaType === '' ? 'it carries no media type' : `its media type is "${mediaType}"`}, so the only way to tell what it is was to read it -- and its bytes are not valid UTF-8, so it is binary of some unidentified kind. Neither API has an arbitrary-file block to carry that. Set \`mediaType\` if you know what it is, or hand the file to the model through a tool.`,
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
// `text/plain` and not the media type it arrived with: a decode establishes
|
|
282
|
+
// "this is text" and nothing more specific, and that is exactly what
|
|
283
|
+
// `text/plain` means. See UNNAMED_TEXT_MEDIA_TYPE.
|
|
284
|
+
return {
|
|
285
|
+
status: 'encodable',
|
|
286
|
+
file: {
|
|
287
|
+
kind: 'text',
|
|
288
|
+
mediaType: UNNAMED_TEXT_MEDIA_TYPE,
|
|
289
|
+
source,
|
|
290
|
+
filename: attachment.filename,
|
|
291
|
+
text: decoded.text,
|
|
292
|
+
},
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (decision.status === 'filtered') {
|
|
297
|
+
return {
|
|
298
|
+
status: 'unencodable',
|
|
299
|
+
reason: `its media type "${mediaType}" is one this kit can encode, but your \`accept\` filter excludes it. The types you allowed are: ${quotedList(policy.types)}. Widen \`accept\` to include it, or stop staging it.`,
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (decision.status === 'unsupported') {
|
|
304
|
+
return {
|
|
305
|
+
status: 'unencodable',
|
|
306
|
+
reason: `its media type "${mediaType}" is not one either API accepts as message content. Supported: ${quotedList(policy.types)}. Extract the content yourself and send it as text, or hand it to the model through a tool.`,
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (decision.kind === 'text') {
|
|
311
|
+
// A remote text file would have to be FETCHED to be inlined, and this layer
|
|
312
|
+
// does no I/O. Refused with the fix rather than silently dropped.
|
|
313
|
+
if (source.type !== 'base64') {
|
|
314
|
+
return {
|
|
315
|
+
status: 'unencodable',
|
|
316
|
+
reason: `it is a text file at a remote URL ("${source.url}"), and text has to ride as text CONTENT -- neither API has an arbitrary-file block to point at a URL with. Reading it here would put I/O in the encoder. Fetch it yourself and stage the \`data:\` URI, or paste the contents as a text part.`,
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
const decoded = decodeBase64Text(source.data);
|
|
320
|
+
if (!decoded.ok) {
|
|
321
|
+
return {
|
|
322
|
+
status: 'unencodable',
|
|
323
|
+
reason: `its media type "${mediaType}" says text, but its bytes are not valid UTF-8 -- so it is binary wearing a text label, and inlining it would send the model garbage. Send it under its real media type, or extract the text yourself.`,
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
return {
|
|
327
|
+
status: 'encodable',
|
|
328
|
+
file: {
|
|
329
|
+
kind: 'text',
|
|
330
|
+
mediaType,
|
|
331
|
+
source,
|
|
332
|
+
filename: attachment.filename,
|
|
333
|
+
text: decoded.text,
|
|
334
|
+
},
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return {
|
|
339
|
+
status: 'encodable',
|
|
340
|
+
file: { kind: decision.kind, mediaType, source, filename: attachment.filename },
|
|
341
|
+
};
|
|
342
|
+
}
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
// Anthropic Messages SSE.
|
|
2
|
+
//
|
|
3
|
+
// This format is STATEFUL. `content_block_delta` names the delta kind
|
|
4
|
+
// (`thinking_delta`, `input_json_delta`, ...) but not which block kind the
|
|
5
|
+
// fragment belongs to, so the block map is populated at `content_block_start`
|
|
6
|
+
// and read on every later event for that index.
|
|
7
|
+
//
|
|
8
|
+
// It is also the only format in the kit carrying a VERBATIM requirement:
|
|
9
|
+
// Anthropic returns 400 if a `thinking` block is modified, reordered or
|
|
10
|
+
// reconstructed on the way back in. The block emitted at `content_block_stop`
|
|
11
|
+
// is ASSEMBLED, not invented: `thinking` is the concatenation of the provider's
|
|
12
|
+
// own thinking_delta payloads and `signature` is the provider's signature_delta,
|
|
13
|
+
// so every byte came off the wire. Nothing is derived from the reasoning PART's
|
|
14
|
+
// text, which a consumer can edit.
|
|
15
|
+
//
|
|
16
|
+
// Three of the chunks below carry NO reasoning text (the block start, every
|
|
17
|
+
// signature_delta, the block stop). They depend on consume.ts gating reasoning
|
|
18
|
+
// on `reasoning !== undefined || reasoningRaw || reasoningSignature` rather than
|
|
19
|
+
// on truthiness. Under the old guard all three vanish.
|
|
20
|
+
import type { MessageSource } from '../../elements/chat-types';
|
|
21
|
+
import type { ModelStreamChunk, ModelUsage, WireFormat, WireFormatReader } from '../chunk';
|
|
22
|
+
|
|
23
|
+
function isRecord(v: unknown): v is Record<string, unknown> {
|
|
24
|
+
return typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function str(v: unknown): string | undefined {
|
|
28
|
+
return typeof v === 'string' ? v : undefined;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function num(v: unknown): number | undefined {
|
|
32
|
+
return typeof v === 'number' ? v : undefined;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
type BlockKind = 'text' | 'thinking' | 'redacted_thinking' | 'tool_use' | 'other';
|
|
36
|
+
|
|
37
|
+
interface BlockState {
|
|
38
|
+
kind: BlockKind;
|
|
39
|
+
/** Accumulated thinking_delta payloads, for the verbatim block at stop. */
|
|
40
|
+
thinking: string;
|
|
41
|
+
/** Accumulated signature_delta payloads. */
|
|
42
|
+
signature?: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function usageOf(raw: unknown): ModelUsage | undefined {
|
|
46
|
+
if (!isRecord(raw)) return undefined;
|
|
47
|
+
const out: ModelUsage = {};
|
|
48
|
+
const input = num(raw.input_tokens);
|
|
49
|
+
if (input !== undefined) out.inputTokens = input;
|
|
50
|
+
const output = num(raw.output_tokens);
|
|
51
|
+
if (output !== undefined) out.outputTokens = output;
|
|
52
|
+
const cached = num(raw.cache_read_input_tokens);
|
|
53
|
+
if (cached !== undefined) out.cachedInputTokens = cached;
|
|
54
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function blockStart(
|
|
58
|
+
frame: Record<string, unknown>,
|
|
59
|
+
blocks: Map<number, BlockState>,
|
|
60
|
+
toolIndexById: Map<string, number>,
|
|
61
|
+
): ModelStreamChunk[] {
|
|
62
|
+
const index = num(frame.index) ?? 0;
|
|
63
|
+
const block = frame.content_block;
|
|
64
|
+
if (!isRecord(block)) return [];
|
|
65
|
+
|
|
66
|
+
switch (str(block.type)) {
|
|
67
|
+
case 'text': {
|
|
68
|
+
blocks.set(index, { kind: 'text', thinking: '' });
|
|
69
|
+
const text = str(block.text);
|
|
70
|
+
return text ? [{ text }] : [];
|
|
71
|
+
}
|
|
72
|
+
case 'thinking': {
|
|
73
|
+
const seed = str(block.thinking) ?? '';
|
|
74
|
+
blocks.set(index, { kind: 'thinking', thinking: seed });
|
|
75
|
+
// An EMPTY reasoning delta on purpose: it opens the reasoning part at the
|
|
76
|
+
// point in the stream where the block started, so block order in `parts`
|
|
77
|
+
// matches block order on the wire. The encoder round-trips that order.
|
|
78
|
+
return [{ reasoning: seed, reasoningIndex: index }];
|
|
79
|
+
}
|
|
80
|
+
case 'redacted_thinking': {
|
|
81
|
+
blocks.set(index, { kind: 'redacted_thinking', thinking: '' });
|
|
82
|
+
// The WHOLE block arrives here; there are no deltas and there is no
|
|
83
|
+
// readable text. It must still round-trip: the docs require sending back
|
|
84
|
+
// every block "including any blocks with empty thinking fields".
|
|
85
|
+
return [
|
|
86
|
+
{
|
|
87
|
+
reasoning: '',
|
|
88
|
+
reasoningIndex: index,
|
|
89
|
+
reasoningRaw: { source: 'anthropic.content_block', payload: block },
|
|
90
|
+
},
|
|
91
|
+
];
|
|
92
|
+
}
|
|
93
|
+
case 'tool_use':
|
|
94
|
+
case 'server_tool_use': {
|
|
95
|
+
const id = str(block.id);
|
|
96
|
+
const name = str(block.name);
|
|
97
|
+
blocks.set(index, { kind: 'tool_use', thinking: '' });
|
|
98
|
+
if (id) toolIndexById.set(id, index);
|
|
99
|
+
return [{ toolCalls: [{ index, ...(id ? { id } : {}), ...(name ? { name } : {}) }] }];
|
|
100
|
+
}
|
|
101
|
+
case 'web_search_tool_result': {
|
|
102
|
+
// A result the PROVIDER executed. It correlates by `tool_use_id`, NOT by
|
|
103
|
+
// its own block index, so it is routed back to the index of the
|
|
104
|
+
// server_tool_use block that opened the call. With no such block the
|
|
105
|
+
// result has nothing to complete and is dropped rather than inventing a
|
|
106
|
+
// panel.
|
|
107
|
+
blocks.set(index, { kind: 'other', thinking: '' });
|
|
108
|
+
const toolUseId = str(block.tool_use_id);
|
|
109
|
+
const target = toolUseId !== undefined ? toolIndexById.get(toolUseId) : undefined;
|
|
110
|
+
if (target === undefined) return [];
|
|
111
|
+
const content = block.content;
|
|
112
|
+
if (isRecord(content) && (str(content.type) ?? '').endsWith('_error')) {
|
|
113
|
+
return [
|
|
114
|
+
{
|
|
115
|
+
toolCalls: [
|
|
116
|
+
{
|
|
117
|
+
index: target,
|
|
118
|
+
...(toolUseId ? { id: toolUseId } : {}),
|
|
119
|
+
outputError: str(content.error_code) ?? 'web_search_tool_result_error',
|
|
120
|
+
},
|
|
121
|
+
],
|
|
122
|
+
},
|
|
123
|
+
];
|
|
124
|
+
}
|
|
125
|
+
return [
|
|
126
|
+
{
|
|
127
|
+
toolCalls: [
|
|
128
|
+
{ index: target, ...(toolUseId ? { id: toolUseId } : {}), output: { content } },
|
|
129
|
+
],
|
|
130
|
+
},
|
|
131
|
+
];
|
|
132
|
+
}
|
|
133
|
+
default:
|
|
134
|
+
blocks.set(index, { kind: 'other', thinking: '' });
|
|
135
|
+
return [];
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function blockDelta(
|
|
140
|
+
frame: Record<string, unknown>,
|
|
141
|
+
blocks: Map<number, BlockState>,
|
|
142
|
+
): ModelStreamChunk[] {
|
|
143
|
+
const index = num(frame.index) ?? 0;
|
|
144
|
+
const delta = frame.delta;
|
|
145
|
+
if (!isRecord(delta)) return [];
|
|
146
|
+
|
|
147
|
+
switch (str(delta.type)) {
|
|
148
|
+
case 'text_delta': {
|
|
149
|
+
const text = str(delta.text);
|
|
150
|
+
return text ? [{ text }] : [];
|
|
151
|
+
}
|
|
152
|
+
case 'thinking_delta': {
|
|
153
|
+
const text = str(delta.thinking) ?? '';
|
|
154
|
+
const state = blocks.get(index);
|
|
155
|
+
if (state) state.thinking += text;
|
|
156
|
+
return [{ reasoning: text, reasoningIndex: index }];
|
|
157
|
+
}
|
|
158
|
+
case 'signature_delta': {
|
|
159
|
+
const signature = str(delta.signature) ?? '';
|
|
160
|
+
const state = blocks.get(index);
|
|
161
|
+
// Concatenated rather than assigned: a split signature must reassemble.
|
|
162
|
+
if (state) state.signature = (state.signature ?? '') + signature;
|
|
163
|
+
// No text. Carried so the part's informational `signature` is populated;
|
|
164
|
+
// the round-trip payload lands at content_block_stop.
|
|
165
|
+
return [{ reasoning: '', reasoningIndex: index, reasoningSignature: signature }];
|
|
166
|
+
}
|
|
167
|
+
case 'input_json_delta': {
|
|
168
|
+
return [{ toolCalls: [{ index, arguments: str(delta.partial_json) ?? '' }] }];
|
|
169
|
+
}
|
|
170
|
+
case 'citations_delta': {
|
|
171
|
+
const citation = delta.citation;
|
|
172
|
+
if (!isRecord(citation)) return [];
|
|
173
|
+
const source: MessageSource = {};
|
|
174
|
+
const url = str(citation.url);
|
|
175
|
+
if (url) source.url = url;
|
|
176
|
+
const title = str(citation.title);
|
|
177
|
+
if (title) source.title = title;
|
|
178
|
+
const snippet = str(citation.cited_text);
|
|
179
|
+
if (snippet) source.snippet = snippet;
|
|
180
|
+
return Object.keys(source).length > 0 ? [{ sources: [source] }] : [];
|
|
181
|
+
}
|
|
182
|
+
default:
|
|
183
|
+
return [];
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function blockStop(
|
|
188
|
+
frame: Record<string, unknown>,
|
|
189
|
+
blocks: Map<number, BlockState>,
|
|
190
|
+
): ModelStreamChunk[] {
|
|
191
|
+
const index = num(frame.index) ?? 0;
|
|
192
|
+
const state = blocks.get(index);
|
|
193
|
+
// Only a streamed `thinking` block needs assembling. `redacted_thinking`
|
|
194
|
+
// already emitted its whole payload at start, and text and tool blocks have
|
|
195
|
+
// no verbatim requirement.
|
|
196
|
+
if (!state || state.kind !== 'thinking') return [];
|
|
197
|
+
const payload: Record<string, unknown> = { type: 'thinking', thinking: state.thinking };
|
|
198
|
+
if (state.signature !== undefined) payload.signature = state.signature;
|
|
199
|
+
return [
|
|
200
|
+
{
|
|
201
|
+
reasoning: '',
|
|
202
|
+
reasoningIndex: index,
|
|
203
|
+
reasoningRaw: { source: 'anthropic.content_block', payload },
|
|
204
|
+
},
|
|
205
|
+
];
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function messageDelta(frame: Record<string, unknown>): ModelStreamChunk[] {
|
|
209
|
+
const out: ModelStreamChunk = {};
|
|
210
|
+
const delta = frame.delta;
|
|
211
|
+
if (isRecord(delta)) {
|
|
212
|
+
const stop = str(delta.stop_reason);
|
|
213
|
+
if (stop) out.finishReason = stop;
|
|
214
|
+
}
|
|
215
|
+
const usage = usageOf(frame.usage);
|
|
216
|
+
if (usage) out.usage = usage;
|
|
217
|
+
return Object.keys(out).length > 0 ? [out] : [];
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function errorFrame(frame: Record<string, unknown>): ModelStreamChunk[] {
|
|
221
|
+
const err = frame.error;
|
|
222
|
+
const message = isRecord(err) ? str(err.message) : undefined;
|
|
223
|
+
const code = isRecord(err) ? str(err.type) : undefined;
|
|
224
|
+
return [
|
|
225
|
+
{
|
|
226
|
+
error: {
|
|
227
|
+
message: message ?? 'The provider sent an error frame with no message.',
|
|
228
|
+
...(code ? { code } : {}),
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
];
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export const anthropicMessagesFormat: WireFormat = {
|
|
235
|
+
id: 'anthropic.messages',
|
|
236
|
+
open(): WireFormatReader {
|
|
237
|
+
// Per-stream state, created HERE so two open() calls share nothing.
|
|
238
|
+
const blocks = new Map<number, BlockState>();
|
|
239
|
+
const toolIndexById = new Map<string, number>();
|
|
240
|
+
return {
|
|
241
|
+
push(frame: unknown): ModelStreamChunk[] {
|
|
242
|
+
if (!isRecord(frame)) return [];
|
|
243
|
+
switch (str(frame.type)) {
|
|
244
|
+
case 'message_start': {
|
|
245
|
+
const message = frame.message;
|
|
246
|
+
if (!isRecord(message)) return [];
|
|
247
|
+
const usage = usageOf(message.usage);
|
|
248
|
+
return usage ? [{ usage }] : [];
|
|
249
|
+
}
|
|
250
|
+
case 'content_block_start':
|
|
251
|
+
return blockStart(frame, blocks, toolIndexById);
|
|
252
|
+
case 'content_block_delta':
|
|
253
|
+
return blockDelta(frame, blocks);
|
|
254
|
+
case 'content_block_stop':
|
|
255
|
+
return blockStop(frame, blocks);
|
|
256
|
+
case 'message_delta':
|
|
257
|
+
return messageDelta(frame);
|
|
258
|
+
case 'error':
|
|
259
|
+
return errorFrame(frame);
|
|
260
|
+
default:
|
|
261
|
+
// ping, message_stop, and anything Anthropic adds later.
|
|
262
|
+
return [];
|
|
263
|
+
}
|
|
264
|
+
},
|
|
265
|
+
};
|
|
266
|
+
},
|
|
267
|
+
};
|