@kitn.ai/ui 0.20.1 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -45
- package/dist/components/artifact-card.d.ts +51 -0
- package/dist/components/artifact.d.ts +1 -1
- package/dist/components/attachments.d.ts +2 -2
- package/dist/components/audio-visualizer/index.d.ts +3 -3
- package/dist/components/audio-visualizer/labs/lab-choreography.d.ts +59 -0
- package/dist/components/audio-visualizer/labs/lab-shaders.d.ts +22 -0
- package/dist/components/audio-visualizer/labs/lab-visualizer.d.ts +18 -0
- package/dist/components/audio-visualizer/variant-aurora.d.ts +2 -2
- package/dist/components/audio-visualizer/variant-bar.d.ts +2 -2
- package/dist/components/audio-visualizer/variant-custom.d.ts +2 -2
- package/dist/components/audio-visualizer/variant-grid.d.ts +1 -1
- package/dist/components/audio-visualizer/variant-radial.d.ts +1 -1
- package/dist/components/audio-visualizer/variant-wave.d.ts +1 -1
- package/dist/components/card-fallback.d.ts +12 -2
- package/dist/components/card-renderer.d.ts +63 -2
- package/dist/components/chat-container.d.ts +3 -0
- package/dist/components/chat-scope-picker.d.ts +2 -2
- package/dist/components/chat-thread.d.ts +68 -18
- package/dist/components/choice-card.d.ts +3 -28
- package/dist/components/composer-dom.d.ts +1 -1
- package/dist/components/composer-highlight.d.ts +1 -1
- package/dist/components/composer-history.d.ts +1 -1
- package/dist/components/composer.d.ts +1 -1
- package/dist/components/confirm-card.d.ts +3 -18
- package/dist/components/context.d.ts +8 -4
- package/dist/components/conversation-item.d.ts +1 -1
- package/dist/components/conversation-list.d.ts +4 -3
- package/dist/components/embed.d.ts +3 -3
- package/dist/components/file-tree.d.ts +11 -22
- package/dist/components/form-widgets.d.ts +1 -1
- package/dist/components/form.d.ts +3 -44
- package/dist/components/link-preview.d.ts +3 -3
- package/dist/components/loader.d.ts +30 -48
- package/dist/components/message-skills.d.ts +4 -0
- package/dist/components/message.d.ts +84 -21
- package/dist/components/model-switcher.d.ts +2 -2
- package/dist/components/prompt-input.d.ts +2 -2
- package/dist/components/response-compare-types.d.ts +11 -2
- package/dist/components/response-compare.d.ts +3 -3
- package/dist/components/source.d.ts +12 -3
- package/dist/components/tasks-card.d.ts +3 -31
- package/dist/components/thread.d.ts +17 -5
- package/dist/components/toast.d.ts +2 -2
- package/dist/components/tool-classify.d.ts +8 -0
- package/dist/components/tool-types.d.ts +29 -1
- package/dist/components/tool.d.ts +3 -3
- package/dist/components/use-card-resolution.d.ts +1 -1
- package/dist/create-tween-D3Y5qiij.js +338 -0
- package/dist/create-tween-DGKY1FDY.js +341 -0
- package/dist/create-tween-paho5Rh-.js +7 -0
- package/dist/custom-elements.json +1681 -454
- package/dist/elements/agent-card.js +1 -1
- package/dist/elements/artifact.js +1 -1
- package/dist/elements/attachments.js +1 -1
- package/dist/elements/audio-visualizer.js +1 -1
- package/dist/elements/avatar.js +1 -1
- package/dist/elements/badge.js +1 -1
- package/dist/elements/button.js +1 -1
- package/dist/elements/card.js +1 -1
- package/dist/elements/cards.js +1 -1
- package/dist/elements/chain-of-thought.js +1 -1
- package/dist/elements/chat-scope-picker.js +1 -1
- package/dist/elements/chat-types.d.ts +70 -10
- package/dist/elements/chat-workspace.js +1 -1
- package/dist/elements/chat.js +1 -1
- package/dist/elements/checkpoint.js +1 -1
- package/dist/elements/choice.js +1 -1
- package/dist/elements/chunks/{Icon-Tj58ES_d.js → Icon-C-2LvGDG.js} +1 -1
- package/dist/elements/chunks/{action-icons-ns1Fnedj.js → action-icons-CaYoImEk.js} +1 -1
- package/dist/elements/chunks/arrow-left-Bb46H7w9.js +1 -0
- package/dist/elements/chunks/artifact-BBIscOxJ.js +1 -0
- package/dist/elements/chunks/{attachments-mCB5F1Fq.js → attachments-BB56zoji.js} +1 -1
- package/dist/elements/chunks/audio-visualizer-BxOx4THz.js +1 -0
- package/dist/elements/chunks/{badge-B4Zuajl2.js → badge-SxLSZ19R.js} +1 -1
- package/dist/elements/chunks/{button-ZWNif3--.js → button-CmkUkz6c.js} +1 -1
- package/dist/elements/chunks/card-renderer-Db4ffkWv.js +1 -0
- package/dist/elements/chunks/card-tags-D8lZ-C_U.js +1 -0
- package/dist/elements/chunks/chat-thread-B3Ks-cKr.js +1 -0
- package/dist/elements/chunks/check-C8desc_h.js +1 -0
- package/dist/elements/chunks/chevron-down-DKtc2HNB.js +1 -0
- package/dist/elements/chunks/chevron-right-Dw4GPhwL.js +1 -0
- package/dist/elements/chunks/choice-card-Q9cYYDyM.js +1 -0
- package/dist/elements/chunks/circle-BeokBsvB.js +1 -0
- package/dist/elements/chunks/circle-check-wJSqt859.js +1 -0
- package/dist/elements/chunks/{circle-x-B7faJFlY.js → circle-x-D0MEfxOM.js} +1 -1
- package/dist/elements/chunks/code-block-C8VzAraE.js +1 -0
- package/dist/elements/chunks/{collapsible-DM1Ckd4e.js → collapsible-Cvj61obE.js} +1 -1
- package/dist/elements/chunks/{composer-BguS0QL_.js → composer-D86ztyL0.js} +2 -2
- package/dist/elements/chunks/confirm-card-BdyDAGLw.js +1 -0
- package/dist/elements/chunks/context-WHDl-xSx.js +1 -0
- package/dist/elements/chunks/conversation-list-CdR14_Y4.js +1 -0
- package/dist/elements/chunks/create-tween-C_A_B638.js +7 -0
- package/dist/elements/chunks/{default-input-Cqqilg9t.js → default-input-CSZqUM81.js} +1 -1
- package/dist/elements/chunks/define-BzPnNsXg.js +1 -0
- package/dist/elements/chunks/{disclosure-CIZ5c8nO.js → disclosure-DVKsoc1F.js} +1 -1
- package/dist/elements/chunks/{download-Dsbg3ste.js → download-BjA54IMJ.js} +1 -1
- package/dist/elements/chunks/{dropdown-BDd1-Zgi.js → dropdown-BjVVHWXH.js} +1 -1
- package/dist/elements/chunks/{ellipsis-Dw6qrss-.js → ellipsis-BNedmqfi.js} +1 -1
- package/dist/elements/chunks/embed-DQsl4XEq.js +1 -0
- package/dist/elements/chunks/{external-link-Vt74zAoL.js → external-link-Di-dw2CU.js} +1 -1
- package/dist/elements/chunks/{file-text-CZrZF6_v.js → file-text-B3sAODCV.js} +1 -1
- package/dist/elements/chunks/{file-tree-DF4okKyo.js → file-tree-Dgto-bMn.js} +1 -1
- package/dist/elements/chunks/{folder-glP5rha4.js → folder-D6rMadFV.js} +1 -1
- package/dist/elements/chunks/form-CILxvhQ7.js +1 -0
- package/dist/elements/chunks/hover-card-BtiF4XiU.js +1 -0
- package/dist/elements/chunks/{icon-BLb8beoi.js → icon-DKkmze3y.js} +1 -1
- package/dist/elements/chunks/{info-CIsv92JK.js → info-CWRjZkuy.js} +1 -1
- package/dist/elements/chunks/{input-Dds1kFyt.js → input--QJBK6BA.js} +1 -1
- package/dist/elements/chunks/{kbd-De_xbgi9.js → kbd-C3zuKh8R.js} +1 -1
- package/dist/elements/chunks/{link-i4r_k7z4.js → link-BL5JA7Q0.js} +1 -1
- package/dist/elements/chunks/link-preview-CU37bbXh.js +1 -0
- package/dist/elements/chunks/{loader-BmQH4s3V.js → loader-XmpyxyRH.js} +1 -1
- package/dist/elements/chunks/{markdown-Zi21WhUz.js → markdown-3Gv5UZ_o.js} +1 -1
- package/dist/elements/chunks/message-CnBA2OUt.js +1 -0
- package/dist/elements/chunks/message-X5NZbq-W.js +1 -0
- package/dist/elements/chunks/message-square-CnpAm_P0.js +1 -0
- package/dist/elements/chunks/{minimize-2--ifeCGrl.js → minimize-2-DwyrUTcM.js} +1 -1
- package/dist/elements/chunks/{model-switcher-B94YAKsM.js → model-switcher-CclaJEHt.js} +1 -1
- package/dist/elements/chunks/{overlay-kD8dwRv_.js → overlay-Cepd-KcS.js} +1 -1
- package/dist/elements/chunks/{paperclip-q21dYOSK.js → paperclip-CdwKjrlu.js} +1 -1
- package/dist/elements/chunks/{progress-bar-BrAIIaaJ.js → progress-bar-DMVXBD3b.js} +1 -1
- package/dist/elements/chunks/{prompt-suggestion-Bjn2K-fR.js → prompt-suggestion-Di_WZE_5.js} +1 -1
- package/dist/elements/chunks/reasoning-D0jMch5u.js +1 -0
- package/dist/elements/chunks/{resizable-Dp4JExql.js → resizable-1znbL1Gv.js} +1 -1
- package/dist/elements/chunks/{rotate-cw-CFWWkpj2.js → rotate-cw-DVhhgx8n.js} +1 -1
- package/dist/elements/chunks/{scroll-area-Bb0kHH1j.js → scroll-area-Bs4bFS1P.js} +1 -1
- package/dist/elements/chunks/scroll-button-Bet5NLc4.js +1 -0
- package/dist/elements/chunks/{separator-9kRgr1X9.js → separator-C5Gh8qGC.js} +1 -1
- package/dist/elements/chunks/{settings-B8lmec4D.js → settings-5vmEFg0K.js} +1 -1
- package/dist/elements/chunks/{settings-group-I0hEuXiN.js → settings-group-BWLB38kz.js} +1 -1
- package/dist/elements/chunks/{share-C3lTiYGt.js → share-BZ3G_DaB.js} +1 -1
- package/dist/elements/chunks/skeleton-DafPJQTp.js +1 -0
- package/dist/elements/chunks/slots-CIw9RlAe.js +1 -0
- package/dist/elements/chunks/source-D_qj_6uJ.js +1 -0
- package/dist/elements/chunks/{star-CSJtGxgv.js → star-Cuv7j2IE.js} +1 -1
- package/dist/elements/chunks/{store-CHyW95TP.js → store-C1StPHfW.js} +1 -1
- package/dist/elements/chunks/tasks-card-EyoBZfpA.js +1 -0
- package/dist/elements/chunks/{text-shimmer-Cn9TmNpI.js → text-shimmer-D9gRSh34.js} +1 -1
- package/dist/elements/chunks/textarea-DuhtHfcm.js +1 -0
- package/dist/elements/chunks/{thumbs-up-PNVVThEF.js → thumbs-up-B9hwv43w.js} +1 -1
- package/dist/elements/chunks/{toast-store-DKbQn3Wn.js → toast-store-Bm3DUEDe.js} +1 -1
- package/dist/elements/chunks/{tool-DjTRpIpl.js → tool-CDtW7L0z.js} +1 -1
- package/dist/elements/chunks/tooltip-eB226mGK.js +1 -0
- package/dist/elements/chunks/{triangle-alert-DsHTHCPm.js → triangle-alert-C1SNzTqK.js} +1 -1
- package/dist/elements/chunks/use-card-resolution-YZh2MvI5.js +1 -0
- package/dist/elements/chunks/{variant-aurora-Dc4nQUia.js → variant-aurora-Cj1F7D0l.js} +2 -2
- package/dist/elements/chunks/{variant-custom-y_3kVQhS.js → variant-custom-T9gpt7QE.js} +1 -1
- package/dist/elements/chunks/{variant-wave-D5OR1-9Z.js → variant-wave-CxU9z-TE.js} +2 -2
- package/dist/elements/chunks/{video-B7vmenoC.js → video-CK4R7Rus.js} +1 -1
- package/dist/elements/chunks/x-D1qx0aLd.js +1 -0
- package/dist/elements/coachmark.js +1 -1
- package/dist/elements/code-block.js +1 -1
- package/dist/elements/command.js +1 -1
- package/dist/elements/compare.js +1 -1
- package/dist/elements/composer.js +1 -1
- package/dist/elements/confirm-card.js +1 -1
- package/dist/elements/context-meter.js +1 -1
- package/dist/elements/conversation-list.js +1 -1
- package/dist/elements/default-input.d.ts +7 -7
- package/dist/elements/define.d.ts +1 -0
- package/dist/elements/dialog.js +1 -1
- package/dist/elements/disclosure.d.ts +1 -1
- package/dist/elements/editable-label.js +1 -1
- package/dist/elements/element-data-types.d.ts +107 -0
- package/dist/elements/embed.js +1 -1
- package/dist/elements/empty.js +1 -1
- package/dist/elements/feedback-bar.js +1 -1
- package/dist/elements/file-tree.js +1 -1
- package/dist/elements/file-upload.js +1 -1
- package/dist/elements/form.js +1 -1
- package/dist/elements/hover-card.js +1 -1
- package/dist/elements/icon.js +1 -1
- package/dist/elements/image.js +1 -1
- package/dist/elements/input.js +1 -1
- package/dist/elements/kbd.js +1 -1
- package/dist/elements/link-preview.js +1 -1
- package/dist/elements/loader.js +1 -1
- package/dist/elements/markdown.js +1 -1
- package/dist/elements/menu.js +1 -1
- package/dist/elements/message-skills.js +1 -1
- package/dist/elements/message.js +1 -1
- package/dist/elements/model-switcher.js +1 -1
- package/dist/elements/nav.js +1 -1
- package/dist/elements/notice.js +1 -1
- package/dist/elements/pane-group.js +1 -1
- package/dist/elements/pane.js +1 -1
- package/dist/elements/popover.js +1 -1
- package/dist/elements/progress-bar.js +1 -1
- package/dist/elements/prompt-dock.js +1 -1
- package/dist/elements/prompt-input.js +1 -1
- package/dist/elements/prompt-suggestions.js +1 -1
- package/dist/elements/reasoning.js +1 -1
- package/dist/elements/register.d.ts +8 -6
- package/dist/elements/remote.js +1 -1
- package/dist/elements/resizable.js +1 -1
- package/dist/elements/response-stream.js +2 -2
- package/dist/elements/screen.js +1 -1
- package/dist/elements/scroll-area.js +1 -1
- package/dist/elements/scroll-button.js +1 -1
- package/dist/elements/search.js +1 -1
- package/dist/elements/segmented.js +1 -1
- package/dist/elements/separator.js +1 -1
- package/dist/elements/setting-item.js +1 -1
- package/dist/elements/settings-group.js +1 -1
- package/dist/elements/skeleton.js +1 -1
- package/dist/elements/slots.d.ts +16 -0
- package/dist/elements/source.js +1 -1
- package/dist/elements/status.js +1 -1
- package/dist/elements/switch.js +1 -1
- package/dist/elements/tabs.js +1 -1
- package/dist/elements/tasks.js +1 -1
- package/dist/elements/text-shimmer.js +1 -1
- package/dist/elements/thinking-bar.js +1 -1
- package/dist/elements/thread.js +1 -1
- package/dist/elements/toast.js +1 -1
- package/dist/elements/tool.js +1 -1
- package/dist/elements/tooltip.js +1 -1
- package/dist/elements/validate-messages.d.ts +25 -0
- package/dist/elements/voice-input.js +1 -1
- package/dist/elements/voice-output.js +1 -1
- package/dist/elements.d.ts +2725 -214
- package/dist/index.d.ts +163 -125
- package/dist/index.js +6336 -5802
- package/dist/index.server.d.ts +5 -0
- package/dist/index.server.js +5446 -4937
- package/dist/kai-provider.es.d.ts +5 -0
- package/dist/kai-provider.es.js +79 -77
- package/dist/kai.es.d.ts +5 -0
- package/dist/kai.es.js +1 -1
- package/dist/llms/llms-full.txt +762 -148
- package/dist/llms/llms.txt +57 -5
- package/dist/mcp.es.js +4010 -770
- package/dist/primitives/card-component-types.d.ts +11 -0
- package/dist/primitives/card-data-types.d.ts +196 -0
- package/dist/primitives/card-host.d.ts +1 -1
- package/dist/primitives/card-recovery.d.ts +3 -3
- package/dist/primitives/card-registry.d.ts +4 -14
- package/dist/primitives/card-resolution.d.ts +1 -1
- package/dist/primitives/card-routing.d.ts +1 -1
- package/dist/primitives/card-tags.d.ts +11 -0
- package/dist/primitives/card-validate-cards.d.ts +57 -0
- package/dist/primitives/card-validate-generator.testlib.d.ts +20 -0
- package/dist/primitives/card-validate-schemas.d.ts +17 -0
- package/dist/primitives/card-validate.d.ts +24 -0
- package/dist/primitives/create-kai-chat.d.ts +3 -3
- package/dist/primitives/embed-providers.d.ts +1 -1
- package/dist/primitives/link-preview.d.ts +1 -1
- package/dist/primitives/message-feedback.d.ts +2 -2
- package/dist/primitives/toast-store.d.ts +1 -1
- package/dist/react/index.d.ts +717 -299
- package/dist/react/use-kai-chat.d.ts +8 -4
- package/dist/react.d.ts +5 -0
- package/dist/react.js +417 -316
- package/dist/register-impl-bzrZ44ft.js +145 -0
- package/dist/remote/host-embed.d.ts +1 -1
- package/dist/remote/index.d.ts +2 -2
- package/dist/remote/provider-runtime.d.ts +2 -2
- package/dist/remote/provider.d.ts +2 -2
- package/dist/remote/validate.d.ts +1 -1
- package/dist/remote/wire.d.ts +1 -1
- package/dist/schemas/artifact.schema.json +92 -0
- package/dist/schemas/embed.schema.json +0 -2
- package/dist/schemas/from-tool-call.d.ts +48 -0
- package/dist/schemas/index.d.ts +48 -0
- package/dist/schemas/link.schema.json +0 -2
- package/dist/schemas/provider-subsets.d.ts +136 -0
- package/dist/schemas/registry.d.ts +140 -0
- package/dist/schemas/tool-defs.d.ts +121 -0
- package/dist/schemas.d.ts +5 -0
- package/dist/schemas.js +717 -0
- package/dist/solid-BCJUQqwS.js +22750 -0
- package/dist/solid-BdENBcEq.js +20108 -0
- package/dist/solid.d.ts +66 -0
- package/dist/solid.js +261 -0
- package/dist/solid.server.d.ts +5 -0
- package/dist/solid.server.js +261 -0
- package/dist/state/index.d.ts +14 -4
- package/dist/state/messages.d.ts +8 -3
- package/dist/state/mock.d.ts +46 -0
- package/dist/state/parts.d.ts +85 -0
- package/dist/state/stream.d.ts +15 -9
- package/dist/state.d.ts +5 -0
- package/dist/state.js +253 -84
- package/dist/ui/action-icons.d.ts +1 -1
- package/dist/ui/collapsible.d.ts +11 -6
- package/dist/ui/dropdown.d.ts +47 -42
- package/dist/ui/pane-group.d.ts +1 -1
- package/dist/variant-aurora-1wMcXRUS.js +381 -0
- package/dist/variant-aurora-BKHHiOgL.js +391 -0
- package/dist/{variant-aurora-DVtGqVH6.js → variant-aurora-DOb_o4pC.js} +1 -1
- package/dist/{variant-custom-Dif-IV4u.js → variant-custom-CJGB0Kbw.js} +1 -1
- package/dist/variant-custom-Co0CkXJF.js +200 -0
- package/dist/variant-custom-DWSSgmd2.js +170 -0
- package/dist/variant-wave-C3-45cL6.js +218 -0
- package/dist/{variant-wave-DbfNdvSi.js → variant-wave-CEODq-9q.js} +1 -1
- package/dist/variant-wave-jdXBnCUZ.js +208 -0
- package/dist/wire/chunk.d.ts +171 -0
- package/dist/wire/consume.d.ts +26 -0
- package/dist/wire/encode.d.ts +148 -0
- package/dist/wire/formats/anthropic.d.ts +2 -0
- package/dist/wire/formats/openai.d.ts +5 -0
- package/dist/wire/index.d.ts +14 -0
- package/dist/wire/read.d.ts +26 -0
- package/dist/wire/sink-helpers.d.ts +21 -0
- package/dist/wire/sse.d.ts +33 -0
- package/dist/wire.d.ts +5 -53
- package/dist/wire.js +898 -0
- package/frameworks/react/index.tsx +183 -154
- package/frameworks/react/runtime.tsx +21 -1
- package/frameworks/react/use-kai-chat.tsx +11 -1
- package/llms-full.txt +762 -148
- package/llms.txt +57 -5
- package/package.json +61 -8
- package/src/agent-tooling/archetypes.ts +96 -0
- package/src/agent-tooling/integrations/anthropic.ts +412 -0
- package/src/agent-tooling/integrations/cloudflare.ts +136 -52
- package/src/agent-tooling/integrations/langgraph.ts +67 -13
- package/src/agent-tooling/integrations/mastra.ts +120 -12
- package/src/agent-tooling/integrations/mock.ts +43 -8
- package/src/agent-tooling/integrations/ollama.ts +61 -24
- package/src/agent-tooling/integrations/openai.ts +87 -0
- package/src/agent-tooling/integrations/openrouter.ts +56 -8
- package/src/agent-tooling/integrations/pi.ts +79 -23
- package/src/agent-tooling/integrations/pydantic-ai.ts +24 -2
- package/src/agent-tooling/integrations/vercel-ai-sdk.ts +84 -13
- package/src/agent-tooling/mcp/manifest.ts +206 -25
- package/src/agent-tooling/mcp/tools/debug.ts +13 -7
- package/src/agent-tooling/mcp/tools/reference.ts +285 -6
- package/src/agent-tooling/mcp/tools/scaffold.ts +3345 -675
- package/src/agent-tooling/registry.ts +17 -0
- package/src/agent-tooling/types.ts +229 -1
- package/src/components/artifact-card.tsx +120 -0
- package/src/components/audio-visualizer/index.tsx +9 -0
- package/src/components/audio-visualizer/labs/lab-choreography.ts +187 -0
- package/src/components/audio-visualizer/labs/lab-shaders.ts +363 -0
- package/src/components/audio-visualizer/labs/lab-visualizer.tsx +102 -0
- package/src/components/audio-visualizer/shader-canvas.tsx +31 -1
- package/src/components/card-fallback.tsx +42 -10
- package/src/components/card-renderer.tsx +146 -9
- package/src/components/chat-container.tsx +4 -0
- package/src/components/chat-thread.tsx +118 -51
- package/src/components/choice-card.tsx +17 -29
- package/src/components/composer.tsx +12 -2
- package/src/components/confirm-card.tsx +24 -20
- package/src/components/context.tsx +12 -4
- package/src/components/conversation-list.tsx +18 -2
- package/src/components/file-tree.tsx +11 -22
- package/src/components/file-upload.tsx +14 -8
- package/src/components/form.tsx +14 -47
- package/src/components/loader.tsx +39 -12
- package/src/components/message-skills.tsx +4 -0
- package/src/components/message.tsx +294 -46
- package/src/components/reasoning.tsx +92 -5
- package/src/components/response-compare-types.ts +25 -0
- package/src/components/response-compare.tsx +4 -9
- package/src/components/source.tsx +50 -24
- package/src/components/tasks-card.tsx +10 -33
- package/src/components/thread.tsx +84 -42
- package/src/components/toast.tsx +11 -5
- package/src/components/tool-classify.ts +26 -0
- package/src/components/tool-types.ts +31 -1
- package/src/elements/agent-card.tsx +11 -11
- package/src/elements/artifact.tsx +15 -7
- package/src/elements/attachments.tsx +6 -4
- package/src/elements/avatar.tsx +1 -1
- package/src/elements/button.tsx +3 -3
- package/src/elements/card.tsx +2 -2
- package/src/elements/cards.tsx +109 -14
- package/src/elements/chain-of-thought.tsx +14 -10
- package/src/elements/chat-scope-picker.tsx +9 -7
- package/src/elements/chat-types.ts +59 -6
- package/src/elements/chat-workspace.tsx +50 -9
- package/src/elements/chat.tsx +64 -3
- package/src/elements/choice.tsx +8 -8
- package/src/elements/coachmark.tsx +1 -1
- package/src/elements/command.tsx +8 -23
- package/src/elements/compare.tsx +7 -6
- package/src/elements/compiled.css +1 -1
- package/src/elements/composer.tsx +8 -8
- package/src/elements/confirm-card.tsx +9 -9
- package/src/elements/context-meter.tsx +4 -18
- package/src/elements/conversation-list.tsx +14 -9
- package/src/elements/default-input.tsx +3 -3
- package/src/elements/define.tsx +178 -43
- package/src/elements/editable-label.tsx +17 -2
- package/src/elements/element-data-types.ts +112 -0
- package/src/elements/element-meta.json +1596 -304
- package/src/elements/element-types.d.ts +2636 -211
- package/src/elements/feedback-bar.tsx +1 -1
- package/src/elements/file-tree.tsx +1 -1
- package/src/elements/file-upload.tsx +1 -1
- package/src/elements/form.tsx +14 -6
- package/src/elements/input.tsx +1 -1
- package/src/elements/kbd.tsx +1 -1
- package/src/elements/menu.tsx +10 -29
- package/src/elements/message-skills.tsx +7 -9
- package/src/elements/message.tsx +200 -32
- package/src/elements/model-switcher.tsx +5 -3
- package/src/elements/pane.tsx +1 -1
- package/src/elements/prompt-dock.tsx +8 -8
- package/src/elements/prompt-input.tsx +10 -10
- package/src/elements/prompt-suggestions.tsx +7 -4
- package/src/elements/reasoning.tsx +1 -1
- package/src/elements/register.ts +9 -0
- package/src/elements/resizable.globals.d.ts +36 -0
- package/src/elements/resizable.tsx +24 -13
- package/src/elements/response-stream.tsx +1 -1
- package/src/elements/scroll-button.tsx +1 -1
- package/src/elements/search.tsx +6 -1
- package/src/elements/segmented.tsx +5 -11
- package/src/elements/separator.tsx +1 -1
- package/src/elements/slots.ts +57 -24
- package/src/elements/source.tsx +21 -22
- package/src/elements/switch.tsx +3 -3
- package/src/elements/tasks.tsx +5 -4
- package/src/elements/thread.tsx +36 -4
- package/src/elements/toast.tsx +3 -2
- package/src/elements/tool.tsx +1 -1
- package/src/elements/validate-messages.ts +76 -0
- package/src/elements/voice-input.tsx +7 -7
- package/src/elements/voice-output.tsx +4 -4
- package/src/index.ts +141 -6
- package/src/primitives/card-component-types.ts +63 -0
- package/src/primitives/card-data-types.ts +312 -0
- package/src/primitives/card-recovery.ts +13 -2
- package/src/primitives/card-registry.tsx +34 -23
- package/src/primitives/card-schemas/artifact.schema.json +92 -0
- package/src/primitives/card-schemas/embed.schema.json +0 -2
- package/src/primitives/card-schemas/link.schema.json +0 -2
- package/src/primitives/card-tags.ts +61 -0
- package/src/primitives/card-validate-cards.ts +255 -0
- package/src/primitives/card-validate-generator.testlib.ts +38 -0
- package/src/primitives/card-validate-schemas.ts +58 -0
- package/src/primitives/card-validate.ts +48 -20
- package/src/primitives/create-tween.ts +54 -2
- package/src/primitives/message-feedback.ts +3 -2
- package/src/primitives/use-audio-analysis.ts +20 -1
- package/src/primitives/use-sequencer.ts +20 -1
- package/src/schemas/from-tool-call.ts +151 -0
- package/src/schemas/index.ts +340 -0
- package/src/schemas/provider-subsets.ts +538 -0
- package/src/schemas/registry.ts +355 -0
- package/src/schemas/tool-defs.ts +492 -0
- package/src/solid.ts +150 -0
- package/src/state/index.ts +27 -1
- package/src/state/messages.ts +21 -4
- package/src/state/mock.ts +211 -0
- package/src/state/parts.ts +295 -0
- package/src/state/stream.ts +56 -58
- package/src/ui/collapsible.tsx +17 -5
- package/src/ui/dropdown.tsx +46 -12
- package/src/ui/overlay.tsx +10 -4
- package/src/wire/chunk.ts +201 -0
- package/src/wire/consume.ts +440 -0
- package/src/wire/encode.ts +474 -0
- package/src/wire/formats/anthropic.ts +267 -0
- package/src/wire/formats/openai.ts +187 -0
- package/src/wire/index.ts +52 -0
- package/src/wire/read.ts +152 -0
- package/src/wire/sink-helpers.ts +50 -0
- package/src/wire/sse.ts +140 -0
- package/dist/create-tween-hvpmSuwW.js +0 -7
- package/dist/elements/chunks/arrow-left-DQocUFvy.js +0 -1
- package/dist/elements/chunks/audio-visualizer-BqNy7dfm.js +0 -1
- package/dist/elements/chunks/card-BkHbp4BA.js +0 -1
- package/dist/elements/chunks/chat-thread-CkRN6pZ7.js +0 -1
- package/dist/elements/chunks/check-B9-gn6SS.js +0 -1
- package/dist/elements/chunks/chevron-down-BNsxc2hk.js +0 -1
- package/dist/elements/chunks/chevron-right-Hv00StBO.js +0 -1
- package/dist/elements/chunks/circle-0e3KJTDr.js +0 -1
- package/dist/elements/chunks/circle-check-BNAS0JEy.js +0 -1
- package/dist/elements/chunks/code-block-BFUHn6Va.js +0 -1
- package/dist/elements/chunks/context-Cb7k4F-v.js +0 -1
- package/dist/elements/chunks/conversation-list-CvoAkWhN.js +0 -1
- package/dist/elements/chunks/create-tween-oVvAIG8r.js +0 -7
- package/dist/elements/chunks/define-D1din7Ld.js +0 -1
- package/dist/elements/chunks/hover-card-Cmm6Q53m.js +0 -1
- package/dist/elements/chunks/message-BqyXDN3-.js +0 -1
- package/dist/elements/chunks/message-feedback-Djr3z6Yb.js +0 -1
- package/dist/elements/chunks/message-square-vFAgbDaX.js +0 -1
- package/dist/elements/chunks/reasoning-DTpCGtnY.js +0 -1
- package/dist/elements/chunks/scroll-button-B0cH5fXf.js +0 -1
- package/dist/elements/chunks/skeleton-DSXW5Yya.js +0 -1
- package/dist/elements/chunks/slots--V19HwUb.js +0 -1
- package/dist/elements/chunks/textarea-CUwqAX0G.js +0 -1
- package/dist/elements/chunks/tooltip-CgBZCVKp.js +0 -1
- package/dist/elements/chunks/use-card-resolution-BSu_XHjH.js +0 -1
- package/dist/elements/chunks/x-GRUeeQFT.js +0 -1
- package/dist/host-embed.d.ts +0 -33
- package/dist/kai-provider.d.ts +0 -2
- package/dist/origin.d.ts +0 -5
- package/dist/provider-runtime.d.ts +0 -27
- package/dist/register-impl-DQyejdfb.js +0 -145
- package/dist/validate.d.ts +0 -4
- package/dist/version.d.ts +0 -4
- package/src/components/component-meta.json +0 -4550
- package/src/elements/card-media.jpg +0 -0
- package/src/elements/resizable.d.ts +0 -27
- package/src/elements/video-poster.jpg +0 -0
package/src/state/index.ts
CHANGED
|
@@ -1,6 +1,32 @@
|
|
|
1
1
|
// Framework-neutral state core for @kitn.ai/ui. Pure functions over ChatMessage[]
|
|
2
2
|
// + a typed streaming handle. No React/Solid runtime — drives a consumer setter.
|
|
3
|
-
export { appendMessage, upsertMessage, updateMessage, removeMessage,
|
|
3
|
+
export { appendMessage, upsertMessage, updateMessage, removeMessage, appendText, textMessage, partsToText } from './messages';
|
|
4
4
|
export { addSuggestion, removeSuggestion } from './suggestions';
|
|
5
5
|
export { createAssistantStream, onStreamSettled } from './stream';
|
|
6
6
|
export type { SetMessages, AssistantStream } from './stream';
|
|
7
|
+
export { appendTextPart, appendReasoningPart, upsertToolPart, upsertCardPart, fingerprint } from './parts';
|
|
8
|
+
export type { ReasoningOpts } from './parts';
|
|
9
|
+
|
|
10
|
+
// The zero-config mock. Produces SSE frames rather than folding parts directly,
|
|
11
|
+
// so the no-backend preview runs through the SAME parser a real provider does —
|
|
12
|
+
// and is deliberately impossible to mistake for one. See ./mock.
|
|
13
|
+
export { createMockResponder, DEFAULT_MOCK_REPLIES, MOCK_BANNER, MOCK_MARKER, MOCK_MARKER_KEY, MOCK_MODEL_ID } from './mock';
|
|
14
|
+
export type { MockResponder, MockResponderOptions } from './mock';
|
|
15
|
+
|
|
16
|
+
// The content-model types every signature above mentions. Without these a
|
|
17
|
+
// consumer importing only from '@kitn.ai/ui/state' cannot annotate the very
|
|
18
|
+
// values these helpers take and return (the missing-annotation gap that made an
|
|
19
|
+
// un-annotated `const history = [...]` widen to `string` and fail TS2322).
|
|
20
|
+
export type {
|
|
21
|
+
ChatMessage, ChatMessageAction, CustomAction, AvatarData, FeedbackVote, MessagePart,
|
|
22
|
+
MessageSource, RawOrigin,
|
|
23
|
+
} from '../elements/chat-types';
|
|
24
|
+
export type { ToolPart } from '../components/tool-types';
|
|
25
|
+
export type { ToolKind } from '../components/tool-classify';
|
|
26
|
+
// upsertToolPart defaults `kind` to classifyTool(type) and reverts it on a type
|
|
27
|
+
// change (see ./parts), and ToolPart.kind's doc comment names the function, so a
|
|
28
|
+
// consumer computing `kind` itself needs the identical classifier — not a
|
|
29
|
+
// hand-rolled copy that drifts from ours.
|
|
30
|
+
export { classifyTool } from '../components/tool-classify';
|
|
31
|
+
export type { CardEnvelope } from '../primitives/card-contract';
|
|
32
|
+
export type { AttachmentData } from '../components/attachment-types';
|
package/src/state/messages.ts
CHANGED
|
@@ -1,4 +1,21 @@
|
|
|
1
|
-
import type { ChatMessage } from '../elements/chat-types';
|
|
1
|
+
import type { ChatMessage, MessagePart } from '../elements/chat-types';
|
|
2
|
+
import { appendTextPart } from './parts';
|
|
3
|
+
|
|
4
|
+
function newId(): string {
|
|
5
|
+
if (typeof crypto !== 'undefined' && crypto.randomUUID) return crypto.randomUUID();
|
|
6
|
+
return 'kai-' + Math.random().toString(36).slice(2);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** Convenience for the common single-text-part message. */
|
|
10
|
+
export function textMessage(role: ChatMessage['role'], text: string, init: Partial<ChatMessage> = {}): ChatMessage {
|
|
11
|
+
return { id: init.id ?? newId(), role, parts: [{ type: 'text', text }], ...init };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Concatenates every text part. Use where a plain string is genuinely needed
|
|
15
|
+
* (copy-to-clipboard, TTS, a length check). Do NOT use it for rendering. */
|
|
16
|
+
export function partsToText(parts: MessagePart[]): string {
|
|
17
|
+
return parts.filter((p) => p.type === 'text').map((p) => p.text).join('');
|
|
18
|
+
}
|
|
2
19
|
|
|
3
20
|
/** Append a message; returns a new array. */
|
|
4
21
|
export function appendMessage(messages: ChatMessage[], msg: ChatMessage): ChatMessage[] {
|
|
@@ -30,7 +47,7 @@ export function removeMessage(messages: ChatMessage[], id: string): ChatMessage[
|
|
|
30
47
|
return messages.filter((x) => x.id !== id);
|
|
31
48
|
}
|
|
32
49
|
|
|
33
|
-
/** Streaming primitive:
|
|
34
|
-
export function
|
|
35
|
-
return messages.map((x) => (x.id === id ? { ...x,
|
|
50
|
+
/** Streaming primitive: append `delta` onto the matched message's trailing text part. */
|
|
51
|
+
export function appendText(messages: ChatMessage[], id: string, delta: string): ChatMessage[] {
|
|
52
|
+
return messages.map((x) => (x.id === id ? { ...x, parts: appendTextPart(x.parts, delta) } : x));
|
|
36
53
|
}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
// The mock responder: the zero-config first win, standardized.
|
|
2
|
+
//
|
|
3
|
+
// WHAT IT IS
|
|
4
|
+
// ----------
|
|
5
|
+
// `createMockResponder()` returns a function that produces a STREAM SOURCE —
|
|
6
|
+
// SSE text frames in the OpenAI chat-completions shape — which the caller hands
|
|
7
|
+
// to `readOpenAIStream` from `@kitn.ai/ui/wire` exactly as it would hand over a
|
|
8
|
+
// `fetch()` Response. There is no network, no key and no provider.
|
|
9
|
+
//
|
|
10
|
+
// WHY IT PRODUCES A WIRE INSTEAD OF FOLDING PARTS DIRECTLY
|
|
11
|
+
// -------------------------------------------------------
|
|
12
|
+
// The obvious cheaper design is to yield text deltas and let the caller fold
|
|
13
|
+
// them onto `parts` with `appendTextPart`. That is what the scaffolder and all
|
|
14
|
+
// five composed starters used to do, in seven separate copies, and it is worse
|
|
15
|
+
// on every axis that matters:
|
|
16
|
+
//
|
|
17
|
+
// · It BYPASSES the kit's own parser. The zero-config default is the code path
|
|
18
|
+
// every new developer runs first, and under the folding design it exercised
|
|
19
|
+
// none of `sseDataFrames` / `sseJson` / `openaiChatFormat` /
|
|
20
|
+
// `consumeModelStream`. Producing a wire makes the first-run path a live
|
|
21
|
+
// regression test of the thing every real integration depends on.
|
|
22
|
+
// · It makes the mock scaffold and the real scaffold STRUCTURALLY DIFFERENT,
|
|
23
|
+
// so "swap the mock for a real backend" is a rewrite of the submit handler
|
|
24
|
+
// rather than a one-expression change. With a wire, the only difference
|
|
25
|
+
// between the two emitted scaffolds is `mockResponse(value)` vs
|
|
26
|
+
// `await fetch('/api/chat', …)`; `createAssistantStream`, `readOpenAIStream`,
|
|
27
|
+
// the try/catch/finally and the abort handling are byte-identical.
|
|
28
|
+
// · The hand-rolled folds drifted. One of them replaced `parts` wholesale,
|
|
29
|
+
// which silently deleted any reasoning/tool parts already on the message.
|
|
30
|
+
//
|
|
31
|
+
// The frames are the OpenAI chat-completions shape because the mock stands in
|
|
32
|
+
// for the consumer's `/api/chat` ROUTE, not for a provider: every integration in
|
|
33
|
+
// the kit's catalog except this one re-frames its provider to that shape
|
|
34
|
+
// server-side (see `readOpenAIStream`), so matching it is what keeps the swap to
|
|
35
|
+
// a real backend a one-line change.
|
|
36
|
+
//
|
|
37
|
+
// WHY YOU CAN TELL IT IS A MOCK — READ THIS BEFORE CHANGING ANY STRING BELOW
|
|
38
|
+
// -------------------------------------------------------------------------
|
|
39
|
+
// A mock that is indistinguishable from a real response is how a fabricated turn
|
|
40
|
+
// gets believed. This repo has already shipped that bug once: a scaffold seeded a
|
|
41
|
+
// tool call from fixture data, the panel rendered "search Completed", and the
|
|
42
|
+
// fabricated turn was POSTed to the provider AHEAD of the user's own message. A
|
|
43
|
+
// human demoing it saw success; only an assertion on the round-2 answer failed.
|
|
44
|
+
//
|
|
45
|
+
// Choosing a real provider's frame SHAPE (above) means shape alone can no longer
|
|
46
|
+
// distinguish the mock, so the tells are deliberate and layered. Each is visible
|
|
47
|
+
// at a different altitude, so whichever one a developer happens to be looking at,
|
|
48
|
+
// something says "no provider was contacted":
|
|
49
|
+
//
|
|
50
|
+
// 1. RAW STREAM — the first bytes are an SSE comment banner (`: kai-mock …`).
|
|
51
|
+
// Comment lines are dropped by `sseDataFrames`, so this is free
|
|
52
|
+
// semantically and unmissable to anyone reading the stream or a capture.
|
|
53
|
+
// 2. EVERY FRAME — carries `_kai_mock`, a full sentence naming this function.
|
|
54
|
+
// The OpenAI reader ignores fields it does not know, so it costs nothing
|
|
55
|
+
// and it survives JSON parsing: logging any single frame shows it.
|
|
56
|
+
// 3. FIELD LEVEL — `model` is `kai-mock`, which is not a model any provider
|
|
57
|
+
// serves. If a mock frame were ever echoed back upstream it would be
|
|
58
|
+
// REJECTED rather than quietly accepted, which is the specific failure the
|
|
59
|
+
// fabricated-tool-call bug needed and did not get.
|
|
60
|
+
// 4. TURN LEVEL — usage is all zeros. A real turn that produced text cannot
|
|
61
|
+
// report zero completion tokens, so `turn.usage` disambiguates in the app,
|
|
62
|
+
// not merely in the bytes.
|
|
63
|
+
// 5. UI LEVEL — the reply says so in words. The WEAKEST tell and the only one
|
|
64
|
+
// a real model could imitate, which is exactly why it is not the only one.
|
|
65
|
+
//
|
|
66
|
+
// CONSTRAINTS
|
|
67
|
+
// -----------
|
|
68
|
+
// Zero dependencies, no DOM, no Solid, no network, and nothing that is not a
|
|
69
|
+
// global in both Node and the browser (`setTimeout`, `JSON`). This module is
|
|
70
|
+
// part of `@kitn.ai/ui/state`, which is import-safe on a server — see
|
|
71
|
+
// `verify:ssr`. It deliberately does NOT import from `@kitn.ai/ui/wire`: it
|
|
72
|
+
// yields `AsyncIterable<string>`, which is structurally a `StreamSource`, so the
|
|
73
|
+
// dependency runs one way (a caller pairs them) and `state` keeps its charter of
|
|
74
|
+
// owning no wire format.
|
|
75
|
+
|
|
76
|
+
/** The `model` every mock frame reports. Not a model any provider serves — see
|
|
77
|
+
* tell 3 in the header. */
|
|
78
|
+
export const MOCK_MODEL_ID = 'kai-mock';
|
|
79
|
+
|
|
80
|
+
/** The marker field carried by every mock frame. Tell 2. */
|
|
81
|
+
export const MOCK_MARKER_KEY = '_kai_mock';
|
|
82
|
+
|
|
83
|
+
/** The value of that marker: a whole sentence, because it is read by a human
|
|
84
|
+
* staring at a logged frame and wondering where the reply came from. */
|
|
85
|
+
export const MOCK_MARKER =
|
|
86
|
+
'no provider was contacted — this reply was generated locally by createMockResponder() from @kitn.ai/ui/state';
|
|
87
|
+
|
|
88
|
+
/** The SSE comment that opens every mock stream. Tell 1. */
|
|
89
|
+
export const MOCK_BANNER = `: kai-mock — NO PROVIDER WAS CONTACTED. ${MOCK_MARKER}.`;
|
|
90
|
+
|
|
91
|
+
/** The default canned replies, cycled per turn so a multi-turn preview stays
|
|
92
|
+
* coherent instead of repeating one line forever. */
|
|
93
|
+
export const DEFAULT_MOCK_REPLIES: readonly string[] = [
|
|
94
|
+
"Hi! I'm a local mock — no backend, no API key, no provider was contacted. I'm streaming through the same parser a real model would, so what you're seeing is the real rendering path with a canned reply.",
|
|
95
|
+
"Still the mock. Swap `createMockResponder()` for a `fetch('/api/chat', …)` and nothing else in this handler changes — that's the whole point of the seam.",
|
|
96
|
+
'Mock again. Every frame I send is tagged `_kai_mock` and my usage reports zero tokens, so nothing here can be mistaken for a real turn.',
|
|
97
|
+
];
|
|
98
|
+
|
|
99
|
+
export interface MockResponderOptions {
|
|
100
|
+
/** Canned replies, cycled one per turn. Defaults to `DEFAULT_MOCK_REPLIES`. */
|
|
101
|
+
replies?: readonly string[];
|
|
102
|
+
/** Delay between chunks, in ms. Defaults to 24 — fast enough to feel alive,
|
|
103
|
+
* slow enough that the streaming is visible. `0` streams as fast as the
|
|
104
|
+
* event loop allows, which is what tests want. */
|
|
105
|
+
delayMs?: number;
|
|
106
|
+
/** How many whitespace-delimited tokens ride in each frame. Defaults to 1
|
|
107
|
+
* (token by token). Larger values coarsen the cadence. */
|
|
108
|
+
chunkSize?: number;
|
|
109
|
+
/** Log a one-time notice on the first turn. Defaults to `true`: the point of
|
|
110
|
+
* this module is that a mock reply is hard to mistake for a real one, and a
|
|
111
|
+
* console line is the fastest way for a human to notice. Pass `false` in
|
|
112
|
+
* tests, or wherever the banner and the frame markers are tell enough. */
|
|
113
|
+
announce?: boolean;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Produces one turn's worth of SSE frames. Structurally a `StreamSource`, so it
|
|
117
|
+
* goes straight into `readOpenAIStream(responder(text), stream)`. */
|
|
118
|
+
export type MockResponder = (prompt?: string) => AsyncIterable<string>;
|
|
119
|
+
|
|
120
|
+
/** Split into whitespace-preserving tokens, so re-joining the deltas reproduces
|
|
121
|
+
* the reply exactly (the separators are tokens too). */
|
|
122
|
+
function tokenize(reply: string, chunkSize: number): string[] {
|
|
123
|
+
const parts = reply.split(/(\s+)/).filter((t) => t !== '');
|
|
124
|
+
if (chunkSize <= 1) return parts;
|
|
125
|
+
const out: string[] = [];
|
|
126
|
+
for (let i = 0; i < parts.length; i += chunkSize) {
|
|
127
|
+
out.push(parts.slice(i, i + chunkSize).join(''));
|
|
128
|
+
}
|
|
129
|
+
return out;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function frame(id: string, body: Record<string, unknown>): string {
|
|
133
|
+
// The marker goes FIRST so it is the first thing visible in a logged or
|
|
134
|
+
// captured frame, before the payload a reader would skim for.
|
|
135
|
+
return `data: ${JSON.stringify({
|
|
136
|
+
[MOCK_MARKER_KEY]: MOCK_MARKER,
|
|
137
|
+
id,
|
|
138
|
+
object: 'chat.completion.chunk',
|
|
139
|
+
model: MOCK_MODEL_ID,
|
|
140
|
+
...body,
|
|
141
|
+
})}\n\n`;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const delay = (ms: number): Promise<void> =>
|
|
145
|
+
ms > 0 ? new Promise((r) => setTimeout(r, ms)) : Promise.resolve();
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Build a mock responder.
|
|
149
|
+
*
|
|
150
|
+
* ```ts
|
|
151
|
+
* import { createAssistantStream, createMockResponder } from '@kitn.ai/ui/state';
|
|
152
|
+
* import { readOpenAIStream } from '@kitn.ai/ui/wire';
|
|
153
|
+
*
|
|
154
|
+
* const mockResponse = createMockResponder();
|
|
155
|
+
* const stream = createAssistantStream(setMessages);
|
|
156
|
+
* await readOpenAIStream(mockResponse(value), stream); // <- swap for fetch()
|
|
157
|
+
* stream.done();
|
|
158
|
+
* ```
|
|
159
|
+
*/
|
|
160
|
+
export function createMockResponder(options: MockResponderOptions = {}): MockResponder {
|
|
161
|
+
const {
|
|
162
|
+
replies = DEFAULT_MOCK_REPLIES,
|
|
163
|
+
delayMs = 24,
|
|
164
|
+
chunkSize = 1,
|
|
165
|
+
announce = true,
|
|
166
|
+
} = options;
|
|
167
|
+
|
|
168
|
+
const pool = replies.length > 0 ? replies : DEFAULT_MOCK_REPLIES;
|
|
169
|
+
let turn = 0;
|
|
170
|
+
let announced = false;
|
|
171
|
+
|
|
172
|
+
return function respond(_prompt?: string): AsyncIterable<string> {
|
|
173
|
+
const reply = pool[turn % pool.length];
|
|
174
|
+
const id = `kai-mock-${turn + 1}`;
|
|
175
|
+
turn += 1;
|
|
176
|
+
|
|
177
|
+
return {
|
|
178
|
+
async *[Symbol.asyncIterator]() {
|
|
179
|
+
if (announce && !announced) {
|
|
180
|
+
announced = true;
|
|
181
|
+
// Not console.warn: nothing is wrong. This is the mock announcing
|
|
182
|
+
// itself so a developer never has to wonder whether the reply they
|
|
183
|
+
// are looking at came from a model.
|
|
184
|
+
console.info(`[kai] ${MOCK_BANNER.slice(2)}`);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Tell 1. Dropped by the parser (`sseDataFrames` skips ':' lines), so it
|
|
188
|
+
// costs nothing semantically — and it exercises that comment-skipping
|
|
189
|
+
// path, which real providers use for keep-alives.
|
|
190
|
+
yield `${MOCK_BANNER}\n\n`;
|
|
191
|
+
|
|
192
|
+
// The role frame a real OpenAI stream opens with: no content, so it also
|
|
193
|
+
// exercises the reader's empty-delta path.
|
|
194
|
+
yield frame(id, { choices: [{ index: 0, delta: { role: 'assistant' }, finish_reason: null }] });
|
|
195
|
+
|
|
196
|
+
for (const token of tokenize(reply, chunkSize)) {
|
|
197
|
+
await delay(delayMs);
|
|
198
|
+
yield frame(id, { choices: [{ index: 0, delta: { content: token }, finish_reason: null }] });
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Tell 4: zero usage. A real turn that produced this much text cannot
|
|
202
|
+
// report zero completion tokens.
|
|
203
|
+
yield frame(id, {
|
|
204
|
+
choices: [{ index: 0, delta: {}, finish_reason: 'stop' }],
|
|
205
|
+
usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 },
|
|
206
|
+
});
|
|
207
|
+
yield 'data: [DONE]\n\n';
|
|
208
|
+
},
|
|
209
|
+
};
|
|
210
|
+
};
|
|
211
|
+
}
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import type { MessagePart, RawOrigin } from '../elements/chat-types';
|
|
2
|
+
import type { ToolPart } from '../components/tool-types';
|
|
3
|
+
import type { CardEnvelope } from '../primitives/card-contract';
|
|
4
|
+
import { classifyTool } from '../components/tool-classify';
|
|
5
|
+
|
|
6
|
+
/** Stable structural fingerprint. Key order independent, so an identical snapshot
|
|
7
|
+
* arriving twice compares equal and can be skipped. */
|
|
8
|
+
export function fingerprint(value: unknown): string {
|
|
9
|
+
const seen = new WeakSet<object>();
|
|
10
|
+
const walk = (v: unknown): unknown => {
|
|
11
|
+
if (v === null || typeof v !== 'object') return v;
|
|
12
|
+
if (seen.has(v as object)) return '[circular]';
|
|
13
|
+
seen.add(v as object);
|
|
14
|
+
const o = v as Record<string, unknown>;
|
|
15
|
+
const out = Array.isArray(v)
|
|
16
|
+
? v.map(walk)
|
|
17
|
+
: Object.fromEntries(Object.keys(o).sort().map((k) => [k, walk(o[k])]));
|
|
18
|
+
seen.delete(v as object);
|
|
19
|
+
return out;
|
|
20
|
+
};
|
|
21
|
+
return JSON.stringify(walk(value)) ?? '';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Appends to the trailing text part, or OPENS A NEW ONE if the last part is not
|
|
25
|
+
* text. This is what stops a post-tool answer being glued onto the pre-tool text. */
|
|
26
|
+
export function appendTextPart(parts: MessagePart[], delta: string): MessagePart[] {
|
|
27
|
+
const last = parts[parts.length - 1];
|
|
28
|
+
if (last?.type === 'text') {
|
|
29
|
+
return [...parts.slice(0, -1), { ...last, text: last.text + delta }];
|
|
30
|
+
}
|
|
31
|
+
return [...parts, { type: 'text', text: delta }];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface ReasoningOpts {
|
|
35
|
+
index?: number;
|
|
36
|
+
/** Namespaces `index` to one provider response stream. Producers that read more
|
|
37
|
+
* than one stream into the SAME message must set it. See `appendReasoningPart`. */
|
|
38
|
+
streamId?: string;
|
|
39
|
+
label?: string;
|
|
40
|
+
signature?: string;
|
|
41
|
+
raw?: RawOrigin;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
type ReasoningPart = Extract<MessagePart, { type: 'reasoning' }>;
|
|
45
|
+
|
|
46
|
+
/** Keyed by `(streamId, index)` so parallel reasoning blocks stay distinct.
|
|
47
|
+
*
|
|
48
|
+
* WHY THE KEY IS A PAIR. A block index alone is NOT unique inside one `parts`
|
|
49
|
+
* array. Anthropic numbers content blocks per MESSAGE and restarts at 0 on the
|
|
50
|
+
* next one, while a tool loop folds every round into a single assistant turn.
|
|
51
|
+
* Keyed on index alone, round 2's thinking block (index 0) merges into round 1's
|
|
52
|
+
* part: the text concatenates and `raw: opts.raw ?? cur.raw` OVERWRITES round 1's
|
|
53
|
+
* verbatim provider payload with round 2's. `toAnthropicMessages` then emits one
|
|
54
|
+
* thinking block where two belong, carrying round 2's signature in round 1's
|
|
55
|
+
* position -- a modified-and-filtered thinking block, which is exactly the 400
|
|
56
|
+
* the verbatim `raw` channel exists to prevent.
|
|
57
|
+
*
|
|
58
|
+
* `streamId` is the namespace: one value per provider response stream, attached
|
|
59
|
+
* by `consumeModelStream`. Two rounds are two streams, so their index 0s are two
|
|
60
|
+
* parts. Producers that drive a sink from a single stream can omit it; `undefined`
|
|
61
|
+
* is its own namespace and behaves exactly as before.
|
|
62
|
+
*
|
|
63
|
+
* Returns the SAME array reference when the merge produces an identical part,
|
|
64
|
+
* for the same reason `upsertToolPart` does: a new `parts` array is the
|
|
65
|
+
* re-render signal, so handing one back for a delta that changed nothing is a
|
|
66
|
+
* spurious render.
|
|
67
|
+
*
|
|
68
|
+
* An EMPTY delta is not a no-op and must still reach here: it is how a redacted
|
|
69
|
+
* reasoning block, a `signature_delta` and an assembled `content_block_stop`
|
|
70
|
+
* block arrive, and how a format opens a block at the right position so block
|
|
71
|
+
* ORDER survives into `parts`. Those carry a new `raw`/`signature`/index and so
|
|
72
|
+
* compare unequal and DO rebuild. What the check absorbs is the other empty
|
|
73
|
+
* frame: one carrying nothing new, which a provider is free to send repeatedly.
|
|
74
|
+
*
|
|
75
|
+
* `signature` and `raw` resolve with `??`, so an explicit `undefined` from a
|
|
76
|
+
* later delta never blanks a value an earlier one established. Pass a DEFINED
|
|
77
|
+
* value to replace either; there is no way to clear them. */
|
|
78
|
+
export function appendReasoningPart(
|
|
79
|
+
parts: MessagePart[],
|
|
80
|
+
delta: string,
|
|
81
|
+
opts: ReasoningOpts = {},
|
|
82
|
+
): MessagePart[] {
|
|
83
|
+
const index = opts.index ?? 0;
|
|
84
|
+
const streamId = opts.streamId;
|
|
85
|
+
const i = parts.findIndex(
|
|
86
|
+
(p) => p.type === 'reasoning' && (p.index ?? 0) === index && p.streamId === streamId,
|
|
87
|
+
);
|
|
88
|
+
if (i < 0) {
|
|
89
|
+
return [...parts, { type: 'reasoning', text: delta, index, streamId, label: opts.label, signature: opts.signature, raw: opts.raw }];
|
|
90
|
+
}
|
|
91
|
+
const cur = parts[i] as ReasoningPart;
|
|
92
|
+
const next: ReasoningPart = {
|
|
93
|
+
...cur,
|
|
94
|
+
text: cur.text + delta,
|
|
95
|
+
label: opts.label ?? cur.label,
|
|
96
|
+
signature: opts.signature ?? cur.signature,
|
|
97
|
+
raw: opts.raw ?? cur.raw,
|
|
98
|
+
};
|
|
99
|
+
if (reasoningEqual(cur, next)) return parts;
|
|
100
|
+
return [...parts.slice(0, i), next, ...parts.slice(i + 1)];
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Every `ReasoningPart` key, in the order `reasoningEqual` checks them. Same
|
|
104
|
+
* exhaustive-by-construction contract as `TOOL_KEYS` below: adding a field to
|
|
105
|
+
* the `reasoning` variant of `MessagePart` fails the build until it is listed
|
|
106
|
+
* here AND given a comparator, so a new field can never be silently ignored by
|
|
107
|
+
* the dedupe check and strand a stale array reference. */
|
|
108
|
+
const REASONING_KEYS = [
|
|
109
|
+
'type', 'text', 'index', 'streamId', 'label', 'signature', 'raw',
|
|
110
|
+
] as const satisfies readonly (keyof ReasoningPart)[];
|
|
111
|
+
|
|
112
|
+
type _ReasoningKeysExhaustive = Exclude<
|
|
113
|
+
keyof ReasoningPart,
|
|
114
|
+
(typeof REASONING_KEYS)[number]
|
|
115
|
+
> extends never
|
|
116
|
+
? true
|
|
117
|
+
: ['MessagePart reasoning variant has a key missing from REASONING_KEYS', Exclude<keyof ReasoningPart, (typeof REASONING_KEYS)[number]>];
|
|
118
|
+
const _reasoningKeysExhaustive: _ReasoningKeysExhaustive = true;
|
|
119
|
+
void _reasoningKeysExhaustive;
|
|
120
|
+
|
|
121
|
+
/** One comparator per key in `REASONING_KEYS`. The mapped type means a key added
|
|
122
|
+
* to that list with no comparator here is ALSO a compile error.
|
|
123
|
+
*
|
|
124
|
+
* `raw` compares by REFERENCE, exactly as `TOOL_COMPARATORS.raw` does: it is the
|
|
125
|
+
* untranslated provider payload, a producer attaches it once, and
|
|
126
|
+
* `appendReasoningPart` itself carries it forward when a delta omits it, so
|
|
127
|
+
* reference equality holds on every real path. Hashing it would walk the whole
|
|
128
|
+
* payload on every empty keep-alive frame, which is the cost this check exists
|
|
129
|
+
* to avoid. Worst case is a producer handing over a fresh-but-equal `raw`: one
|
|
130
|
+
* extra re-render, never a wrong render. */
|
|
131
|
+
const REASONING_COMPARATORS: {
|
|
132
|
+
[K in (typeof REASONING_KEYS)[number]]: (a: ReasoningPart, b: ReasoningPart) => boolean;
|
|
133
|
+
} = {
|
|
134
|
+
type: (a, b) => a.type === b.type,
|
|
135
|
+
text: (a, b) => a.text === b.text,
|
|
136
|
+
index: (a, b) => a.index === b.index,
|
|
137
|
+
streamId: (a, b) => a.streamId === b.streamId,
|
|
138
|
+
label: (a, b) => a.label === b.label,
|
|
139
|
+
signature: (a, b) => a.signature === b.signature,
|
|
140
|
+
raw: (a, b) => a.raw === b.raw,
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
function reasoningEqual(a: ReasoningPart, b: ReasoningPart): boolean {
|
|
144
|
+
return REASONING_KEYS.every((key) => REASONING_COMPARATORS[key](a, b));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
type CardPart = Extract<MessagePart, { type: 'card' }>;
|
|
148
|
+
|
|
149
|
+
/** Creates or REPLACES a card part, keyed on `envelope.id`. Returns the SAME array
|
|
150
|
+
* reference when the incoming envelope is structurally identical to the current
|
|
151
|
+
* one, for the same reason `upsertToolPart` does: a new `parts` array is the
|
|
152
|
+
* re-render signal, so handing one back for a revision that changed nothing is a
|
|
153
|
+
* spurious render.
|
|
154
|
+
*
|
|
155
|
+
* WHY THIS REPLACES WHERE `upsertToolPart` MERGES. A tool part is patched
|
|
156
|
+
* fragment-by-fragment as its arguments stream in, which is why that function
|
|
157
|
+
* needs carry-forward rules for `raw` and `kind` — a later patch that omits a
|
|
158
|
+
* field is not asserting the field is gone. A card envelope is the opposite: it
|
|
159
|
+
* arrives WHOLE, as one complete tool result, so an omitted field IS an
|
|
160
|
+
* assertion. Last-write-wins is both simpler and the only semantics under which
|
|
161
|
+
* a host can CLEAR `resolution` to re-open a dismissed card — a field-by-field
|
|
162
|
+
* merge can only ever set that field, never unset it, so `CardPolicy.onReopen`
|
|
163
|
+
* (see `primitives/card-contract.ts`) would have no way to express its result.
|
|
164
|
+
*
|
|
165
|
+
* The PART-level `raw` is preserved across a revision. It is a different field
|
|
166
|
+
* from anything inside the envelope: the untranslated provider payload the part
|
|
167
|
+
* was built from, attached once by the producer, which a fresh envelope carries
|
|
168
|
+
* no opinion about.
|
|
169
|
+
*
|
|
170
|
+
* Position is preserved: a revised card stays where it first appeared in the
|
|
171
|
+
* thread rather than jumping past the text that followed it. */
|
|
172
|
+
export function upsertCardPart(parts: MessagePart[], envelope: CardEnvelope): MessagePart[] {
|
|
173
|
+
const i = parts.findIndex((p) => p.type === 'card' && p.envelope.id === envelope.id);
|
|
174
|
+
if (i < 0) return [...parts, { type: 'card', envelope }];
|
|
175
|
+
const cur = parts[i] as CardPart;
|
|
176
|
+
if (fingerprint(cur.envelope) === fingerprint(envelope)) return parts;
|
|
177
|
+
return [...parts.slice(0, i), { ...cur, envelope }, ...parts.slice(i + 1)];
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** Creates or merges a tool part. Returns the SAME array reference when the merge
|
|
181
|
+
* produces an identical tool, so repeated snapshots do not trigger a re-render.
|
|
182
|
+
*
|
|
183
|
+
* Two fields do NOT follow plain spread semantics, because a streaming provider
|
|
184
|
+
* hands them over on one fragment and then keeps patching the rest:
|
|
185
|
+
* - `kind`: a value the consumer set is preserved across later patches instead
|
|
186
|
+
* of being reverted to `classifyTool(type)` (see `resolveKind`).
|
|
187
|
+
* - `raw`: an explicit `raw: undefined` never blanks a `raw` an earlier patch
|
|
188
|
+
* established. Pass a DEFINED `raw` to replace it; there is no way to clear it. */
|
|
189
|
+
export function upsertToolPart(
|
|
190
|
+
parts: MessagePart[],
|
|
191
|
+
toolCallId: string,
|
|
192
|
+
patch: Partial<ToolPart>,
|
|
193
|
+
): MessagePart[] {
|
|
194
|
+
const i = parts.findIndex((p) => p.type === 'tool' && p.tool.toolCallId === toolCallId);
|
|
195
|
+
if (i < 0) {
|
|
196
|
+
const tool: ToolPart = {
|
|
197
|
+
type: patch.type ?? 'unknown',
|
|
198
|
+
state: patch.state ?? 'input-streaming',
|
|
199
|
+
...patch,
|
|
200
|
+
toolCallId,
|
|
201
|
+
};
|
|
202
|
+
tool.kind = tool.kind ?? classifyTool(tool.type);
|
|
203
|
+
return [...parts, { type: 'tool', tool }];
|
|
204
|
+
}
|
|
205
|
+
const cur = (parts[i] as Extract<MessagePart, { type: 'tool' }>).tool;
|
|
206
|
+
const merged: ToolPart = { ...cur, ...patch, toolCallId };
|
|
207
|
+
merged.kind = resolveKind(cur, patch, merged.type);
|
|
208
|
+
if (merged.raw === undefined && cur.raw !== undefined) merged.raw = cur.raw;
|
|
209
|
+
if (toolsEqual(cur, merged)) return parts;
|
|
210
|
+
return [...parts.slice(0, i), { type: 'tool', tool: merged }, ...parts.slice(i + 1)];
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/** `kind` precedence on an update, in order:
|
|
214
|
+
* 1. an explicit `patch.kind` always wins;
|
|
215
|
+
* 2. a `kind` the consumer set that is NOT merely the auto-derivation of the
|
|
216
|
+
* current `type` is preserved. Create honors `patch.kind`, so an update must
|
|
217
|
+
* not silently revert a custom classification mid-stream (a tool created as
|
|
218
|
+
* `{ type: 'my_widget_tool', kind: 'mcp' }` stays `mcp` after a plain
|
|
219
|
+
* `{ state: 'output-available' }` patch);
|
|
220
|
+
* 3. otherwise re-derive from the merged `type`, so a tool whose real name only
|
|
221
|
+
* arrives in a later fragment still classifies (the streaming path: created
|
|
222
|
+
* as `unknown`/`generic`, patched to `web_search` -> `search`).
|
|
223
|
+
*
|
|
224
|
+
* A consumer `kind` that happens to equal `classifyTool(type)` is
|
|
225
|
+
* indistinguishable from the derived one, which is harmless: they agree until
|
|
226
|
+
* the `type` changes, and then re-deriving is the better answer anyway. */
|
|
227
|
+
function resolveKind(cur: ToolPart, patch: Partial<ToolPart>, nextType: string): ToolPart['kind'] {
|
|
228
|
+
if (patch.kind !== undefined) return patch.kind;
|
|
229
|
+
const derived = cur.kind === undefined || cur.kind === classifyTool(cur.type);
|
|
230
|
+
return derived ? classifyTool(nextType) : cur.kind;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/** Every `ToolPart` key, in the order `toolsEqual` checks them (cheap identity
|
|
234
|
+
* checks first, the two fingerprint-based checks last). See `ToolPart` in
|
|
235
|
+
* `components/tool-types.ts`, which points back here.
|
|
236
|
+
*
|
|
237
|
+
* This is not just documentation: `_toolKeysExhaustive` below fails to compile
|
|
238
|
+
* if `ToolPart` gains a key that is not listed here, and `TOOL_COMPARATORS`
|
|
239
|
+
* fails to compile if this list gains a key with no comparator. Adding a field
|
|
240
|
+
* to `ToolPart` without wiring it into both is a BUILD failure, not a silently
|
|
241
|
+
* stale array reference. */
|
|
242
|
+
const TOOL_KEYS = [
|
|
243
|
+
'type', 'state', 'kind', 'toolCallId', 'errorText', 'rawInput', 'raw', 'input', 'output',
|
|
244
|
+
] as const satisfies readonly (keyof ToolPart)[];
|
|
245
|
+
|
|
246
|
+
// If `ToolPart` gains a key absent from `TOOL_KEYS`, `Exclude<...>` stops being
|
|
247
|
+
// `never`, the assigned literal type no longer matches `true`, and `tsc` reports
|
|
248
|
+
// the offending key name right here.
|
|
249
|
+
type _ToolKeysExhaustive = Exclude<keyof ToolPart, (typeof TOOL_KEYS)[number]> extends never
|
|
250
|
+
? true
|
|
251
|
+
: ['ToolPart has a key missing from TOOL_KEYS', Exclude<keyof ToolPart, (typeof TOOL_KEYS)[number]>];
|
|
252
|
+
const _toolKeysExhaustive: _ToolKeysExhaustive = true;
|
|
253
|
+
void _toolKeysExhaustive;
|
|
254
|
+
|
|
255
|
+
/** One comparator per key in `TOOL_KEYS`. The mapped type over
|
|
256
|
+
* `(typeof TOOL_KEYS)[number]` means a key added to `TOOL_KEYS` with no
|
|
257
|
+
* comparator here is ALSO a compile error ("Property 'x' is missing"), so the
|
|
258
|
+
* guard reaches the actual comparison, not just the key list.
|
|
259
|
+
*
|
|
260
|
+
* Semantics are unchanged from before this table existed:
|
|
261
|
+
* - `raw` compares by REFERENCE on purpose. It is the untranslated provider
|
|
262
|
+
* payload; a producer attaches it once and never rebuilds it (upsertToolPart
|
|
263
|
+
* itself carries `cur.raw` forward when a patch omits it), so reference
|
|
264
|
+
* equality holds on every real path. Hashing it would walk the accumulated
|
|
265
|
+
* argument string a second time, which is the cost this table exists to
|
|
266
|
+
* remove. The worst case is a producer handing over a fresh-but-equal `raw`,
|
|
267
|
+
* which costs one extra re-render and never a wrong render.
|
|
268
|
+
* - `input` and `output` are the two fields that are genuinely object-shaped:
|
|
269
|
+
* reference equality first, then `fingerprint()` as a structural fallback so
|
|
270
|
+
* a fresh-but-identical object still dedupes.
|
|
271
|
+
* - everything else, including `rawInput`, compares with `===`. `rawInput`
|
|
272
|
+
* against `!==` is exactly the test we want (did the accumulated text
|
|
273
|
+
* change?) and it is cheap: two strings of different lengths are unequal
|
|
274
|
+
* after the length check, so it never walks the full string.
|
|
275
|
+
*
|
|
276
|
+
* This replaces `fingerprint(merged) === fingerprint(cur)`, which serialized
|
|
277
|
+
* the ENTIRE ToolPart on every patch. A streaming tool call is patched once
|
|
278
|
+
* per argument fragment while `rawInput` grows toward the full argument JSON,
|
|
279
|
+
* so hashing the whole part per fragment is quadratic in the argument size:
|
|
280
|
+
* fine at 4 KB, not at 200 KB. */
|
|
281
|
+
const TOOL_COMPARATORS: { [K in (typeof TOOL_KEYS)[number]]: (a: ToolPart, b: ToolPart) => boolean } = {
|
|
282
|
+
type: (a, b) => a.type === b.type,
|
|
283
|
+
state: (a, b) => a.state === b.state,
|
|
284
|
+
kind: (a, b) => a.kind === b.kind,
|
|
285
|
+
toolCallId: (a, b) => a.toolCallId === b.toolCallId,
|
|
286
|
+
errorText: (a, b) => a.errorText === b.errorText,
|
|
287
|
+
rawInput: (a, b) => a.rawInput === b.rawInput,
|
|
288
|
+
raw: (a, b) => a.raw === b.raw,
|
|
289
|
+
input: (a, b) => a.input === b.input || fingerprint(a.input) === fingerprint(b.input),
|
|
290
|
+
output: (a, b) => a.output === b.output || fingerprint(a.output) === fingerprint(b.output),
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
function toolsEqual(a: ToolPart, b: ToolPart): boolean {
|
|
294
|
+
return TOOL_KEYS.every((key) => TOOL_COMPARATORS[key](a, b));
|
|
295
|
+
}
|