@kitn.ai/ui 0.20.0 → 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 +16913 -0
- 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 +65 -7
- 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
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
// Anthropic Messages SSE.
|
|
2
|
+
//
|
|
3
|
+
// This format is STATEFUL. `content_block_delta` names the delta kind
|
|
4
|
+
// (`thinking_delta`, `input_json_delta`, ...) but not which block kind the
|
|
5
|
+
// fragment belongs to, so the block map is populated at `content_block_start`
|
|
6
|
+
// and read on every later event for that index.
|
|
7
|
+
//
|
|
8
|
+
// It is also the only format in the kit carrying a VERBATIM requirement:
|
|
9
|
+
// Anthropic returns 400 if a `thinking` block is modified, reordered or
|
|
10
|
+
// reconstructed on the way back in. The block emitted at `content_block_stop`
|
|
11
|
+
// is ASSEMBLED, not invented: `thinking` is the concatenation of the provider's
|
|
12
|
+
// own thinking_delta payloads and `signature` is the provider's signature_delta,
|
|
13
|
+
// so every byte came off the wire. Nothing is derived from the reasoning PART's
|
|
14
|
+
// text, which a consumer can edit.
|
|
15
|
+
//
|
|
16
|
+
// Three of the chunks below carry NO reasoning text (the block start, every
|
|
17
|
+
// signature_delta, the block stop). They depend on consume.ts gating reasoning
|
|
18
|
+
// on `reasoning !== undefined || reasoningRaw || reasoningSignature` rather than
|
|
19
|
+
// on truthiness. Under the old guard all three vanish.
|
|
20
|
+
import type { MessageSource } from '../../elements/chat-types';
|
|
21
|
+
import type { ModelStreamChunk, ModelUsage, WireFormat, WireFormatReader } from '../chunk';
|
|
22
|
+
|
|
23
|
+
function isRecord(v: unknown): v is Record<string, unknown> {
|
|
24
|
+
return typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function str(v: unknown): string | undefined {
|
|
28
|
+
return typeof v === 'string' ? v : undefined;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function num(v: unknown): number | undefined {
|
|
32
|
+
return typeof v === 'number' ? v : undefined;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
type BlockKind = 'text' | 'thinking' | 'redacted_thinking' | 'tool_use' | 'other';
|
|
36
|
+
|
|
37
|
+
interface BlockState {
|
|
38
|
+
kind: BlockKind;
|
|
39
|
+
/** Accumulated thinking_delta payloads, for the verbatim block at stop. */
|
|
40
|
+
thinking: string;
|
|
41
|
+
/** Accumulated signature_delta payloads. */
|
|
42
|
+
signature?: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function usageOf(raw: unknown): ModelUsage | undefined {
|
|
46
|
+
if (!isRecord(raw)) return undefined;
|
|
47
|
+
const out: ModelUsage = {};
|
|
48
|
+
const input = num(raw.input_tokens);
|
|
49
|
+
if (input !== undefined) out.inputTokens = input;
|
|
50
|
+
const output = num(raw.output_tokens);
|
|
51
|
+
if (output !== undefined) out.outputTokens = output;
|
|
52
|
+
const cached = num(raw.cache_read_input_tokens);
|
|
53
|
+
if (cached !== undefined) out.cachedInputTokens = cached;
|
|
54
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function blockStart(
|
|
58
|
+
frame: Record<string, unknown>,
|
|
59
|
+
blocks: Map<number, BlockState>,
|
|
60
|
+
toolIndexById: Map<string, number>,
|
|
61
|
+
): ModelStreamChunk[] {
|
|
62
|
+
const index = num(frame.index) ?? 0;
|
|
63
|
+
const block = frame.content_block;
|
|
64
|
+
if (!isRecord(block)) return [];
|
|
65
|
+
|
|
66
|
+
switch (str(block.type)) {
|
|
67
|
+
case 'text': {
|
|
68
|
+
blocks.set(index, { kind: 'text', thinking: '' });
|
|
69
|
+
const text = str(block.text);
|
|
70
|
+
return text ? [{ text }] : [];
|
|
71
|
+
}
|
|
72
|
+
case 'thinking': {
|
|
73
|
+
const seed = str(block.thinking) ?? '';
|
|
74
|
+
blocks.set(index, { kind: 'thinking', thinking: seed });
|
|
75
|
+
// An EMPTY reasoning delta on purpose: it opens the reasoning part at the
|
|
76
|
+
// point in the stream where the block started, so block order in `parts`
|
|
77
|
+
// matches block order on the wire. The encoder round-trips that order.
|
|
78
|
+
return [{ reasoning: seed, reasoningIndex: index }];
|
|
79
|
+
}
|
|
80
|
+
case 'redacted_thinking': {
|
|
81
|
+
blocks.set(index, { kind: 'redacted_thinking', thinking: '' });
|
|
82
|
+
// The WHOLE block arrives here; there are no deltas and there is no
|
|
83
|
+
// readable text. It must still round-trip: the docs require sending back
|
|
84
|
+
// every block "including any blocks with empty thinking fields".
|
|
85
|
+
return [
|
|
86
|
+
{
|
|
87
|
+
reasoning: '',
|
|
88
|
+
reasoningIndex: index,
|
|
89
|
+
reasoningRaw: { source: 'anthropic.content_block', payload: block },
|
|
90
|
+
},
|
|
91
|
+
];
|
|
92
|
+
}
|
|
93
|
+
case 'tool_use':
|
|
94
|
+
case 'server_tool_use': {
|
|
95
|
+
const id = str(block.id);
|
|
96
|
+
const name = str(block.name);
|
|
97
|
+
blocks.set(index, { kind: 'tool_use', thinking: '' });
|
|
98
|
+
if (id) toolIndexById.set(id, index);
|
|
99
|
+
return [{ toolCalls: [{ index, ...(id ? { id } : {}), ...(name ? { name } : {}) }] }];
|
|
100
|
+
}
|
|
101
|
+
case 'web_search_tool_result': {
|
|
102
|
+
// A result the PROVIDER executed. It correlates by `tool_use_id`, NOT by
|
|
103
|
+
// its own block index, so it is routed back to the index of the
|
|
104
|
+
// server_tool_use block that opened the call. With no such block the
|
|
105
|
+
// result has nothing to complete and is dropped rather than inventing a
|
|
106
|
+
// panel.
|
|
107
|
+
blocks.set(index, { kind: 'other', thinking: '' });
|
|
108
|
+
const toolUseId = str(block.tool_use_id);
|
|
109
|
+
const target = toolUseId !== undefined ? toolIndexById.get(toolUseId) : undefined;
|
|
110
|
+
if (target === undefined) return [];
|
|
111
|
+
const content = block.content;
|
|
112
|
+
if (isRecord(content) && (str(content.type) ?? '').endsWith('_error')) {
|
|
113
|
+
return [
|
|
114
|
+
{
|
|
115
|
+
toolCalls: [
|
|
116
|
+
{
|
|
117
|
+
index: target,
|
|
118
|
+
...(toolUseId ? { id: toolUseId } : {}),
|
|
119
|
+
outputError: str(content.error_code) ?? 'web_search_tool_result_error',
|
|
120
|
+
},
|
|
121
|
+
],
|
|
122
|
+
},
|
|
123
|
+
];
|
|
124
|
+
}
|
|
125
|
+
return [
|
|
126
|
+
{
|
|
127
|
+
toolCalls: [
|
|
128
|
+
{ index: target, ...(toolUseId ? { id: toolUseId } : {}), output: { content } },
|
|
129
|
+
],
|
|
130
|
+
},
|
|
131
|
+
];
|
|
132
|
+
}
|
|
133
|
+
default:
|
|
134
|
+
blocks.set(index, { kind: 'other', thinking: '' });
|
|
135
|
+
return [];
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function blockDelta(
|
|
140
|
+
frame: Record<string, unknown>,
|
|
141
|
+
blocks: Map<number, BlockState>,
|
|
142
|
+
): ModelStreamChunk[] {
|
|
143
|
+
const index = num(frame.index) ?? 0;
|
|
144
|
+
const delta = frame.delta;
|
|
145
|
+
if (!isRecord(delta)) return [];
|
|
146
|
+
|
|
147
|
+
switch (str(delta.type)) {
|
|
148
|
+
case 'text_delta': {
|
|
149
|
+
const text = str(delta.text);
|
|
150
|
+
return text ? [{ text }] : [];
|
|
151
|
+
}
|
|
152
|
+
case 'thinking_delta': {
|
|
153
|
+
const text = str(delta.thinking) ?? '';
|
|
154
|
+
const state = blocks.get(index);
|
|
155
|
+
if (state) state.thinking += text;
|
|
156
|
+
return [{ reasoning: text, reasoningIndex: index }];
|
|
157
|
+
}
|
|
158
|
+
case 'signature_delta': {
|
|
159
|
+
const signature = str(delta.signature) ?? '';
|
|
160
|
+
const state = blocks.get(index);
|
|
161
|
+
// Concatenated rather than assigned: a split signature must reassemble.
|
|
162
|
+
if (state) state.signature = (state.signature ?? '') + signature;
|
|
163
|
+
// No text. Carried so the part's informational `signature` is populated;
|
|
164
|
+
// the round-trip payload lands at content_block_stop.
|
|
165
|
+
return [{ reasoning: '', reasoningIndex: index, reasoningSignature: signature }];
|
|
166
|
+
}
|
|
167
|
+
case 'input_json_delta': {
|
|
168
|
+
return [{ toolCalls: [{ index, arguments: str(delta.partial_json) ?? '' }] }];
|
|
169
|
+
}
|
|
170
|
+
case 'citations_delta': {
|
|
171
|
+
const citation = delta.citation;
|
|
172
|
+
if (!isRecord(citation)) return [];
|
|
173
|
+
const source: MessageSource = {};
|
|
174
|
+
const url = str(citation.url);
|
|
175
|
+
if (url) source.url = url;
|
|
176
|
+
const title = str(citation.title);
|
|
177
|
+
if (title) source.title = title;
|
|
178
|
+
const snippet = str(citation.cited_text);
|
|
179
|
+
if (snippet) source.snippet = snippet;
|
|
180
|
+
return Object.keys(source).length > 0 ? [{ sources: [source] }] : [];
|
|
181
|
+
}
|
|
182
|
+
default:
|
|
183
|
+
return [];
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function blockStop(
|
|
188
|
+
frame: Record<string, unknown>,
|
|
189
|
+
blocks: Map<number, BlockState>,
|
|
190
|
+
): ModelStreamChunk[] {
|
|
191
|
+
const index = num(frame.index) ?? 0;
|
|
192
|
+
const state = blocks.get(index);
|
|
193
|
+
// Only a streamed `thinking` block needs assembling. `redacted_thinking`
|
|
194
|
+
// already emitted its whole payload at start, and text and tool blocks have
|
|
195
|
+
// no verbatim requirement.
|
|
196
|
+
if (!state || state.kind !== 'thinking') return [];
|
|
197
|
+
const payload: Record<string, unknown> = { type: 'thinking', thinking: state.thinking };
|
|
198
|
+
if (state.signature !== undefined) payload.signature = state.signature;
|
|
199
|
+
return [
|
|
200
|
+
{
|
|
201
|
+
reasoning: '',
|
|
202
|
+
reasoningIndex: index,
|
|
203
|
+
reasoningRaw: { source: 'anthropic.content_block', payload },
|
|
204
|
+
},
|
|
205
|
+
];
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function messageDelta(frame: Record<string, unknown>): ModelStreamChunk[] {
|
|
209
|
+
const out: ModelStreamChunk = {};
|
|
210
|
+
const delta = frame.delta;
|
|
211
|
+
if (isRecord(delta)) {
|
|
212
|
+
const stop = str(delta.stop_reason);
|
|
213
|
+
if (stop) out.finishReason = stop;
|
|
214
|
+
}
|
|
215
|
+
const usage = usageOf(frame.usage);
|
|
216
|
+
if (usage) out.usage = usage;
|
|
217
|
+
return Object.keys(out).length > 0 ? [out] : [];
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function errorFrame(frame: Record<string, unknown>): ModelStreamChunk[] {
|
|
221
|
+
const err = frame.error;
|
|
222
|
+
const message = isRecord(err) ? str(err.message) : undefined;
|
|
223
|
+
const code = isRecord(err) ? str(err.type) : undefined;
|
|
224
|
+
return [
|
|
225
|
+
{
|
|
226
|
+
error: {
|
|
227
|
+
message: message ?? 'The provider sent an error frame with no message.',
|
|
228
|
+
...(code ? { code } : {}),
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
];
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export const anthropicMessagesFormat: WireFormat = {
|
|
235
|
+
id: 'anthropic.messages',
|
|
236
|
+
open(): WireFormatReader {
|
|
237
|
+
// Per-stream state, created HERE so two open() calls share nothing.
|
|
238
|
+
const blocks = new Map<number, BlockState>();
|
|
239
|
+
const toolIndexById = new Map<string, number>();
|
|
240
|
+
return {
|
|
241
|
+
push(frame: unknown): ModelStreamChunk[] {
|
|
242
|
+
if (!isRecord(frame)) return [];
|
|
243
|
+
switch (str(frame.type)) {
|
|
244
|
+
case 'message_start': {
|
|
245
|
+
const message = frame.message;
|
|
246
|
+
if (!isRecord(message)) return [];
|
|
247
|
+
const usage = usageOf(message.usage);
|
|
248
|
+
return usage ? [{ usage }] : [];
|
|
249
|
+
}
|
|
250
|
+
case 'content_block_start':
|
|
251
|
+
return blockStart(frame, blocks, toolIndexById);
|
|
252
|
+
case 'content_block_delta':
|
|
253
|
+
return blockDelta(frame, blocks);
|
|
254
|
+
case 'content_block_stop':
|
|
255
|
+
return blockStop(frame, blocks);
|
|
256
|
+
case 'message_delta':
|
|
257
|
+
return messageDelta(frame);
|
|
258
|
+
case 'error':
|
|
259
|
+
return errorFrame(frame);
|
|
260
|
+
default:
|
|
261
|
+
// ping, message_stop, and anything Anthropic adds later.
|
|
262
|
+
return [];
|
|
263
|
+
}
|
|
264
|
+
},
|
|
265
|
+
};
|
|
266
|
+
},
|
|
267
|
+
};
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
// OpenAI chat-completions SSE. Also the shape every non-mock integration in the
|
|
2
|
+
// kit's catalog re-frames to server-side, so this one decoder covers all nine.
|
|
3
|
+
//
|
|
4
|
+
// Input is an ALREADY DECODED JSON frame typed `unknown`. Nothing here imports a
|
|
5
|
+
// provider SDK, and nothing here throws: a frame this format does not recognise
|
|
6
|
+
// yields [] so a provider adding a field cannot take a turn down.
|
|
7
|
+
import type { MessageSource } from '../../elements/chat-types';
|
|
8
|
+
import type {
|
|
9
|
+
ModelStreamChunk,
|
|
10
|
+
ModelToolCallDelta,
|
|
11
|
+
ModelUsage,
|
|
12
|
+
WireFormat,
|
|
13
|
+
WireFormatReader,
|
|
14
|
+
} from '../chunk';
|
|
15
|
+
|
|
16
|
+
function isRecord(v: unknown): v is Record<string, unknown> {
|
|
17
|
+
return typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function str(v: unknown): string | undefined {
|
|
21
|
+
return typeof v === 'string' ? v : undefined;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function num(v: unknown): number | undefined {
|
|
25
|
+
return typeof v === 'number' ? v : undefined;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function usageOf(raw: unknown): ModelUsage | undefined {
|
|
29
|
+
if (!isRecord(raw)) return undefined;
|
|
30
|
+
const out: ModelUsage = {};
|
|
31
|
+
const input = num(raw.prompt_tokens);
|
|
32
|
+
if (input !== undefined) out.inputTokens = input;
|
|
33
|
+
const output = num(raw.completion_tokens);
|
|
34
|
+
if (output !== undefined) out.outputTokens = output;
|
|
35
|
+
const total = num(raw.total_tokens);
|
|
36
|
+
if (total !== undefined) out.totalTokens = total;
|
|
37
|
+
const details = raw.completion_tokens_details;
|
|
38
|
+
if (isRecord(details)) {
|
|
39
|
+
// The one number that proves reasoning happened even when no reasoning text
|
|
40
|
+
// was streamed back.
|
|
41
|
+
const reasoning = num(details.reasoning_tokens);
|
|
42
|
+
if (reasoning !== undefined) out.reasoningTokens = reasoning;
|
|
43
|
+
}
|
|
44
|
+
const promptDetails = raw.prompt_tokens_details;
|
|
45
|
+
if (isRecord(promptDetails)) {
|
|
46
|
+
const cached = num(promptDetails.cached_tokens);
|
|
47
|
+
if (cached !== undefined) out.cachedInputTokens = cached;
|
|
48
|
+
}
|
|
49
|
+
const cost = num(raw.cost);
|
|
50
|
+
if (cost !== undefined) out.costUsd = cost;
|
|
51
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function toolCallDelta(raw: unknown, position: number): ModelToolCallDelta | undefined {
|
|
55
|
+
if (!isRecord(raw)) return undefined;
|
|
56
|
+
const fn = isRecord(raw.function) ? raw.function : undefined;
|
|
57
|
+
const out: ModelToolCallDelta = { index: num(raw.index) ?? position };
|
|
58
|
+
const id = str(raw.id);
|
|
59
|
+
if (id) out.id = id;
|
|
60
|
+
const name = str(fn?.name);
|
|
61
|
+
if (name) out.name = name;
|
|
62
|
+
const args = str(fn?.arguments);
|
|
63
|
+
if (args !== undefined) out.arguments = args;
|
|
64
|
+
return out;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Sum the READABLE text across reasoning_details entries. Entries with no
|
|
68
|
+
* readable text (`reasoning.encrypted`, opaque signed blobs) contribute nothing
|
|
69
|
+
* here; they still ride along whole in `reasoningRaw`. */
|
|
70
|
+
function detailText(details: unknown[]): string {
|
|
71
|
+
let out = '';
|
|
72
|
+
for (const d of details) {
|
|
73
|
+
if (!isRecord(d)) continue;
|
|
74
|
+
const text = str(d.text) ?? str(d.summary);
|
|
75
|
+
if (text) out += text;
|
|
76
|
+
}
|
|
77
|
+
return out;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function detailField(details: unknown[], key: 'index' | 'signature'): unknown {
|
|
81
|
+
for (const d of details) {
|
|
82
|
+
if (isRecord(d) && d[key] !== undefined) return d[key];
|
|
83
|
+
}
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* FINDINGS: OpenRouter frequently puts the SAME text in `reasoning` AND in
|
|
89
|
+
* `reasoning_details` on the same delta. Concatenating both doubles every
|
|
90
|
+
* reasoning token, so `reasoning` wins and details are only a text FALLBACK.
|
|
91
|
+
*
|
|
92
|
+
* `reasoning_details` is still read in BOTH cases, for `reasoningRaw`, the block
|
|
93
|
+
* index and the signature. It is the provider's own block list, and dropping it
|
|
94
|
+
* is exactly the Anthropic 400 this entry exists to avoid.
|
|
95
|
+
*/
|
|
96
|
+
function applyReasoning(delta: Record<string, unknown>, out: ModelStreamChunk): void {
|
|
97
|
+
const details = Array.isArray(delta.reasoning_details) ? delta.reasoning_details : undefined;
|
|
98
|
+
const primaryRaw = str(delta.reasoning);
|
|
99
|
+
const primary = primaryRaw !== undefined && primaryRaw !== '' ? primaryRaw : undefined;
|
|
100
|
+
const fallback = details ? detailText(details) : undefined;
|
|
101
|
+
const hasDetails = details !== undefined && details.length > 0;
|
|
102
|
+
const text = primary ?? fallback;
|
|
103
|
+
if (text === undefined && !hasDetails) return;
|
|
104
|
+
|
|
105
|
+
// '' is deliberate and meaningful: an encrypted-only detail entry has no
|
|
106
|
+
// readable text but must still produce a reasoning part carrying its payload.
|
|
107
|
+
out.reasoning = text ?? '';
|
|
108
|
+
if (!hasDetails) return;
|
|
109
|
+
|
|
110
|
+
out.reasoningRaw = { source: 'openai.reasoning_details', payload: details };
|
|
111
|
+
const index = num(detailField(details, 'index'));
|
|
112
|
+
if (index !== undefined) out.reasoningIndex = index;
|
|
113
|
+
const signature = str(detailField(details, 'signature'));
|
|
114
|
+
if (signature !== undefined) out.reasoningSignature = signature;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function sourcesOf(delta: Record<string, unknown>): MessageSource[] {
|
|
118
|
+
if (!Array.isArray(delta.annotations)) return [];
|
|
119
|
+
const out: MessageSource[] = [];
|
|
120
|
+
for (const a of delta.annotations) {
|
|
121
|
+
if (!isRecord(a)) continue;
|
|
122
|
+
const citation = isRecord(a.url_citation) ? a.url_citation : undefined;
|
|
123
|
+
const url = str(citation?.url);
|
|
124
|
+
if (!url) continue;
|
|
125
|
+
const source: MessageSource = { url };
|
|
126
|
+
const title = str(citation?.title);
|
|
127
|
+
if (title) source.title = title;
|
|
128
|
+
const snippet = str(citation?.content);
|
|
129
|
+
if (snippet) source.snippet = snippet;
|
|
130
|
+
out.push(source);
|
|
131
|
+
}
|
|
132
|
+
return out;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function pushOpenAI(frame: unknown): ModelStreamChunk[] {
|
|
136
|
+
if (!isRecord(frame)) return [];
|
|
137
|
+
const out: ModelStreamChunk = {};
|
|
138
|
+
|
|
139
|
+
const err = frame.error;
|
|
140
|
+
if (isRecord(err)) {
|
|
141
|
+
const message = str(err.message);
|
|
142
|
+
if (message) {
|
|
143
|
+
out.error = { message };
|
|
144
|
+
const code = err.code;
|
|
145
|
+
if (typeof code === 'string' || typeof code === 'number') out.error.code = code;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const usage = usageOf(frame.usage);
|
|
150
|
+
if (usage) out.usage = usage;
|
|
151
|
+
|
|
152
|
+
const choices = Array.isArray(frame.choices) ? frame.choices : [];
|
|
153
|
+
const choice = isRecord(choices[0]) ? choices[0] : undefined;
|
|
154
|
+
if (choice) {
|
|
155
|
+
const delta = isRecord(choice.delta) ? choice.delta : undefined;
|
|
156
|
+
if (delta) {
|
|
157
|
+
const content = str(delta.content);
|
|
158
|
+
if (content) out.text = content;
|
|
159
|
+
|
|
160
|
+
applyReasoning(delta, out);
|
|
161
|
+
|
|
162
|
+
if (Array.isArray(delta.tool_calls) && delta.tool_calls.length > 0) {
|
|
163
|
+
const calls = delta.tool_calls
|
|
164
|
+
.map((raw, i) => toolCallDelta(raw, i))
|
|
165
|
+
.filter((c): c is ModelToolCallDelta => c !== undefined);
|
|
166
|
+
if (calls.length > 0) out.toolCalls = calls;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const sources = sourcesOf(delta);
|
|
170
|
+
if (sources.length > 0) out.sources = sources;
|
|
171
|
+
}
|
|
172
|
+
const finish = str(choice.finish_reason);
|
|
173
|
+
if (finish) out.finishReason = finish;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return Object.keys(out).length > 0 ? [out] : [];
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** Stateless: every frame is self-describing, so `open()` returns a reader with
|
|
180
|
+
* no closure state. The interface still calls it per stream, which is what lets
|
|
181
|
+
* the stateful Anthropic format use the same seam. */
|
|
182
|
+
export const openaiChatFormat: WireFormat = {
|
|
183
|
+
id: 'openai.chat-completions',
|
|
184
|
+
open(): WireFormatReader {
|
|
185
|
+
return { push: pushOpenAI };
|
|
186
|
+
},
|
|
187
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// @kitn.ai/ui/wire: the model-stream adapter.
|
|
2
|
+
//
|
|
3
|
+
// Separate from ./state on purpose. `state` is I/O-free pure functions over
|
|
4
|
+
// ChatMessage[]; `wire` touches Response, TextDecoder and byte streams. Keeping
|
|
5
|
+
// them apart leaves the bring-your-own-transport consumer at zero cost, and
|
|
6
|
+
// gives a future AG-UI format an obvious home. The cost is that importing both
|
|
7
|
+
// entries ships state/parts.ts twice, about 2 KB.
|
|
8
|
+
//
|
|
9
|
+
// The kit PARSES. The consumer FETCHES. There is no client, no key handling and
|
|
10
|
+
// no provider SDK anywhere below this file.
|
|
11
|
+
|
|
12
|
+
export { readModelStream, readOpenAIStream, readAnthropicStream, WireError } from './read';
|
|
13
|
+
export type { StreamSource, ReadOptions } from './read';
|
|
14
|
+
|
|
15
|
+
export { consumeModelStream, createToolCallAccumulator } from './consume';
|
|
16
|
+
export { applyToolOutput, applyToolFailure, bufferText } from './sink-helpers';
|
|
17
|
+
|
|
18
|
+
export { toOpenAIMessages, toAnthropicMessages, WireEncodeError } from './encode';
|
|
19
|
+
export type {
|
|
20
|
+
AnthropicContentBlock,
|
|
21
|
+
AnthropicWireMessage,
|
|
22
|
+
OpenAIEncodeOptions,
|
|
23
|
+
OpenAIReasoningDetail,
|
|
24
|
+
OpenAIToolCall,
|
|
25
|
+
OpenAIWireMessage,
|
|
26
|
+
} from './encode';
|
|
27
|
+
|
|
28
|
+
export { openaiChatFormat } from './formats/openai';
|
|
29
|
+
export { anthropicMessagesFormat } from './formats/anthropic';
|
|
30
|
+
|
|
31
|
+
export { sseDataFrames, sseJson, readableToAsyncIterable } from './sse';
|
|
32
|
+
export type { ByteSource } from './sse';
|
|
33
|
+
|
|
34
|
+
export { normalizeStopReason } from './chunk';
|
|
35
|
+
export type {
|
|
36
|
+
AssistantStreamSink,
|
|
37
|
+
ConsumeOptions,
|
|
38
|
+
ModelStreamChunk,
|
|
39
|
+
ModelToolCall,
|
|
40
|
+
ModelToolCallDelta,
|
|
41
|
+
ModelTurn,
|
|
42
|
+
ModelUsage,
|
|
43
|
+
StopReason,
|
|
44
|
+
WireFormat,
|
|
45
|
+
WireFormatReader,
|
|
46
|
+
} from './chunk';
|
|
47
|
+
|
|
48
|
+
// The content-model types every signature above mentions, re-exported so a
|
|
49
|
+
// consumer importing only from '@kitn.ai/ui/wire' can annotate the values these
|
|
50
|
+
// functions take and return without a second import.
|
|
51
|
+
export type { ChatMessage, MessagePart, MessageSource, RawOrigin } from '../elements/chat-types';
|
|
52
|
+
export type { ToolPart } from '../components/tool-types';
|
package/src/wire/read.ts
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
// The transport seam. The kit PARSES the stream; the consumer OWNS the
|
|
2
|
+
// transport. Auth, proxies, retries, aborts and rate limits are app decisions,
|
|
3
|
+
// so `StreamSource` is the entire surface: hand over a Response, a
|
|
4
|
+
// ReadableStream, or any async iterable of bytes or strings.
|
|
5
|
+
//
|
|
6
|
+
// Deliberately absent: retries, reconnect, Last-Event-ID, backoff, an
|
|
7
|
+
// AbortSignal parameter (abort the fetch), a URL-taking client, and key
|
|
8
|
+
// handling. Untested retry logic is worse than none.
|
|
9
|
+
//
|
|
10
|
+
// SSR: nothing here references Response or ReadableStream at module scope. A
|
|
11
|
+
// Response is detected by duck-typing, not `instanceof`, so this module imports
|
|
12
|
+
// cleanly in a runtime where the global does not exist.
|
|
13
|
+
import { sseJson, type ByteSource } from './sse';
|
|
14
|
+
import { consumeModelStream } from './consume';
|
|
15
|
+
import type {
|
|
16
|
+
AssistantStreamSink,
|
|
17
|
+
ConsumeOptions,
|
|
18
|
+
ModelStreamChunk,
|
|
19
|
+
ModelTurn,
|
|
20
|
+
WireFormat,
|
|
21
|
+
} from './chunk';
|
|
22
|
+
import { openaiChatFormat } from './formats/openai';
|
|
23
|
+
import { anthropicMessagesFormat } from './formats/anthropic';
|
|
24
|
+
|
|
25
|
+
export type StreamSource =
|
|
26
|
+
| Response
|
|
27
|
+
| ReadableStream<Uint8Array>
|
|
28
|
+
| AsyncIterable<Uint8Array | string>;
|
|
29
|
+
|
|
30
|
+
export interface ReadOptions extends ConsumeOptions {
|
|
31
|
+
format: WireFormat;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** A non-ok HTTP response from the model endpoint, with the provider's own error
|
|
35
|
+
* body attached when there is one. Thrown before a single chunk is read, so a
|
|
36
|
+
* caller can distinguish "the request failed" from "the stream carried an
|
|
37
|
+
* error", which is `ModelTurn.error`. */
|
|
38
|
+
export class WireError extends Error {
|
|
39
|
+
readonly status: number;
|
|
40
|
+
readonly statusText: string;
|
|
41
|
+
/** The response body parsed as JSON, or undefined when it was not JSON (an
|
|
42
|
+
* HTML error page from a proxy, most often). */
|
|
43
|
+
readonly body: unknown;
|
|
44
|
+
/** The raw response body, always. */
|
|
45
|
+
readonly bodyText: string;
|
|
46
|
+
|
|
47
|
+
constructor(status: number, statusText: string, bodyText: string, body: unknown) {
|
|
48
|
+
super(buildMessage(status, statusText, bodyText, body));
|
|
49
|
+
// Restores the prototype chain when this class is DOWNLEVELLED to ES5 by a
|
|
50
|
+
// consumer's build. Without it `err instanceof WireError` is false there,
|
|
51
|
+
// and the documented way to tell a transport failure from an in-band error
|
|
52
|
+
// stops working. A no-op in the shipped ES2020+ output.
|
|
53
|
+
Object.setPrototypeOf(this, WireError.prototype);
|
|
54
|
+
this.name = 'WireError';
|
|
55
|
+
this.status = status;
|
|
56
|
+
this.statusText = statusText;
|
|
57
|
+
this.bodyText = bodyText;
|
|
58
|
+
this.body = body;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function providerMessage(body: unknown): string | undefined {
|
|
63
|
+
if (typeof body !== 'object' || body === null) return undefined;
|
|
64
|
+
const error = (body as { error?: unknown }).error;
|
|
65
|
+
if (typeof error === 'string') return error;
|
|
66
|
+
if (typeof error === 'object' && error !== null) {
|
|
67
|
+
const message = (error as { message?: unknown }).message;
|
|
68
|
+
if (typeof message === 'string') return message;
|
|
69
|
+
}
|
|
70
|
+
const top = (body as { message?: unknown }).message;
|
|
71
|
+
return typeof top === 'string' ? top : undefined;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function buildMessage(status: number, statusText: string, bodyText: string, body: unknown): string {
|
|
75
|
+
const head = `Model request failed: HTTP ${status}${statusText ? ` ${statusText}` : ''}`;
|
|
76
|
+
const provider = providerMessage(body);
|
|
77
|
+
if (provider) return `${head}: ${provider}`;
|
|
78
|
+
const snippet = bodyText.trim().slice(0, 200);
|
|
79
|
+
return snippet ? `${head}: ${snippet}` : head;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Duck-typed, because `Response` may not be a global in every runtime the kit
|
|
83
|
+
* is imported into. A ReadableStream has no `ok`, and an async iterable that
|
|
84
|
+
* happens to have all three of these is not a shape any transport produces. */
|
|
85
|
+
function isResponse(source: StreamSource): source is Response {
|
|
86
|
+
return (
|
|
87
|
+
typeof source === 'object' &&
|
|
88
|
+
source !== null &&
|
|
89
|
+
'ok' in source &&
|
|
90
|
+
'status' in source &&
|
|
91
|
+
'body' in source
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function wireErrorFrom(res: Response): Promise<WireError> {
|
|
96
|
+
const bodyText = await res.text().catch(() => '');
|
|
97
|
+
let body: unknown;
|
|
98
|
+
try {
|
|
99
|
+
body = bodyText ? JSON.parse(bodyText) : undefined;
|
|
100
|
+
} catch {
|
|
101
|
+
body = undefined; // an HTML error page, or anything else non-JSON
|
|
102
|
+
}
|
|
103
|
+
return new WireError(res.status, res.statusText, bodyText, body);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async function toByteSource(source: StreamSource): Promise<ByteSource> {
|
|
107
|
+
if (!isResponse(source)) return source;
|
|
108
|
+
if (!source.ok) throw await wireErrorFrom(source);
|
|
109
|
+
if (!source.body) {
|
|
110
|
+
throw new Error(
|
|
111
|
+
'The model response has no body to stream. Check that the request set stream: true and that nothing between you and the provider buffered it.',
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
return source.body;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Read one turn off the wire in `opts.format` and drive `sink` with it. */
|
|
118
|
+
export async function readModelStream(
|
|
119
|
+
source: StreamSource,
|
|
120
|
+
sink: AssistantStreamSink,
|
|
121
|
+
opts: ReadOptions,
|
|
122
|
+
): Promise<ModelTurn> {
|
|
123
|
+
const bytes = await toByteSource(source);
|
|
124
|
+
// Opened ONCE per stream, which is the whole point of the open()/push() shape:
|
|
125
|
+
// a stateful format (Anthropic) gets a fresh block map per call.
|
|
126
|
+
const reader = opts.format.open();
|
|
127
|
+
async function* chunks(): AsyncGenerator<ModelStreamChunk> {
|
|
128
|
+
for await (const frame of sseJson<unknown>(bytes)) {
|
|
129
|
+
for (const chunk of reader.push(frame)) yield chunk;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return consumeModelStream(chunks(), sink, opts);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** OpenAI chat-completions SSE. Also what all nine catalog integrations except
|
|
136
|
+
* `mock` re-frame to server-side, so this is the common path. */
|
|
137
|
+
export function readOpenAIStream(
|
|
138
|
+
source: StreamSource,
|
|
139
|
+
sink: AssistantStreamSink,
|
|
140
|
+
opts: ConsumeOptions = {},
|
|
141
|
+
): Promise<ModelTurn> {
|
|
142
|
+
return readModelStream(source, sink, { ...opts, format: openaiChatFormat });
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Anthropic Messages SSE. */
|
|
146
|
+
export function readAnthropicStream(
|
|
147
|
+
source: StreamSource,
|
|
148
|
+
sink: AssistantStreamSink,
|
|
149
|
+
opts: ConsumeOptions = {},
|
|
150
|
+
): Promise<ModelTurn> {
|
|
151
|
+
return readModelStream(source, sink, { ...opts, format: anthropicMessagesFormat });
|
|
152
|
+
}
|