@kitn.ai/ui 0.25.2 → 0.27.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 +17 -6
- package/bin/mcp.js +16 -3
- package/bin/route.js +23 -0
- package/bin/route.test.js +58 -0
- package/dist/{elements/chunks/Icon-b5A8hlHs.js → Icon-BnFKqqtY.js} +1 -1
- package/dist/action-icons-CBskfBMz.js +1 -0
- package/dist/arrow-down-Br0qtpsz.js +1 -0
- package/dist/arrow-left-Dj7xgE12.js +1 -0
- package/dist/artifact-CvXEm1lS.js +1 -0
- package/dist/assets/dev-5XzXYR21.js +91 -0
- package/dist/attachments-C43ZegEq.js +1 -0
- package/dist/audio-visualizer-B-79y1Tj.js +1 -0
- package/dist/badge-BpUbq8c0.js +1 -0
- package/dist/bash-InADTalH.js +1 -6
- package/dist/bell-sn17Ip9v.js +1 -0
- package/dist/{elements/chunks/button-B9okqG73.js → button-BH6rN0U-.js} +1 -1
- package/dist/card-renderer-Cb1gQmth.js +1 -0
- package/dist/card-routing-DifQgS7n.js +1 -0
- package/dist/check-DPeeMUYx.js +1 -0
- package/dist/checkbox-Dc9Yov_B.js +1 -0
- package/dist/checkbox-group-B1kDLLYk.js +1 -0
- package/dist/chevron-down-CHSWk7ZT.js +1 -0
- package/dist/chevron-right-B7l_fMgx.js +1 -0
- package/dist/choice-card-BT3c1S8g.js +1 -0
- package/dist/circle-Bb0yOpqM.js +1 -0
- package/dist/circle-check-BGZpCiz7.js +1 -0
- package/dist/circle-x-wobWkoSx.js +1 -0
- package/dist/{elements/chunks/cn-CU7UAWzk.js → cn-DN5AWfiS.js} +1 -1
- package/dist/code-block-CUQ5mtv0.js +1 -0
- package/dist/collapsible-CPJbf6FM.js +1 -0
- package/dist/components/attachment-types.d.ts +53 -1
- package/dist/components/attachments.d.ts +31 -0
- package/dist/components/audio-visualizer/fit-scale.d.ts +50 -0
- package/dist/components/audio-visualizer/index.d.ts +15 -1
- package/dist/components/audio-visualizer/variant-bar.d.ts +24 -1
- package/dist/components/card-renderer.d.ts +13 -0
- package/dist/components/card.d.ts +4 -0
- package/dist/components/chat-thread.d.ts +49 -42
- package/dist/components/code-block.d.ts +22 -0
- package/dist/components/conversation-item.d.ts +66 -1
- package/dist/components/conversation-list.d.ts +77 -0
- package/dist/components/form-widgets.d.ts +67 -2
- package/dist/components/form.d.ts +89 -0
- package/dist/components/message.d.ts +29 -0
- package/dist/components/reasoning.d.ts +6 -0
- package/dist/components/scroll-button.d.ts +11 -0
- package/dist/components/thread.d.ts +5 -0
- package/dist/components/toast.d.ts +2 -1
- package/dist/components/tool-types.d.ts +9 -0
- package/dist/components/voice-input.d.ts +8 -0
- package/dist/components/voice-output.d.ts +12 -1
- package/dist/components/workspace-shell.d.ts +87 -0
- package/dist/composer-CjngLzal.js +106 -0
- package/dist/confirm-card-DCx_A4bU.js +1 -0
- package/dist/construct-cli.es.js +1169 -0
- package/dist/context-BY8V-Nlp.js +1 -0
- package/dist/controllable-D43BKmtX.js +1 -0
- package/dist/conversation-list-D4lLU_yS.js +1 -0
- package/dist/copy-B6DO4PqB.js +1 -0
- package/dist/core-AYMC6_lb.js +12 -5874
- package/dist/{create-tween-CUzOlaMS.js → create-tween-BPzaefTp.js} +1 -1
- package/dist/{create-tween-B5Y4Im8G.js → create-tween-CnMHviKB.js} +1 -1
- package/dist/{create-tween-4z1XYLkR.js → create-tween-D1aiGZZh.js} +1 -1
- package/dist/{elements/chunks/create-tween-CUrM6D_o.js → create-tween-DUSQUKpr.js} +2 -2
- package/dist/css-M7EaDHN_.js +1 -6
- package/dist/custom-elements.json +3468 -1927
- package/dist/default-input-CucQEOqH.js +1 -0
- package/dist/define-CGGX-7Vg.js +1 -0
- package/dist/define.d.ts +4 -0
- package/dist/define.js +510 -0
- package/dist/define.server.d.ts +5 -0
- package/dist/define.server.js +506 -0
- package/dist/diagnostics/hook.d.ts +81 -0
- package/dist/diagnostics/index.d.ts +7 -0
- package/dist/diagnostics/report-request.d.ts +61 -0
- package/dist/diagnostics.d.ts +5 -0
- package/dist/diagnostics.js +209 -0
- package/dist/disclosure-CbLtajUQ.js +1 -0
- package/dist/dropdown-BwhViJFd.js +1 -0
- 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/autoloader.js +1 -1
- package/dist/elements/avatar.js +1 -1
- package/dist/elements/badge.js +1 -1
- package/dist/elements/button.js +1 -1
- package/dist/elements/card.js +1 -1
- package/dist/elements/cards.js +1 -1
- package/dist/elements/chain-of-thought.js +1 -1
- package/dist/elements/chat-scope-picker.js +1 -1
- package/dist/elements/chat-workspace.js +1 -1
- package/dist/elements/chat.js +1 -1
- package/dist/elements/checkbox-group.d.ts +1 -0
- package/dist/elements/checkbox-group.js +1 -0
- package/dist/elements/checkbox.d.ts +1 -0
- package/dist/elements/checkbox.js +1 -0
- package/dist/elements/checkpoint.js +1 -1
- package/dist/elements/choice.js +1 -1
- package/dist/elements/coachmark.js +1 -1
- package/dist/elements/code-block.js +1 -1
- package/dist/elements/command.js +1 -1
- package/dist/elements/compare.js +1 -1
- package/dist/elements/composer.js +1 -1
- package/dist/elements/confirm-card.js +1 -1
- package/dist/elements/context-meter.js +1 -1
- package/dist/elements/conversation-item.d.ts +1 -0
- package/dist/elements/conversation-item.js +1 -0
- package/dist/elements/conversation-list.js +1 -1
- package/dist/elements/default-input.d.ts +3 -3
- package/dist/elements/define-entry.d.ts +12 -0
- package/dist/elements/define.d.ts +27 -0
- package/dist/elements/diagnostic-events.d.ts +74 -0
- package/dist/elements/dialog.js +1 -1
- package/dist/elements/dock.d.ts +1 -0
- package/dist/elements/dock.js +243 -0
- package/dist/elements/dropdown.d.ts +1 -0
- package/dist/elements/dropdown.js +1 -0
- package/dist/elements/editable-label.js +1 -1
- package/dist/elements/element-data-types.d.ts +37 -0
- package/dist/elements/element-diagnostics.d.ts +81 -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-grid.d.ts +1 -0
- package/dist/elements/pane-grid.js +1 -0
- package/dist/elements/pane-group.js +1 -1
- package/dist/elements/pane.js +1 -1
- package/dist/elements/popover.js +1 -1
- package/dist/elements/progress-bar.js +1 -1
- package/dist/elements/prompt-dock.js +1 -1
- package/dist/elements/prompt-input.js +1 -1
- package/dist/elements/prompt-suggestions.js +1 -1
- package/dist/elements/radio-group.d.ts +1 -0
- package/dist/elements/radio-group.js +1 -0
- package/dist/elements/reasoning.js +1 -1
- package/dist/elements/register.d.ts +2 -0
- package/dist/elements/remote.js +1 -1
- package/dist/elements/resizable.js +1 -1
- package/dist/elements/response-stream.js +3 -3
- package/dist/elements/screen.js +1 -1
- package/dist/elements/scroll-area.js +1 -1
- package/dist/elements/scroll-button.js +1 -1
- package/dist/elements/search.js +1 -1
- package/dist/elements/segmented.js +1 -1
- package/dist/elements/select.d.ts +1 -0
- package/dist/elements/select.js +1 -0
- package/dist/elements/separator.js +1 -1
- package/dist/elements/setting-item.js +1 -1
- package/dist/elements/settings-group.js +1 -1
- package/dist/elements/skeleton.js +1 -1
- package/dist/elements/slider.d.ts +1 -0
- package/dist/elements/slider.js +1 -0
- package/dist/elements/slots.d.ts +36 -4
- package/dist/elements/source.js +1 -1
- package/dist/elements/status.js +1 -1
- package/dist/elements/switch.js +1 -1
- package/dist/elements/tabs.js +1 -1
- package/dist/elements/tasks.js +1 -1
- package/dist/elements/text-shimmer.js +1 -1
- package/dist/elements/thinking-bar.js +1 -1
- package/dist/elements/thread.js +1 -1
- package/dist/elements/toast.js +1 -1
- package/dist/elements/tool.js +1 -1
- package/dist/elements/tooltip.js +1 -1
- package/dist/elements/voice-input.js +1 -1
- package/dist/elements/voice-output.js +1 -1
- package/dist/elements.d.ts +589 -167
- package/dist/{elements/chunks/ellipsis-CVxqfsDZ.js → ellipsis-DFr4Vqr3.js} +1 -1
- package/dist/embed-5s_vU4J0.js +1 -0
- package/dist/engine-javascript-vq0WuIJl.js +14 -2516
- package/dist/{elements/chunks/external-link-DG2sRdQL.js → external-link-u9d85ORQ.js} +1 -1
- package/dist/file-text-CX7_x42o.js +1 -0
- package/dist/{elements/chunks/file-tree-yGWNK0Js.js → file-tree-D3kBjXI5.js} +1 -1
- package/dist/folder-V-YdIL8f.js +1 -0
- package/dist/form-BRNUMCN2.js +1 -0
- package/dist/github-dark-dimmed-DUshB20C.js +1 -4
- package/dist/github-light-JYsPkUQd.js +1 -4
- package/dist/hover-card-DaeiYrTA.js +1 -0
- package/dist/html-CPZ3oZQ7.js +1 -10
- package/dist/icon-BpixdZNq.js +1 -0
- package/dist/{elements/chunks/index-BCRhi84a.js → index-xu_pdcPi.js} +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +8473 -6470
- package/dist/index.server.js +6495 -4958
- package/dist/info-8mFsIjS3.js +1 -0
- package/dist/input-d1Yuu-hz.js +1 -0
- package/dist/javascript-C25yR2R2.js +1 -6
- package/dist/json-DxJze_jm.js +1 -6
- package/dist/kai-provider.es.js +40 -40
- package/dist/kai.es.js +1 -1
- package/dist/kbd-961zMPFB.js +1 -0
- package/dist/link-DlZZBGaR.js +1 -0
- package/dist/{elements/chunks/link-preview-Cf5KOP-i.js → link-preview-DIIbV2Sd.js} +1 -1
- package/dist/loader-YQszHFaW.js +1 -0
- package/dist/{elements/chunks/markdown-DbozyYqa.js → markdown-CVk1MPsj.js} +1 -1
- package/dist/mcp.es.js +3873 -544
- package/dist/message-CwKVEdZg.js +1 -0
- package/dist/message-DS8lzHc1.js +1 -0
- package/dist/message-circle-Xf8Q-vZp.js +1 -0
- package/dist/message-square-Cxnwgkns.js +1 -0
- package/dist/{elements/chunks/minimize-2-SegJ_oku.js → minimize-2-UEZmP2QT.js} +1 -1
- package/dist/{elements/chunks/model-switcher-D7JrFFHp.js → model-switcher-Kk1jEQ5t.js} +1 -1
- package/dist/{elements/chunks/overlay-CV7z2YuX.js → overlay-C1od6LOz.js} +1 -1
- package/dist/panel-right-ryET0yyn.js +1 -0
- package/dist/paperclip-BDNoZFJP.js +1 -0
- package/dist/play-OFIvYXUQ.js +1 -0
- package/dist/primitives/card-data-types.d.ts +49 -0
- package/dist/primitives/card-routing.d.ts +1 -33
- package/dist/primitives/card-validate.d.ts +2 -2
- package/dist/primitives/chat-config.d.ts +29 -2
- package/dist/primitives/field-mask.d.ts +96 -0
- package/dist/primitives/field-semantics.d.ts +36 -0
- package/dist/primitives/input-mask.d.ts +46 -0
- package/dist/primitives/url-scheme-policy.d.ts +34 -0
- package/dist/primitives/use-resize-observer.d.ts +23 -0
- package/dist/primitives/use-speech-recognition.d.ts +8 -0
- package/dist/primitives/use-text-stream.d.ts +5 -0
- package/dist/progress-bar-BvYX0aoy.js +1 -0
- package/dist/prompt-suggestion-BRFu2lcu.js +1 -0
- package/dist/radio-DcL23cNJ.js +1 -0
- package/dist/react/index.d.ts +306 -283
- package/dist/react.js +309 -247
- package/dist/reasoning-Cackq4Rh.js +1 -0
- package/dist/register-impl-C7rENvPK.js +424 -0
- package/dist/{elements/chunks/resizable-Dpzjlvp4.js → resizable-Cp9HUYih.js} +1 -1
- package/dist/rotate-ccw-0DLRDHH5.js +1 -0
- package/dist/rotate-cw-DOB9IGcU.js +1 -0
- package/dist/schemas/form.schema.json +21 -1
- package/dist/schemas/index.d.ts +1 -1
- package/dist/schemas/tool-defs.d.ts +45 -0
- package/dist/schemas.js +398 -305
- package/dist/scroll-area-emVmWP4K.js +1 -0
- package/dist/scroll-button-CeP_gv1x.js +1 -0
- package/dist/select-CWmu00tS.js +1 -0
- package/dist/{elements/chunks/separator-DH2r_ZSZ.js → separator-CE6vly6z.js} +1 -1
- package/dist/{elements/chunks/settings-BFMOd6yB.js → settings-CE-78SvZ.js} +1 -1
- package/dist/{elements/chunks/settings-group-B3VZKIcz.js → settings-group-ZFWNxFQR.js} +1 -1
- package/dist/{elements/chunks/skeleton-N8BuWYmf.js → skeleton-BebdP4mf.js} +1 -1
- package/dist/slider-BAKRYUlz.js +1 -0
- package/dist/slots-DaNjX0rc.js +1 -0
- package/dist/{solid-BMuDmFo_.js → solid-BE5ui6sy.js} +10672 -8116
- package/dist/{solid-CGYNdLRR.js → solid-DFhsce3O.js} +8137 -6056
- package/dist/solid.d.ts +15 -0
- package/dist/solid.js +248 -235
- package/dist/solid.server.js +248 -235
- package/dist/source-B0FlguSx.js +1 -0
- package/dist/star-CBMZvLuN.js +1 -0
- package/dist/state/index.d.ts +5 -1
- package/dist/state/mock.d.ts +32 -2
- package/dist/state/persistence.d.ts +51 -0
- package/dist/state/stream.d.ts +50 -0
- package/dist/state/threads.d.ts +71 -0
- package/dist/state.js +297 -172
- package/dist/store-7eoadydQ.js +1 -0
- package/dist/svelte--5p79yCD.js +1 -15
- package/dist/switch-CViARZCy.js +1 -0
- package/dist/tasks-card-MjCcOhR_.js +1 -0
- package/dist/text-shimmer-DKlwRF2G.js +1 -0
- package/dist/textarea-3ay2Me2-.js +1 -0
- package/dist/theme.tokens.css +82 -13
- package/dist/{elements/chunks/thumbs-up-DQe0hvkZ.js → thumbs-up-DQcu2HMO.js} +1 -1
- package/dist/toast-store-C6yKOhbk.js +1 -0
- package/dist/{elements/chunks/tool-C1YtxwPu.js → tool-BCYNlfKy.js} +1 -1
- package/dist/{elements/chunks/tooltip-BqwnFWhn.js → tooltip-05zI4hjs.js} +1 -1
- package/dist/trash-2-BR1RxGky.js +1 -0
- package/dist/{elements/chunks/triangle-alert-B6or7F5u.js → triangle-alert-DHohDOCy.js} +1 -1
- package/dist/tsx-B8rCNbgL.js +1 -6
- package/dist/types.d.ts +4 -2
- package/dist/typescript-RycA9KXf.js +1 -6
- package/dist/ui/checkbox-group.d.ts +83 -0
- package/dist/ui/checkbox.d.ts +40 -0
- package/dist/ui/dock.d.ts +131 -0
- package/dist/ui/hover-card.d.ts +44 -0
- package/dist/ui/icon.d.ts +3 -0
- package/dist/ui/input.d.ts +48 -2
- package/dist/ui/radio.d.ts +85 -0
- package/dist/ui/select.d.ts +71 -0
- package/dist/ui/slider.d.ts +65 -0
- package/dist/ui/switch.d.ts +9 -2
- package/dist/upload-BbGW6A_u.js +1 -0
- package/dist/url-scheme-policy-DHpoTJwB.js +1 -0
- package/dist/use-card-resolution-CJW4grFa.js +1 -0
- package/dist/{variant-aurora-DwSOfraQ.js → variant-aurora-BUOVgPOB.js} +27 -27
- package/dist/{variant-aurora-wKvAQ5wp.js → variant-aurora-CUsUJvFI.js} +28 -28
- package/dist/{variant-aurora-XEkPT0_z.js → variant-aurora-CW2i4bfr.js} +2 -2
- package/dist/{elements/chunks/variant-aurora-QXTZPyST.js → variant-aurora-xiMPty8f.js} +2 -2
- package/dist/variant-custom-BQtOnmeO.js +1 -0
- package/dist/{variant-custom-BhS9wIpU.js → variant-custom-CAm9L-E5.js} +65 -65
- package/dist/{variant-custom-eFIg2XWQ.js → variant-custom-CAnzzppW.js} +40 -40
- package/dist/variant-custom-D91HjAKz.js +1 -0
- package/dist/{variant-wave-CUWGx9Bn.js → variant-wave-Ca1Ls4_y.js} +21 -21
- package/dist/{variant-wave-CbVsmgwT.js → variant-wave-CxX39DKd.js} +25 -25
- package/dist/{variant-wave-BwzrHD1s.js → variant-wave-DYyzIIZv.js} +2 -2
- package/dist/{elements/chunks/variant-wave-DDCwhLas.js → variant-wave-uY5crU_K.js} +2 -2
- package/dist/vue-BmIZj4XD.js +1 -33
- package/dist/wire/chunk.d.ts +47 -0
- package/dist/wire/diagnostics.d.ts +575 -0
- package/dist/wire/encode-probe.d.ts +70 -0
- package/dist/wire/encode.d.ts +24 -0
- package/dist/wire/index.d.ts +2 -0
- package/dist/wire/sse.d.ts +10 -2
- package/dist/wire.js +917 -480
- package/dist/x-C5m1JFgi.js +1 -0
- package/frameworks/react/index.tsx +325 -108
- package/llms-full.txt +1354 -82
- package/llms.txt +6 -3
- package/package.json +39 -6
- package/src/agent-tooling/archetypes.ts +41 -9
- package/src/agent-tooling/catalog/README.md +676 -0
- package/src/agent-tooling/catalog/catalog-types.ts +231 -0
- package/src/agent-tooling/catalog/fabrications.ts +96 -0
- package/src/agent-tooling/catalog/invariants.ts +284 -0
- package/src/agent-tooling/catalog/labs-titles.ts +114 -0
- package/src/agent-tooling/catalog/scenarios.ts +87 -0
- package/src/agent-tooling/catalog/surfaces.ts +347 -0
- package/src/agent-tooling/construct/cli-entry.ts +8 -0
- package/src/agent-tooling/construct/cli.ts +131 -0
- package/src/agent-tooling/construct/codegen.ts +1353 -0
- package/src/agent-tooling/construct/construct.v1.schema.json +250 -0
- package/src/agent-tooling/construct/dev.ts +148 -0
- package/src/agent-tooling/construct/fixtures/demo-widget.construct.json +7 -0
- package/src/agent-tooling/construct/fixtures/ops-console.construct.json +44 -0
- package/src/agent-tooling/construct/fixtures/owner-widget.construct.json +23 -0
- package/src/agent-tooling/construct/schema.ts +371 -0
- package/src/agent-tooling/integrations/anthropic.ts +7 -1
- package/src/agent-tooling/integrations/cloudflare.ts +43 -2
- package/src/agent-tooling/integrations/langgraph.ts +7 -1
- package/src/agent-tooling/integrations/mastra.ts +7 -1
- package/src/agent-tooling/integrations/mock.ts +92 -17
- package/src/agent-tooling/integrations/ollama.ts +7 -1
- package/src/agent-tooling/integrations/openai.ts +7 -1
- package/src/agent-tooling/integrations/openrouter.ts +7 -1
- package/src/agent-tooling/integrations/pi.ts +56 -1
- package/src/agent-tooling/integrations/vercel-ai-sdk.ts +7 -1
- package/src/agent-tooling/mcp/css-raw.d.ts +12 -0
- package/src/agent-tooling/mcp/manifest.ts +100 -1
- package/src/agent-tooling/mcp/server.ts +63 -6
- package/src/agent-tooling/mcp/tools/construct.ts +130 -0
- package/src/agent-tooling/mcp/tools/debug.ts +126 -4
- package/src/agent-tooling/mcp/tools/reference.ts +542 -5
- package/src/agent-tooling/mcp/tools/scaffold.ts +1834 -307
- package/src/agent-tooling/mcp/tools/theme.ts +146 -30
- package/src/agent-tooling/mcp/validate-args.ts +141 -0
- package/src/agent-tooling/recipes/composed-thread.ts +714 -0
- package/src/agent-tooling/recipes/index.ts +22 -0
- package/src/agent-tooling/recipes/types.ts +32 -0
- package/src/agent-tooling/registry.ts +24 -7
- package/src/agent-tooling/route-emit.ts +26 -2
- package/src/components/attachment-types.ts +54 -1
- package/src/components/attachments.tsx +169 -22
- package/src/components/audio-visualizer/fit-scale.ts +112 -0
- package/src/components/audio-visualizer/index.tsx +80 -3
- package/src/components/audio-visualizer/variant-aurora.tsx +10 -4
- package/src/components/audio-visualizer/variant-bar.tsx +38 -6
- package/src/components/audio-visualizer/variant-custom.tsx +8 -2
- package/src/components/audio-visualizer/variant-grid.tsx +16 -11
- package/src/components/audio-visualizer/variant-radial.tsx +15 -10
- package/src/components/audio-visualizer/variant-wave.tsx +7 -2
- package/src/components/card-renderer.tsx +39 -8
- package/src/components/card.tsx +10 -2
- package/src/components/chat-container.tsx +4 -1
- package/src/components/chat-scope-picker.tsx +2 -2
- package/src/components/chat-thread.tsx +105 -13
- package/src/components/choice-card.tsx +102 -37
- package/src/components/coachmark.tsx +1 -1
- package/src/components/code-block.tsx +81 -5
- package/src/components/composer.tsx +89 -8
- package/src/components/confirm-card.tsx +20 -4
- package/src/components/conversation-item.tsx +138 -4
- package/src/components/conversation-list.tsx +230 -15
- package/src/components/embed.tsx +4 -0
- package/src/components/empty.tsx +14 -3
- package/src/components/file-tree.tsx +5 -1
- package/src/components/form-widgets.tsx +257 -195
- package/src/components/form.tsx +336 -21
- package/src/components/loader.tsx +12 -5
- package/src/components/message-skills.tsx +1 -1
- package/src/components/message.tsx +142 -12
- package/src/components/prompt-input.tsx +7 -1
- package/src/components/prompt-suggestion.tsx +4 -1
- package/src/components/reasoning.tsx +30 -3
- package/src/components/response-compare.tsx +2 -2
- package/src/components/response-stream.tsx +13 -7
- package/src/components/screen.tsx +5 -0
- package/src/components/scroll-button.tsx +63 -4
- package/src/components/source.tsx +6 -1
- package/src/components/tasks-card.tsx +58 -16
- package/src/components/thread.tsx +10 -1
- package/src/components/toast.tsx +11 -5
- package/src/components/tool-types.ts +9 -0
- package/src/components/voice-input.tsx +35 -3
- package/src/components/voice-output.tsx +46 -6
- package/src/components/workspace-shell.tsx +285 -0
- package/src/diagnostics/hook.ts +352 -0
- package/src/diagnostics/index.ts +82 -0
- package/src/diagnostics/report-request.ts +296 -0
- package/src/elements/attachments.tsx +9 -8
- package/src/elements/audio-visualizer.tsx +48 -4
- package/src/elements/chat-workspace.tsx +159 -287
- package/src/elements/chat.tsx +31 -10
- package/src/elements/checkbox-group.tsx +164 -0
- package/src/elements/checkbox.tsx +134 -0
- package/src/elements/code-block.tsx +19 -1
- package/src/elements/command.tsx +4 -1
- package/src/elements/compiled.css +1 -1
- package/src/elements/composer.tsx +12 -1
- package/src/elements/conversation-item.tsx +120 -0
- package/src/elements/conversation-list.tsx +60 -11
- package/src/elements/default-input.tsx +5 -5
- package/src/elements/define-entry.ts +12 -0
- package/src/elements/define.tsx +168 -8
- package/src/elements/diagnostic-events.ts +114 -0
- package/src/elements/dialog.tsx +47 -0
- package/src/elements/disclosure.ts +34 -12
- package/src/elements/dock.tsx +215 -0
- package/src/elements/dropdown.tsx +123 -0
- package/src/elements/element-data-types.ts +40 -0
- package/src/elements/element-diagnostics.ts +392 -0
- package/src/elements/element-manifest.json +36 -0
- package/src/elements/element-meta.json +1483 -560
- package/src/elements/element-nonscalar.json +185 -0
- package/src/elements/element-types.d.ts +589 -167
- package/src/elements/feedback-bar.tsx +5 -0
- package/src/elements/icon-names.json +29 -0
- package/src/elements/input.tsx +312 -23
- package/src/elements/menu.tsx +13 -0
- package/src/elements/message.tsx +2 -0
- package/src/elements/pane-grid.tsx +111 -0
- package/src/elements/prompt-dock.tsx +2 -1
- package/src/elements/prompt-input.tsx +16 -10
- package/src/elements/radio-group.tsx +129 -0
- package/src/elements/register-impl.ts +52 -0
- package/src/elements/register.ts +28 -0
- package/src/elements/resizable.tsx +52 -25
- package/src/elements/scroll-button.tsx +59 -6
- package/src/elements/select.tsx +165 -0
- package/src/elements/slider.tsx +171 -0
- package/src/elements/slots.ts +108 -13
- package/src/elements/styles.css +119 -9
- package/src/elements/thinking-bar.tsx +16 -7
- package/src/elements/thread.tsx +2 -0
- package/src/elements/toast.tsx +17 -1
- package/src/elements/tool.tsx +26 -5
- package/src/elements/voice-input.tsx +11 -2
- package/src/elements/voice-output.tsx +16 -3
- package/src/index.ts +4 -1
- package/src/primitives/card-data-types.ts +49 -0
- package/src/primitives/card-host.tsx +4 -2
- package/src/primitives/card-routing.ts +7 -56
- package/src/primitives/card-schemas/form.schema.json +21 -1
- package/src/primitives/card-validate.ts +10 -2
- package/src/primitives/chat-config.tsx +33 -6
- package/src/primitives/field-mask.ts +256 -0
- package/src/primitives/field-semantics.ts +115 -0
- package/src/primitives/input-mask.ts +853 -0
- package/src/primitives/toast-store.ts +66 -4
- package/src/primitives/url-scheme-policy.ts +70 -0
- package/src/primitives/use-resize-observer.ts +33 -0
- package/src/primitives/use-speech-recognition.ts +12 -1
- package/src/primitives/use-stick-to-bottom.ts +34 -2
- package/src/primitives/use-text-stream.ts +33 -2
- package/src/remote/provider-runtime.ts +6 -3
- package/src/schemas/index.ts +1 -0
- package/src/schemas/tool-defs.ts +307 -6
- package/src/solid.ts +15 -0
- package/src/state/index.ts +13 -1
- package/src/state/mock.ts +92 -6
- package/src/state/persistence.ts +180 -0
- package/src/state/stream.ts +71 -4
- package/src/state/threads.ts +142 -0
- package/src/types.ts +4 -2
- package/src/ui/agent-card.tsx +2 -2
- package/src/ui/avatar.tsx +1 -1
- package/src/ui/checkbox-group.tsx +153 -0
- package/src/ui/checkbox.tsx +63 -0
- package/src/ui/dialog.tsx +6 -0
- package/src/ui/dock.tsx +584 -0
- package/src/ui/dropdown.tsx +67 -23
- package/src/ui/hover-card.tsx +156 -6
- package/src/ui/icon.tsx +53 -0
- package/src/ui/input.tsx +275 -16
- package/src/ui/kbd.tsx +1 -1
- package/src/ui/nav.tsx +1 -1
- package/src/ui/pane-group.tsx +2 -2
- package/src/ui/radio.tsx +150 -0
- package/src/ui/resizable.tsx +11 -0
- package/src/ui/select.tsx +168 -0
- package/src/ui/slider.tsx +178 -0
- package/src/ui/switch.tsx +49 -18
- package/src/utils/cn.ts +11 -5
- package/src/wire/chunk.ts +47 -0
- package/src/wire/consume.ts +185 -26
- package/src/wire/diagnostics.ts +727 -0
- package/src/wire/encode-probe.ts +214 -0
- package/src/wire/encode.ts +267 -14
- package/src/wire/formats/anthropic.ts +8 -1
- package/src/wire/formats/openai.ts +5 -0
- package/src/wire/index.ts +22 -0
- package/src/wire/read.ts +250 -6
- package/src/wire/sse.ts +16 -3
- package/theme.css +84 -13
- package/dist/elements/chunks/action-icons-DW9muWrY.js +0 -1
- package/dist/elements/chunks/arrow-left-iJVnQmxA.js +0 -1
- package/dist/elements/chunks/artifact-BSuPYFHY.js +0 -1
- package/dist/elements/chunks/attachments-CL6TaqIb.js +0 -1
- package/dist/elements/chunks/audio-visualizer-BuUqQmaO.js +0 -1
- package/dist/elements/chunks/badge-TIMiJFKc.js +0 -1
- package/dist/elements/chunks/bash-InADTalH.js +0 -1
- package/dist/elements/chunks/card-renderer-CSoTL_e1.js +0 -1
- package/dist/elements/chunks/card-routing-BK09BdmL.js +0 -1
- package/dist/elements/chunks/chat-thread-DxoKaStk.js +0 -1
- package/dist/elements/chunks/check-MdLzbxrm.js +0 -1
- package/dist/elements/chunks/chevron-down-MK7IzDqe.js +0 -1
- package/dist/elements/chunks/chevron-right-C5jXhmtV.js +0 -1
- package/dist/elements/chunks/choice-card-CGMc3kek.js +0 -1
- package/dist/elements/chunks/circle-BG7VkPep.js +0 -1
- package/dist/elements/chunks/circle-check-GqvO2UBs.js +0 -1
- package/dist/elements/chunks/circle-x-mhH7pOK2.js +0 -1
- package/dist/elements/chunks/code-block-ClhQO9RW.js +0 -1
- package/dist/elements/chunks/collapsible-DBcBkFPh.js +0 -1
- package/dist/elements/chunks/composer-CZZmtYOT.js +0 -69
- package/dist/elements/chunks/confirm-card-BWjf2-xo.js +0 -1
- package/dist/elements/chunks/context-hKjNqiyQ.js +0 -1
- package/dist/elements/chunks/conversation-list-CsGJjSKG.js +0 -1
- package/dist/elements/chunks/core-AYMC6_lb.js +0 -12
- package/dist/elements/chunks/css-M7EaDHN_.js +0 -1
- package/dist/elements/chunks/default-input-B1Mem5wu.js +0 -1
- package/dist/elements/chunks/define-B_tXMYRm.js +0 -1
- package/dist/elements/chunks/disclosure-DAoYOKew.js +0 -1
- package/dist/elements/chunks/download-DnCyWFJE.js +0 -1
- package/dist/elements/chunks/dropdown-CmNna5lT.js +0 -1
- package/dist/elements/chunks/embed-CjAD2jSi.js +0 -1
- package/dist/elements/chunks/engine-javascript-vq0WuIJl.js +0 -141
- package/dist/elements/chunks/file-text-D0J9F6M7.js +0 -1
- package/dist/elements/chunks/folder-NV1pi_ud.js +0 -1
- package/dist/elements/chunks/form-Cp8Y00Wn.js +0 -1
- package/dist/elements/chunks/github-dark-dimmed-DUshB20C.js +0 -1
- package/dist/elements/chunks/github-light-JYsPkUQd.js +0 -1
- package/dist/elements/chunks/hover-card-6KtXpLwT.js +0 -1
- package/dist/elements/chunks/html-CPZ3oZQ7.js +0 -1
- package/dist/elements/chunks/icon-97R8SR2p.js +0 -1
- package/dist/elements/chunks/info-C8r5Q6cc.js +0 -1
- package/dist/elements/chunks/input-DrseEjhp.js +0 -1
- package/dist/elements/chunks/javascript-C25yR2R2.js +0 -1
- package/dist/elements/chunks/json-DxJze_jm.js +0 -1
- package/dist/elements/chunks/kbd-OGJDUT-O.js +0 -1
- package/dist/elements/chunks/link-BdsfLB7b.js +0 -1
- package/dist/elements/chunks/loader-B5QzK_8_.js +0 -1
- package/dist/elements/chunks/message-CE4k-qaj.js +0 -1
- package/dist/elements/chunks/message-DtXKPqiR.js +0 -1
- package/dist/elements/chunks/message-square-C9ovmtOo.js +0 -1
- package/dist/elements/chunks/paperclip-Bg0ITU3b.js +0 -1
- package/dist/elements/chunks/progress-bar-D1_rzt6N.js +0 -1
- package/dist/elements/chunks/prompt-suggestion-D5Sm8xF1.js +0 -1
- package/dist/elements/chunks/reasoning-BrxQfpfI.js +0 -1
- package/dist/elements/chunks/rotate-cw-DzVDJOA1.js +0 -1
- package/dist/elements/chunks/scroll-area-93A1bbeG.js +0 -1
- package/dist/elements/chunks/scroll-button-CrTo7wm4.js +0 -1
- package/dist/elements/chunks/share-DgNbkJKY.js +0 -1
- package/dist/elements/chunks/slots-CIw9RlAe.js +0 -1
- package/dist/elements/chunks/source-w9V0NnBn.js +0 -1
- package/dist/elements/chunks/star-DHvACxtm.js +0 -1
- package/dist/elements/chunks/store-HIPGhNz9.js +0 -1
- package/dist/elements/chunks/svelte--5p79yCD.js +0 -1
- package/dist/elements/chunks/tasks-card-lPxoodlA.js +0 -1
- package/dist/elements/chunks/text-shimmer-BY_rI3s5.js +0 -1
- package/dist/elements/chunks/textarea-DPfuhswM.js +0 -1
- package/dist/elements/chunks/toast-store-VUZZBhzH.js +0 -1
- package/dist/elements/chunks/tsx-B8rCNbgL.js +0 -1
- package/dist/elements/chunks/typescript-RycA9KXf.js +0 -1
- package/dist/elements/chunks/use-card-resolution-DJg32jGH.js +0 -1
- package/dist/elements/chunks/variant-custom-D5ZlmziF.js +0 -1
- package/dist/elements/chunks/video-Bb86fVYI.js +0 -1
- package/dist/elements/chunks/vue-BmIZj4XD.js +0 -1
- package/dist/elements/chunks/x-BOhjBeOd.js +0 -1
- package/dist/llms/llms-full.txt +0 -2834
- package/dist/llms/llms.txt +0 -156
- package/dist/primitives/card-validate-generator.testlib.d.ts +0 -20
- package/dist/register-impl-DGmGzrEM.js +0 -145
- package/dist/ui/stat.d.ts +0 -16
- package/dist/variant-custom-MkTNTycd.js +0 -1
- package/src/primitives/card-validate-generator.testlib.ts +0 -38
- package/src/ui/stat.tsx +0 -41
- /package/dist/{elements/chunks/card-tags-D8lZ-C_U.js → card-tags-D8lZ-C_U.js} +0 -0
- /package/dist/{elements/chunks/link-preview-DNILK391.js → link-preview-DNILK391.js} +0 -0
package/llms-full.txt
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
# @kitn.ai/ui
|
|
5
5
|
|
|
6
|
-
> Framework-agnostic, Shadow-DOM web components for building AI chat interfaces — works in React, Vue, Angular, Svelte, or plain HTML.
|
|
6
|
+
> Framework-agnostic, Shadow-DOM web components for building AI chat interfaces — works in React, Vue, Angular, Svelte, or plain HTML. 89 custom elements, every one prefixed `kai-` (e.g. `<kai-chat>`, `<kai-artifact>`): streaming responses, markdown + code rendering, reasoning/tool panels, attachments, conversation sidebar, voice input. Zero framework dependency for consumers; the SolidJS runtime it is authored in is bundled in, so the host needs nothing.
|
|
7
7
|
|
|
8
8
|
## Install
|
|
9
9
|
|
|
@@ -37,7 +37,7 @@ Drop an element into any framework (React, Vue, plain HTML). Data in via JS prop
|
|
|
37
37
|
- `<kai-prompt-input>` — standalone composer with send button.
|
|
38
38
|
|
|
39
39
|
**Layer 2 — composable primitives** (`import { … } from '@kitn.ai/ui'`):
|
|
40
|
-
All
|
|
40
|
+
All 89 elements are also exported individually. Use them for custom layouts or features `<kai-chat>` does not expose (ChainOfThought, FeedbackBar, ThinkingBar, VoiceInput, …). Your bundler tree-shakes the rest.
|
|
41
41
|
|
|
42
42
|
## Key rules for the web components
|
|
43
43
|
|
|
@@ -151,7 +151,10 @@ For Tailwind builds: `@import "@kitn.ai/ui/theme.css"` in your CSS.
|
|
|
151
151
|
|
|
152
152
|
## Docs
|
|
153
153
|
|
|
154
|
-
-
|
|
154
|
+
- Element reference (all 89 elements, every prop/event/method/slot/part): the "Element reference" section of ./llms-full.txt — https://kitn.dev/llms-full.txt
|
|
155
|
+
- Programmatic layer (`@kitn.ai/ui/state` + `@kitn.ai/ui/wire`: streaming folds, the mock responder, the SSE readers/encoders): the "Programmatic layer" section of llms-full.txt
|
|
156
|
+
- How to build a chat app in 5 steps (install → pick a layer → handle submit + stream → wire features → theme, with working code): the "How to build a chat app in 5 steps" section of llms-full.txt
|
|
157
|
+
- Streaming recipe (the two rules that bite: reassign new references per chunk, fold deltas onto the trailing text part): the "Streaming recipe" section of llms-full.txt
|
|
155
158
|
- Machine-readable Custom Elements Manifest: https://unpkg.com/@kitn.ai/ui/dist/custom-elements.json
|
|
156
159
|
- Working examples: https://github.com/kitn-ai/ui/tree/main/examples
|
|
157
160
|
- Storybook: https://storybook.kitn.dev
|
|
@@ -248,7 +251,979 @@ The same reassign rule applies to every array/object property (`models`, `contex
|
|
|
248
251
|
|
|
249
252
|
---
|
|
250
253
|
|
|
251
|
-
|
|
254
|
+
<!-- kai:programmatic:start -->
|
|
255
|
+
## Programmatic layer — `@kitn.ai/ui/state` + `@kitn.ai/ui/wire`
|
|
256
|
+
|
|
257
|
+
<!-- generated by scripts/gen-llms-programmatic.mjs from the shipped dist/*.d.ts — do not edit by hand -->
|
|
258
|
+
|
|
259
|
+
The API you write a HOST against — everything below is what a hand-composed surface
|
|
260
|
+
(no `<kai-chat>`) wires together. Signatures and docs below are the shipped declaration
|
|
261
|
+
files themselves, so they cannot drift from what your editor shows.
|
|
262
|
+
|
|
263
|
+
The streaming loop, end to end:
|
|
264
|
+
|
|
265
|
+
```ts
|
|
266
|
+
import { createAssistantStream, createMockResponder } from '@kitn.ai/ui/state';
|
|
267
|
+
import { readOpenAIStream } from '@kitn.ai/ui/wire';
|
|
268
|
+
|
|
269
|
+
// 1. A setter with the ONE universal contract: functional updater, new array out.
|
|
270
|
+
const stream = createAssistantStream((update) => { el.messages = update(el.messages ?? []); });
|
|
271
|
+
|
|
272
|
+
// 2. You fetch (or preview with the mock — real SSE frames, no provider, no key):
|
|
273
|
+
const mock = createMockResponder(); // or: fetch("/api/chat", …).then(r => r.body)
|
|
274
|
+
const result = await readOpenAIStream(mock(prompt), stream);
|
|
275
|
+
|
|
276
|
+
// 3. THE HOST RESOLVES TOOL CALLS. A provider (and the mock) only ANNOUNCES a tool
|
|
277
|
+
// call — the part sits at state "input-available" forever unless your code answers
|
|
278
|
+
// it. Executing the tool is the app's decision, and this is the call that answers.
|
|
279
|
+
// The ONE exception: a call with `providerExecuted: true` (see ModelToolCall below)
|
|
280
|
+
// was already run by the provider, in-stream — the host must NOT execute those.
|
|
281
|
+
for (const call of result.toolCalls) {
|
|
282
|
+
if (call.providerExecuted) continue;
|
|
283
|
+
stream.upsertTool(call.id, { state: 'output-available', output: await runTool(call) });
|
|
284
|
+
}
|
|
285
|
+
stream.done(); // seal the turn; late sink calls are dropped
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
Scripting a mock tool call (so the tool panel renders with zero backend):
|
|
289
|
+
|
|
290
|
+
```ts
|
|
291
|
+
const mock = createMockResponder({
|
|
292
|
+
replies: ['Plain text turn', { text: 'Let me check.', toolCalls: [{ name: 'search_docs', arguments: { query: 'threads' } }] }],
|
|
293
|
+
});
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### `@kitn.ai/ui/state`
|
|
297
|
+
|
|
298
|
+
I/O-free pure folds over `ChatMessage[]`. No client, no fetch — you own the transport; these functions own the array-identity discipline the elements re-render on.
|
|
299
|
+
|
|
300
|
+
Every export (56, derived from `dist/state/index.d.ts`):
|
|
301
|
+
|
|
302
|
+
| Export | Kind | Module |
|
|
303
|
+
|---|---|---|
|
|
304
|
+
| `appendMessage` | value | `messages` |
|
|
305
|
+
| `upsertMessage` | value | `messages` |
|
|
306
|
+
| `updateMessage` | value | `messages` |
|
|
307
|
+
| `removeMessage` | value | `messages` |
|
|
308
|
+
| `appendText` | value | `messages` |
|
|
309
|
+
| `textMessage` | value | `messages` |
|
|
310
|
+
| `partsToText` | value | `messages` |
|
|
311
|
+
| `addSuggestion` | value | `suggestions` |
|
|
312
|
+
| `removeSuggestion` | value | `suggestions` |
|
|
313
|
+
| `createAssistantStream` | value | `stream` |
|
|
314
|
+
| `onStreamSettled` | value | `stream` |
|
|
315
|
+
| `SetMessages` | type | `stream` |
|
|
316
|
+
| `AssistantStream` | type | `stream` |
|
|
317
|
+
| `appendTextPart` | value | `parts` |
|
|
318
|
+
| `appendReasoningPart` | value | `parts` |
|
|
319
|
+
| `upsertToolPart` | value | `parts` |
|
|
320
|
+
| `upsertCardPart` | value | `parts` |
|
|
321
|
+
| `fingerprint` | value | `parts` |
|
|
322
|
+
| `ReasoningOpts` | type | `parts` |
|
|
323
|
+
| `updateThreadMessages` | value | `threads` |
|
|
324
|
+
| `bindThreadMessages` | value | `threads` |
|
|
325
|
+
| `createThreadSessions` | value | `threads` |
|
|
326
|
+
| `ThreadLike` | type | `threads` |
|
|
327
|
+
| `SetThreads` | type | `threads` |
|
|
328
|
+
| `BindThreadOptions` | type | `threads` |
|
|
329
|
+
| `ThreadSessions` | type | `threads` |
|
|
330
|
+
| `parseStoredThread` | value | `persistence` |
|
|
331
|
+
| `createSaveScheduler` | value | `persistence` |
|
|
332
|
+
| `ParsedThread` | type | `persistence` |
|
|
333
|
+
| `DroppedStored` | type | `persistence` |
|
|
334
|
+
| `SaveScheduler` | type | `persistence` |
|
|
335
|
+
| `SaveSchedulerOptions` | type | `persistence` |
|
|
336
|
+
| `createMockResponder` | value | `mock` |
|
|
337
|
+
| `DEFAULT_MOCK_REPLIES` | value | `mock` |
|
|
338
|
+
| `MOCK_BANNER` | value | `mock` |
|
|
339
|
+
| `MOCK_MARKER` | value | `mock` |
|
|
340
|
+
| `MOCK_MARKER_KEY` | value | `mock` |
|
|
341
|
+
| `MOCK_MODEL_ID` | value | `mock` |
|
|
342
|
+
| `MockReply` | type | `mock` |
|
|
343
|
+
| `MockResponder` | type | `mock` |
|
|
344
|
+
| `MockResponderOptions` | type | `mock` |
|
|
345
|
+
| `MockToolCall` | type | `mock` |
|
|
346
|
+
| `MockTurn` | type | `mock` |
|
|
347
|
+
| `ChatMessage` | type | `../elements/chat-types` |
|
|
348
|
+
| `ChatMessageAction` | type | `../elements/chat-types` |
|
|
349
|
+
| `CustomAction` | type | `../elements/chat-types` |
|
|
350
|
+
| `AvatarData` | type | `../elements/chat-types` |
|
|
351
|
+
| `FeedbackVote` | type | `../elements/chat-types` |
|
|
352
|
+
| `MessagePart` | type | `../elements/chat-types` |
|
|
353
|
+
| `MessageSource` | type | `../elements/chat-types` |
|
|
354
|
+
| `RawOrigin` | type | `../elements/chat-types` |
|
|
355
|
+
| `ToolPart` | type | `../components/tool-types` |
|
|
356
|
+
| `ToolKind` | type | `../components/tool-classify` |
|
|
357
|
+
| `classifyTool` | value | `../components/tool-classify` |
|
|
358
|
+
| `CardEnvelope` | type | `../primitives/card-contract` |
|
|
359
|
+
| `AttachmentData` | type | `../components/attachment-types` |
|
|
360
|
+
|
|
361
|
+
#### `@kitn.ai/ui/state` · `stream` — the shipped declarations
|
|
362
|
+
|
|
363
|
+
```ts
|
|
364
|
+
/** The one universal contract: a functional-updater setter (React setState shape). */
|
|
365
|
+
export type SetMessages = (updater: (prev: ChatMessage[]) => ChatMessage[]) => void;
|
|
366
|
+
/** Every OBJECT payload a `MessagePart` variant carries. `type`/`raw` are the
|
|
367
|
+
* variant's own bookkeeping, not payload; the primitive payloads (`text`,
|
|
368
|
+
* `label`, `index`, ...) drop out at `Extract<..., object>`. */
|
|
369
|
+
type PartPayload = Extract<MessagePart extends infer P ? (P extends object ? P[Exclude<keyof P, 'type' | 'raw'>] : never) : never, object>;
|
|
370
|
+
/** Every bag one of these mutators takes: the part payloads plus the options
|
|
371
|
+
* bags that are not payloads themselves. */
|
|
372
|
+
type MutatorBag = PartPayload | ReasoningOpts;
|
|
373
|
+
/** `keyof` over a union member-by-member. The bare `keyof (A | B)` is the
|
|
374
|
+
* INTERSECTION of their keys, which is the opposite of what this needs. */
|
|
375
|
+
type KeysOf<T> = T extends unknown ? keyof T : never;
|
|
376
|
+
/** `Shape`, but any key that belongs exclusively to a SIBLING bag is a compile
|
|
377
|
+
* error. Keys `Shape` never heard of are untouched, so a consumer's own
|
|
378
|
+
* superset of a citation still passes — only the mix-ups fail. That is a
|
|
379
|
+
* denylist, not an exact type, and deliberately so: boundary point 1. */
|
|
380
|
+
type Unmixed<Shape> = Shape & {
|
|
381
|
+
[K in Exclude<KeysOf<MutatorBag>, keyof Shape>]?: never;
|
|
382
|
+
};
|
|
383
|
+
/** A fluent builder for one in-flight assistant message. Owns no state. */
|
|
384
|
+
export interface AssistantStream {
|
|
385
|
+
readonly id: string;
|
|
386
|
+
appendText(delta: string): AssistantStream;
|
|
387
|
+
appendReasoning(delta: string, opts?: Unmixed<ReasoningOpts>): AssistantStream;
|
|
388
|
+
upsertTool(toolCallId: string, patch: Unmixed<Partial<ToolPart>>): AssistantStream;
|
|
389
|
+
/** Adds a card, or REPLACES the existing one with the same `envelope.id`. A
|
|
390
|
+
* model that revises a card mid-turn re-sends the whole envelope, so a second
|
|
391
|
+
* call with a known id revises that card in place rather than rendering a
|
|
392
|
+
* second copy of it. See `upsertCardPart`. */
|
|
393
|
+
addCard(envelope: CardEnvelope): AssistantStream;
|
|
394
|
+
addSource(source: Unmixed<Source>): AssistantStream;
|
|
395
|
+
addFile(attachment: AttachmentData): AssistantStream;
|
|
396
|
+
done(): void;
|
|
397
|
+
/** Settles the turn as FAILED and puts `reason` where the reader can find it.
|
|
398
|
+
*
|
|
399
|
+
* WHERE THE REASON LANDS, in order:
|
|
400
|
+
* 1. every tool part that has NOT produced a result flips to `output-error`
|
|
401
|
+
* with `errorText: reason`, so no panel spins forever. A tool ALREADY in
|
|
402
|
+
* `output-error` with its own non-empty `errorText` is left exactly as it
|
|
403
|
+
* is: "search index offline" is the answer to what went wrong and
|
|
404
|
+
* "Connection lost." is the generic outer symptom, so overwriting the
|
|
405
|
+
* specific with the generic loses the only actionable thing on the
|
|
406
|
+
* message. It still counts as carrying the failure. A tool in
|
|
407
|
+
* `output-error` with NO text does get filled in — an error panel with
|
|
408
|
+
* nothing in it says no more than a blank bubble does.
|
|
409
|
+
* 2. if no part was able to carry it, the reason is APPENDED as its own text
|
|
410
|
+
* part.
|
|
411
|
+
*
|
|
412
|
+
* Never both — a turn does not report the same failure twice.
|
|
413
|
+
*
|
|
414
|
+
* "Where the reader can FIND it" is deliberate, and rule 1 and rule 2 are not
|
|
415
|
+
* equally loud. Rule 2 is text in the thread: read without any interaction.
|
|
416
|
+
* Rule 1 is a tool panel, which renders COLLAPSED — the header shows an error
|
|
417
|
+
* icon and a badge, so the failure is unmissable, but the `errorText` itself
|
|
418
|
+
* is one click away. That is the right default for a failed tool inside an
|
|
419
|
+
* otherwise readable answer, and it is why rule 2 exists rather than always
|
|
420
|
+
* stamping the panel and calling it reported.
|
|
421
|
+
*
|
|
422
|
+
* Rule 2 is the whole point and it used to be missing. `abort` only ever did
|
|
423
|
+
* rule 1, so a TEXT-ONLY turn — every turn of a text-only support widget —
|
|
424
|
+
* had nothing to stamp: the string was discarded and a failed request
|
|
425
|
+
* rendered an EMPTY assistant bubble, while the consumer that passed a
|
|
426
|
+
* perfectly good sentence believed it had reported the failure. That is the
|
|
427
|
+
* repo's "decide loudly" rule broken on the one path where being quiet is
|
|
428
|
+
* worst, and it shipped because the discard is invisible from the call site.
|
|
429
|
+
*
|
|
430
|
+
* It is a NEW part, never a merge onto the trailing text: gluing "Connection
|
|
431
|
+
* lost." onto the model's half-finished sentence reads as the model saying
|
|
432
|
+
* it. Text that already streamed stays exactly where it is.
|
|
433
|
+
*
|
|
434
|
+
* The reason is TRIMMED, and `abort()` with no reason — or one that is empty
|
|
435
|
+
* or all whitespace — appends nothing: there is no reason to discard, and the
|
|
436
|
+
* kit will not invent copy the consumer did not write. Rule 1 still runs,
|
|
437
|
+
* with `errorText: undefined`. Whitespace is not a pedantic case here: the
|
|
438
|
+
* scaffold hands over `err.message`, and an `Error` is free to carry `''`,
|
|
439
|
+
* which would otherwise render an INVISIBLE text part — a blank bubble that
|
|
440
|
+
* also claims to have said something.
|
|
441
|
+
*
|
|
442
|
+
* Settled is settled: after `done()` or a first `abort()`, this is a no-op
|
|
443
|
+
* like every other mutator, so the reason cannot be appended twice.
|
|
444
|
+
*
|
|
445
|
+
* The reason is CONSUMER-facing text and is rendered as markdown like any
|
|
446
|
+
* other text part. Pass a sentence a visitor can read, not a stack trace. */
|
|
447
|
+
abort(reason?: string): void;
|
|
448
|
+
}
|
|
449
|
+
/** Start an assistant message and drive it through `set`. New refs on every mutation. */
|
|
450
|
+
export declare function createAssistantStream(set: SetMessages, init?: Partial<ChatMessage>): AssistantStream;
|
|
451
|
+
/** Wrap a stream so `onSettle` fires on done/abort (used to toggle a `loading` flag).
|
|
452
|
+
* Preserves the fluent chain by returning the wrapper from every mutator. */
|
|
453
|
+
export declare function onStreamSettled(inner: AssistantStream, onSettle: () => void): AssistantStream;
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
#### `@kitn.ai/ui/state` · `parts` — the shipped declarations
|
|
457
|
+
|
|
458
|
+
```ts
|
|
459
|
+
/** Stable structural fingerprint. Key order independent, so an identical snapshot
|
|
460
|
+
* arriving twice compares equal and can be skipped. */
|
|
461
|
+
export declare function fingerprint(value: unknown): string;
|
|
462
|
+
/** Appends to the trailing text part, or OPENS A NEW ONE if the last part is not
|
|
463
|
+
* text. This is what stops a post-tool answer being glued onto the pre-tool text. */
|
|
464
|
+
export declare function appendTextPart(parts: MessagePart[], delta: string): MessagePart[];
|
|
465
|
+
export interface ReasoningOpts {
|
|
466
|
+
index?: number;
|
|
467
|
+
/** Namespaces `index` to one provider response stream. Producers that read more
|
|
468
|
+
* than one stream into the SAME message must set it. See `appendReasoningPart`. */
|
|
469
|
+
streamId?: string;
|
|
470
|
+
label?: string;
|
|
471
|
+
signature?: string;
|
|
472
|
+
raw?: RawOrigin;
|
|
473
|
+
}
|
|
474
|
+
/** Keyed by `(streamId, index)` so parallel reasoning blocks stay distinct.
|
|
475
|
+
*
|
|
476
|
+
* WHY THE KEY IS A PAIR. A block index alone is NOT unique inside one `parts`
|
|
477
|
+
* array. Anthropic numbers content blocks per MESSAGE and restarts at 0 on the
|
|
478
|
+
* next one, while a tool loop folds every round into a single assistant turn.
|
|
479
|
+
* Keyed on index alone, round 2's thinking block (index 0) merges into round 1's
|
|
480
|
+
* part: the text concatenates and `raw: opts.raw ?? cur.raw` OVERWRITES round 1's
|
|
481
|
+
* verbatim provider payload with round 2's. `toAnthropicMessages` then emits one
|
|
482
|
+
* thinking block where two belong, carrying round 2's signature in round 1's
|
|
483
|
+
* position -- a modified-and-filtered thinking block, which is exactly the 400
|
|
484
|
+
* the verbatim `raw` channel exists to prevent.
|
|
485
|
+
*
|
|
486
|
+
* `streamId` is the namespace: one value per provider response stream, attached
|
|
487
|
+
* by `consumeModelStream`. Two rounds are two streams, so their index 0s are two
|
|
488
|
+
* parts. Producers that drive a sink from a single stream can omit it; `undefined`
|
|
489
|
+
* is its own namespace and behaves exactly as before.
|
|
490
|
+
*
|
|
491
|
+
* Returns the SAME array reference when the merge produces an identical part,
|
|
492
|
+
* for the same reason `upsertToolPart` does: a new `parts` array is the
|
|
493
|
+
* re-render signal, so handing one back for a delta that changed nothing is a
|
|
494
|
+
* spurious render.
|
|
495
|
+
*
|
|
496
|
+
* An EMPTY delta is not a no-op and must still reach here: it is how a redacted
|
|
497
|
+
* reasoning block, a `signature_delta` and an assembled `content_block_stop`
|
|
498
|
+
* block arrive, and how a format opens a block at the right position so block
|
|
499
|
+
* ORDER survives into `parts`. Those carry a new `raw`/`signature`/index and so
|
|
500
|
+
* compare unequal and DO rebuild. What the check absorbs is the other empty
|
|
501
|
+
* frame: one carrying nothing new, which a provider is free to send repeatedly.
|
|
502
|
+
*
|
|
503
|
+
* `signature` and `raw` resolve with `??`, so an explicit `undefined` from a
|
|
504
|
+
* later delta never blanks a value an earlier one established. Pass a DEFINED
|
|
505
|
+
* value to replace either; there is no way to clear them. */
|
|
506
|
+
export declare function appendReasoningPart(parts: MessagePart[], delta: string, opts?: ReasoningOpts): MessagePart[];
|
|
507
|
+
/** Creates or REPLACES a card part, keyed on `envelope.id`. Returns the SAME array
|
|
508
|
+
* reference when the incoming envelope is structurally identical to the current
|
|
509
|
+
* one, for the same reason `upsertToolPart` does: a new `parts` array is the
|
|
510
|
+
* re-render signal, so handing one back for a revision that changed nothing is a
|
|
511
|
+
* spurious render.
|
|
512
|
+
*
|
|
513
|
+
* WHY THIS REPLACES WHERE `upsertToolPart` MERGES. A tool part is patched
|
|
514
|
+
* fragment-by-fragment as its arguments stream in, which is why that function
|
|
515
|
+
* needs carry-forward rules for `raw` and `kind` — a later patch that omits a
|
|
516
|
+
* field is not asserting the field is gone. A card envelope is the opposite: it
|
|
517
|
+
* arrives WHOLE, as one complete tool result, so an omitted field IS an
|
|
518
|
+
* assertion. Last-write-wins is both simpler and the only semantics under which
|
|
519
|
+
* a host can CLEAR `resolution` to re-open a dismissed card — a field-by-field
|
|
520
|
+
* merge can only ever set that field, never unset it, so `CardPolicy.onReopen`
|
|
521
|
+
* (see `primitives/card-contract.ts`) would have no way to express its result.
|
|
522
|
+
*
|
|
523
|
+
* The PART-level `raw` is preserved across a revision. It is a different field
|
|
524
|
+
* from anything inside the envelope: the untranslated provider payload the part
|
|
525
|
+
* was built from, attached once by the producer, which a fresh envelope carries
|
|
526
|
+
* no opinion about.
|
|
527
|
+
*
|
|
528
|
+
* Position is preserved: a revised card stays where it first appeared in the
|
|
529
|
+
* thread rather than jumping past the text that followed it. */
|
|
530
|
+
export declare function upsertCardPart(parts: MessagePart[], envelope: CardEnvelope): MessagePart[];
|
|
531
|
+
/** Creates or merges a tool part. Returns the SAME array reference when the merge
|
|
532
|
+
* produces an identical tool, so repeated snapshots do not trigger a re-render.
|
|
533
|
+
*
|
|
534
|
+
* Two fields do NOT follow plain spread semantics, because a streaming provider
|
|
535
|
+
* hands them over on one fragment and then keeps patching the rest:
|
|
536
|
+
* - `kind`: a value the consumer set is preserved across later patches instead
|
|
537
|
+
* of being reverted to `classifyTool(type)` (see `resolveKind`).
|
|
538
|
+
* - `raw`: an explicit `raw: undefined` never blanks a `raw` an earlier patch
|
|
539
|
+
* established. Pass a DEFINED `raw` to replace it; there is no way to clear it. */
|
|
540
|
+
export declare function upsertToolPart(parts: MessagePart[], toolCallId: string, patch: Partial<ToolPart>): MessagePart[];
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
#### `@kitn.ai/ui/state` · `mock` — the shipped declarations
|
|
544
|
+
|
|
545
|
+
```ts
|
|
546
|
+
/** The `model` every mock frame reports. Not a model any provider serves — see
|
|
547
|
+
* tell 3 in the header. */
|
|
548
|
+
export declare const MOCK_MODEL_ID = "kai-mock";
|
|
549
|
+
/** The marker field carried by every mock frame. Tell 2. */
|
|
550
|
+
export declare const MOCK_MARKER_KEY = "_kai_mock";
|
|
551
|
+
/** The value of that marker: a whole sentence, because it is read by a human
|
|
552
|
+
* staring at a logged frame and wondering where the reply came from. */
|
|
553
|
+
export declare const MOCK_MARKER = "no provider was contacted \u2014 this reply was generated locally by createMockResponder() from @kitn.ai/ui/state";
|
|
554
|
+
/** The SSE comment that opens every mock stream. Tell 1. */
|
|
555
|
+
export declare const MOCK_BANNER = ": kai-mock \u2014 NO PROVIDER WAS CONTACTED. no provider was contacted \u2014 this reply was generated locally by createMockResponder() from @kitn.ai/ui/state.";
|
|
556
|
+
/** The default canned replies, cycled per turn so a multi-turn preview stays
|
|
557
|
+
* coherent instead of repeating one line forever. */
|
|
558
|
+
export declare const DEFAULT_MOCK_REPLIES: readonly string[];
|
|
559
|
+
/** One scripted tool call for a mock turn. Framed exactly the way the OpenAI
|
|
560
|
+
* chat-completions wire frames a real one — an announce fragment carrying
|
|
561
|
+
* `id`/`function.name`, then the argument JSON streamed in fragments — so the
|
|
562
|
+
* kit's own reader (`readOpenAIStream`) reassembles it through the same path a
|
|
563
|
+
* real provider's call takes. */
|
|
564
|
+
export interface MockToolCall {
|
|
565
|
+
/** The tool name, e.g. `'get_weather'` or a card tool like `'kai_confirm'`. */
|
|
566
|
+
name: string;
|
|
567
|
+
/** The call's arguments. Serialized with `JSON.stringify` and streamed as
|
|
568
|
+
* fragments, like a real provider. Defaults to `{}`. */
|
|
569
|
+
arguments?: unknown;
|
|
570
|
+
/** Explicit tool-call id. Defaults to `call_kai-mock-<turn>-<n>`, which keeps
|
|
571
|
+
* the mock's naming tell (see tell 3): no provider issues ids in that shape. */
|
|
572
|
+
id?: string;
|
|
573
|
+
}
|
|
574
|
+
/** A scripted mock turn: optional text, then optional tool calls. A turn with
|
|
575
|
+
* tool calls finishes `finish_reason: 'tool_calls'`, exactly as a real
|
|
576
|
+
* tool-calling turn does; a turn without them finishes `'stop'`. */
|
|
577
|
+
export interface MockTurn {
|
|
578
|
+
/** Text streamed (token by token) before the tool calls. */
|
|
579
|
+
text?: string;
|
|
580
|
+
/** Tool calls announced this turn, in order. */
|
|
581
|
+
toolCalls?: readonly MockToolCall[];
|
|
582
|
+
}
|
|
583
|
+
/** A canned reply: plain text, or a scripted turn. A string is exactly
|
|
584
|
+
* `{ text }` — the pre-tool-call API unchanged. */
|
|
585
|
+
export type MockReply = string | MockTurn;
|
|
586
|
+
export interface MockResponderOptions {
|
|
587
|
+
/** Canned replies, cycled one per turn. Plain strings stream as text; a
|
|
588
|
+
* `MockTurn` can also script tool calls (`{ text, toolCalls }`), which is
|
|
589
|
+
* what lets the zero-config mock exercise the kit's tool/card path without
|
|
590
|
+
* hand-rolled SSE framing. Defaults to `DEFAULT_MOCK_REPLIES`. */
|
|
591
|
+
replies?: readonly MockReply[];
|
|
592
|
+
/** Delay between chunks, in ms. Defaults to 24 — fast enough to feel alive,
|
|
593
|
+
* slow enough that the streaming is visible. `0` streams as fast as the
|
|
594
|
+
* event loop allows, which is what tests want. */
|
|
595
|
+
delayMs?: number;
|
|
596
|
+
/** How many whitespace-delimited tokens ride in each frame. Defaults to 1
|
|
597
|
+
* (token by token). Larger values coarsen the cadence. */
|
|
598
|
+
chunkSize?: number;
|
|
599
|
+
/** Log a one-time notice on the first turn. Defaults to `true`: the point of
|
|
600
|
+
* this module is that a mock reply is hard to mistake for a real one, and a
|
|
601
|
+
* console line is the fastest way for a human to notice. Pass `false` in
|
|
602
|
+
* tests, or wherever the banner and the frame markers are tell enough. */
|
|
603
|
+
announce?: boolean;
|
|
604
|
+
}
|
|
605
|
+
/** Produces one turn's worth of SSE frames. Structurally a `StreamSource`, so it
|
|
606
|
+
* goes straight into `readOpenAIStream(responder(text), stream)`. */
|
|
607
|
+
export type MockResponder = (prompt?: string) => AsyncIterable<string>;
|
|
608
|
+
/**
|
|
609
|
+
* Build a mock responder.
|
|
610
|
+
*
|
|
611
|
+
* ```ts
|
|
612
|
+
* import { createAssistantStream, createMockResponder } from '@kitn.ai/ui/state';
|
|
613
|
+
* import { readOpenAIStream } from '@kitn.ai/ui/wire';
|
|
614
|
+
*
|
|
615
|
+
* const mockResponse = createMockResponder();
|
|
616
|
+
* const stream = createAssistantStream(setMessages);
|
|
617
|
+
* await readOpenAIStream(mockResponse(value), stream); // <- swap for fetch()
|
|
618
|
+
* stream.done();
|
|
619
|
+
* ```
|
|
620
|
+
*/
|
|
621
|
+
export declare function createMockResponder(options?: MockResponderOptions): MockResponder;
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
### `@kitn.ai/ui/wire`
|
|
625
|
+
|
|
626
|
+
The model-stream adapter. The kit PARSES, the consumer FETCHES: you make the request (your endpoint, your key), hand the response body to a reader, and it folds provider SSE onto message parts. The encoders turn the thread back into provider messages.
|
|
627
|
+
|
|
628
|
+
Every export (66, derived from `dist/wire/index.d.ts`):
|
|
629
|
+
|
|
630
|
+
| Export | Kind | Module |
|
|
631
|
+
|---|---|---|
|
|
632
|
+
| `readModelStream` | value | `read` |
|
|
633
|
+
| `readOpenAIStream` | value | `read` |
|
|
634
|
+
| `readAnthropicStream` | value | `read` |
|
|
635
|
+
| `WireError` | value | `read` |
|
|
636
|
+
| `StreamSource` | type | `read` |
|
|
637
|
+
| `ReadOptions` | type | `read` |
|
|
638
|
+
| `consumeModelStream` | value | `consume` |
|
|
639
|
+
| `createToolCallAccumulator` | value | `consume` |
|
|
640
|
+
| `applyToolOutput` | value | `sink-helpers` |
|
|
641
|
+
| `applyToolFailure` | value | `sink-helpers` |
|
|
642
|
+
| `bufferText` | value | `sink-helpers` |
|
|
643
|
+
| `toOpenAIMessages` | value | `encode` |
|
|
644
|
+
| `toAnthropicMessages` | value | `encode` |
|
|
645
|
+
| `WireEncodeError` | value | `encode` |
|
|
646
|
+
| `AnthropicContentBlock` | type | `encode` |
|
|
647
|
+
| `AnthropicEncodeOptions` | type | `encode` |
|
|
648
|
+
| `AnthropicWireMessage` | type | `encode` |
|
|
649
|
+
| `FileEncodeOptions` | type | `encode` |
|
|
650
|
+
| `OpenAIContentPart` | type | `encode` |
|
|
651
|
+
| `OpenAIEncodeOptions` | type | `encode` |
|
|
652
|
+
| `OpenAIReasoningDetail` | type | `encode` |
|
|
653
|
+
| `OpenAIToolCall` | type | `encode` |
|
|
654
|
+
| `OpenAIWireMessage` | type | `encode` |
|
|
655
|
+
| `UnencodableFilePolicy` | type | `encode` |
|
|
656
|
+
| `encodableMediaTypes` | value | `media-types` |
|
|
657
|
+
| `resolveMediaPolicy` | value | `media-types` |
|
|
658
|
+
| `EncodableKind` | type | `media-types` |
|
|
659
|
+
| `MediaDecision` | type | `media-types` |
|
|
660
|
+
| `MediaPolicy` | type | `media-types` |
|
|
661
|
+
| `MediaPolicyOptions` | type | `media-types` |
|
|
662
|
+
| `MediaTypeFilter` | type | `media-types` |
|
|
663
|
+
| `openaiChatFormat` | value | `formats/openai` |
|
|
664
|
+
| `anthropicMessagesFormat` | value | `formats/anthropic` |
|
|
665
|
+
| `sseDataFrames` | value | `sse` |
|
|
666
|
+
| `sseJson` | value | `sse` |
|
|
667
|
+
| `readableToAsyncIterable` | value | `sse` |
|
|
668
|
+
| `ByteSource` | type | `sse` |
|
|
669
|
+
| `subscribeWireDiagnostics` | value | `diagnostics` |
|
|
670
|
+
| `AppRequestEvent` | type | `diagnostics` |
|
|
671
|
+
| `EncodeAttachmentReport` | type | `diagnostics` |
|
|
672
|
+
| `EncodeDroppedEvent` | type | `diagnostics` |
|
|
673
|
+
| `EncodeRequestEvent` | type | `diagnostics` |
|
|
674
|
+
| `WireCloseEvent` | type | `diagnostics` |
|
|
675
|
+
| `WireDiagnosticBase` | type | `diagnostics` |
|
|
676
|
+
| `WireDiagnosticEvent` | type | `diagnostics` |
|
|
677
|
+
| `WireFailedEvent` | type | `diagnostics` |
|
|
678
|
+
| `WireFrameEvent` | type | `diagnostics` |
|
|
679
|
+
| `WireInterruptedEvent` | type | `diagnostics` |
|
|
680
|
+
| `WireOpenEvent` | type | `diagnostics` |
|
|
681
|
+
| `WirePartEvent` | type | `diagnostics` |
|
|
682
|
+
| `normalizeStopReason` | value | `chunk` |
|
|
683
|
+
| `AssistantStreamSink` | type | `chunk` |
|
|
684
|
+
| `ConsumeOptions` | type | `chunk` |
|
|
685
|
+
| `ModelStreamChunk` | type | `chunk` |
|
|
686
|
+
| `ModelToolCall` | type | `chunk` |
|
|
687
|
+
| `ModelToolCallDelta` | type | `chunk` |
|
|
688
|
+
| `ModelTurn` | type | `chunk` |
|
|
689
|
+
| `ModelUsage` | type | `chunk` |
|
|
690
|
+
| `StopReason` | type | `chunk` |
|
|
691
|
+
| `WireFormat` | type | `chunk` |
|
|
692
|
+
| `WireFormatReader` | type | `chunk` |
|
|
693
|
+
| `ChatMessage` | type | `../elements/chat-types` |
|
|
694
|
+
| `MessagePart` | type | `../elements/chat-types` |
|
|
695
|
+
| `MessageSource` | type | `../elements/chat-types` |
|
|
696
|
+
| `RawOrigin` | type | `../elements/chat-types` |
|
|
697
|
+
| `ToolPart` | type | `../components/tool-types` |
|
|
698
|
+
|
|
699
|
+
#### `@kitn.ai/ui/wire` · `read` — the shipped declarations
|
|
700
|
+
|
|
701
|
+
```ts
|
|
702
|
+
export type StreamSource = Response | ReadableStream<Uint8Array> | AsyncIterable<Uint8Array | string>;
|
|
703
|
+
export interface ReadOptions extends ConsumeOptions {
|
|
704
|
+
format: WireFormat;
|
|
705
|
+
}
|
|
706
|
+
/** A non-ok HTTP response from the model endpoint, with the provider's own error
|
|
707
|
+
* body attached when there is one. Thrown before a single chunk is read, so a
|
|
708
|
+
* caller can distinguish "the request failed" from "the stream carried an
|
|
709
|
+
* error", which is `ModelTurn.error`. */
|
|
710
|
+
export declare class WireError extends Error {
|
|
711
|
+
readonly status: number;
|
|
712
|
+
readonly statusText: string;
|
|
713
|
+
/** The response body parsed as JSON, or undefined when it was not JSON (an
|
|
714
|
+
* HTML error page from a proxy, most often). */
|
|
715
|
+
readonly body: unknown;
|
|
716
|
+
/** The raw response body, always. */
|
|
717
|
+
readonly bodyText: string;
|
|
718
|
+
constructor(status: number, statusText: string, bodyText: string, body: unknown);
|
|
719
|
+
}
|
|
720
|
+
/** Read one turn off the wire in `opts.format` and drive `sink` with it. */
|
|
721
|
+
export declare function readModelStream(source: StreamSource, sink: AssistantStreamSink, opts: ReadOptions): Promise<ModelTurn>;
|
|
722
|
+
/** OpenAI chat-completions SSE. Also what all nine catalog integrations except
|
|
723
|
+
* `mock` re-frame to server-side, so this is the common path. */
|
|
724
|
+
export declare function readOpenAIStream(source: StreamSource, sink: AssistantStreamSink, opts?: ConsumeOptions): Promise<ModelTurn>;
|
|
725
|
+
/** Anthropic Messages SSE. */
|
|
726
|
+
export declare function readAnthropicStream(source: StreamSource, sink: AssistantStreamSink, opts?: ConsumeOptions): Promise<ModelTurn>;
|
|
727
|
+
```
|
|
728
|
+
|
|
729
|
+
#### `@kitn.ai/ui/wire` · `encode` — the shipped declarations
|
|
730
|
+
|
|
731
|
+
```ts
|
|
732
|
+
export interface OpenAIToolCall {
|
|
733
|
+
id: string;
|
|
734
|
+
type: 'function';
|
|
735
|
+
function: {
|
|
736
|
+
name: string;
|
|
737
|
+
arguments: string;
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
/** One `reasoning_details` entry. Provider-owned open shape, kept as a record
|
|
741
|
+
* for the same reason `AnthropicContentBlock` is: an opaque entry has to pass
|
|
742
|
+
* through UNTOUCHED, and a closed type would be a list of the fields we happen
|
|
743
|
+
* to have seen. */
|
|
744
|
+
export type OpenAIReasoningDetail = Record<string, unknown>;
|
|
745
|
+
/** A multimodal user message's content entries. `image_url` takes an https URL
|
|
746
|
+
* or a `data:` URI in the same field; `file` takes `file_data`, which is a DATA
|
|
747
|
+
* URI on this wire (`data:application/pdf;base64,...`) and not bare base64. */
|
|
748
|
+
export type OpenAIContentPart = {
|
|
749
|
+
type: 'text';
|
|
750
|
+
text: string;
|
|
751
|
+
} | {
|
|
752
|
+
type: 'image_url';
|
|
753
|
+
image_url: {
|
|
754
|
+
url: string;
|
|
755
|
+
};
|
|
756
|
+
} | {
|
|
757
|
+
type: 'file';
|
|
758
|
+
file: {
|
|
759
|
+
filename?: string;
|
|
760
|
+
file_data: string;
|
|
761
|
+
};
|
|
762
|
+
};
|
|
763
|
+
export interface OpenAIWireMessage {
|
|
764
|
+
role: 'system' | 'user' | 'assistant' | 'tool';
|
|
765
|
+
/** An ARRAY only when the turn carries an encodable `file` part. A text-only
|
|
766
|
+
* turn stays a plain string, so adding attachment support changed nothing
|
|
767
|
+
* about what an existing thread puts on the wire. */
|
|
768
|
+
content: string | OpenAIContentPart[] | null;
|
|
769
|
+
tool_calls?: OpenAIToolCall[];
|
|
770
|
+
tool_call_id?: string;
|
|
771
|
+
name?: string;
|
|
772
|
+
/** Only ever present when `toOpenAIMessages` was asked for it. See
|
|
773
|
+
* `OpenAIEncodeOptions.reasoning`. */
|
|
774
|
+
reasoning_details?: OpenAIReasoningDetail[];
|
|
775
|
+
}
|
|
776
|
+
/** EXTENDS rather than restates the file options: `onUnencodableFile` and
|
|
777
|
+
* `accept` mean the same thing on both wires, and a second declaration of them
|
|
778
|
+
* here is a second place to forget to update. */
|
|
779
|
+
export interface OpenAIEncodeOptions extends FileEncodeOptions {
|
|
780
|
+
/**
|
|
781
|
+
* Whether to send the assistant's own reasoning back with the thread.
|
|
782
|
+
*
|
|
783
|
+
* DEFAULT `'omit'`, and that default is a measurement, not caution. Omitting
|
|
784
|
+
* reasoning is accepted by every configuration tested -- five live omission
|
|
785
|
+
* trials plus 28 recorded live requests per configuration across the spike's
|
|
786
|
+
* conformance sweep, zero 400s -- so the path that ships today demonstrably
|
|
787
|
+
* works, while including reasoning cost about 25% more prompt tokens per round
|
|
788
|
+
* when measured (665 -> 834 on a two-round loop). A library does not get to
|
|
789
|
+
* raise every consumer's bill and add a new provider-validation surface as a
|
|
790
|
+
* side effect of a bug fix.
|
|
791
|
+
*
|
|
792
|
+
* `'include'` is for a multi-round TOOL loop, which is where OpenRouter says it
|
|
793
|
+
* pays: "when you post tool results, including the original reasoning ensures
|
|
794
|
+
* the model can continue its reasoning from where it left off". Measured
|
|
795
|
+
* accepted (HTTP 200) for a signed Anthropic block and for an OpenAI encrypted
|
|
796
|
+
* block, over the OpenAI-compatible wire.
|
|
797
|
+
*
|
|
798
|
+
* The Anthropic wire has no such knob because it has no such choice: a filtered
|
|
799
|
+
* or rebuilt thinking block there is a hard 400.
|
|
800
|
+
*/
|
|
801
|
+
reasoning?: 'omit' | 'include';
|
|
802
|
+
}
|
|
803
|
+
/**
|
|
804
|
+
* What to do with a `file` part this wire cannot carry.
|
|
805
|
+
*
|
|
806
|
+
* DEFAULT `'throw'`, and the default is the whole point. Skipping is how
|
|
807
|
+
* attachments came to render perfectly in the thread and reach the model as
|
|
808
|
+
* nothing: the developer wires up upload, watches it work, and ships a model
|
|
809
|
+
* that cannot see the file. A throw here names the message, the part and the
|
|
810
|
+
* reason, which is strictly more than a 400 at request time would tell you.
|
|
811
|
+
*
|
|
812
|
+
* `'skip'` restores the lenient behaviour for a host that would rather send a
|
|
813
|
+
* degraded turn than fail one. It is silent, but it is silence the developer
|
|
814
|
+
* asked for by name, which is the difference that matters.
|
|
815
|
+
*/
|
|
816
|
+
export type UnencodableFilePolicy = 'throw' | 'skip';
|
|
817
|
+
export interface FileEncodeOptions {
|
|
818
|
+
onUnencodableFile?: UnencodableFilePolicy;
|
|
819
|
+
/**
|
|
820
|
+
* Narrow which attachment media types reach the wire, as HTML `accept` syntax
|
|
821
|
+
* (`'image/*,application/pdf'`) or an array of the same.
|
|
822
|
+
*
|
|
823
|
+
* THE SAME STRING the composer takes as `<kai-chat accept="...">`, resolved by
|
|
824
|
+
* the same function against the same declaration -- so a developer writes the
|
|
825
|
+
* set once as a constant and hands it to both ends. Omitted means the kit's
|
|
826
|
+
* full capability set, which is `encodableMediaTypes()`.
|
|
827
|
+
*
|
|
828
|
+
* It can only NARROW. Naming a type the encoders cannot represent does not
|
|
829
|
+
* enable it; that would just move the failure to a provider 400.
|
|
830
|
+
*/
|
|
831
|
+
accept?: MediaTypeFilter;
|
|
832
|
+
/**
|
|
833
|
+
* The app's own id for the logical turn this encode belongs to, carried onto
|
|
834
|
+
* every diagnostic event the encode emits. Purely diagnostic: nothing here
|
|
835
|
+
* branches on it and it never reaches a provider.
|
|
836
|
+
*
|
|
837
|
+
* THE SAME FIELD, THE SAME MEANING, as `ConsumeOptions.traceId` -- and that
|
|
838
|
+
* symmetry is the whole payoff. Encoding happens BEFORE a read opens, so
|
|
839
|
+
* there is no stream to attach an encode to and the kit will not invent one.
|
|
840
|
+
* Pass the same id to both halves:
|
|
841
|
+
*
|
|
842
|
+
* const body = toOpenAIMessages(messages, { traceId: 'turn-42' });
|
|
843
|
+
* readOpenAIStream(res, sink, { traceId: 'turn-42' });
|
|
844
|
+
*
|
|
845
|
+
* and the request and the response it produced sit together, with a tool loop
|
|
846
|
+
* or a sub-agent fan-out grouping into one trace. Without it you still see
|
|
847
|
+
* both halves; they are simply unlinked, which is the honest rendering --
|
|
848
|
+
* pinning an encode to "the next stream that opens" would be a guess, and an
|
|
849
|
+
* encode may be followed by no stream at all.
|
|
850
|
+
*/
|
|
851
|
+
traceId?: string;
|
|
852
|
+
/** The app's name for this call inside its trace (`'planner'`, `'retry-2'`).
|
|
853
|
+
* Same field and same meaning as `ConsumeOptions.label`. Absent when not
|
|
854
|
+
* supplied. */
|
|
855
|
+
label?: string;
|
|
856
|
+
}
|
|
857
|
+
export type AnthropicEncodeOptions = FileEncodeOptions;
|
|
858
|
+
/** Anthropic content blocks are an open, provider-owned union. Keeping them as
|
|
859
|
+
* records is what lets a verbatim `thinking` payload pass through UNTOUCHED,
|
|
860
|
+
* which is the entire point of this encoder. */
|
|
861
|
+
export type AnthropicContentBlock = Record<string, unknown>;
|
|
862
|
+
export interface AnthropicWireMessage {
|
|
863
|
+
role: 'user' | 'assistant';
|
|
864
|
+
content: AnthropicContentBlock[];
|
|
865
|
+
}
|
|
866
|
+
/** A message cannot be encoded without losing something the provider will reject.
|
|
867
|
+
* Thrown at encode time, on purpose: a throw here beats a 400 at request time,
|
|
868
|
+
* because here you still know which message and which part caused it. */
|
|
869
|
+
export declare class WireEncodeError extends Error {
|
|
870
|
+
readonly messageId: string;
|
|
871
|
+
readonly partIndex: number;
|
|
872
|
+
constructor(message: string, messageId: string, partIndex: number);
|
|
873
|
+
}
|
|
874
|
+
/**
|
|
875
|
+
* ChatMessage[] to an OpenAI chat-completions `messages` array.
|
|
876
|
+
*
|
|
877
|
+
* ONE ChatMessage CAN BECOME SEVERAL WIRE MESSAGES. The kit streams a whole
|
|
878
|
+
* assistant turn into a single message, so text, a tool call and the model's
|
|
879
|
+
* answer to that call all live in one `parts` array. The OpenAI wire has no such
|
|
880
|
+
* shape: a `role:'tool'` result must sit between the assistant message that
|
|
881
|
+
* announced the call and whatever the model said afterwards. So the turn is
|
|
882
|
+
* SPLIT at each tool boundary, into
|
|
883
|
+
*
|
|
884
|
+
* assistant(pre-tool text + tool_calls) -> tool(result)... -> assistant(answer)
|
|
885
|
+
*
|
|
886
|
+
* Flattening instead would put the model's answer BEFORE the result it was based
|
|
887
|
+
* on. No endpoint rejects that, which is exactly why it is worth spelling out:
|
|
888
|
+
* it quietly degrades every later round of a tool loop.
|
|
889
|
+
*
|
|
890
|
+
* Consecutive tool parts stay in ONE assistant message, because parallel calls
|
|
891
|
+
* are announced together and their results follow together.
|
|
892
|
+
*
|
|
893
|
+
* A turn that encodes to nothing is SKIPPED, never sent as `{ content: null }`
|
|
894
|
+
* with no `tool_calls`: OpenAI treats `content` as required unless `tool_calls`
|
|
895
|
+
* is present, and strict-compatible endpoints reject it.
|
|
896
|
+
*
|
|
897
|
+
* REASONING IS OPT-IN, and off by default. OpenRouter's OpenAI-compatible
|
|
898
|
+
* endpoint does have a channel on the way back in -- `reasoning_details` on the
|
|
899
|
+
* assistant message -- and `{ reasoning: 'include' }` uses it, one entry per
|
|
900
|
+
* reasoning part, in part order, reassembled by `reasoningDetailOf` rather than
|
|
901
|
+
* echoed out of `part.raw`. Read that function for which blocks make it and why.
|
|
902
|
+
* The default omits, because omitting is measured-accepted everywhere and costs
|
|
903
|
+
* about 25% fewer prompt tokens per round; see `OpenAIEncodeOptions.reasoning`.
|
|
904
|
+
*
|
|
905
|
+
* Reasoning alone still encodes to NOTHING. A block is content the model already
|
|
906
|
+
* produced, not a reason to send a turn, so a message carrying reasoning and no
|
|
907
|
+
* text and no settled tool is skipped exactly as before, rather than becoming
|
|
908
|
+
* `{ content: null }` with no `tool_calls`.
|
|
909
|
+
*
|
|
910
|
+
* `card` and `source` parts are never encoded; they are kit-side.
|
|
911
|
+
*
|
|
912
|
+
* `file` parts ARE encoded, on a USER turn, and a turn carrying nothing but an
|
|
913
|
+
* attachment is now a real message rather than nothing. Images become
|
|
914
|
+
* `image_url` (https URL or `data:` URI alike); a base64 PDF becomes a `file`
|
|
915
|
+
* part whose `file_data` is the data URI. Two cases have no form here and THROW
|
|
916
|
+
* by default: a remote PDF, because this wire's `file` part has no URL variant,
|
|
917
|
+
* and anything that is neither -- see `UnencodableFilePolicy` for why the
|
|
918
|
+
* default is a throw and not a skip.
|
|
919
|
+
*
|
|
920
|
+
* A `file` part on an ASSISTANT turn is still dropped. Neither API accepts image
|
|
921
|
+
* or document content in an assistant message, so there is nothing to encode it
|
|
922
|
+
* to; attachments belong to the user turn that sent them.
|
|
923
|
+
*/
|
|
924
|
+
export declare function toOpenAIMessages(messages: ChatMessage[], options?: OpenAIEncodeOptions): OpenAIWireMessage[];
|
|
925
|
+
/**
|
|
926
|
+
* ChatMessage[] to an Anthropic Messages `messages` array. THE ROUND-TRIP
|
|
927
|
+
* ENCODER.
|
|
928
|
+
*
|
|
929
|
+
* A reasoning block is emitted as `part.raw.payload` verbatim and is NEVER
|
|
930
|
+
* rebuilt from `text` plus `signature`: Anthropic returns 400 if a thinking
|
|
931
|
+
* block in the most recent assistant message is modified, reordered, filtered or
|
|
932
|
+
* reconstructed. A reasoning part with no `raw`, or with a `raw` captured from
|
|
933
|
+
* some other format, therefore THROWS rather than silently producing a request
|
|
934
|
+
* that will fail.
|
|
935
|
+
*
|
|
936
|
+
* Block order follows part order, which follows stream order, with no filtering,
|
|
937
|
+
* because the API validates order too. An empty-text reasoning part (an omitted
|
|
938
|
+
* or redacted block) is still emitted: the docs require sending back every block
|
|
939
|
+
* "including any blocks with empty thinking fields".
|
|
940
|
+
*
|
|
941
|
+
* ONE ChatMessage CAN BECOME SEVERAL WIRE MESSAGES, for the same reason as
|
|
942
|
+
* `toOpenAIMessages`: the kit streams a whole assistant turn into one message, so
|
|
943
|
+
* the tool call and the model's answer to it share a `parts` array, but Anthropic
|
|
944
|
+
* carries the result in a SEPARATE user message that has to sit between them. So
|
|
945
|
+
* the turn is SPLIT at each tool boundary, into
|
|
946
|
+
*
|
|
947
|
+
* assistant(pre-tool blocks + tool_use) -> user(tool_result)... -> assistant(answer)
|
|
948
|
+
*
|
|
949
|
+
* Flattening instead puts the model's answer BEFORE the result it was based on,
|
|
950
|
+
* and strands every later round's thinking block in the first assistant message.
|
|
951
|
+
* Consecutive tool parts stay in ONE assistant message, because parallel calls are
|
|
952
|
+
* announced together and their results come back together.
|
|
953
|
+
*
|
|
954
|
+
* Adjacent user messages are MERGED. The API combines consecutive same-role turns
|
|
955
|
+
* itself rather than rejecting them, so this is not what stands between you and a
|
|
956
|
+
* 400; it is emitted anyway because the tool-result turn and a following user turn
|
|
957
|
+
* are one turn, several OpenAI-compatible Anthropic proxies do enforce strict
|
|
958
|
+
* alternation, and the merged form is what the models are trained on. Ordering is
|
|
959
|
+
* safe by construction: `results` is only non-empty when `blocks` is, so a
|
|
960
|
+
* tool_result message always follows its assistant message and can never be
|
|
961
|
+
* appended after a plain user turn.
|
|
962
|
+
*
|
|
963
|
+
* `file` parts on a USER turn become `image` and `document` blocks, in part
|
|
964
|
+
* order. Both take `source: {type:'base64'}` and `source: {type:'url'}`, so this
|
|
965
|
+
* wire can carry a remote PDF that `toOpenAIMessages` has to refuse. Anything
|
|
966
|
+
* neither API accepts as message content THROWS by default; see
|
|
967
|
+
* `UnencodableFilePolicy`. A `file` part on an ASSISTANT turn is dropped, because
|
|
968
|
+
* an assistant message here carries only text, thinking and tool_use.
|
|
969
|
+
*
|
|
970
|
+
* Asymmetry worth knowing: `tool_use.input` is a parsed OBJECT on this wire, not
|
|
971
|
+
* a string, so it uses `input` and not `rawInput`. Only thinking blocks carry a
|
|
972
|
+
* verbatim requirement.
|
|
973
|
+
*/
|
|
974
|
+
export declare function toAnthropicMessages(messages: ChatMessage[], options?: AnthropicEncodeOptions): AnthropicWireMessage[];
|
|
975
|
+
```
|
|
976
|
+
|
|
977
|
+
#### `@kitn.ai/ui/wire` · `chunk` — the shipped declarations
|
|
978
|
+
|
|
979
|
+
```ts
|
|
980
|
+
/** One fragment of a tool call. */
|
|
981
|
+
export interface ModelToolCallDelta {
|
|
982
|
+
/**
|
|
983
|
+
* The ONLY thing correlating fragments, and its NAMESPACE IS FORMAT-DEFINED.
|
|
984
|
+
* `openaiChatFormat` uses the position in `delta.tool_calls`;
|
|
985
|
+
* `anthropicMessagesFormat` uses the content-block index. Both are correct and
|
|
986
|
+
* both are stable within one stream, but they are not the same number, so a
|
|
987
|
+
* third-party format must pick one and stay consistent with itself.
|
|
988
|
+
*/
|
|
989
|
+
index: number;
|
|
990
|
+
id?: string;
|
|
991
|
+
/** Usually whole on the first fragment; a few providers split it. */
|
|
992
|
+
name?: string;
|
|
993
|
+
/** A FRAGMENT of the JSON arguments string, not valid JSON on its own. */
|
|
994
|
+
arguments?: string;
|
|
995
|
+
/** A result the PROVIDER executed (Anthropic web_search_tool_result, an OpenAI
|
|
996
|
+
* built-in). Completes the panel with no host work. */
|
|
997
|
+
output?: Record<string, unknown>;
|
|
998
|
+
/** A provider-executed tool that failed. */
|
|
999
|
+
outputError?: string;
|
|
1000
|
+
}
|
|
1001
|
+
/** Field names are deliberately provider-neutral. OpenAI says prompt/completion,
|
|
1002
|
+
* Anthropic says input/output; input/output is the one that reads correctly for
|
|
1003
|
+
* both. */
|
|
1004
|
+
export interface ModelUsage {
|
|
1005
|
+
inputTokens?: number;
|
|
1006
|
+
outputTokens?: number;
|
|
1007
|
+
totalTokens?: number;
|
|
1008
|
+
/** Non-zero proves the model reasoned even when no reasoning text streamed. */
|
|
1009
|
+
reasoningTokens?: number;
|
|
1010
|
+
cachedInputTokens?: number;
|
|
1011
|
+
costUsd?: number;
|
|
1012
|
+
}
|
|
1013
|
+
export interface ModelStreamChunk {
|
|
1014
|
+
text?: string;
|
|
1015
|
+
/**
|
|
1016
|
+
* The model id the RESPONSE stated, verbatim; REPORT, NEVER INFER.
|
|
1017
|
+
*
|
|
1018
|
+
* Read from the response rather than the request, which is what makes it work
|
|
1019
|
+
* at all when the app builds its own fetch and the kit never sees what was
|
|
1020
|
+
* asked for. Providers commonly resolve an alias (ask for `gpt-4o`, get
|
|
1021
|
+
* `gpt-4o-2024-08-06`); through a gateway the value is the gateway's own id.
|
|
1022
|
+
* Both are reasons to pass the string through untouched.
|
|
1023
|
+
*
|
|
1024
|
+
* It is NOT guaranteed. A proxy can strip or rewrite it and a custom endpoint
|
|
1025
|
+
* may omit it, so a consumer renders it as ABSENT when it is absent. Filling
|
|
1026
|
+
* the gap with the requested id would lie in exactly the requested-vs-served
|
|
1027
|
+
* mismatch this field exists to catch.
|
|
1028
|
+
*/
|
|
1029
|
+
model?: string;
|
|
1030
|
+
/**
|
|
1031
|
+
* Reasoning delta. `''` is MEANINGFUL, not a no-op: a redacted block has no
|
|
1032
|
+
* readable text but still carries a payload that must round-trip, and a format
|
|
1033
|
+
* uses an empty delta to OPEN a reasoning part at the right position in the
|
|
1034
|
+
* stream so block order survives into `parts`.
|
|
1035
|
+
*/
|
|
1036
|
+
reasoning?: string;
|
|
1037
|
+
/** The provider's BLOCK index. Keeps parallel reasoning blocks distinct.
|
|
1038
|
+
* Omitted means block 0, the single-block case every provider degrades to. */
|
|
1039
|
+
reasoningIndex?: number;
|
|
1040
|
+
/**
|
|
1041
|
+
* The UNTRANSLATED provider payload for this reasoning block. Valid on a chunk
|
|
1042
|
+
* with NO reasoning text at all, which is the whole point: Anthropic returns
|
|
1043
|
+
* 400 if a `thinking` block is modified, reordered or RECONSTRUCTED, so an
|
|
1044
|
+
* encoder has to echo the original block rather than rebuild one from `text`
|
|
1045
|
+
* plus `signature`.
|
|
1046
|
+
*/
|
|
1047
|
+
reasoningRaw?: RawOrigin;
|
|
1048
|
+
/** Informational. `reasoningRaw` is the round-trip channel, not this. */
|
|
1049
|
+
reasoningSignature?: string;
|
|
1050
|
+
toolCalls?: ModelToolCallDelta[];
|
|
1051
|
+
/** Citations the model produced. A run of consecutive `source` parts renders
|
|
1052
|
+
* as one citation row (`part="citations"`), outside the message bubble. */
|
|
1053
|
+
sources?: MessageSource[];
|
|
1054
|
+
/** Provider VERBATIM: 'stop' | 'tool_calls' | 'end_turn' | 'max_tokens' | ...
|
|
1055
|
+
* Normalizing in place would destroy information consumers branch on. */
|
|
1056
|
+
finishReason?: string | null;
|
|
1057
|
+
usage?: ModelUsage;
|
|
1058
|
+
/** An in-band provider error (the HTTP response was already 200). */
|
|
1059
|
+
error?: {
|
|
1060
|
+
code?: string | number;
|
|
1061
|
+
message: string;
|
|
1062
|
+
};
|
|
1063
|
+
}
|
|
1064
|
+
/** One vocabulary across formats, for code that has to BRANCH. `finishReason`
|
|
1065
|
+
* stays beside it, verbatim, for code that has to REPORT. */
|
|
1066
|
+
export type StopReason = 'stop' | 'length' | 'tool-calls' | 'content-filter' | 'error' | 'other';
|
|
1067
|
+
/** Unknown reasons degrade to 'other' rather than throwing: providers add stop
|
|
1068
|
+
* reasons without warning and a new one must not take a turn down. */
|
|
1069
|
+
export declare function normalizeStopReason(finishReason: string | null | undefined): StopReason | undefined;
|
|
1070
|
+
/**
|
|
1071
|
+
* The subset of the kit's `AssistantStream` the adapter drives. Declared
|
|
1072
|
+
* STRUCTURALLY so the adapter has no runtime dependency on a stream
|
|
1073
|
+
* implementation and can be tested against a recorder. The kit's real
|
|
1074
|
+
* `AssistantStream` satisfies it as-is: same method names, same arities, and its
|
|
1075
|
+
* `AssistantStream` returns are assignable to `unknown`.
|
|
1076
|
+
*
|
|
1077
|
+
* `addSource` is optional so a hand-rolled three-method sink still compiles.
|
|
1078
|
+
*/
|
|
1079
|
+
export interface AssistantStreamSink {
|
|
1080
|
+
appendText(delta: string): unknown;
|
|
1081
|
+
appendReasoning(delta: string, opts?: ReasoningOpts): unknown;
|
|
1082
|
+
/** Create-or-merge. There is no separate "announce" call: handing a patch for
|
|
1083
|
+
* an unknown `toolCallId` creates the ToolPart, and every later patch merges. */
|
|
1084
|
+
upsertTool(toolCallId: string, patch: Partial<ToolPart>): unknown;
|
|
1085
|
+
addSource?(source: MessageSource): unknown;
|
|
1086
|
+
}
|
|
1087
|
+
/** One tool call reassembled out of the stream's fragments. */
|
|
1088
|
+
export interface ModelToolCall {
|
|
1089
|
+
/** The delta index that correlated this call's fragments. */
|
|
1090
|
+
index: number;
|
|
1091
|
+
/** Provider call id (synthesised as `call_<index>` if the provider omits it). */
|
|
1092
|
+
id: string;
|
|
1093
|
+
name: string;
|
|
1094
|
+
/** The RAW accumulated argument fragments. Echo THIS back on the next turn,
|
|
1095
|
+
* not a re-stringified parse. */
|
|
1096
|
+
argumentsText: string;
|
|
1097
|
+
/** Parsed arguments: present only when `argumentsText` was a valid JSON object. */
|
|
1098
|
+
input?: Record<string, unknown>;
|
|
1099
|
+
/** Present only for a call the PROVIDER executed. */
|
|
1100
|
+
output?: Record<string, unknown>;
|
|
1101
|
+
/** True when the provider ran the tool and returned its result in-stream. The
|
|
1102
|
+
* host must NOT execute these. */
|
|
1103
|
+
providerExecuted?: boolean;
|
|
1104
|
+
/** Why this call is unusable (malformed or truncated args, missing name). */
|
|
1105
|
+
error?: string;
|
|
1106
|
+
}
|
|
1107
|
+
/** Everything one assistant turn produced. */
|
|
1108
|
+
export interface ModelTurn {
|
|
1109
|
+
/** The turn as ORDERED MESSAGE PARTS, built with the kit's own part builders,
|
|
1110
|
+
* so it is exactly what the sink was driven with. Covers this turn only. */
|
|
1111
|
+
parts: MessagePart[];
|
|
1112
|
+
/** Flat concatenation of the text deltas. The provider wire format is a flat
|
|
1113
|
+
* string, so this is kept for encoders. Not the content model. */
|
|
1114
|
+
text: string;
|
|
1115
|
+
/** Flat concatenation of the reasoning deltas, for the same reason. */
|
|
1116
|
+
reasoning: string;
|
|
1117
|
+
toolCalls: ModelToolCall[];
|
|
1118
|
+
sources: MessageSource[];
|
|
1119
|
+
/** The provider's own word for why it stopped. Never normalized. */
|
|
1120
|
+
finishReason: string | null;
|
|
1121
|
+
/** The same fact in one vocabulary. Branch on this. */
|
|
1122
|
+
stopReason?: StopReason;
|
|
1123
|
+
error?: {
|
|
1124
|
+
code?: string | number;
|
|
1125
|
+
message: string;
|
|
1126
|
+
};
|
|
1127
|
+
usage?: ModelUsage;
|
|
1128
|
+
/** How many chunks carried a NON-EMPTY reasoning delta. Zero with a non-zero
|
|
1129
|
+
* `usage.reasoningTokens` means the provider hid the thinking text. */
|
|
1130
|
+
reasoningChunks: number;
|
|
1131
|
+
chunks: number;
|
|
1132
|
+
}
|
|
1133
|
+
export interface ConsumeOptions {
|
|
1134
|
+
/** Label for the reasoning disclosure. Defaults to 'Thinking'. */
|
|
1135
|
+
reasoningLabel?: string;
|
|
1136
|
+
/**
|
|
1137
|
+
* Correlates diagnostics and namespaces reasoning parts for this consume call;
|
|
1138
|
+
* assigned automatically when absent.
|
|
1139
|
+
*
|
|
1140
|
+
* Supply one only to tie a read to an id you already hold. Two reads into the
|
|
1141
|
+
* SAME sink must not share a value: the id is what keeps a second round's
|
|
1142
|
+
* block 0 from merging into the first round's reasoning part.
|
|
1143
|
+
*/
|
|
1144
|
+
streamId?: string;
|
|
1145
|
+
/**
|
|
1146
|
+
* The app's own grouping of several reads into ONE logical turn, carried onto
|
|
1147
|
+
* every diagnostic event this read emits.
|
|
1148
|
+
*
|
|
1149
|
+
* THE KIT REPORTS WHAT THE APP DECLARES AND GROUPS NOTHING ON ITS OWN. A chat
|
|
1150
|
+
* app running a tool loop, or fanning out to sub-agents, makes several model
|
|
1151
|
+
* calls that belong to one turn; the kit sees one Response at a time and has
|
|
1152
|
+
* no way to know which ones those are. So it does not guess:
|
|
1153
|
+
*
|
|
1154
|
+
* readOpenAIStream(res, sink, { traceId: 'turn-42', label: 'planner' })
|
|
1155
|
+
*
|
|
1156
|
+
* Absent when not supplied -- the key is not present on the events at all,
|
|
1157
|
+
* rather than present and undefined. Purely diagnostic: nothing in the parse
|
|
1158
|
+
* branches on it and it never reaches a provider.
|
|
1159
|
+
*/
|
|
1160
|
+
traceId?: string;
|
|
1161
|
+
/** The app's name for THIS read inside its trace (`'planner'`,
|
|
1162
|
+
* `'executor'`, `'retry-2'`). Carried onto every diagnostic event, and
|
|
1163
|
+
* absent when not supplied. Never derived from the format or the model.
|
|
1164
|
+
*
|
|
1165
|
+
* Not to be confused with `reasoningLabel`, which is UI copy for the
|
|
1166
|
+
* reasoning disclosure; this one is never rendered to an end user. */
|
|
1167
|
+
label?: string;
|
|
1168
|
+
/** Fires once per tool call the moment its arguments parse cleanly. This is
|
|
1169
|
+
* the hook a host's tool loop waits on. There is deliberately no
|
|
1170
|
+
* per-fragment callback: `ToolPart.rawInput` is written on every fragment,
|
|
1171
|
+
* so the streaming text is already on the part. */
|
|
1172
|
+
onToolCallReady?: (call: ModelToolCall) => void;
|
|
1173
|
+
}
|
|
1174
|
+
/** Per-stream state for one format. */
|
|
1175
|
+
export interface WireFormatReader {
|
|
1176
|
+
/**
|
|
1177
|
+
* Map one decoded frame onto zero or more neutral chunks. Returns an ARRAY
|
|
1178
|
+
* because the mapping is not one-to-one: an Anthropic `message_start` yields
|
|
1179
|
+
* usage, a `content_block_start` for `tool_use` yields an id-plus-name delta,
|
|
1180
|
+
* a `ping` yields nothing.
|
|
1181
|
+
*
|
|
1182
|
+
* MUST NOT throw on an unrecognized frame. Return `[]` instead: providers add
|
|
1183
|
+
* event types without warning.
|
|
1184
|
+
*/
|
|
1185
|
+
push(frame: unknown): ModelStreamChunk[];
|
|
1186
|
+
}
|
|
1187
|
+
/** A pluggable wire format. Values, not a flag, so a third party can add one
|
|
1188
|
+
* without a PR to this repo. */
|
|
1189
|
+
export interface WireFormat {
|
|
1190
|
+
readonly id: string;
|
|
1191
|
+
/** Called once per stream so a format can hold per-stream state. Two calls
|
|
1192
|
+
* must share NOTHING. */
|
|
1193
|
+
open(): WireFormatReader;
|
|
1194
|
+
}
|
|
1195
|
+
```
|
|
1196
|
+
|
|
1197
|
+
### The `ChatRequestBody` preamble (what your route receives)
|
|
1198
|
+
|
|
1199
|
+
Every backend route the `kai` MCP scaffolds narrows `await request.json()` ONCE at the
|
|
1200
|
+
edge, through this type — `request.json()` is `Promise<unknown>` under a Node/undici
|
|
1201
|
+
tsconfig, so destructuring it raw fails a stock `npm run build` even though it ran fine
|
|
1202
|
+
in dev. The front end sends `toOpenAIMessages(thread)`; this is what that produces, so
|
|
1203
|
+
the two halves stay pinned to one type. Do not hand-roll a second narrowing.
|
|
1204
|
+
|
|
1205
|
+
```ts
|
|
1206
|
+
/**
|
|
1207
|
+
* What the front end POSTs. `request.json()` is `unknown` (it is whatever the
|
|
1208
|
+
* client sent), so the body is narrowed once here instead of at every use —
|
|
1209
|
+
* without it this route does not compile under a server tsconfig. Widen it as
|
|
1210
|
+
* you add fields of your own.
|
|
1211
|
+
*/
|
|
1212
|
+
type ChatRequestBody = {
|
|
1213
|
+
messages: OpenAIWireMessage[];
|
|
1214
|
+
model?: string;
|
|
1215
|
+
tools?: unknown[];
|
|
1216
|
+
};
|
|
1217
|
+
```
|
|
1218
|
+
|
|
1219
|
+
The scaffolded routes pair it with a `readChatRequest(request)` guard that turns a bare
|
|
1220
|
+
GET or malformed JSON into a status response instead of an unhandled throw — re-scaffold
|
|
1221
|
+
any integration with the `kai` MCP `scaffold` tool to get the full preamble.
|
|
1222
|
+
<!-- kai:programmatic:end -->
|
|
1223
|
+
|
|
1224
|
+
---
|
|
1225
|
+
|
|
1226
|
+
## Element reference (89 elements, generated from custom-elements.json)
|
|
252
1227
|
|
|
253
1228
|
Every element also accepts the `theme` attribute. Array/object properties are marked with a `—` attribute: they must be set as JS properties.
|
|
254
1229
|
|
|
@@ -339,7 +1314,7 @@ Every element also accepts the `theme` attribute. Array/object properties are ma
|
|
|
339
1314
|
| Property | Attribute | Type | Description |
|
|
340
1315
|
|---|---|---|---|
|
|
341
1316
|
| `theme` | `theme` | `"light" \| "dark" \| "auto"` | Color mode (`auto` follows prefers-color-scheme). |
|
|
342
|
-
| `items` | — | `undefined \| { id: string; type: "file" \| "source-document"; filename?: undefined \| string; mediaType?: undefined \| string; url?: undefined \| string; title?: undefined \| string }[]` | The attachments to render. Omit (or pass an empty array) for the empty state, which shows `emptyText` if set and nothing otherwise. Set as a JS property (array). |
|
|
1317
|
+
| `items` | — | `undefined \| { id: string; type: "file" \| "source-document"; filename?: undefined \| string; mediaType?: undefined \| string; url?: undefined \| string; title?: undefined \| string }[]` | The attachments to render. Omit (or pass an empty array) for the empty state, which shows `emptyText` if set and nothing otherwise. Set as a JS property (array). Each item's `url` must be a `data:` URI or an https URL, never `URL.createObjectURL`: a `blob:` URL previews here but the wire encoders (`toOpenAIMessages`/`toAnthropicMessages`) refuse it. |
|
|
343
1318
|
| `variant` | `variant` | `undefined \| "grid" \| "inline" \| "list"` | Layout: `grid` = visual tiles, `inline` = icon + label chips, `list` = rows. |
|
|
344
1319
|
| `hoverCard` | `hover-card` | `undefined \| false \| true` | Wrap each item in a hover card that previews its details. |
|
|
345
1320
|
| `removable` | `removable` | `undefined \| false \| true` | Show a remove button per item; clicking it fires a `kai-remove` event. |
|
|
@@ -357,6 +1332,8 @@ Every element also accepts the `theme` attribute. Array/object properties are ma
|
|
|
357
1332
|
| Part | Description |
|
|
358
1333
|
|---|---|
|
|
359
1334
|
| `::part(preview)` | The image shown in an attachment’s hover-card preview. Bounded by default (max ~320×256, aspect preserved) so a large image never blows up the card. Raise or lower the cap from outside. — `kai-attachments::part(preview) { max-width: 32rem; max-height: 24rem }` |
|
|
1335
|
+
| `::part(attachment)` | One attachment item: the chip, row or tile, whichever variant is rendering. Restyle its background, radius or border from outside without caring which layout it is. — `kai-chat::part(attachment) { border-radius: 0.25rem }` |
|
|
1336
|
+
| `::part(attachment-name)` | The attachment’s filename label. Present in every variant that shows one (a grid tile omits it for an image, which is its own label). Retune its type or hide it entirely. — `kai-chat::part(attachment-name) { font-size: 0.75rem }` |
|
|
360
1337
|
|
|
361
1338
|
---
|
|
362
1339
|
|
|
@@ -378,9 +1355,10 @@ Every element also accepts the `theme` attribute. Array/object properties are ma
|
|
|
378
1355
|
| `color` | `color` | `undefined \| string` | CSS color for the geometry, overriding the inherited `currentColor`. Attribute: `color`. |
|
|
379
1356
|
| `complexity` | `complexity` | `undefined \| number` | Shader variants only: pattern density, 0..1. Attribute: `complexity`. |
|
|
380
1357
|
| `label` | `label` | `undefined \| string` | Setting this makes the element an announced image (`role="img"`) instead of decorative (`aria-hidden`). Attribute: `label`. |
|
|
381
|
-
| `stream` | — | `undefined \| MediaStream` | Live microphone or WebRTC audio to analyze. JS property only. |
|
|
382
|
-
| `audioElement` | — | `undefined \| HTMLMediaElement` | An `<audio>` or `<video>` element to tap for its audio. JS property only. |
|
|
383
|
-
| `bands` | — | `undefined \| number[]` | Pre-computed levels, 0..1. Set this and no AudioContext is ever built, which is what keeps headless/SSR rendering and browser-speech-synthesis playback (which exposes no audio node) free of Web Audio entirely. JS property only. A new array reference is required for each update; mutating the existing array in place will not re-render. |
|
|
1358
|
+
| `stream` | — | `undefined \| MediaStream` | Live microphone or WebRTC audio to analyze. JS property only. NOTE: amplitude renders only while state is "speaking" unless listening-amplitude is set; every other state plays its scripted animation and ignores the audio. |
|
|
1359
|
+
| `audioElement` | — | `undefined \| HTMLMediaElement` | An `<audio>` or `<video>` element to tap for its audio. JS property only. NOTE: amplitude renders only while state is "speaking" unless listening-amplitude is set; every other state plays its scripted animation and ignores the audio. |
|
|
1360
|
+
| `bands` | — | `undefined \| number[]` | Pre-computed levels, 0..1. Set this and no AudioContext is ever built, which is what keeps headless/SSR rendering and browser-speech-synthesis playback (which exposes no audio node) free of Web Audio entirely. JS property only. A new array reference is required for each update; mutating the existing array in place will not re-render. NOTE: amplitude renders only while state is "speaking" unless listening-amplitude is set; every other state plays its scripted animation and ignores the audio. |
|
|
1361
|
+
| `listeningAmplitude` | `listening-amplitude` | `undefined \| false \| true` | Render live amplitude during the listening state as well, using the same presentation as speaking. Off by default, which keeps LiveKit parity: amplitude from stream, audio-element or bands renders only while state is "speaking". Set it to show a real mic-level picture while the user is the one talking. Boolean. Attribute: `listening-amplitude` (a bare attribute means true; reflected, so the property reads back what the attribute set). |
|
|
384
1362
|
| `shader` | — | `undefined \| { fragment: string; uniforms?: undefined \| Record<string, { type: "1f" \| "1i" \| "1fv" \| "2f" \| "3f" \| "3fv" \| "4f" \| "4fv" \| "Matrix2fv" \| "Matrix3fv" \| "Matrix4fv"; value: number \| number[] }> }` | Custom fragment shader for `variant="custom"`. JS property only. |
|
|
385
1363
|
| `animateWhenNotVisible` | `animate-when-not-visible` | `undefined \| false \| true` | Shader variants only: keep animating while scrolled off screen. Off by default, which stops drawing and releases the WebGL context until the element comes back (browsers ration contexts to roughly 16 a page). Does not override `prefers-reduced-motion`. Attribute: `animate-when-not-visible`. |
|
|
386
1364
|
|
|
@@ -542,7 +1520,7 @@ _No events._
|
|
|
542
1520
|
| `cards` | — | `undefined \| { type: string; id: string; data: unknown; title?: undefined \| string; resolution?: undefined \| { kind: "action"; action: string; payload?: unknown; at?: undefined \| string } \| { kind: "submit"; data: unknown; at?: undefined \| string } \| { kind: "dismissed"; at?: undefined \| string } \| { kind: "expired"; reason?: undefined \| string; at?: undefined \| string } }[]` | The stream of card envelopes to render. Set as a JS PROPERTY: `el.cards = [...]`. |
|
|
543
1521
|
| `types` | — | `undefined \| Record<string, string>` | Optional type→tag overrides/additions (merged over the built-ins). Property: `el.types`. Typed as a plain string map (not the `CardTagMap` alias) so the generated React wrapper inlines it instead of emitting an unresolved named type. |
|
|
544
1522
|
| `schemas` | — | `undefined \| Record<string, object>` | JSON Schemas for the card types this app renders, keyed by envelope type. The companion of `types`, which says what DRAWS a card while this says what a VALID one looks like. An OBJECT, so it is a JS property only: `el.schemas = { 'pricing-table': pricingSchema }`, never an attribute. `createCardRegistry(...).validationSchemas` is exactly this shape. Without it the kit validates its own seven built-ins and leaves your own card type, the one your app actually cares about, as the only unchecked thing on screen. A schema here WINS over a built-in of the same name, matching `mergeCardTags`, where your entry is spread over ours. Typed `Record<string, object>` rather than `Record<string, JsonSchema>` deliberately: an imported `.json` schema widens `"type"` to `string`, and an authored one carries `$schema`/`title`/`description`/`additionalProperties`, so the tighter type would reject both of the normal ways to supply one. See `CardSchemaMap` in components/card-renderer.tsx. |
|
|
545
|
-
| `policy` | — | `undefined \| { onSubmit?: undefined \| (cardId: string, data: unknown) => void; onAction?: undefined \| (cardId: string, action: string, payload?: unknown) => void; onSendPrompt?: undefined \| (text: string, opts: { mode: "compose" \| "send"; context?: unknown; }) => void; onOpen?: undefined \| (url: string, target: "tab" \| "artifact") => void; onState?: undefined \| (cardId: string, patch: unknown) => void; onDismiss?: undefined \| (cardId: string) => void; onReopen?: undefined \| (cardId: string) => void; onError?: undefined \| (cardId: string, message: string) => void; maxSendPromptMode?: undefined \| "compose" \| "send" }` | Optional CardPolicy handling child events. Property: `el.policy`. |
|
|
1523
|
+
| `policy` | — | `undefined \| { onSubmit?: undefined \| ((cardId: string, data: unknown) => void); onAction?: undefined \| ((cardId: string, action: string, payload?: unknown) => void); onSendPrompt?: undefined \| ((text: string, opts: { mode: "compose" \| "send"; context?: unknown; }) => void); onOpen?: undefined \| ((url: string, target: "tab" \| "artifact") => void); onState?: undefined \| ((cardId: string, patch: unknown) => void); onDismiss?: undefined \| ((cardId: string) => void); onReopen?: undefined \| ((cardId: string) => void); onError?: undefined \| ((cardId: string, message: string) => void); maxSendPromptMode?: undefined \| "compose" \| "send" }` | Optional CardPolicy handling child events. Property: `el.policy`. |
|
|
546
1524
|
| `validateCards` | `validate-cards` | `undefined \| false \| true` | Validate each envelope's `data` against the schema for its type before rendering it, using a built-in's own schema or yours from `schemas`. Default `true`; set `validate-cards="false"` (or `el.validateCards = false`) to opt out. A hard failure (wrong type, a missing required field) renders a diagnostic naming the field instead of the card; a soft failure (bounds) renders the card unchanged. Both emit a contract `error` event. On in production too: a model emitting a bad shape is a production failure mode, so stripping the check there would hide it from exactly the person who needs to see it. |
|
|
547
1525
|
|
|
548
1526
|
**Events** (non-bubbling `CustomEvent`s — listen directly on the element):
|
|
@@ -602,7 +1580,6 @@ _No events._
|
|
|
602
1580
|
| Property | Attribute | Type | Description |
|
|
603
1581
|
|---|---|---|---|
|
|
604
1582
|
| `theme` | `theme` | `"light" \| "dark" \| "auto"` | Color mode (`auto` follows prefers-color-scheme). |
|
|
605
|
-
| `search` | `search` | `undefined \| false \| true` | Show a Search (Globe) button in the input toolbar; fires a `search` event. |
|
|
606
1583
|
| `value` | — | `undefined \| string \| ({ type: "text"; text: string } \| { type: "entity"; entity: { kind: string; id: string; label: string; icon?: undefined \| string; promptText?: undefined \| string; data?: undefined \| Record<string, unknown> } })[]` | Value of the input. A **string** is controlled (the host owns the text and updates it on `kai-value-change`). A **ComposerDoc** is a one-time seed that pre-populates pills; the user then edits freely. Leave unset for uncontrolled. |
|
|
607
1584
|
| `placeholder` | `placeholder` | `undefined \| string` | Placeholder text shown in the empty input. |
|
|
608
1585
|
| `loading` | `loading` | `undefined \| false \| true` | When true, shows the loading/streaming state and disables submit (use while awaiting the assistant's reply). |
|
|
@@ -612,6 +1589,8 @@ _No events._
|
|
|
612
1589
|
| `proseSize` | `prose-size` | `undefined \| "xs" \| "sm" \| "base" \| "lg"` | Body/prose font scale for rendered markdown (`'xs' \| 'sm' \| 'base' \| 'lg'`). Defaults to `'sm'`. |
|
|
613
1590
|
| `codeTheme` | `code-theme` | `undefined \| string` | Shiki theme name for syntax-highlighted code blocks (e.g. `'github-dark-dimmed'`). |
|
|
614
1591
|
| `codeHighlight` | `code-highlight` | `undefined \| false \| true` | Enable Shiki syntax highlighting in code blocks. Turn off to render plain `<pre>` blocks (lighter, no highlighter load). Default true. |
|
|
1592
|
+
| `reasoning` | `reasoning` | `undefined \| "full" \| "compact" \| "off"` | How `reasoning` parts render across the thread. `'full'` (default) is the current collapsible-disclosure behavior; `'compact'` shows only a shimmer loader while a reasoning part streams and nothing once it settles (no expandable detail); `'off'` renders reasoning parts not at all. Forwarded to every `MessageBody` as `reasoningMode`. |
|
|
1593
|
+
| `reasoningOpen` | `reasoning-open` | `undefined \| false \| true` | Seeds the reasoning disclosure open AND keeps it tracking the stream (open while streaming, closes when it settles): the pre-Task-19f `full` behavior. Default false/absent: the panel starts closed (just the "Thinking" shimmer chip) and only opens on click, the current default (owner ruling, 2026-08-26). Meaningless when `reasoning` is `'compact'` or `'off'`. Forwarded to every `MessageBody` as `reasoningDefaultOpen`. |
|
|
615
1594
|
| `chatTitle` | `chat-title` | `undefined \| string` | Optional header title shown on the left of the header. |
|
|
616
1595
|
| `models` | — | `undefined \| { id: string; name: string; provider?: undefined \| string; description?: undefined \| string; group?: undefined \| string }[]` | Optional model list. When set (>1 model) a ModelSwitcher is shown in the header and a `kai-model-change` event fires on selection. |
|
|
617
1596
|
| `currentModel` | `current-model` | `undefined \| string` | The currently selected model id (pairs with `models`). |
|
|
@@ -625,6 +1604,8 @@ _No events._
|
|
|
625
1604
|
| `composer` | `composer` | `undefined \| false \| true` | REPLACE: full custom composer in place of the built-in prompt input. The projected content wires its own submit (the data-flow boundary). |
|
|
626
1605
|
| `composerActions` | `composer-actions` | `undefined \| false \| true` | INJECT: accessory row just above the composer (e.g. extra actions). |
|
|
627
1606
|
| `footer` | `footer` | `undefined \| false \| true` | INJECT: footer row below the composer (disclaimers, token meter, …). |
|
|
1607
|
+
| `attach` | `attach` | `undefined \| false \| true` | When `false`, hides the built-in paperclip attach button. Defaults to `true` (undeclared keeps today's behavior: attach visible), matching `DefaultPromptInput`'s own default: only an explicit `false` hides it. |
|
|
1608
|
+
| `webSearch` | `web-search` | `undefined \| false \| true` | Show a web-search (Globe) button in the input toolbar; calls `onWebSearch`. |
|
|
628
1609
|
| `voice` | `voice` | `undefined \| false \| true` | Show a Voice (Mic) button in the input toolbar; fires a `voice` event. |
|
|
629
1610
|
| `triggers` | — | `undefined \| { char: string; kind: string; items?: undefined \| { id: string; label: string; icon?: undefined \| string; description?: undefined \| string; group?: undefined \| string; kind?: undefined \| string; promptText?: undefined \| string; data?: undefined \| Record<string, unknown> }[] }[]` | Rich entity triggers. Each `{ char, kind, items }` opens a caret-anchored menu that inserts an atomic pill (`/` skills, `@` agents/plugins). Set as a JS property; forwarded to the input. |
|
|
630
1611
|
| `kindIcons` | — | `undefined \| Record<string, string>` | Default icon per entity kind (kind → image src) for pills/menu items. |
|
|
@@ -642,11 +1623,11 @@ _No events._
|
|
|
642
1623
|
| `kai-attachments-rejected` | `CustomEvent<{ rejected: { filename: string; mediaType: string; reason: "filtered" \| "unsupported" }[] }>` | One or more picked files were refused because `accept` excluded them. The element renders NO message of its own: it reports the facts (name, media type, whether the kit could have sent it) and what the user should see is the application's call. Only ever fires when `accept` is set. |
|
|
643
1624
|
| `kai-message-action` | `CustomEvent<{ messageId: string; action: string; state?: undefined \| "on" \| "off" }>` | An action button on a message was clicked. `action` is the built-in name or custom id. `state` is present only for the toggleable feedback votes: `'on'` when a like/dislike is set, `'off'` when re-tapped to clear. |
|
|
644
1625
|
| `kai-model-change` | `CustomEvent<{ modelId: string }>` | The header model switcher changed. |
|
|
645
|
-
| `kai-search` | `CustomEvent<Record<string, never>>` | The Search button was clicked. |
|
|
646
1626
|
| `kai-submit` | `CustomEvent<{ value: string; attachments: { id: string; type: "file" \| "source-document"; filename?: undefined \| string; mediaType?: undefined \| string; url?: undefined \| string; title?: undefined \| string }[] }>` | User submitted a message. |
|
|
647
1627
|
| `kai-suggestion-click` | `CustomEvent<{ value: string }>` | A suggestion chip was clicked (only in `suggestion-mode="fill"`). |
|
|
648
1628
|
| `kai-value-change` | `CustomEvent<{ value: string }>` | Fired on every input change. |
|
|
649
1629
|
| `kai-voice` | `CustomEvent<Record<string, never>>` | The Mic / voice button was clicked. |
|
|
1630
|
+
| `kai-web-search` | `CustomEvent<Record<string, never>>` | The web-search (Globe) toolbar button was clicked. |
|
|
650
1631
|
|
|
651
1632
|
**Methods** (call on the element instance: `document.querySelector('kai-chat').focus(…)`):
|
|
652
1633
|
|
|
@@ -682,6 +1663,64 @@ _No events._
|
|
|
682
1663
|
|
|
683
1664
|
---
|
|
684
1665
|
|
|
1666
|
+
### `kai-checkbox` / `Checkbox`
|
|
1667
|
+
|
|
1668
|
+
**Properties** (every element also accepts `theme="light|dark|auto"`; only scalar props work as HTML attributes):
|
|
1669
|
+
|
|
1670
|
+
| Property | Attribute | Type | Description |
|
|
1671
|
+
|---|---|---|---|
|
|
1672
|
+
| `theme` | `theme` | `"light" \| "dark" \| "auto"` | Color mode (`auto` follows prefers-color-scheme). |
|
|
1673
|
+
| `checked` | `checked` | `undefined \| false \| true` | Controlled checked state. Settable and reflected to the `checked` attribute. `el.checked = true` (or `<kai-checkbox checked>`) drives it; ticking the box updates it and fires `kai-change`. Read `el.checked` for live state. |
|
|
1674
|
+
| `defaultChecked` | `default-checked` | `undefined \| false \| true` | Initial checked state on mount (uncontrolled seed). Bare attribute (`<kai-checkbox default-checked>`) turns it on. |
|
|
1675
|
+
| `indeterminate` | `indeterminate` | `undefined \| false \| true` | The mixed state, for a parent box whose children are partly ticked. Visual plus an accessibility hint: the box still reports `checked === false`. |
|
|
1676
|
+
| `disabled` | `disabled` | `undefined \| false \| true` | Disable interaction. |
|
|
1677
|
+
| `required` | `required` | `undefined \| false \| true` | Set the native `required` attribute, and nothing more. Whether an unticked box is an error is the application's rule, not the kit's. |
|
|
1678
|
+
| `label` | `label` | `undefined \| string` | Accessible label. The visible text beside the box is the consumer's to render. |
|
|
1679
|
+
| `name` | `name` | `undefined \| string` | Form-control name (paired with `value`). |
|
|
1680
|
+
| `value` | `value` | `undefined \| string` | Submitted value when checked (paired with `name`). Defaults to `'on'`. |
|
|
1681
|
+
|
|
1682
|
+
**Events** (non-bubbling `CustomEvent`s — listen directly on the element):
|
|
1683
|
+
|
|
1684
|
+
| Event | `detail` type | Description |
|
|
1685
|
+
|---|---|---|
|
|
1686
|
+
| `kai-change` | `CustomEvent<{ checked: false \| true }>` | The box was ticked or unticked. |
|
|
1687
|
+
|
|
1688
|
+
**Methods** (call on the element instance: `document.querySelector('kai-checkbox').toggle()`):
|
|
1689
|
+
|
|
1690
|
+
| Method | Signature | Description |
|
|
1691
|
+
|---|---|---|
|
|
1692
|
+
| `toggle` | `(): void` | Flip the box and fire `kai-change` (no-op while disabled). |
|
|
1693
|
+
| `focus` | `(options?: FocusOptions): void` | Focus the inner input (the host element can't reach it). |
|
|
1694
|
+
|
|
1695
|
+
---
|
|
1696
|
+
|
|
1697
|
+
### `kai-checkbox-group` / `CheckboxGroup`
|
|
1698
|
+
|
|
1699
|
+
**Properties** (every element also accepts `theme="light|dark|auto"`; only scalar props work as HTML attributes):
|
|
1700
|
+
|
|
1701
|
+
| Property | Attribute | Type | Description |
|
|
1702
|
+
|---|---|---|---|
|
|
1703
|
+
| `theme` | `theme` | `"light" \| "dark" \| "auto"` | Color mode (`auto` follows prefers-color-scheme). |
|
|
1704
|
+
| `options` | — | `{ value: string; label: string; description?: undefined \| string; disabled?: undefined \| false \| true }[]` | The choices, top to bottom. Set as a JS PROPERTY (array), never an attribute. Rendered in full: the kit never truncates, re-orders or de-duplicates them. |
|
|
1705
|
+
| `value` | `value` | `undefined \| string` | The FIRST selected value. Settable and reflected to the `value` attribute, so `:host([value])` and `el.value` see live state, and a seed can be written in markup. Writing it makes that the whole selection; to read or drive the rest, use `el.values`. |
|
|
1706
|
+
| `name` | `name` | `undefined \| string` | The shared form-control name every box carries, so `FormData.getAll(name)` reads the whole selection back under one key. NO DEFAULT, unlike `<kai-radio-group>`. A radio set needs a shared `name` for the browser to make it exclusive and arrow-navigable, so one is generated when none is given; checkboxes are independent controls and behave correctly with no name at all. Generating one here would submit the selection under a random key, which is worse than submitting nothing. The element is NOT form-associated (no `ElementInternals`, no `setFormValue()`), the same known gap `<kai-input>` records: the boxes live in a shadow root, so a surrounding `<form>` collects nothing from them whether or not `name` is set. Read `el.values`. The name still lands on every inner input, so it is right the day form association arrives. |
|
|
1707
|
+
| `disabled` | `disabled` | `undefined \| false \| true` | Disable every row. Individual rows carry their own `disabled`. |
|
|
1708
|
+
| `label` | `label` | `undefined \| string` | Accessible name for the group. |
|
|
1709
|
+
|
|
1710
|
+
**Events** (non-bubbling `CustomEvent`s — listen directly on the element):
|
|
1711
|
+
|
|
1712
|
+
| Event | `detail` type | Description |
|
|
1713
|
+
|---|---|---|
|
|
1714
|
+
| `kai-change` | `CustomEvent<{ value: string; values: string[] }>` | A row was ticked or unticked. `values` is the whole selection after the change, which is what a multi-select control needs; `value` is the first of them (empty when nothing is selected). Both are always present, so neither shape silently loses the other. This is `<kai-select>`'s detail, deliberately. |
|
|
1715
|
+
|
|
1716
|
+
**Methods** (call on the element instance: `document.querySelector('kai-checkbox-group').focus(…)`):
|
|
1717
|
+
|
|
1718
|
+
| Method | Signature | Description |
|
|
1719
|
+
|---|---|---|
|
|
1720
|
+
| `focus` | `(options?: FocusOptions): void` | Focus the group's first box. Not "the first ticked one", which is `<kai-radio-group>`'s rule: a radio group is ONE tab stop that lands on the selection, while every checkbox here is its own tab stop, so the entry point is simply the top of the list. |
|
|
1721
|
+
|
|
1722
|
+
---
|
|
1723
|
+
|
|
685
1724
|
### `kai-checkpoint` / `Checkpoint`
|
|
686
1725
|
|
|
687
1726
|
**Properties** (every element also accepts `theme="light|dark|auto"`; only scalar props work as HTML attributes):
|
|
@@ -795,10 +1834,17 @@ _No events._
|
|
|
795
1834
|
| `language` | `language` | `undefined \| string` | Language grammar (e.g. `js`, `python`). Defaults to `tsx`. |
|
|
796
1835
|
| `codeTheme` | `code-theme` | `undefined \| string` | Shiki theme name. |
|
|
797
1836
|
| `codeHighlight` | `code-highlight` | `undefined \| false \| true` | Disable syntax highlighting (renders plain text, no Shiki). |
|
|
1837
|
+
| `copy` | `copy` | `undefined \| false \| true` | Show the copy button. **Defaults to ON**, because this element is documented as shipping one. Opt out with `copy="false"` or `el.copy = false`. |
|
|
798
1838
|
| `proseSize` | `prose-size` | `undefined \| "xs" \| "sm" \| "base" \| "lg"` | Code text sizing. |
|
|
799
1839
|
|
|
800
1840
|
_No events._
|
|
801
1841
|
|
|
1842
|
+
**Styleable parts** (restyle from outside via `kai-code-block::part(name)`):
|
|
1843
|
+
|
|
1844
|
+
| Part | Description |
|
|
1845
|
+
|---|---|
|
|
1846
|
+
| `::part(copy)` | The copy-to-clipboard button in the header row. Hide it with `copy="false"` rather than CSS. — `kai-code-block::part(copy) { color: var(--color-primary) }` |
|
|
1847
|
+
|
|
802
1848
|
---
|
|
803
1849
|
|
|
804
1850
|
### `kai-command` / `Command`
|
|
@@ -893,7 +1939,7 @@ _No events._
|
|
|
893
1939
|
| `kai-entity-add` | `CustomEvent<{ entity: { kind: string; id: string; label: string; icon?: undefined \| string; promptText?: undefined \| string; data?: undefined \| Record<string, unknown> } }>` | An entity pill was inserted into the composer. |
|
|
894
1940
|
| `kai-entity-remove` | `CustomEvent<{ entity: { kind: string; id: string; label: string; icon?: undefined \| string; promptText?: undefined \| string; data?: undefined \| Record<string, unknown> } }>` | An entity pill was deleted from the composer. |
|
|
895
1941
|
| `kai-focus` | `CustomEvent<{ originalEvent: FocusEvent }>` | The composer gained focus. `focus`/`blur` are NOT composed natively, so they don't escape the shadow root; these re-expose them on the host. (For `keydown`/`paste`/`focusin`/`focusout`, listen NATIVELY on `<kai-composer>`: they're composed and already cross the shadow boundary.) |
|
|
896
|
-
| `kai-submit` | `CustomEvent<{ doc: ({ type: "text"; text: string } \| { type: "entity"; entity: { kind: string; id: string; label: string; icon?: undefined \| string; promptText?: undefined \| string; data?: undefined \| Record<string, unknown> } })[]; text: string; entities: { kind: string; id: string; label: string; icon?: undefined \| string; promptText?: undefined \| string; data?: undefined \| Record<string, unknown> }[] }>` | The user submitted the composer (Enter or programmatic submit). |
|
|
1942
|
+
| `kai-submit` | `CustomEvent<{ doc: ({ type: "text"; text: string } \| { type: "entity"; entity: { kind: string; id: string; label: string; icon?: undefined \| string; promptText?: undefined \| string; data?: undefined \| Record<string, unknown> } })[]; text: string; entities: { kind: string; id: string; label: string; icon?: undefined \| string; promptText?: undefined \| string; data?: undefined \| Record<string, unknown> }[] }>` | The user submitted the composer (Enter or programmatic submit). Note the detail carries no `attachments`; `<kai-composer>` is the bare editing surface: no send button, toolbar, or attachments. For a drop-in composer row with all three, reach for `<kai-prompt-input>`, which is built on this. |
|
|
897
1943
|
| `kai-trigger` | `CustomEvent<{ char: string; query: string; rect: DOMRect }>` | A trigger character was detected at the caret (e.g. `/` or `@`). |
|
|
898
1944
|
| `kai-trigger-close` | `CustomEvent<Record<string, never>>` | The active trigger was dismissed (Escape, space, or outside click). |
|
|
899
1945
|
| `kai-value-change` | `CustomEvent<{ doc: ({ type: "text"; text: string } \| { type: "entity"; entity: { kind: string; id: string; label: string; icon?: undefined \| string; promptText?: undefined \| string; data?: undefined \| Record<string, unknown> } })[]; text: string; entities: { kind: string; id: string; label: string; icon?: undefined \| string; promptText?: undefined \| string; data?: undefined \| Record<string, unknown> }[] }>` | The content changed (fires on every input event). |
|
|
@@ -962,6 +2008,45 @@ _No events._
|
|
|
962
2008
|
|
|
963
2009
|
---
|
|
964
2010
|
|
|
2011
|
+
### `kai-conversation-item` / `ConversationItem`
|
|
2012
|
+
|
|
2013
|
+
**Properties** (every element also accepts `theme="light|dark|auto"`; only scalar props work as HTML attributes):
|
|
2014
|
+
|
|
2015
|
+
| Property | Attribute | Type | Description |
|
|
2016
|
+
|---|---|---|---|
|
|
2017
|
+
| `theme` | `theme` | `"light" \| "dark" \| "auto"` | Color mode (`auto` follows prefers-color-scheme). |
|
|
2018
|
+
| `conversationId` | `conversation-id` | `undefined \| string` | The row's identity: the `conversation-id` attribute (host `id` is the fallback). Inside `<kai-conversations>` it is handed to the container's selection contract (`kai-conversation-select`); standalone it is the `id` in this element's own `kai-select` detail. |
|
|
2019
|
+
| `active` | `active` | `undefined \| false \| true` | Selected state. Reflected as `aria-current` on the row body and a `data-active` styling hook on the row; inside a container the container drives it from its `activeId`, standalone you set it yourself. |
|
|
2020
|
+
| `compact` | `compact` | `undefined \| false \| true` | Dense single-line row padding. |
|
|
2021
|
+
|
|
2022
|
+
**Events** (non-bubbling `CustomEvent`s — listen directly on the element):
|
|
2023
|
+
|
|
2024
|
+
| Event | `detail` type | Description |
|
|
2025
|
+
|---|---|---|
|
|
2026
|
+
| `kai-select` | `CustomEvent<{ id: string }>` | STANDALONE activation only: the row was activated (click, Enter or Space on its body) while the item is NOT a direct child of `<kai-conversations>`. `id` is the row's identity: the `conversation-id` attribute, else the host `id`. Inside a container this never fires: activation surfaces once, as `kai-conversation-select` on the container. |
|
|
2027
|
+
|
|
2028
|
+
**Slots** (project your own markup via `slot="name"` on a light-DOM child):
|
|
2029
|
+
|
|
2030
|
+
| Slot | Mode | Description |
|
|
2031
|
+
|---|---|---|
|
|
2032
|
+
| _(default)_ | inject | The row title. `leading`, `meta` and `menu` are the named regions around it. |
|
|
2033
|
+
| `leading` | inject | Leading region before the title (an icon or avatar). |
|
|
2034
|
+
| `meta` | inject | Meta region under the title (a timestamp or status line). |
|
|
2035
|
+
| `menu` | inject | Your own row menu (a popover trigger). Never selects the row. |
|
|
2036
|
+
|
|
2037
|
+
**Styleable parts** (restyle from outside via `kai-conversation-item::part(name)`):
|
|
2038
|
+
|
|
2039
|
+
| Part | Description |
|
|
2040
|
+
|---|---|
|
|
2041
|
+
| `::part(body)` | The activation surface inside the row (button role; carries aria-current and the roving tabindex). The focus ring paints here. — `kai-conversation-item::part(body) { outline-offset: 2px }` |
|
|
2042
|
+
| `::part(row)` | The whole row surface. Carries `data-active` while selected. — `kai-conversation-item::part(row) { border-radius: 0.5rem }` |
|
|
2043
|
+
| `::part(title)` | The title line (the default slot renders inside it). — `kai-conversation-item::part(title) { font-weight: 600 }` |
|
|
2044
|
+
| `::part(leading)` | Leading region before the title (an icon or avatar). |
|
|
2045
|
+
| `::part(meta)` | Meta region under the title (a timestamp or status line). |
|
|
2046
|
+
| `::part(menu)` | Your own row menu (a popover trigger). Never selects the row. |
|
|
2047
|
+
|
|
2048
|
+
---
|
|
2049
|
+
|
|
965
2050
|
### `kai-conversations` / `Conversations`
|
|
966
2051
|
|
|
967
2052
|
**Properties** (every element also accepts `theme="light|dark|auto"`; only scalar props work as HTML attributes):
|
|
@@ -970,17 +2055,18 @@ _No events._
|
|
|
970
2055
|
|---|---|---|---|
|
|
971
2056
|
| `theme` | `theme` | `"light" \| "dark" \| "auto"` | Color mode (`auto` follows prefers-color-scheme). |
|
|
972
2057
|
| `groups` | — | `undefined \| { id: string; userId?: undefined \| string; teamId?: undefined \| string; name: string; sortOrder: number; createdAt: string }[]` | The list's section headers (`{ id, name, sortOrder, createdAt }`), rendered in array order. A group carries no conversations of its own; it is matched against `conversations` by id, so the two props are complementary rather than alternatives. Omit for an ungrouped list. Set as a JS property. |
|
|
973
|
-
| `conversations` | — | `undefined \| { id: string; title: string; groupId?: undefined \| string; scope
|
|
2058
|
+
| `conversations` | — | `undefined \| { id: string; title: string; groupId?: undefined \| string; scope?: undefined \| { type: "document" \| "collection"; documentId?: undefined \| string; filters?: undefined \| { tags?: undefined \| string[]; authors?: undefined \| string[]; contentType?: undefined \| "transcript" \| "markdown"; dateRange?: undefined \| { from: string; to: string } } }; messageCount: number; lastMessageAt?: undefined \| string; updatedAt: string; trailing?: undefined \| string }[]` | Every conversation the list renders, flat. Each one is filed under the group whose `id` equals its `groupId`; one with no `groupId`, or with a `groupId` matching no entry in `groups`, falls into a trailing "Ungrouped" section, so nothing you pass in is ever dropped. There is no recency bucketing. Set as a JS property. Omit to supply them as `<kai-conversation>` light-DOM children instead, or for the empty state. A search query that matches nothing shows a visible "No conversations match your search" state, distinct from the zero-conversations empty state. Slotted `<kai-conversation-item>` children switch the list into item mode instead: your own rows win and this array is not rendered. |
|
|
974
2059
|
| `activeId` | `active-id` | `undefined \| string` | The id of the currently-open conversation, highlighted in the list. |
|
|
975
2060
|
| `collapsed` | `collapsed` | `undefined \| false \| true` | Controlled collapsed state. Set as a JS property (`el.collapsed = true`) to drive the rail from your app, updating it in response to `kai-collapse-toggle`. Omit for uncontrolled (the element manages it). Collapsed shrinks the rail to a floating reopen button. |
|
|
976
2061
|
| `defaultCollapsed` | `default-collapsed` | `undefined \| false \| true` | Initial collapsed state when uncontrolled (default false). Use the `default-collapsed` attribute to start collapsed in plain HTML. |
|
|
2062
|
+
| `compact` | `compact` | `undefined \| false \| true` | Dense single-line rows (a leading dot + title, no message count). |
|
|
977
2063
|
|
|
978
2064
|
**Events** (non-bubbling `CustomEvent`s — listen directly on the element):
|
|
979
2065
|
|
|
980
2066
|
| Event | `detail` type | Description |
|
|
981
2067
|
|---|---|---|
|
|
982
2068
|
| `kai-collapse-toggle` | `CustomEvent<{ collapsed: false \| true }>` | The rail was collapsed or expanded (via the toggle, the reopen button, or a `collapse()`/`expand()`/`toggle()` call). |
|
|
983
|
-
| `kai-conversation-select` | `CustomEvent<{ id: string }>` | A conversation was selected. |
|
|
2069
|
+
| `kai-conversation-select` | `CustomEvent<{ id: string }>` | A conversation was selected. The selection event in BOTH modes: a batteries data row, or an activated `<kai-conversation-item>` child (click, Enter or Space). |
|
|
984
2070
|
| `kai-new-chat` | `CustomEvent<Record<string, never>>` | The "New chat" button was clicked. |
|
|
985
2071
|
| `kai-search` | `CustomEvent<{ query: string }>` | The built-in search box query changed (typing, or a programmatic `clear()` which fires it with `''`). Lets a consumer mirror or server-side the filter. |
|
|
986
2072
|
| `kai-toggle-sidebar` | `CustomEvent<Record<string, never>>` | The sidebar toggle was clicked. |
|
|
@@ -1000,6 +2086,7 @@ _No events._
|
|
|
1000
2086
|
|
|
1001
2087
|
| Slot | Mode | Description |
|
|
1002
2088
|
|---|---|---|
|
|
2089
|
+
| _(default)_ | inject | Your own `<kai-conversation-item>` rows (item mode: the consumer-owned loop). Data rows do not render while any are present. |
|
|
1003
2090
|
| `header` | replace | Full custom title bar; replaces the built-in toggle / "Chats" / New-chat row. |
|
|
1004
2091
|
| `empty` | replace | Custom zero-state shown when there are no conversations; replaces the built-in "No conversations yet". |
|
|
1005
2092
|
| `footer` | inject | A row below the list: account, settings, or usage. |
|
|
@@ -1008,13 +2095,14 @@ _No events._
|
|
|
1008
2095
|
|
|
1009
2096
|
| Child element | Attributes | Text content | Notes |
|
|
1010
2097
|
|---|---|---|---|
|
|
1011
|
-
| `<kai-conversation>` | `group-id`, `id` | yes | Parse a single light-DOM `<kai-conversation>` element into a `ConversationSummary`. Attribute mapping: - `id` → ConversationSummary.id - `group-id` → ConversationSummary.groupId (optional) - textContent → ConversationSummary.title
|
|
2098
|
+
| `<kai-conversation>` | `group-id`, `id` | yes | Parse a single light-DOM `<kai-conversation>` element into a `ConversationSummary`. Attribute mapping: - `id` → ConversationSummary.id - `group-id` → ConversationSummary.groupId (optional) - textContent → ConversationSummary.title Fields not expressible as HTML attributes are NOT fabricated: the optional `scope` and `lastMessageAt` stay absent, and the required `messageCount`/`updatedAt` get honest defaults — zero messages, and an empty `updatedAt` from which no trailing relative time is derived (the epoch it used to fabricate rendered a bogus "many days ago" on every declarative row). |
|
|
1012
2099
|
|
|
1013
2100
|
**Styleable parts** (restyle from outside via `kai-conversations::part(name)`):
|
|
1014
2101
|
|
|
1015
2102
|
| Part | Description |
|
|
1016
2103
|
|---|---|
|
|
1017
2104
|
| `::part(trailing)` | The right-aligned trailing text on each conversation row (a count, status, or relative time). Set it per item via the `trailing` field; otherwise a short auto relative time is derived from `updatedAt`. Recolor or resize it from outside. — `kai-conversations::part(trailing) { color: var(--color-primary); font-variant-numeric: tabular-nums }` |
|
|
2105
|
+
| `::part(items)` | The item-mode listbox region wrapping your slotted `<kai-conversation-item>` children. — `kai-conversations::part(items) { gap: 2px }` |
|
|
1018
2106
|
|
|
1019
2107
|
---
|
|
1020
2108
|
|
|
@@ -1027,6 +2115,7 @@ _No events._
|
|
|
1027
2115
|
| `theme` | `theme` | `"light" \| "dark" \| "auto"` | Color mode (`auto` follows prefers-color-scheme). |
|
|
1028
2116
|
| `open` | `open` | `undefined \| false \| true` | Drive/observe open state (Shoelace-style: settable + reflected to the `open` attribute; the element still self-manages on Escape/backdrop). Set `el.open = true`, or `<kai-dialog open>`; listen for `kai-open-change`. |
|
|
1029
2117
|
| `defaultOpen` | `default-open` | `undefined \| false \| true` | Initial open state on mount (uncontrolled seed). |
|
|
2118
|
+
| `label` | `label` | `undefined \| string` | Accessible name for the modal, used when no `header` slot is projected: `<kai-dialog label="Delete workspace">`. A projected `header` WINS over this (it becomes `aria-labelledby`), because ARIA resolves `aria-labelledby` ahead of `aria-label` and the visible heading is the name both a sighted and a screen-reader user can be talked through. Defaults to `Dialog` so a modal is never nameless. |
|
|
1030
2119
|
|
|
1031
2120
|
**Events** (non-bubbling `CustomEvent`s — listen directly on the element):
|
|
1032
2121
|
|
|
@@ -1063,6 +2152,97 @@ _No events._
|
|
|
1063
2152
|
|
|
1064
2153
|
---
|
|
1065
2154
|
|
|
2155
|
+
### `kai-dock` / `Dock`
|
|
2156
|
+
|
|
2157
|
+
**Properties** (every element also accepts `theme="light|dark|auto"`; only scalar props work as HTML attributes):
|
|
2158
|
+
|
|
2159
|
+
| Property | Attribute | Type | Description |
|
|
2160
|
+
|---|---|---|---|
|
|
2161
|
+
| `theme` | `theme` | `"light" \| "dark" \| "auto"` | Color mode (`auto` follows prefers-color-scheme). |
|
|
2162
|
+
| `open` | `open` | `undefined \| false \| true` | Drive/observe open state (Shoelace-style: settable + reflected to the `open` attribute; the element still self-manages on the launcher and Escape). Set `el.open = true`, or `<kai-dock open>`; listen for `kai-open-change`. |
|
|
2163
|
+
| `defaultOpen` | `default-open` | `undefined \| false \| true` | Initial open state on mount (uncontrolled seed). |
|
|
2164
|
+
| `position` | `position` | `undefined \| "bottom-end" \| "bottom-start" \| "top-end" \| "top-start"` | Which corner the dock sits in. Logical, so `-end` follows the writing direction and an RTL page docks on the left. Attribute: `position`. |
|
|
2165
|
+
| `label` | `label` | `undefined \| string` | The widget's NAME. Derives the panel's accessible name and both launcher names (`Open ${label}` / `Close ${label}`). Defaults to `Chat`. |
|
|
2166
|
+
| `openLabel` | `open-label` | `undefined \| string` | i18n override for the launcher's name while closed (default `Open ${label}`). |
|
|
2167
|
+
| `closeLabel` | `close-label` | `undefined \| string` | i18n override for the launcher's name while open (default `Close ${label}`). |
|
|
2168
|
+
| `unread` | `unread` | `undefined \| false \| true` | Show the unread dot. YOURS: it renders only while closed, and the dock never writes it back. Clear it in your `kai-open-change` handler. |
|
|
2169
|
+
| `disabled` | `disabled` | `undefined \| false \| true` | Disable the launcher; `show()` and `toggle()` are gated on it. |
|
|
2170
|
+
| `hideClose` | `hide-close` | `undefined \| false \| true` | Suppress the dock's own built-in mobile close X. Set this when your slotted panel content supplies its own close affordance (e.g. a `<kai-chat slot="header-end">` close button), otherwise the two stack. TRADEOFF: the mobile panel reserves a padding band above its content so the built-in X never paints over slotted content; that band stays reserved unless you set this true, so only set it once your own control is actually in place. Attribute: `hide-close`. |
|
|
2171
|
+
| `focusOnOpen` | `focus-on-open` | `undefined \| "content" \| "panel" \| "none"` | Where focus lands on open: `content` (default, the first element you slotted), `panel`, or `none`. Attribute: `focus-on-open`. |
|
|
2172
|
+
|
|
2173
|
+
**Events** (non-bubbling `CustomEvent`s — listen directly on the element):
|
|
2174
|
+
|
|
2175
|
+
| Event | `detail` type | Description |
|
|
2176
|
+
|---|---|---|
|
|
2177
|
+
| `kai-open-change` | `CustomEvent<{ open: false \| true }>` | The dock opened or closed (the launcher, Escape, a driven `open`, or a method). |
|
|
2178
|
+
|
|
2179
|
+
**Methods** (call on the element instance: `document.querySelector('kai-dock').show()`):
|
|
2180
|
+
|
|
2181
|
+
| Method | Signature | Description |
|
|
2182
|
+
|---|---|---|
|
|
2183
|
+
| `show` | `(): void` | Open it programmatically (no-op while disabled). |
|
|
2184
|
+
| `hide` | `(): void` | Close it programmatically. |
|
|
2185
|
+
| `toggle` | `(): void` | Flip the open state (closes while disabled). |
|
|
2186
|
+
| `focus` | `(options?: FocusOptions): void` | Move focus to the panel while open, or to the launcher while closed. |
|
|
2187
|
+
|
|
2188
|
+
**Slots** (project your own markup via `slot="name"` on a light-DOM child):
|
|
2189
|
+
|
|
2190
|
+
| Slot | Mode | Description |
|
|
2191
|
+
|---|---|---|
|
|
2192
|
+
| _(default)_ | inject | The panel body, the same region as `slot="panel"`. |
|
|
2193
|
+
| `panel` | replace | The panel body. ANY element: a `<kai-chat>`, a form, your own component. The dock never reads or types it, and the default slot is the same region. |
|
|
2194
|
+
| `launcher` | inject | Content inside the built-in button while CLOSED; defaults to a chat glyph. Text works as well as an icon: the button keeps its height and grows sideways into a pill, so a label like "Support" is not clipped. The BUTTON is never slotted away, because it owns aria-expanded, aria-controls, the toggle wiring and the focus return. |
|
|
2195
|
+
| `launcher-open` | inject | Content inside the button while OPEN; defaults to a ✕. Fill only `launcher` and that glyph stays while open rather than morphing into a built-in that clashes with it. |
|
|
2196
|
+
|
|
2197
|
+
**Styleable parts** (restyle from outside via `kai-dock::part(name)`):
|
|
2198
|
+
|
|
2199
|
+
| Part | Description |
|
|
2200
|
+
|---|---|
|
|
2201
|
+
| `::part(launcher)` | The launcher button pinned to the corner: a disc by default, a pill once you slot a text label. Restyle its surface or shadow; --kai-dock-launcher-size sets its height and its minimum width. — `kai-dock::part(launcher) { background: var(--color-info) }` |
|
|
2202
|
+
| `::part(badge)` | The unread dot on the launcher, rendered only while closed and only when `unread` is set. Restyle its color or size. — `kai-dock::part(badge) { background: var(--color-success) }` |
|
|
2203
|
+
| `::part(panel)` | The panel body. ANY element: a `<kai-chat>`, a form, your own component. The dock never reads or types it, and the default slot is the same region. |
|
|
2204
|
+
|
|
2205
|
+
---
|
|
2206
|
+
|
|
2207
|
+
### `kai-dropdown` / `Dropdown`
|
|
2208
|
+
|
|
2209
|
+
**Properties** (every element also accepts `theme="light|dark|auto"`; only scalar props work as HTML attributes):
|
|
2210
|
+
|
|
2211
|
+
| Property | Attribute | Type | Description |
|
|
2212
|
+
|---|---|---|---|
|
|
2213
|
+
| `theme` | `theme` | `"light" \| "dark" \| "auto"` | Color mode (`auto` follows prefers-color-scheme). |
|
|
2214
|
+
| `triggerIcon` | `trigger-icon` | `undefined \| string` | Built-in trigger: leading icon (a named icon like `"plus"`, an image URL/data-URI, or text). A slotted `slot="trigger"` overrides it. |
|
|
2215
|
+
| `triggerLabel` | `trigger-label` | `undefined \| string` | Built-in trigger: a text label. This is the trigger's VISIBLE text, so it is also its accessible name, and `label` does not override it: an accessible name that does not contain the visible text is unreachable by speech input (WCAG 2.5.3, Label in Name). Same rule `kai-menu` follows. |
|
|
2216
|
+
| `triggerIconTrailing` | `trigger-icon-trailing` | `undefined \| string` | Built-in trigger: a trailing icon (e.g. `"chevron-down"` for a select look). |
|
|
2217
|
+
| `label` | `label` | `undefined \| string` | Accessible name for a trigger with no visible label. Ignored when `triggerLabel` is set, which is already the visible name. It DOES name a slotted `slot="trigger"`, which is VISUAL content with the name supplied separately: the same two-slot distinction `kai-menu` documents. |
|
|
2218
|
+
| `full` | `full` | `undefined \| false \| true` | Stretch the trigger to the full width of its container (a block row). Attribute: `full`. |
|
|
2219
|
+
| `open` | `open` | `undefined \| false \| true` | Drive/observe open state (Shoelace-style: settable + reflected to the `open` attribute, the menu still self-manages on click/keyboard). Set `el.open = true`, or `<kai-dropdown open>`; listen for `kai-open-change`. |
|
|
2220
|
+
| `defaultOpen` | `default-open` | `undefined \| false \| true` | Initial open state on mount (uncontrolled seed). |
|
|
2221
|
+
| `disabled` | `disabled` | `undefined \| false \| true` | Disable the trigger: click/keyboard and `show()` no longer open the menu. |
|
|
2222
|
+
|
|
2223
|
+
**Events** (non-bubbling `CustomEvent`s — listen directly on the element):
|
|
2224
|
+
|
|
2225
|
+
| Event | `detail` type | Description |
|
|
2226
|
+
|---|---|---|
|
|
2227
|
+
| `kai-open-change` | `CustomEvent<{ open: false \| true }>` | The menu opened or closed (click, keyboard, Escape, outside-click, or a method). |
|
|
2228
|
+
|
|
2229
|
+
**Methods** (call on the element instance: `document.querySelector('kai-dropdown').show()`):
|
|
2230
|
+
|
|
2231
|
+
| Method | Signature | Description |
|
|
2232
|
+
|---|---|---|
|
|
2233
|
+
| `show` | `(): void` | Open it programmatically (no-op while disabled). |
|
|
2234
|
+
| `hide` | `(): void` | Close it programmatically. |
|
|
2235
|
+
| `toggle` | `(): void` | Flip the open state (closes while disabled). |
|
|
2236
|
+
|
|
2237
|
+
**Slots** (project your own markup via `slot="name"` on a light-DOM child):
|
|
2238
|
+
|
|
2239
|
+
| Slot | Mode | Description |
|
|
2240
|
+
|---|---|---|
|
|
2241
|
+
| _(default)_ | inject | The menu body: your own rows. Give each `role="menuitem"`. The control that opens it is the `trigger` slot. |
|
|
2242
|
+
| `trigger` | replace | Visual content of the trigger button (an icon, text, an `<svg>`). Replaces the built-in trigger* content; name it with `label`. |
|
|
2243
|
+
|
|
2244
|
+
---
|
|
2245
|
+
|
|
1066
2246
|
### `kai-editable-label` / `EditableLabel`
|
|
1067
2247
|
|
|
1068
2248
|
**Properties** (every element also accepts `theme="light|dark|auto"`; only scalar props work as HTML attributes):
|
|
@@ -1221,7 +2401,7 @@ _No events._
|
|
|
1221
2401
|
| Property | Attribute | Type | Description |
|
|
1222
2402
|
|---|---|---|---|
|
|
1223
2403
|
| `theme` | `theme` | `"light" \| "dark" \| "auto"` | Color mode (`auto` follows prefers-color-scheme). |
|
|
1224
|
-
| `data` | — | `undefined \| { type: "object"; title?: undefined \| string; description?: undefined \| string; required?: undefined \| string[]; properties: Record<string, { type: "string" \| "number" \| "integer" \| "boolean" \| "array" \| "object"; title?: undefined \| string; description?: undefined \| string; default?: unknown; enum?: undefined \| unknown[]; format?: undefined \| "email" \| "uri" \| "url" \| "date" \| "date-time" \| "time"; minimum?: undefined \| number; maximum?: undefined \| number; minLength?: undefined \| number; maxLength?: undefined \| number; pattern?: undefined \| string; minItems?: undefined \| number; maxItems?: undefined \| number; items?: undefined \| Record<string, unknown> \| { enum: unknown[] }; properties?: undefined \| Record<string, Record<string, unknown>>; required?: undefined \| string[]; readOnly?: undefined \| false \| true; "x-kai-widget"?: undefined \| "textarea" \| "slider" \| "rating" \| "radio" \| "select" \| "checkbox" \| "password" \| "switch"; "x-kai-placeholder"?: undefined \| string; "x-kai-step"?: undefined \| number }>; "x-kai-order"?: undefined \| string[]; "x-kai-inlineMax"?: undefined \| number; "x-kai-submitLabel"?: undefined \| string; "x-kai-dismissible"?: undefined \| false \| true; "x-kai-actions"?: undefined \| { id: string; label: string; variant?: undefined \| "default" \| "ghost" \| "outline" }[] }` | The form definition: a JSON Schema (`type:'object'`) + `x-kai-*` UI hints (the CardEnvelope.data). Set as a JS PROPERTY: `el.data = { type:'object', properties:{…} }`. Import the `FormDefinition` type from `@kitn.ai/ui` for the full shape. It IS self-referential (`FormField.properties` is another `FormField` map), and the generated `element-types.d.ts` inlines every named type, so the shipped declaration bottoms out in a `Record<string, unknown>` placeholder one level down rather than carrying the recursion. That is why `FormDefinition` is a `type` alias: an interface gets no implicit index signature, so it would not be assignable to that placeholder. |
|
|
2404
|
+
| `data` | — | `undefined \| { type: "object"; title?: undefined \| string; description?: undefined \| string; required?: undefined \| string[]; properties: Record<string, { type: "string" \| "number" \| "integer" \| "boolean" \| "array" \| "object"; title?: undefined \| string; description?: undefined \| string; default?: unknown; enum?: undefined \| unknown[]; format?: undefined \| "email" \| "uri" \| "url" \| "date" \| "date-time" \| "time"; minimum?: undefined \| number; maximum?: undefined \| number; minLength?: undefined \| number; maxLength?: undefined \| number; pattern?: undefined \| string; minItems?: undefined \| number; maxItems?: undefined \| number; items?: undefined \| Record<string, unknown> \| { enum: unknown[] }; properties?: undefined \| Record<string, Record<string, unknown>>; required?: undefined \| string[]; readOnly?: undefined \| false \| true; "x-kai-widget"?: undefined \| "textarea" \| "slider" \| "rating" \| "radio" \| "select" \| "checkbox" \| "password" \| "switch"; "x-kai-placeholder"?: undefined \| string; "x-kai-step"?: undefined \| number; "x-kai-format"?: undefined \| "tel" \| "ssn" \| "credit-card" \| "custom"; "x-kai-mask"?: undefined \| string; "x-kai-mask-guide"?: undefined \| string }>; "x-kai-order"?: undefined \| string[]; "x-kai-inlineMax"?: undefined \| number; "x-kai-submitLabel"?: undefined \| string; "x-kai-dismissible"?: undefined \| false \| true; "x-kai-actions"?: undefined \| { id: string; label: string; variant?: undefined \| "default" \| "ghost" \| "outline" }[] }` | The form definition: a JSON Schema (`type:'object'`) + `x-kai-*` UI hints (the CardEnvelope.data). Set as a JS PROPERTY: `el.data = { type:'object', properties:{…} }`. Import the `FormDefinition` type from `@kitn.ai/ui` for the full shape. It IS self-referential (`FormField.properties` is another `FormField` map), and the generated `element-types.d.ts` inlines every named type, so the shipped declaration bottoms out in a `Record<string, unknown>` placeholder one level down rather than carrying the recursion. That is why `FormDefinition` is a `type` alias: an interface gets no implicit index signature, so it would not be assignable to that placeholder. |
|
|
1225
2405
|
| `cardId` | `card-id` | `undefined \| string` | Stable card id correlating every emitted CardEvent. Attribute: `card-id`. |
|
|
1226
2406
|
| `heading` | `heading` | `undefined \| string` | Heading rendered in the card chrome (= CardEnvelope.title). Attribute: `heading`. |
|
|
1227
2407
|
| `resolution` | — | `undefined \| Record<string, unknown>` | Set when the user resolved this card; renders the read-only view. Property: `el.resolution = { kind:'submit', data:{…} }`. |
|
|
@@ -1329,7 +2509,7 @@ _No events._
|
|
|
1329
2509
|
|---|---|---|---|
|
|
1330
2510
|
| `theme` | `theme` | `"light" \| "dark" \| "auto"` | Color mode (`auto` follows prefers-color-scheme). |
|
|
1331
2511
|
| `type` | `type` | `undefined \| string` | Native input type: `text` (default) · `email` · `url` · `search` · `tel` · `password` · `number`. Single-line only. |
|
|
1332
|
-
| `value` | `value` | `undefined \| string` | Controlled value
|
|
2512
|
+
| `value` | `value` | `undefined \| string` | Controlled value, and always the CANONICAL one when a mask is active: digits for `tel` / `ssn` / `credit-card`, the formatted text for `custom`. Settable and reflected to the `value` attribute. `el.value = '5551234567'` drives it (no event) and is re-fitted to the mask on the way in, so the field shows `555-123-4567`. Read `el.value` for live state; the formatted text rides along on every `kai-input` / `kai-change` detail as `formattedValue`. |
|
|
1333
2513
|
| `placeholder` | `placeholder` | `undefined \| string` | Placeholder shown when empty. |
|
|
1334
2514
|
| `label` | `label` | `undefined \| string` | Field label, linked to the input. |
|
|
1335
2515
|
| `hint` | `hint` | `undefined \| string` | Helper text below the control. |
|
|
@@ -1342,13 +2522,19 @@ _No events._
|
|
|
1342
2522
|
| `name` | `name` | `undefined \| string` | Form-control name. |
|
|
1343
2523
|
| `autocomplete` | `autocomplete` | `undefined \| string` | Autofill hint forwarded to the inner input (e.g. `email`, `current-password`). |
|
|
1344
2524
|
| `inputmode` | `inputmode` | `undefined \| string` | Virtual-keyboard hint forwarded to the inner input (e.g. `numeric`, `email`). |
|
|
2525
|
+
| `format` | `format` | `undefined \| string` | Mask pattern: `#` a digit, `@` a letter or digit, `*` an obscurable letter or digit, and every other character a positional literal (`@@@-####` → `CHG-4821`). The literal `default` is the opt-in sentinel: it resolves to the default format of `semantic` (`tel` → `###-###-####`). A bare `semantic` never starts masking on its own, so an opt-in token is what turns tier 2 on. |
|
|
2526
|
+
| `guide` | `guide` | `undefined \| string` | Placeholder guide shown at unfilled positions, aligned position for position with `format`: `mm/dd/yyyy` against `##/##/####`. Spaces are a valid guide character, so a guide of blanks and separators is how a phone field shows its shape without showing letters. Without a guide the field shows only up to the last typed character. A guide is a visual aid, never an accessible name: keep the `hint` text as well. |
|
|
2527
|
+
| `semantic` | `semantic` | `undefined \| "credit-card" \| "custom" \| "ssn" \| "tel"` | Semantic field type: `tel` · `ssn` · `credit-card` · `custom`. On its own it sets `inputmode` / `autocomplete` / `spellcheck` / `autocorrect` / `autocapitalize` and decides the canonical value; it never starts masking by itself. |
|
|
2528
|
+
| `caseMode` | `case-mode` | `undefined \| "preserve" \| "upper" \| "lower"` | Case folding applied to typed and pasted text: `preserve` (default) · `upper` · `lower`. Attribute: `case-mode`. |
|
|
2529
|
+
| `copyPolicy` | `copy-policy` | `undefined \| "formatted" \| "canonical" \| "obscured" \| "blocked"` | What a copy or cut of a masked field puts on the clipboard: `canonical` (default) · `formatted` · `obscured` · `blocked`. Attribute: `copy-policy`. |
|
|
1345
2530
|
|
|
1346
2531
|
**Events** (non-bubbling `CustomEvent`s — listen directly on the element):
|
|
1347
2532
|
|
|
1348
2533
|
| Event | `detail` type | Description |
|
|
1349
2534
|
|---|---|---|
|
|
1350
|
-
| `kai-change` | `CustomEvent<{ value: string }>` | The value was committed (blur). |
|
|
1351
|
-
| `kai-input` | `CustomEvent<{ value: string }>` | The value changed per keystroke. |
|
|
2535
|
+
| `kai-change` | `CustomEvent<{ value: string; formattedValue: string }>` | The value was committed (blur). Same detail shape as `kai-input`. |
|
|
2536
|
+
| `kai-input` | `CustomEvent<{ value: string; formattedValue: string }>` | The value changed per keystroke. `value` is the canonical value (what a backend wants); `formattedValue` is the text on screen. With no mask the two are equal. |
|
|
2537
|
+
| `kai-input-rejected` | `CustomEvent<{ reason: "full" \| "wrong-class" \| "over-capacity" \| "format-change-clipped"; data: string }>` | A mask refused, or partly refused, some content. The reasons are `full` (no free position left), `wrong-class` (a letter into a digit position), `over-capacity` (a paste longer than the mask holds; what fits was kept), and `format-change-clipped` (the `format` changed under a value that no longer fits). `data` is the content that was refused. The first three are USER-INPUT errors, and are the ones worth announcing in a polite live region. `format-change-clipped` is not one: it follows the app changing its own configuration, so it reports and nothing more. None of the four touches validity, so `invalid` and `error` stay the consumer decision. |
|
|
1352
2538
|
|
|
1353
2539
|
**Methods** (call on the element instance: `document.querySelector('kai-input').focus(…)`):
|
|
1354
2540
|
|
|
@@ -1356,7 +2542,9 @@ _No events._
|
|
|
1356
2542
|
|---|---|---|
|
|
1357
2543
|
| `focus` | `(options?: FocusOptions): void` | Focus the inner input (the host can't reach into the shadow root). |
|
|
1358
2544
|
| `select` | `(): void` | Select the inner input's text. |
|
|
1359
|
-
| `
|
|
2545
|
+
| `getRawValue` | `(): string` | The canonical value: digits for `tel` / `ssn` / `credit-card`, the formatted text for `custom`, and the field text when no mask is on. Identical to reading `el.value`, under the name backends use for the submitted form of a masked field. The mask engine has a third, narrower notion of raw (the fill characters with no literals at all) and that one is internal: it is not what any backend wants and it is not exposed here. |
|
|
2546
|
+
| `getFormattedValue` | `(): string` | The text on screen, literals and guide included. The counterpart to `formattedValue` on the `kai-input` / `kai-change` details, for a consumer that needs it outside an event. |
|
|
2547
|
+
| `clear` | `(): void` | Empty the value and fire `kai-change` with `''`. On a masked field this resets the mask itself, not just the text on screen, so the next character starts over. |
|
|
1360
2548
|
|
|
1361
2549
|
**Slots** (project your own markup via `slot="name"` on a light-DOM child):
|
|
1362
2550
|
|
|
@@ -1462,6 +2650,7 @@ _No events._
|
|
|
1462
2650
|
| `triggerLabel` | `trigger-label` | `undefined \| string` | Built-in trigger: a text label (e.g. `"High"`). This is the trigger's VISIBLE text, so it is also its accessible name, and `label` does not override it: an accessible name that does not contain the visible text is unreachable by speech input, which is what WCAG 2.5.3 (Label in Name) exists for. A slotted `slot="trigger"` replaces this built-in trigger entirely and is named differently; see `label`. |
|
|
1463
2651
|
| `triggerIconTrailing` | `trigger-icon-trailing` | `undefined \| string` | Built-in trigger: a trailing icon (e.g. `"chevron-down"` for a select look). |
|
|
1464
2652
|
| `label` | `label` | `undefined \| string` | Accessible name for a trigger with no visible label. Ignored when `triggerLabel` is set, which is already the visible name. It DOES name a slotted `slot="trigger"`, and that is a difference in what the two slots MEAN, not a limitation. `<kai-button>`'s slot IS the button's label, so text slotted there is the name and `label` steps aside. This slot is VISUAL content, a `+` or an `<svg>`, with the name supplied separately: decoration beside a name, never a second name competing with one. So `label` names the trigger here by design. Slotting a real WORD rather than a glyph makes that word a visible label, and an accessible name has to contain the visible text. Then either drop `label` or make it contain the word you slotted. |
|
|
2653
|
+
| `full` | `full` | `undefined \| false \| true` | Stretch the trigger to the full width of the menu's container (a block row), e.g. a sidebar-footer account row. Same affordance as `<kai-button full>`. Attribute: `full`. |
|
|
1465
2654
|
| `open` | `open` | `undefined \| false \| true` | Drive/observe open state (Shoelace-style: settable + reflected to the `open` attribute, the menu still self-manages on click/keyboard). Set `el.open = true`, or `<kai-menu open>`; listen for `kai-open-change`. |
|
|
1466
2655
|
| `defaultOpen` | `default-open` | `undefined \| false \| true` | Initial open state on mount (uncontrolled seed). |
|
|
1467
2656
|
| `disabled` | `disabled` | `undefined \| false \| true` | Disable the trigger: click/keyboard and `show()` no longer open the menu. |
|
|
@@ -1550,6 +2739,8 @@ _No events._
|
|
|
1550
2739
|
| `::part(content)` | The rendered message text/markdown region (same node as `bubble`). Target it to tune typography from outside. — `kai-message::part(content) { font-size: 0.9375rem }` |
|
|
1551
2740
|
| `::part(actions)` | The action-bar row (copy / like / regenerate …). Restyle its spacing or hide it entirely from outside. — `kai-message::part(actions) { gap: 0.25rem }` |
|
|
1552
2741
|
| `::part(citations)` | The citation row rendered from the message’s `source` parts: a wrapped row of chips below the bubble, never inside it. Restyle its spacing or hide it entirely from outside. — `kai-message::part(citations) { gap: 0.5rem }` |
|
|
2742
|
+
| `::part(attachment)` | One attachment item: the chip, row or tile, whichever variant is rendering. Restyle its background, radius or border from outside without caring which layout it is. — `kai-chat::part(attachment) { border-radius: 0.25rem }` |
|
|
2743
|
+
| `::part(attachment-name)` | The attachment’s filename label. Present in every variant that shows one (a grid tile omits it for an image, which is its own label). Retune its type or hide it entirely. — `kai-chat::part(attachment-name) { font-size: 0.75rem }` |
|
|
1553
2744
|
| `::part(avatar)` | Replaces the built-in avatar rail with your own node. Use `avatar="none"` to omit the rail and let the body span the full row. |
|
|
1554
2745
|
|
|
1555
2746
|
---
|
|
@@ -1701,6 +2892,23 @@ _No events._
|
|
|
1701
2892
|
|
|
1702
2893
|
---
|
|
1703
2894
|
|
|
2895
|
+
### `kai-pane-grid` / `PaneGrid`
|
|
2896
|
+
|
|
2897
|
+
**Properties** (every element also accepts `theme="light|dark|auto"`; only scalar props work as HTML attributes):
|
|
2898
|
+
|
|
2899
|
+
| Property | Attribute | Type | Description |
|
|
2900
|
+
|---|---|---|---|
|
|
2901
|
+
| `theme` | `theme` | `"light" \| "dark" \| "auto"` | Color mode (`auto` follows prefers-color-scheme). |
|
|
2902
|
+
| `minPaneWidth` | `min-pane-width` | `undefined \| number` | Minimum width of every pane, in px, before columns drop / the grid scrolls. Defaults to `280`. Attribute: `min-pane-width`. |
|
|
2903
|
+
| `minPaneHeight` | `min-pane-height` | `undefined \| number` | Minimum height of every pane, in px, before the grid scrolls vertically. Defaults to `200`. Attribute: `min-pane-height`. |
|
|
2904
|
+
| `maxColumns` | `max-columns` | `undefined \| number` | Column cap when the container is wide (default `3`). Attribute: `max-columns`. |
|
|
2905
|
+
| `gap` | `gap` | `undefined \| string` | Gap between panes, any CSS length. Defaults to the kit gap (`var(--kai-pane-grid-gap, 0.5rem)`). Attribute: `gap`. |
|
|
2906
|
+
| `maximizedIndex` | — | `undefined \| number \| null` | When set to a valid child index, render ONLY that pane full-bleed: a simple maximize hook the consumer drives (pair it with `<kai-pane>`'s `kai-maximize` event). Clear it (or point out of range) for the full tiled grid. Attribute: `maximized-index`. |
|
|
2907
|
+
|
|
2908
|
+
_No events._
|
|
2909
|
+
|
|
2910
|
+
---
|
|
2911
|
+
|
|
1704
2912
|
### `kai-pane-group` / `PaneGroup`
|
|
1705
2913
|
|
|
1706
2914
|
**Properties** (every element also accepts `theme="light|dark|auto"`; only scalar props work as HTML attributes):
|
|
@@ -1847,12 +3055,12 @@ _No events._
|
|
|
1847
3055
|
| `loading` | `loading` | `undefined \| false \| true` | Show the loading/streaming state and block submit (use while awaiting a reply). |
|
|
1848
3056
|
| `suggestions` | — | `undefined \| string[]` | Starter prompts shown above the input. Clicking one follows `suggestionMode`. Set as a JS property. |
|
|
1849
3057
|
| `suggestionMode` | `suggestion-mode` | `undefined \| "submit" \| "fill"` | What clicking a suggestion does: `'submit'` (default) sends it immediately as if typed and submitted; `'fill'` just places it in the input. |
|
|
1850
|
-
| `
|
|
3058
|
+
| `webSearch` | `web-search` | `undefined \| false \| true` | Show a web-search (Globe) button in the left toolbar; clicking it fires a `kai-web-search` event. Attribute: `web-search`. |
|
|
1851
3059
|
| `voice` | `voice` | `undefined \| false \| true` | Show a Voice (Mic) button in the left toolbar; clicking it fires a `voice` event. |
|
|
1852
3060
|
| `stoppable` | `stoppable` | `undefined \| false \| true` | When set and `loading` is true, the send button is replaced by a Stop button (square icon, "Stop" aria-label). Clicking it fires `kai-stop`. |
|
|
1853
3061
|
| `submit` | `submit` | `undefined \| "always" \| "auto"` | Send-button visibility. `'always'` (default) always shows it; `'auto'` shows it only when there's text/attachments (an empty composer hides it, though Enter still submits). To hide it entirely (Enter-only), it's pure CSS: `::part(send){display:none}`, no prop needed. Restyle via `::part(send)`. The Stop button (`stoppable` + `loading`) is unaffected. |
|
|
1854
3062
|
| `attach` | `attach` | `undefined \| false \| true` | When `false`, hides the built-in paperclip attach button even though the element otherwise supports attachments. Use this when a `+` menu in `toolbar-start` already exposes "Add files", to avoid a duplicate control. Defaults to `true`. |
|
|
1855
|
-
| `attachments` | — | `undefined \| { id: string; type: "file" \| "source-document"; filename?: undefined \| string; mediaType?: undefined \| string; url?: undefined \| string; title?: undefined \| string }[]` | Attachments to seed the input with (so a consumer can pre-populate staged files without an upload). Set as a JS property; the element then manages its own attachment state from there (add via the paperclip, remove per chip). |
|
|
3063
|
+
| `attachments` | — | `undefined \| { id: string; type: "file" \| "source-document"; filename?: undefined \| string; mediaType?: undefined \| string; url?: undefined \| string; title?: undefined \| string }[]` | Attachments to seed the input with (so a consumer can pre-populate staged files without an upload). Set as a JS property; the element then manages its own attachment state from there (add via the paperclip, remove per chip). Each item's `url` must be a `data:` URI or an https URL, never `URL.createObjectURL`: a `blob:` URL previews perfectly and is meaningless outside this tab, so `toOpenAIMessages`/`toAnthropicMessages` refuse it. (The built-in paperclip already stages files as `data:` URIs.) |
|
|
1856
3064
|
| `triggers` | — | `undefined \| { char: string; kind: string; items?: undefined \| { id: string; label: string; icon?: undefined \| string; description?: undefined \| string; group?: undefined \| string; kind?: undefined \| string; promptText?: undefined \| string; data?: undefined \| Record<string, unknown> }[] }[]` | Rich entity triggers. Each `{ char, kind, items }` opens a caret-anchored menu that inserts an atomic pill. Convention: `/` → skills, `@` → agents (plugins are the grouping/provenance of those items). Set as a JS property. |
|
|
1857
3065
|
| `kindIcons` | — | `undefined \| Record<string, string>` | Default icon per entity kind (kind → image URL/data-URI) for pills/menu items without their own `icon`. Overrides the built-in agent/plugin glyphs. JS property. |
|
|
1858
3066
|
|
|
@@ -1861,13 +3069,13 @@ _No events._
|
|
|
1861
3069
|
| Event | `detail` type | Description |
|
|
1862
3070
|
|---|---|---|
|
|
1863
3071
|
| `kai-attachments-change` | `CustomEvent<{ attachments: { id: string; type: "file" \| "source-document"; filename?: undefined \| string; mediaType?: undefined \| string; url?: undefined \| string; title?: undefined \| string }[] }>` | The staged attachments changed: a file was added (via the paperclip) or removed (per-chip ×). Carries the full current list so a consumer can react in real time (validate, show upload progress, toggle the send button). |
|
|
1864
|
-
| `kai-search` | `CustomEvent<Record<string, never>>` | The Search (Globe) toolbar button was clicked. |
|
|
1865
3072
|
| `kai-stop` | `CustomEvent<Record<string, never>>` | The Stop button was clicked while `stoppable` and `loading` are both true. |
|
|
1866
|
-
| `kai-submit` | `CustomEvent<{ value: string; doc: ({ type: "text"; text: string } \| { type: "entity"; entity: { kind: string; id: string; label: string; icon?: undefined \| string; promptText?: undefined \| string; data?: undefined \| Record<string, unknown> } })[]; entities: { kind: string; id: string; label: string; icon?: undefined \| string; promptText?: undefined \| string; data?: undefined \| Record<string, unknown> }[]; attachments: { id: string; type: "file" \| "source-document"; filename?: undefined \| string; mediaType?: undefined \| string; url?: undefined \| string; title?: undefined \| string }[] }>` | The user submitted the prompt (Enter or send button). `value` is the flattened text (back-compat); `doc` is the structured document and `entities` the inserted pills (skills/agents) for downstream expansion. |
|
|
3073
|
+
| `kai-submit` | `CustomEvent<{ value: string; doc: ({ type: "text"; text: string } \| { type: "entity"; entity: { kind: string; id: string; label: string; icon?: undefined \| string; promptText?: undefined \| string; data?: undefined \| Record<string, unknown> } })[]; entities: { kind: string; id: string; label: string; icon?: undefined \| string; promptText?: undefined \| string; data?: undefined \| Record<string, unknown> }[]; attachments: { id: string; type: "file" \| "source-document"; filename?: undefined \| string; mediaType?: undefined \| string; url?: undefined \| string; title?: undefined \| string }[] }>` | The user submitted the prompt (Enter or send button). `value` is the flattened text (back-compat); `doc` is the structured document and `entities` the inserted pills (skills/agents) for downstream expansion. `<kai-prompt-input>` is the batteries-included composer row (send button, toolbar, attachment staging) built on `<kai-composer>`, the bare editor. |
|
|
1867
3074
|
| `kai-suggestion-click` | `CustomEvent<{ value: string }>` | A suggestion was clicked while `suggestion-mode="fill"`. |
|
|
1868
3075
|
| `kai-toolbar-action` | `CustomEvent<{ action: string }>` | A custom `<kai-action>` toolbar button was clicked. `action` is the `id` of the `<kai-action>` element that was clicked. |
|
|
1869
3076
|
| `kai-value-change` | `CustomEvent<{ value: string; doc: ({ type: "text"; text: string } \| { type: "entity"; entity: { kind: string; id: string; label: string; icon?: undefined \| string; promptText?: undefined \| string; data?: undefined \| Record<string, unknown> } })[]; entities: { kind: string; id: string; label: string; icon?: undefined \| string; promptText?: undefined \| string; data?: undefined \| Record<string, unknown> }[] }>` | The input changed (fires on every edit). Carries the flattened `value` plus the structured `doc` + `entities`. |
|
|
1870
3077
|
| `kai-voice` | `CustomEvent<Record<string, never>>` | The Voice (Mic) toolbar button was clicked. |
|
|
3078
|
+
| `kai-web-search` | `CustomEvent<Record<string, never>>` | The web-search (Globe) toolbar button was clicked. |
|
|
1871
3079
|
|
|
1872
3080
|
**Methods** (call on the element instance: `document.querySelector('kai-prompt-input').focus(…)`):
|
|
1873
3081
|
|
|
@@ -1900,6 +3108,33 @@ _No events._
|
|
|
1900
3108
|
|
|
1901
3109
|
---
|
|
1902
3110
|
|
|
3111
|
+
### `kai-radio-group` / `RadioGroup`
|
|
3112
|
+
|
|
3113
|
+
**Properties** (every element also accepts `theme="light|dark|auto"`; only scalar props work as HTML attributes):
|
|
3114
|
+
|
|
3115
|
+
| Property | Attribute | Type | Description |
|
|
3116
|
+
|---|---|---|---|
|
|
3117
|
+
| `theme` | `theme` | `"light" \| "dark" \| "auto"` | Color mode (`auto` follows prefers-color-scheme). |
|
|
3118
|
+
| `options` | — | `{ value: string; label: string; description?: undefined \| string; disabled?: undefined \| false \| true }[]` | The choices, top to bottom. Set as a JS PROPERTY (array), never an attribute. |
|
|
3119
|
+
| `value` | `value` | `undefined \| string` | Controlled selected `value`. Settable and reflected to the `value` attribute. `el.value = 'degraded'` drives it; choosing a row updates it and fires `kai-change`. Read `el.value` for live state. |
|
|
3120
|
+
| `name` | `name` | `undefined \| string` | Shared form-control name for every radio in the group. Defaults to a generated id, so the group is exclusive and keyboard-navigable even when nothing is submitted. |
|
|
3121
|
+
| `disabled` | `disabled` | `undefined \| false \| true` | Disable every row. Individual rows carry their own `disabled`. |
|
|
3122
|
+
| `label` | `label` | `undefined \| string` | Accessible name for the group. |
|
|
3123
|
+
|
|
3124
|
+
**Events** (non-bubbling `CustomEvent`s — listen directly on the element):
|
|
3125
|
+
|
|
3126
|
+
| Event | `detail` type | Description |
|
|
3127
|
+
|---|---|---|
|
|
3128
|
+
| `kai-change` | `CustomEvent<{ value: string }>` | A row was chosen. |
|
|
3129
|
+
|
|
3130
|
+
**Methods** (call on the element instance: `document.querySelector('kai-radio-group').focus(…)`):
|
|
3131
|
+
|
|
3132
|
+
| Method | Signature | Description |
|
|
3133
|
+
|---|---|---|
|
|
3134
|
+
| `focus` | `(options?: FocusOptions): void` | Focus the group's tab stop. That is the selected radio, or the first row when nothing is selected yet. |
|
|
3135
|
+
|
|
3136
|
+
---
|
|
3137
|
+
|
|
1903
3138
|
### `kai-reasoning` / `Reasoning`
|
|
1904
3139
|
|
|
1905
3140
|
**Properties** (every element also accepts `theme="light|dark|auto"`; only scalar props work as HTML attributes):
|
|
@@ -2145,6 +3380,8 @@ _No events._
|
|
|
2145
3380
|
| `for` | `for` | `undefined \| string` | CSS id of the scroll container to control. When omitted the element walks up the DOM (outside its own shadow root) to find the nearest scrollable ancestor. Mirrors the `for` convention of `<label for="...">`. |
|
|
2146
3381
|
| `variant` | `variant` | `undefined \| "outline" \| "ghost" \| "default"` | Button visual variant: `'outline' \| 'ghost' \| 'default'`. Defaults to `'outline'`. |
|
|
2147
3382
|
| `size` | `size` | `undefined \| "sm" \| "md" \| "lg" \| "icon" \| "icon-sm"` | Button size token. Defaults to `'icon'` (square). |
|
|
3383
|
+
| `label` | `label` | `undefined \| string` | The button's accessible name. It is announced whether or not the label is visible, so the text is always localisable. Defaults to `'Scroll to bottom'`. |
|
|
3384
|
+
| `showLabel` | `show-label` | `undefined \| false \| true` | Also render `label` visibly beside the icon. Defaults to `false`, which is the icon-only button. When the text is visible it IS the accessible name, so nothing gets announced twice. |
|
|
2148
3385
|
|
|
2149
3386
|
**Events** (non-bubbling `CustomEvent`s — listen directly on the element):
|
|
2150
3387
|
|
|
@@ -2219,6 +3456,37 @@ _No events._
|
|
|
2219
3456
|
|
|
2220
3457
|
---
|
|
2221
3458
|
|
|
3459
|
+
### `kai-select` / `Select`
|
|
3460
|
+
|
|
3461
|
+
**Properties** (every element also accepts `theme="light|dark|auto"`; only scalar props work as HTML attributes):
|
|
3462
|
+
|
|
3463
|
+
| Property | Attribute | Type | Description |
|
|
3464
|
+
|---|---|---|---|
|
|
3465
|
+
| `theme` | `theme` | `"light" \| "dark" \| "auto"` | Color mode (`auto` follows prefers-color-scheme). |
|
|
3466
|
+
| `options` | — | `{ value: string; label?: undefined \| string; disabled?: undefined \| false \| true }[]` | The choices, in display order. Set as a JS PROPERTY (array), never an attribute. Rendered in full: the kit never truncates, re-orders or de-duplicates them. |
|
|
3467
|
+
| `value` | `value` | `undefined \| string` | Controlled selected value. Settable and reflected to the `value` attribute. `el.value = 'high'` drives it; choosing an option updates it and fires `kai-change`. Read `el.value` for live state; for a `multiple` select read `el.values` instead. |
|
|
3468
|
+
| `placeholder` | `placeholder` | `undefined \| string` | Text for a leading, disabled, empty option: the "nothing chosen yet" row. Omitted means no such row at all; there is no default wording, because inventing one would put words in your UI. |
|
|
3469
|
+
| `multiple` | `multiple` | `undefined \| false \| true` | Allow more than one selection. Turns the control into the platform's list box, so the kit's chevron is not drawn. |
|
|
3470
|
+
| `invalid` | `invalid` | `undefined \| false \| true` | Force the invalid (destructive-border) state. |
|
|
3471
|
+
| `disabled` | `disabled` | `undefined \| false \| true` | Disable interaction. |
|
|
3472
|
+
| `required` | `required` | `undefined \| false \| true` | Set the native `required` attribute, and nothing more. Whether an empty select is an error is the application's rule, not the kit's. |
|
|
3473
|
+
| `label` | `label` | `undefined \| string` | Accessible label for the control. |
|
|
3474
|
+
| `name` | `name` | `undefined \| string` | Form-control name, for a native form submit. |
|
|
3475
|
+
|
|
3476
|
+
**Events** (non-bubbling `CustomEvent`s — listen directly on the element):
|
|
3477
|
+
|
|
3478
|
+
| Event | `detail` type | Description |
|
|
3479
|
+
|---|---|---|
|
|
3480
|
+
| `kai-change` | `CustomEvent<{ value: string; values: string[] }>` | A choice was made. `value` is the first selected option (empty when nothing is selected); `values` is every selected option, which is what a `multiple` select needs. Both are always present, so neither shape silently loses the other. |
|
|
3481
|
+
|
|
3482
|
+
**Methods** (call on the element instance: `document.querySelector('kai-select').focus(…)`):
|
|
3483
|
+
|
|
3484
|
+
| Method | Signature | Description |
|
|
3485
|
+
|---|---|---|
|
|
3486
|
+
| `focus` | `(options?: FocusOptions): void` | Focus the inner select (the host element can't reach it). |
|
|
3487
|
+
|
|
3488
|
+
---
|
|
3489
|
+
|
|
2222
3490
|
### `kai-separator` / `Separator`
|
|
2223
3491
|
|
|
2224
3492
|
**Properties** (every element also accepts `theme="light|dark|auto"`; only scalar props work as HTML attributes):
|
|
@@ -2333,6 +3601,37 @@ _No events._
|
|
|
2333
3601
|
|
|
2334
3602
|
---
|
|
2335
3603
|
|
|
3604
|
+
### `kai-slider` / `Slider`
|
|
3605
|
+
|
|
3606
|
+
**Properties** (every element also accepts `theme="light|dark|auto"`; only scalar props work as HTML attributes):
|
|
3607
|
+
|
|
3608
|
+
| Property | Attribute | Type | Description |
|
|
3609
|
+
|---|---|---|---|
|
|
3610
|
+
| `theme` | `theme` | `"light" \| "dark" \| "auto"` | Color mode (`auto` follows prefers-color-scheme). |
|
|
3611
|
+
| `min` | `min` | `undefined \| number` | Lowest selectable value. Required: a range with no bounds is a guess, and the guess belongs to whoever knows what the number means. |
|
|
3612
|
+
| `max` | `max` | `undefined \| number` | Highest selectable value. Required, for the same reason as `min`. |
|
|
3613
|
+
| `step` | `step` | `undefined \| number \| "any"` | Granularity. Omitted means the native default of 1; `any` means continuous. |
|
|
3614
|
+
| `value` | `value` | `undefined \| number` | Controlled value. Settable and reflected to the `value` attribute. `el.value = 40` drives it; dragging updates it and fires `kai-input` per step, `kai-change` on release. Read `el.value` for live state. |
|
|
3615
|
+
| `disabled` | `disabled` | `undefined \| false \| true` | Disable interaction. |
|
|
3616
|
+
| `label` | `label` | `undefined \| string` | Accessible label for the slider. |
|
|
3617
|
+
| `name` | `name` | `undefined \| string` | Form-control name, for a native form submit. |
|
|
3618
|
+
| `valueLabel` | — | `undefined \| false \| true \| ((value: number) => string)` | Show the current value beside the track. Off by default. Two ways in, because one of them is not a scalar. As a bare ATTRIBUTE (`<kai-slider value-label>`) it renders the raw number. As a JS PROPERTY it also accepts a formatter function (`el.valueLabel = (v) => v + '%'`), for a slider that is not counting bare numbers. A function cannot survive an attribute, so that half is property-only. The readout is hidden from assistive tech: the slider already reports the same number, and an exposed copy would be announced twice. |
|
|
3619
|
+
|
|
3620
|
+
**Events** (non-bubbling `CustomEvent`s — listen directly on the element):
|
|
3621
|
+
|
|
3622
|
+
| Event | `detail` type | Description |
|
|
3623
|
+
|---|---|---|
|
|
3624
|
+
| `kai-change` | `CustomEvent<{ value: number }>` | The value was committed: pointer released, or a key press finished. |
|
|
3625
|
+
| `kai-input` | `CustomEvent<{ value: number }>` | The thumb moved. Fires per step during a drag or a key press. |
|
|
3626
|
+
|
|
3627
|
+
**Methods** (call on the element instance: `document.querySelector('kai-slider').focus(…)`):
|
|
3628
|
+
|
|
3629
|
+
| Method | Signature | Description |
|
|
3630
|
+
|---|---|---|
|
|
3631
|
+
| `focus` | `(options?: FocusOptions): void` | Focus the inner range input (the host element can't reach it). |
|
|
3632
|
+
|
|
3633
|
+
---
|
|
3634
|
+
|
|
2336
3635
|
### `kai-source` / `Source`
|
|
2337
3636
|
|
|
2338
3637
|
**Properties** (every element also accepts `theme="light|dark|auto"`; only scalar props work as HTML attributes):
|
|
@@ -2601,7 +3900,7 @@ _No events._
|
|
|
2601
3900
|
| Property | Attribute | Type | Description |
|
|
2602
3901
|
|---|---|---|---|
|
|
2603
3902
|
| `theme` | `theme` | `"light" \| "dark" \| "auto"` | Color mode (`auto` follows prefers-color-scheme). |
|
|
2604
|
-
| `toasts` | — | `undefined \| { id: string; message: string; variant?: undefined \| "neutral" \| "success" \| "warning" \| "error" \| "info"; appearance?: undefined \| "pill" \| "card"; inverse?: undefined \| false \| true; description?: undefined \| string; action?: undefined \| { label: string; onAction: () => void \| false }; duration?: undefined \| number; dismissible?: undefined \| false \| true; target?: undefined \| HTMLElement }[]` | The toasts to render. Newest is shown on top. Set as a JS property (array); pass a new array reference to update. Omit for an empty region, which is the normal resting state and how the imperative `toast()` API starts. |
|
|
3903
|
+
| `toasts` | — | `undefined \| { id: string; message: string; variant?: undefined \| "neutral" \| "success" \| "warning" \| "error" \| "info"; appearance?: undefined \| "pill" \| "card"; inverse?: undefined \| false \| true; description?: undefined \| string; action?: undefined \| { label: string; onAction: () => void \| false }; duration?: undefined \| number; dismissible?: undefined \| false \| true; target?: undefined \| HTMLElement }[]` | The toasts to render. Newest is shown on top. Set as a JS property (array); pass a new array reference to update. Omit for an empty region, which is the normal resting state and how the imperative `toast()` API starts. Note the handover: the first `toast()` call ADOPTS a region you placed in markup (no second region mounts) and binds the imperative store to this property, replacing any array you set. Drive a region as data OR via `toast()`, not both at once. |
|
|
2605
3904
|
| `position` | `position` | `undefined \| "top-center" \| "top-right" \| "top-left" \| "bottom-center" \| "bottom-right" \| "bottom-left"` | Stack anchor: `'top-center'` (default), `'top-right'`, `'bottom-center'`, … |
|
|
2606
3905
|
| `max` | `max` | `undefined \| number` | Max simultaneously-visible toasts; the rest queue. Defaults to `3`. |
|
|
2607
3906
|
| `stack` | `stack` | `undefined \| "expanded" \| "collapsed"` | Stacking: 'expanded' (default, full column) \| 'collapsed' (Sonner-style pile that expands on hover/focus). Attribute: stack. |
|
|
@@ -2690,7 +3989,7 @@ _No events._
|
|
|
2690
3989
|
| Property | Attribute | Type | Description |
|
|
2691
3990
|
|---|---|---|---|
|
|
2692
3991
|
| `theme` | `theme` | `"light" \| "dark" \| "auto"` | Color mode (`auto` follows prefers-color-scheme). |
|
|
2693
|
-
| `transcribe` | — | `undefined \| (audio: Blob) => Promise<string
|
|
3992
|
+
| `transcribe` | — | `undefined \| ((audio: Blob) => Promise<string>)` | Transcriber the host supplies: records audio, returns the text. This is a **function-valued property** (`el.transcribe = async blob => '...'`) because a value-returning callback can't be modelled as a fire-and-forget event. |
|
|
2694
3993
|
| `disabled` | `disabled` | `undefined \| false \| true` | Disable the mic button (non-interactive). |
|
|
2695
3994
|
| `recognitionLang` | `recognition-lang` | `undefined \| string` | BCP-47 language tag for the native `SpeechRecognition` path (e.g. `en-US`). Attribute: `recognition-lang` (the plain `lang` attribute is reserved by `HTMLElement` and can't be a custom-element property). No effect when `transcribe` is set or the browser lacks SpeechRecognition. |
|
|
2696
3995
|
| `interim` | `interim` | `undefined \| false \| true` | Emit live partial transcripts (`kai-transcript-interim`) during native recognition. Attribute: `interim`. No-op on the transcribe/fallback paths. |
|
|
@@ -2703,6 +4002,7 @@ _No events._
|
|
|
2703
4002
|
| `kai-recording-change` | `CustomEvent<{ recording: false \| true }>` | Recording started or stopped. Lets the host drive its own UI (waveform, push-to-talk indicator) in sync with the mic. Fires on real transitions only (manual click and programmatic start()/stop()), never on mount. |
|
|
2704
4003
|
| `kai-transcript-interim` | `CustomEvent<{ text: string }>` | Live partial transcript during native recognition (only when `interim` is set). Fires repeatedly before the final `kai-transcription`. |
|
|
2705
4004
|
| `kai-transcription` | `CustomEvent<{ text: string }>` | Final transcript: the `transcribe` property resolved, OR native `SpeechRecognition` produced final text (no `transcribe` set). |
|
|
4005
|
+
| `kai-voice-error` | `CustomEvent<{ source: "recognition"; error: string; message: string }>` | A voice session failed, so no failure is ever silent. `detail.source` names the failing side (`recognition` on `<kai-voice-input>`, `synthesis` on `<kai-voice-output>`), `detail.error` carries the platform error code, the thrown exception's name, or `no-result` when recognition ended with no error and no text (the user said nothing), and `detail.message` is human-readable. Deliberate cancellation does not fire. |
|
|
2706
4006
|
|
|
2707
4007
|
**Methods** (call on the element instance: `document.querySelector('kai-voice-input').start()`):
|
|
2708
4008
|
|
|
@@ -2722,15 +4022,16 @@ _No events._
|
|
|
2722
4022
|
| `theme` | `theme` | `"light" \| "dark" \| "auto"` | Color mode (`auto` follows prefers-color-scheme). |
|
|
2723
4023
|
| `text` | `text` | `undefined \| string` | The utterance to read aloud. |
|
|
2724
4024
|
| `autoplay` | `autoplay` | `undefined \| false \| true` | Speak automatically when `text` is set/changed. |
|
|
2725
|
-
| `synthesize` | — | `undefined \| (text: string) => Promise<Blob
|
|
4025
|
+
| `synthesize` | — | `undefined \| ((text: string) => Promise<Blob>)` | TTS model seam the host supplies: given text, returns an audio `Blob` to play. This is a **function-valued property** (`el.synthesize = async text => blob`); when set, the native `speechSynthesis` path is bypassed. Mirrors `<kai-voice-input>`'s `transcribe`. A value-returning callback can't be modelled as a fire-and-forget event, hence a property. |
|
|
2726
4026
|
| `disabled` | `disabled` | `undefined \| false \| true` | Disable the button (non-interactive). |
|
|
2727
4027
|
|
|
2728
4028
|
**Events** (non-bubbling `CustomEvent`s — listen directly on the element):
|
|
2729
4029
|
|
|
2730
4030
|
| Event | `detail` type | Description |
|
|
2731
4031
|
|---|---|---|
|
|
2732
|
-
| `kai-speaking-change` | `CustomEvent<{ speaking: false \| true }>` | Playback started or stopped. Drive your own UI in sync. Fires on real transitions only (manual click and programmatic speak()/stop()), never on mount. |
|
|
4032
|
+
| `kai-speaking-change` | `CustomEvent<{ speaking: false \| true }>` | Playback started or stopped. Drive your own UI in sync. `speaking: true` fires when audio actually starts (utterance.onstart natively; audio playback beginning on the `synthesize` path), not when speak() is called; earlier releases fired it optimistically inside speak() itself. Fires on real transitions only (manual click and programmatic speak()/stop()), never on mount. |
|
|
2733
4033
|
| `kai-synthesized` | `CustomEvent<{ blob: Blob }>` | The model path (`synthesize`) resolved audio: the raw `Blob` before playback. |
|
|
4034
|
+
| `kai-voice-error` | `CustomEvent<{ source: "synthesis"; error: string; message: string }>` | A voice session failed, so no failure is ever silent. `detail.source` names the failing side (`recognition` on `<kai-voice-input>`, `synthesis` on `<kai-voice-output>`), `detail.error` carries the platform error code, the thrown exception's name, or `no-result` when recognition ended with no error and no text (the user said nothing), and `detail.message` is human-readable. Deliberate cancellation does not fire. |
|
|
2734
4035
|
|
|
2735
4036
|
**Methods** (call on the element instance: `document.querySelector('kai-voice-output').speak()`):
|
|
2736
4037
|
|
|
@@ -2756,79 +4057,50 @@ _No events._
|
|
|
2756
4057
|
| Property | Attribute | Type | Description |
|
|
2757
4058
|
|---|---|---|---|
|
|
2758
4059
|
| `theme` | `theme` | `"light" \| "dark" \| "auto"` | Color mode (`auto` follows prefers-color-scheme). |
|
|
2759
|
-
| `
|
|
2760
|
-
| `
|
|
2761
|
-
| `
|
|
2762
|
-
| `
|
|
2763
|
-
| `
|
|
2764
|
-
| `
|
|
2765
|
-
| `
|
|
2766
|
-
| `suggestions` | — | `undefined \| string[]` | |
|
|
2767
|
-
| `suggestionMode` | `suggestion-mode` | `undefined \| "submit" \| "fill"` | |
|
|
2768
|
-
| `proseSize` | `prose-size` | `undefined \| "xs" \| "sm" \| "base" \| "lg"` | |
|
|
2769
|
-
| `codeTheme` | `code-theme` | `undefined \| string` | |
|
|
2770
|
-
| `codeHighlight` | `code-highlight` | `undefined \| false \| true` | |
|
|
2771
|
-
| `chatTitle` | `chat-title` | `undefined \| string` | |
|
|
2772
|
-
| `models` | — | `undefined \| { id: string; name: string; provider?: undefined \| string; description?: undefined \| string; group?: undefined \| string }[]` | |
|
|
2773
|
-
| `currentModel` | `current-model` | `undefined \| string` | |
|
|
2774
|
-
| `context` | — | `undefined \| { usedTokens: number; maxTokens: number; inputTokens?: undefined \| number; outputTokens?: undefined \| number; estimatedCost?: undefined \| number }` | |
|
|
2775
|
-
| `scrollButton` | `scroll-button` | `undefined \| false \| true` | |
|
|
2776
|
-
| `search` | `search` | `undefined \| false \| true` | |
|
|
2777
|
-
| `voice` | `voice` | `undefined \| false \| true` | |
|
|
2778
|
-
| `triggers` | — | `undefined \| { char: string; kind: string; items?: undefined \| { id: string; label: string; icon?: undefined \| string; description?: undefined \| string; group?: undefined \| string; kind?: undefined \| string; promptText?: undefined \| string; data?: undefined \| Record<string, unknown> }[] }[]` | Rich entity triggers (`/` skills, `@` agents/plugins) forwarded to the input. |
|
|
2779
|
-
| `kindIcons` | — | `undefined \| Record<string, string>` | Default icon per entity kind (kind → image src) forwarded to the input. |
|
|
2780
|
-
| `sidebarWidth` | `sidebar-width` | `undefined \| number` | Sidebar default width as a percent of the workspace (default 26). |
|
|
2781
|
-
| `sidebarMinWidth` | `sidebar-min-width` | `undefined \| number` | Sidebar min width in px (default 240). |
|
|
2782
|
-
| `sidebarMaxWidth` | `sidebar-max-width` | `undefined \| number` | Sidebar max width in px (default 420). |
|
|
2783
|
-
| `sidebarCollapsed` | `sidebar-collapsed` | `undefined \| false \| true` | Controlled collapsed state. Set this as a JS property (`el.sidebarCollapsed = true`) to drive the sidebar from your app, updating it in response to the `kai-sidebar-toggle` event. Omit for uncontrolled (the element manages it). |
|
|
2784
|
-
| `defaultSidebarCollapsed` | `default-sidebar-collapsed` | `undefined \| false \| true` | Initial collapsed state when uncontrolled (default false). Use the `default-sidebar-collapsed` attribute to start collapsed in plain HTML. |
|
|
2785
|
-
| `collapseBelow` | `collapse-below` | `undefined \| number` | Auto-collapse the rail when the workspace's own width drops below this many px, and re-expand when it grows back above. Uncontrolled only (it never fights an app-driven `sidebarCollapsed`); omit to disable. Fires `kai-sidebar-toggle`. Attribute: `collapse-below`. |
|
|
2786
|
-
| `compact` | `compact` | `undefined \| false \| true` | Render Recents as dense single-line rows (a leading dot + title, no count). |
|
|
2787
|
-
| `noConversations` | `no-conversations` | `undefined \| false \| true` | Suppress the built-in ConversationList so the `sidebar-header` slot owns the whole rail flex region (for apps that supply their own rail nav). Default false. Attribute: `no-conversations`. |
|
|
2788
|
-
| `cardTypes` | — | `undefined \| Record<string, string>` | Optional card type -> custom-element tag overrides/additions for `card` parts (merged over the built-ins). Property: `el.cardTypes`. Typed as a plain string map (not the `CardTagMap` alias) so the generated React wrapper inlines it instead of emitting an unresolved named type. |
|
|
2789
|
-
| `cardSchemas` | — | `undefined \| Record<string, object>` | JSON Schemas for the card types this app renders, keyed by envelope type. The companion of `cardTypes`, which says what DRAWS a card while this says what a VALID one looks like. An OBJECT, so it is a JS property only: `el.cardSchemas = { 'pricing-table': pricingSchema }`, never an attribute. `createCardRegistry(...).validationSchemas` is exactly this shape. Without it the kit validates its own seven built-ins and leaves your own card type, the one your app actually cares about, as the only unchecked thing on screen. A schema here WINS over a built-in of the same name. Typed `Record<string, object>` rather than `Record<string, JsonSchema>` deliberately: an imported `.json` schema widens `"type"` to `string`, and an authored one carries `$schema`/`title`/`description`/`additionalProperties`, so the tighter type would reject both of the normal ways to supply one. |
|
|
4060
|
+
| `startCollapsed` | `start-collapsed` | `undefined \| false \| true` | Controlled collapsed state of the start aside. Set this as a JS property (`el.startCollapsed = true`) to drive the aside from your app, updating it in response to the `kai-aside-toggle` event. Omit for uncontrolled (the element manages it). |
|
|
4061
|
+
| `defaultStartCollapsed` | `default-start-collapsed` | `undefined \| false \| true` | Initial collapsed state of the start aside when uncontrolled (default false). Use the `default-start-collapsed` attribute to start collapsed in plain HTML. |
|
|
4062
|
+
| `endCollapsed` | `end-collapsed` | `undefined \| false \| true` | Controlled collapsed state of the end aside. Set this as a JS property (`el.endCollapsed = true`) to drive the aside from your app, updating it in response to the `kai-aside-toggle` event. Omit for uncontrolled (the element manages it). |
|
|
4063
|
+
| `defaultEndCollapsed` | `default-end-collapsed` | `undefined \| false \| true` | Initial collapsed state of the end aside when uncontrolled (default false). Use the `default-end-collapsed` attribute to start collapsed in plain HTML. |
|
|
4064
|
+
| `collapseBelow` | `collapse-below` | `undefined \| number` | Auto-collapse both asides when the shell's own width drops below this many px, and re-expand when it grows back above. Applies to uncontrolled asides only (it never fights an app-driven collapsed prop); omit to disable. Fires `kai-aside-toggle`. Attribute: `collapse-below`. |
|
|
4065
|
+
| `drawerBelow` | `drawer-below` | `undefined \| number` | Below this shell width in px, an expanded aside renders as an overlay drawer over the main region instead of a column beside it. Escape inside the drawer closes it and returns focus to the element focused before it opened. Omit to disable. Attribute: `drawer-below`. |
|
|
4066
|
+
| `compact` | `compact` | `undefined \| false \| true` | Density hint. Reflected as a `data-compact` hook on the root (and as the `compact` attribute on the element) for your CSS and slotted content; the shell itself keeps no other opinion about density. |
|
|
2790
4067
|
|
|
2791
4068
|
**Events** (non-bubbling `CustomEvent`s — listen directly on the element):
|
|
2792
4069
|
|
|
2793
4070
|
| Event | `detail` type | Description |
|
|
2794
4071
|
|---|---|---|
|
|
2795
|
-
| `kai-
|
|
2796
|
-
| `kai-
|
|
2797
|
-
| `kai-model-change` | `CustomEvent<{ modelId: string }>` | The header model switcher changed. |
|
|
2798
|
-
| `kai-new-chat` | `CustomEvent<Record<string, never>>` | The "New chat" button was clicked. |
|
|
2799
|
-
| `kai-search` | `CustomEvent<Record<string, never>>` | The Search button was clicked. |
|
|
2800
|
-
| `kai-sidebar-toggle` | `CustomEvent<{ collapsed: false \| true }>` | The sidebar was collapsed or expanded. |
|
|
2801
|
-
| `kai-submit` | `CustomEvent<{ value: string; attachments: { id: string; type: "file" \| "source-document"; filename?: undefined \| string; mediaType?: undefined \| string; url?: undefined \| string; title?: undefined \| string }[] }>` | User submitted a message. |
|
|
2802
|
-
| `kai-suggestion-click` | `CustomEvent<{ value: string }>` | A suggestion chip was clicked (only in `suggestion-mode="fill"`). |
|
|
2803
|
-
| `kai-value-change` | `CustomEvent<{ value: string }>` | Fired on every input change. |
|
|
2804
|
-
| `kai-voice` | `CustomEvent<Record<string, never>>` | The Mic / voice button was clicked. |
|
|
4072
|
+
| `kai-aside-resize` | `CustomEvent<{ side: "start" \| "end"; width: number }>` | The aside was resized (fires per drag step, keyboard nudge, or a handle double-click reset), width in px. |
|
|
4073
|
+
| `kai-aside-toggle` | `CustomEvent<{ side: "start" \| "end"; collapsed: false \| true }>` | An aside collapsed or expanded (a method, the breakpoint, the drawer's Escape). |
|
|
2805
4074
|
|
|
2806
|
-
**Methods** (call on the element instance: `document.querySelector('kai-workspace').
|
|
4075
|
+
**Methods** (call on the element instance: `document.querySelector('kai-workspace').toggleAside(…)`):
|
|
2807
4076
|
|
|
2808
4077
|
| Method | Signature | Description |
|
|
2809
4078
|
|---|---|---|
|
|
2810
|
-
| `
|
|
2811
|
-
| `
|
|
2812
|
-
| `
|
|
2813
|
-
| `focus` | `(options?: FocusOptions): void` | Focus the thread's composer. |
|
|
2814
|
-
| `clear` | `(): void` | Clear the thread draft + staged attachments. |
|
|
2815
|
-
| `send` | `(): void` | Submit the current thread draft programmatically (fires `kai-submit`). |
|
|
2816
|
-
| `scrollToBottom` | `(behavior?: ScrollBehavior): void` | Scroll the thread to the newest message. |
|
|
4079
|
+
| `toggleAside` | `(side: WorkspaceAsideSide): void` | Collapse/expand one aside (fires `kai-aside-toggle`). |
|
|
4080
|
+
| `collapseAside` | `(side: WorkspaceAsideSide): void` | Force one aside collapsed (fires `kai-aside-toggle`). |
|
|
4081
|
+
| `expandAside` | `(side: WorkspaceAsideSide): void` | Force one aside expanded (fires `kai-aside-toggle`). |
|
|
2817
4082
|
|
|
2818
4083
|
**Slots** (project your own markup via `slot="name"` on a light-DOM child):
|
|
2819
4084
|
|
|
2820
4085
|
| Slot | Mode | Description |
|
|
2821
4086
|
|---|---|---|
|
|
2822
|
-
|
|
|
2823
|
-
| `
|
|
2824
|
-
| `
|
|
2825
|
-
| `main` |
|
|
4087
|
+
| _(default)_ | inject | The main region content (same region as the `main` slot): your `<kai-chat>`, or any app view. |
|
|
4088
|
+
| `header` | inject | The top band across the full shell width (app bar, tabs, breadcrumbs). |
|
|
4089
|
+
| `start` | inject | The inline-start aside column (a conversation rail, a nav, a file tree). Resizable and collapsible. |
|
|
4090
|
+
| `main` | inject | The main region. Unnamed children project here too, via the default slot. |
|
|
4091
|
+
| `end` | inject | The inline-end aside column (inspector, notes, preview). Resizable and collapsible. |
|
|
4092
|
+
| `footer` | inject | The bottom band across the full shell width (status bar, disclaimers). |
|
|
2826
4093
|
|
|
2827
4094
|
**Styleable parts** (restyle from outside via `kai-workspace::part(name)`):
|
|
2828
4095
|
|
|
2829
4096
|
| Part | Description |
|
|
2830
4097
|
|---|---|
|
|
2831
|
-
| `::part(
|
|
4098
|
+
| `::part(aside)` | Both aside columns match this part (each also matches its own start/end part). Restyle the shared aside surface or border from outside; the --kai-workspace-start-* and --kai-workspace-end-* custom properties set the widths. — `kai-workspace::part(aside) { background: var(--color-card) }` |
|
|
4099
|
+
| `::part(header)` | The top band across the full shell width (app bar, tabs, breadcrumbs). |
|
|
4100
|
+
| `::part(start)` | The inline-start aside column (a conversation rail, a nav, a file tree). Resizable and collapsible. |
|
|
4101
|
+
| `::part(main)` | The main region. Unnamed children project here too, via the default slot. |
|
|
4102
|
+
| `::part(end)` | The inline-end aside column (inspector, notes, preview). Resizable and collapsible. |
|
|
4103
|
+
| `::part(footer)` | The bottom band across the full shell width (status bar, disclaimers). |
|
|
2832
4104
|
|
|
2833
4105
|
---
|
|
2834
4106
|
|