@kitn.ai/ui 0.20.1 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -45
- package/dist/components/artifact-card.d.ts +51 -0
- package/dist/components/artifact.d.ts +1 -1
- package/dist/components/attachments.d.ts +2 -2
- package/dist/components/audio-visualizer/index.d.ts +3 -3
- package/dist/components/audio-visualizer/labs/lab-choreography.d.ts +59 -0
- package/dist/components/audio-visualizer/labs/lab-shaders.d.ts +22 -0
- package/dist/components/audio-visualizer/labs/lab-visualizer.d.ts +18 -0
- package/dist/components/audio-visualizer/variant-aurora.d.ts +2 -2
- package/dist/components/audio-visualizer/variant-bar.d.ts +2 -2
- package/dist/components/audio-visualizer/variant-custom.d.ts +2 -2
- package/dist/components/audio-visualizer/variant-grid.d.ts +1 -1
- package/dist/components/audio-visualizer/variant-radial.d.ts +1 -1
- package/dist/components/audio-visualizer/variant-wave.d.ts +1 -1
- package/dist/components/card-fallback.d.ts +12 -2
- package/dist/components/card-renderer.d.ts +63 -2
- package/dist/components/chat-container.d.ts +3 -0
- package/dist/components/chat-scope-picker.d.ts +2 -2
- package/dist/components/chat-thread.d.ts +77 -18
- package/dist/components/choice-card.d.ts +3 -28
- package/dist/components/composer-dom.d.ts +1 -1
- package/dist/components/composer-highlight.d.ts +1 -1
- package/dist/components/composer-history.d.ts +1 -1
- package/dist/components/composer.d.ts +1 -1
- package/dist/components/confirm-card.d.ts +3 -18
- package/dist/components/context.d.ts +8 -4
- package/dist/components/conversation-item.d.ts +1 -1
- package/dist/components/conversation-list.d.ts +4 -3
- package/dist/components/embed.d.ts +3 -3
- package/dist/components/file-tree.d.ts +11 -22
- package/dist/components/form-widgets.d.ts +1 -1
- package/dist/components/form.d.ts +3 -44
- package/dist/components/link-preview.d.ts +3 -3
- package/dist/components/loader.d.ts +30 -48
- package/dist/components/message-skills.d.ts +4 -0
- package/dist/components/message.d.ts +84 -21
- package/dist/components/model-switcher.d.ts +2 -2
- package/dist/components/prompt-input.d.ts +2 -2
- package/dist/components/response-compare-types.d.ts +11 -2
- package/dist/components/response-compare.d.ts +3 -3
- package/dist/components/source.d.ts +12 -3
- package/dist/components/tasks-card.d.ts +3 -31
- package/dist/components/thread.d.ts +17 -5
- package/dist/components/toast.d.ts +2 -2
- package/dist/components/tool-classify.d.ts +8 -0
- package/dist/components/tool-types.d.ts +29 -1
- package/dist/components/tool.d.ts +3 -3
- package/dist/components/use-card-resolution.d.ts +1 -1
- package/dist/create-tween-COnPwAQN.js +338 -0
- package/dist/create-tween-DmTUaXWE.js +341 -0
- package/dist/create-tween-qs-jjvNA.js +7 -0
- package/dist/custom-elements.json +1726 -463
- package/dist/elements/agent-card.js +1 -1
- package/dist/elements/artifact.js +1 -1
- package/dist/elements/attachments.js +1 -1
- package/dist/elements/audio-visualizer.js +1 -1
- package/dist/elements/avatar.js +1 -1
- package/dist/elements/badge.js +1 -1
- package/dist/elements/button.js +1 -1
- package/dist/elements/card.js +1 -1
- package/dist/elements/cards.js +1 -1
- package/dist/elements/chain-of-thought.js +1 -1
- package/dist/elements/chat-scope-picker.js +1 -1
- package/dist/elements/chat-types.d.ts +70 -10
- package/dist/elements/chat-workspace.js +1 -1
- package/dist/elements/chat.js +1 -1
- package/dist/elements/checkpoint.js +1 -1
- package/dist/elements/choice.js +1 -1
- package/dist/elements/chunks/{Icon-Tj58ES_d.js → Icon-CHG5z5L7.js} +1 -1
- package/dist/elements/chunks/{action-icons-ns1Fnedj.js → action-icons-BAtCEEgg.js} +1 -1
- package/dist/elements/chunks/arrow-left-CFPTeL1Y.js +1 -0
- package/dist/elements/chunks/artifact-fBZOE4SO.js +1 -0
- package/dist/elements/chunks/{attachments-mCB5F1Fq.js → attachments-DpYMe5VQ.js} +1 -1
- package/dist/elements/chunks/audio-visualizer-DFwzAcuL.js +1 -0
- package/dist/elements/chunks/{badge-B4Zuajl2.js → badge-D6zodm6E.js} +1 -1
- package/dist/elements/chunks/{button-ZWNif3--.js → button-C42vrGvO.js} +1 -1
- package/dist/elements/chunks/card-renderer-DMZsSuvU.js +1 -0
- package/dist/elements/chunks/card-tags-D8lZ-C_U.js +1 -0
- package/dist/elements/chunks/chat-thread-CNNEhol-.js +1 -0
- package/dist/elements/chunks/check-7Q3K-iXE.js +1 -0
- package/dist/elements/chunks/chevron-down-CpNYLn7E.js +1 -0
- package/dist/elements/chunks/chevron-right-DrH85Fha.js +1 -0
- package/dist/elements/chunks/choice-card-YoWjpsFV.js +1 -0
- package/dist/elements/chunks/circle-C9KRjKYv.js +1 -0
- package/dist/elements/chunks/circle-check-Dmi7Y4zw.js +1 -0
- package/dist/elements/chunks/{circle-x-B7faJFlY.js → circle-x-QIxXda6k.js} +1 -1
- package/dist/elements/chunks/code-block-CqeugReY.js +1 -0
- package/dist/elements/chunks/{collapsible-DM1Ckd4e.js → collapsible-sTv-_5lF.js} +1 -1
- package/dist/elements/chunks/{composer-BguS0QL_.js → composer-ldQyeVEB.js} +2 -2
- package/dist/elements/chunks/confirm-card-C0RNOwfn.js +1 -0
- package/dist/elements/chunks/context-5qgypNHC.js +1 -0
- package/dist/elements/chunks/conversation-list-DLzOacXz.js +1 -0
- package/dist/elements/chunks/create-tween-V4TXZ93X.js +7 -0
- package/dist/elements/chunks/default-input-DeeRSDHc.js +1 -0
- package/dist/elements/chunks/define-lE-QvQFn.js +1 -0
- package/dist/elements/chunks/{disclosure-CIZ5c8nO.js → disclosure-DZSa9jog.js} +1 -1
- package/dist/elements/chunks/{download-Dsbg3ste.js → download-B2ZmnGqW.js} +1 -1
- package/dist/elements/chunks/{dropdown-BDd1-Zgi.js → dropdown-R3B7cSRE.js} +1 -1
- package/dist/elements/chunks/{ellipsis-Dw6qrss-.js → ellipsis-BmxceY4l.js} +1 -1
- package/dist/elements/chunks/embed-BVrtTZPo.js +1 -0
- package/dist/elements/chunks/{external-link-Vt74zAoL.js → external-link-BbdR7YO7.js} +1 -1
- package/dist/elements/chunks/{file-text-CZrZF6_v.js → file-text-D7dijo1A.js} +1 -1
- package/dist/elements/chunks/{file-tree-DF4okKyo.js → file-tree-0upJ5jeb.js} +1 -1
- package/dist/elements/chunks/{folder-glP5rha4.js → folder-D8z58t7B.js} +1 -1
- package/dist/elements/chunks/form-p5BKJNw5.js +1 -0
- package/dist/elements/chunks/hover-card-CN9s2md5.js +1 -0
- package/dist/elements/chunks/{icon-BLb8beoi.js → icon-CNiKP37E.js} +1 -1
- package/dist/elements/chunks/{info-CIsv92JK.js → info-BSnuJiHI.js} +1 -1
- package/dist/elements/chunks/{input-Dds1kFyt.js → input-BauDmzxX.js} +1 -1
- package/dist/elements/chunks/{kbd-De_xbgi9.js → kbd-BlHJaP9b.js} +1 -1
- package/dist/elements/chunks/{link-i4r_k7z4.js → link-DEi5iUWC.js} +1 -1
- package/dist/elements/chunks/link-preview-BaHyM6W4.js +1 -0
- package/dist/elements/chunks/{loader-BmQH4s3V.js → loader-CmJG_Fkq.js} +1 -1
- package/dist/elements/chunks/{markdown-Zi21WhUz.js → markdown-D2jaMpCN.js} +1 -1
- package/dist/elements/chunks/message-C5UfXZ2Z.js +1 -0
- package/dist/elements/chunks/message-CXWLmmLn.js +1 -0
- package/dist/elements/chunks/message-square-hXylwsy0.js +1 -0
- package/dist/elements/chunks/{minimize-2--ifeCGrl.js → minimize-2-C9ytZFMU.js} +1 -1
- package/dist/elements/chunks/model-switcher-CivGfD7-.js +1 -0
- package/dist/elements/chunks/{overlay-kD8dwRv_.js → overlay-DJL_sbOm.js} +1 -1
- package/dist/elements/chunks/{paperclip-q21dYOSK.js → paperclip-CFaB8naM.js} +1 -1
- package/dist/elements/chunks/{progress-bar-BrAIIaaJ.js → progress-bar-Br5SksfR.js} +1 -1
- package/dist/elements/chunks/{prompt-suggestion-Bjn2K-fR.js → prompt-suggestion-CMIvtAhU.js} +1 -1
- package/dist/elements/chunks/reasoning-DpZcNzLq.js +1 -0
- package/dist/elements/chunks/{resizable-Dp4JExql.js → resizable-Q8i3T9a_.js} +1 -1
- package/dist/elements/chunks/{rotate-cw-CFWWkpj2.js → rotate-cw-B5IHCBFV.js} +1 -1
- package/dist/elements/chunks/{scroll-area-Bb0kHH1j.js → scroll-area-eLeydBje.js} +1 -1
- package/dist/elements/chunks/scroll-button-DWbz0yA1.js +1 -0
- package/dist/elements/chunks/{separator-9kRgr1X9.js → separator-Xic78AD_.js} +1 -1
- package/dist/elements/chunks/{settings-B8lmec4D.js → settings-B8H51pAx.js} +1 -1
- package/dist/elements/chunks/{settings-group-I0hEuXiN.js → settings-group-CsG9eYLe.js} +1 -1
- package/dist/elements/chunks/{share-C3lTiYGt.js → share-CNTx0z__.js} +1 -1
- package/dist/elements/chunks/skeleton-CLlHTcMI.js +1 -0
- package/dist/elements/chunks/slots-CIw9RlAe.js +1 -0
- package/dist/elements/chunks/source-BoVcU5ok.js +1 -0
- package/dist/elements/chunks/{star-CSJtGxgv.js → star-CxQVTVhI.js} +1 -1
- package/dist/elements/chunks/{store-CHyW95TP.js → store-CLtS5CUG.js} +1 -1
- package/dist/elements/chunks/tasks-card-CzlkLmUt.js +1 -0
- package/dist/elements/chunks/{text-shimmer-Cn9TmNpI.js → text-shimmer-D5NhpIQ1.js} +1 -1
- package/dist/elements/chunks/textarea-x5Uxsmtm.js +1 -0
- package/dist/elements/chunks/{thumbs-up-PNVVThEF.js → thumbs-up-UobcJnx9.js} +1 -1
- package/dist/elements/chunks/{toast-store-DKbQn3Wn.js → toast-store-BOfs9eXq.js} +1 -1
- package/dist/elements/chunks/{tool-DjTRpIpl.js → tool-Be6Dh_dt.js} +1 -1
- package/dist/elements/chunks/tooltip-RxMxyCLF.js +1 -0
- package/dist/elements/chunks/{triangle-alert-DsHTHCPm.js → triangle-alert-C4yxH2XN.js} +1 -1
- package/dist/elements/chunks/use-card-resolution-jva9ehH6.js +1 -0
- package/dist/elements/chunks/{variant-aurora-Dc4nQUia.js → variant-aurora-Dx4VRcjS.js} +2 -2
- package/dist/elements/chunks/{variant-custom-y_3kVQhS.js → variant-custom-Y1ZTWgEa.js} +1 -1
- package/dist/elements/chunks/{variant-wave-D5OR1-9Z.js → variant-wave-B9gml9Cg.js} +2 -2
- package/dist/elements/chunks/{video-B7vmenoC.js → video-CrPHsytT.js} +1 -1
- package/dist/elements/chunks/x-_-OPHept.js +1 -0
- package/dist/elements/coachmark.js +1 -1
- package/dist/elements/code-block.js +1 -1
- package/dist/elements/command.js +1 -1
- package/dist/elements/compare.js +1 -1
- package/dist/elements/composer.js +1 -1
- package/dist/elements/confirm-card.js +1 -1
- package/dist/elements/context-meter.js +1 -1
- package/dist/elements/conversation-list.js +1 -1
- package/dist/elements/default-input.d.ts +34 -7
- package/dist/elements/define.d.ts +1 -0
- package/dist/elements/dialog.js +1 -1
- package/dist/elements/disclosure.d.ts +1 -1
- package/dist/elements/editable-label.js +1 -1
- package/dist/elements/element-data-types.d.ts +107 -0
- package/dist/elements/embed.js +1 -1
- package/dist/elements/empty.js +1 -1
- package/dist/elements/feedback-bar.js +1 -1
- package/dist/elements/file-tree.js +1 -1
- package/dist/elements/file-upload.js +1 -1
- package/dist/elements/form.js +1 -1
- package/dist/elements/hover-card.js +1 -1
- package/dist/elements/icon.js +1 -1
- package/dist/elements/image.js +1 -1
- package/dist/elements/input.js +1 -1
- package/dist/elements/kbd.js +1 -1
- package/dist/elements/link-preview.js +1 -1
- package/dist/elements/loader.js +1 -1
- package/dist/elements/markdown.js +1 -1
- package/dist/elements/menu.js +1 -1
- package/dist/elements/message-skills.js +1 -1
- package/dist/elements/message.js +1 -1
- package/dist/elements/model-switcher.js +1 -1
- package/dist/elements/nav.js +1 -1
- package/dist/elements/notice.js +1 -1
- package/dist/elements/pane-group.js +1 -1
- package/dist/elements/pane.js +1 -1
- package/dist/elements/popover.js +1 -1
- package/dist/elements/progress-bar.js +1 -1
- package/dist/elements/prompt-dock.js +1 -1
- package/dist/elements/prompt-input.js +1 -1
- package/dist/elements/prompt-suggestions.js +1 -1
- package/dist/elements/reasoning.js +1 -1
- package/dist/elements/register.d.ts +8 -6
- package/dist/elements/remote.js +1 -1
- package/dist/elements/resizable.js +1 -1
- package/dist/elements/response-stream.js +1 -1
- package/dist/elements/screen.js +1 -1
- package/dist/elements/scroll-area.js +1 -1
- package/dist/elements/scroll-button.js +1 -1
- package/dist/elements/search.js +1 -1
- package/dist/elements/segmented.js +1 -1
- package/dist/elements/separator.js +1 -1
- package/dist/elements/setting-item.js +1 -1
- package/dist/elements/settings-group.js +1 -1
- package/dist/elements/skeleton.js +1 -1
- package/dist/elements/slot-text.d.ts +51 -0
- package/dist/elements/slots.d.ts +21 -0
- package/dist/elements/source.js +1 -1
- package/dist/elements/status.js +1 -1
- package/dist/elements/switch.js +1 -1
- package/dist/elements/tabs.js +1 -1
- package/dist/elements/tasks.js +1 -1
- package/dist/elements/text-shimmer.js +1 -1
- package/dist/elements/thinking-bar.js +1 -1
- package/dist/elements/thread.js +1 -1
- package/dist/elements/toast.js +1 -1
- package/dist/elements/tool.js +1 -1
- package/dist/elements/tooltip.js +1 -1
- package/dist/elements/validate-messages.d.ts +25 -0
- package/dist/elements/voice-input.js +1 -1
- package/dist/elements/voice-output.js +1 -1
- package/dist/elements.d.ts +2734 -217
- package/dist/index.d.ts +163 -125
- package/dist/index.js +6572 -5990
- package/dist/index.server.d.ts +5 -0
- package/dist/index.server.js +5403 -4860
- package/dist/kai-provider.es.d.ts +5 -0
- package/dist/kai-provider.es.js +79 -77
- package/dist/kai.es.d.ts +5 -0
- package/dist/kai.es.js +1 -1
- package/dist/llms/llms-full.txt +774 -151
- package/dist/llms/llms.txt +57 -5
- package/dist/mcp.es.js +4756 -782
- package/dist/primitives/card-component-types.d.ts +11 -0
- package/dist/primitives/card-data-types.d.ts +196 -0
- package/dist/primitives/card-host.d.ts +1 -1
- package/dist/primitives/card-recovery.d.ts +3 -3
- package/dist/primitives/card-registry.d.ts +4 -14
- package/dist/primitives/card-resolution.d.ts +1 -1
- package/dist/primitives/card-routing.d.ts +1 -1
- package/dist/primitives/card-tags.d.ts +11 -0
- package/dist/primitives/card-validate-cards.d.ts +57 -0
- package/dist/primitives/card-validate-generator.testlib.d.ts +20 -0
- package/dist/primitives/card-validate-schemas.d.ts +17 -0
- package/dist/primitives/card-validate.d.ts +24 -0
- package/dist/primitives/create-kai-chat.d.ts +3 -3
- package/dist/primitives/embed-providers.d.ts +1 -1
- package/dist/primitives/link-preview.d.ts +1 -1
- package/dist/primitives/message-feedback.d.ts +2 -2
- package/dist/primitives/toast-store.d.ts +1 -1
- package/dist/react/index.d.ts +730 -302
- package/dist/react/use-kai-chat.d.ts +8 -4
- package/dist/react.d.ts +5 -0
- package/dist/react.js +418 -317
- package/dist/register-impl-AxIlqjo7.js +145 -0
- package/dist/remote/host-embed.d.ts +1 -1
- package/dist/remote/index.d.ts +2 -2
- package/dist/remote/provider-runtime.d.ts +2 -2
- package/dist/remote/provider.d.ts +2 -2
- package/dist/remote/validate.d.ts +1 -1
- package/dist/remote/wire.d.ts +1 -1
- package/dist/schemas/artifact.schema.json +92 -0
- package/dist/schemas/embed.schema.json +0 -2
- package/dist/schemas/from-tool-call.d.ts +48 -0
- package/dist/schemas/index.d.ts +48 -0
- package/dist/schemas/link.schema.json +0 -2
- package/dist/schemas/provider-subsets.d.ts +136 -0
- package/dist/schemas/registry.d.ts +140 -0
- package/dist/schemas/tool-defs.d.ts +121 -0
- package/dist/schemas.d.ts +5 -0
- package/dist/schemas.js +717 -0
- package/dist/solid-BGYSJyHG.js +22960 -0
- package/dist/solid-Dt2nEwfl.js +20194 -0
- package/dist/solid.d.ts +66 -0
- package/dist/solid.js +261 -0
- package/dist/solid.server.d.ts +5 -0
- package/dist/solid.server.js +261 -0
- package/dist/state/index.d.ts +14 -4
- package/dist/state/messages.d.ts +8 -3
- package/dist/state/mock.d.ts +46 -0
- package/dist/state/parts.d.ts +85 -0
- package/dist/state/stream.d.ts +15 -9
- package/dist/state.d.ts +5 -0
- package/dist/state.js +253 -84
- package/dist/ui/action-icons.d.ts +1 -1
- package/dist/ui/collapsible.d.ts +11 -6
- package/dist/ui/dropdown.d.ts +47 -42
- package/dist/ui/pane-group.d.ts +1 -1
- package/dist/{variant-aurora-DVtGqVH6.js → variant-aurora-B44X69AG.js} +1 -1
- package/dist/variant-aurora-BRhV8Flk.js +391 -0
- package/dist/variant-aurora-Ds5ou9BH.js +381 -0
- package/dist/variant-custom-B20FhtYj.js +170 -0
- package/dist/{variant-custom-Dif-IV4u.js → variant-custom-BvA1Schl.js} +1 -1
- package/dist/variant-custom-D6Rtvneb.js +200 -0
- package/dist/{variant-wave-DbfNdvSi.js → variant-wave-BUQtkryq.js} +1 -1
- package/dist/variant-wave-BVYnqy9L.js +218 -0
- package/dist/variant-wave-DXC6_LcH.js +208 -0
- package/dist/wire/chunk.d.ts +171 -0
- package/dist/wire/consume.d.ts +26 -0
- package/dist/wire/encode.d.ts +221 -0
- package/dist/wire/files.d.ts +116 -0
- package/dist/wire/formats/anthropic.d.ts +2 -0
- package/dist/wire/formats/openai.d.ts +5 -0
- package/dist/wire/index.d.ts +16 -0
- package/dist/wire/media-types.d.ts +106 -0
- package/dist/wire/read.d.ts +26 -0
- package/dist/wire/sink-helpers.d.ts +21 -0
- package/dist/wire/sse.d.ts +33 -0
- package/dist/wire.d.ts +5 -53
- package/dist/wire.js +1126 -0
- package/frameworks/react/index.tsx +191 -158
- package/frameworks/react/runtime.tsx +21 -1
- package/frameworks/react/use-kai-chat.tsx +11 -1
- package/llms-full.txt +774 -151
- package/llms.txt +57 -5
- package/package.json +62 -8
- package/src/agent-tooling/archetypes.ts +127 -0
- package/src/agent-tooling/integrations/anthropic.ts +436 -0
- package/src/agent-tooling/integrations/cloudflare.ts +141 -52
- package/src/agent-tooling/integrations/langgraph.ts +80 -13
- package/src/agent-tooling/integrations/mastra.ts +161 -12
- package/src/agent-tooling/integrations/mock.ts +47 -8
- package/src/agent-tooling/integrations/ollama.ts +68 -24
- package/src/agent-tooling/integrations/openai.ts +89 -0
- package/src/agent-tooling/integrations/openrouter.ts +58 -8
- package/src/agent-tooling/integrations/pi.ts +104 -23
- package/src/agent-tooling/integrations/pydantic-ai.ts +61 -5
- package/src/agent-tooling/integrations/vercel-ai-sdk.ts +120 -13
- package/src/agent-tooling/mcp/manifest.ts +206 -25
- package/src/agent-tooling/mcp/tools/debug.ts +13 -7
- package/src/agent-tooling/mcp/tools/reference.ts +285 -6
- package/src/agent-tooling/mcp/tools/scaffold.ts +4152 -661
- package/src/agent-tooling/registry.ts +74 -0
- package/src/agent-tooling/types.ts +361 -1
- package/src/components/artifact-card.tsx +120 -0
- package/src/components/audio-visualizer/index.tsx +9 -0
- package/src/components/audio-visualizer/labs/lab-choreography.ts +187 -0
- package/src/components/audio-visualizer/labs/lab-shaders.ts +363 -0
- package/src/components/audio-visualizer/labs/lab-visualizer.tsx +102 -0
- package/src/components/audio-visualizer/shader-canvas.tsx +31 -1
- package/src/components/card-fallback.tsx +42 -10
- package/src/components/card-renderer.tsx +146 -9
- package/src/components/chat-container.tsx +4 -0
- package/src/components/chat-thread.tsx +128 -52
- package/src/components/choice-card.tsx +17 -29
- package/src/components/composer.tsx +12 -2
- package/src/components/confirm-card.tsx +24 -20
- package/src/components/context.tsx +30 -16
- package/src/components/conversation-item.tsx +6 -0
- package/src/components/conversation-list.tsx +18 -2
- package/src/components/file-tree.tsx +11 -22
- package/src/components/file-upload.tsx +14 -8
- package/src/components/form.tsx +14 -47
- package/src/components/loader.tsx +39 -12
- package/src/components/message-skills.tsx +4 -0
- package/src/components/message.tsx +294 -46
- package/src/components/model-switcher.tsx +38 -8
- package/src/components/reasoning.tsx +92 -5
- package/src/components/response-compare-types.ts +25 -0
- package/src/components/response-compare.tsx +4 -9
- package/src/components/source.tsx +50 -24
- package/src/components/tasks-card.tsx +10 -33
- package/src/components/thread.tsx +84 -42
- package/src/components/toast.tsx +17 -6
- package/src/components/tool-classify.ts +26 -0
- package/src/components/tool-types.ts +31 -1
- package/src/elements/agent-card.tsx +11 -11
- package/src/elements/artifact.tsx +15 -7
- package/src/elements/attachments.tsx +6 -4
- package/src/elements/avatar.tsx +1 -1
- package/src/elements/button.tsx +29 -6
- package/src/elements/card.tsx +2 -2
- package/src/elements/cards.tsx +109 -14
- package/src/elements/chain-of-thought.tsx +14 -10
- package/src/elements/chat-scope-picker.tsx +9 -7
- package/src/elements/chat-types.ts +59 -6
- package/src/elements/chat-workspace.tsx +50 -9
- package/src/elements/chat.tsx +90 -3
- package/src/elements/choice.tsx +8 -8
- package/src/elements/coachmark.tsx +1 -1
- package/src/elements/command.tsx +8 -23
- package/src/elements/compare.tsx +7 -6
- package/src/elements/compiled.css +1 -1
- package/src/elements/composer.tsx +8 -8
- package/src/elements/confirm-card.tsx +9 -9
- package/src/elements/context-meter.tsx +4 -18
- package/src/elements/conversation-list.tsx +14 -9
- package/src/elements/default-input.tsx +131 -8
- package/src/elements/define.tsx +178 -43
- package/src/elements/editable-label.tsx +17 -2
- package/src/elements/element-data-types.ts +112 -0
- package/src/elements/element-meta.json +1624 -306
- package/src/elements/element-types.d.ts +2645 -214
- package/src/elements/feedback-bar.tsx +1 -1
- package/src/elements/file-tree.tsx +1 -1
- package/src/elements/file-upload.tsx +1 -1
- package/src/elements/form.tsx +14 -6
- package/src/elements/input.tsx +1 -1
- package/src/elements/kbd.tsx +1 -1
- package/src/elements/menu.tsx +40 -32
- package/src/elements/message-skills.tsx +7 -9
- package/src/elements/message.tsx +200 -32
- package/src/elements/model-switcher.tsx +5 -3
- package/src/elements/pane.tsx +1 -1
- package/src/elements/prompt-dock.tsx +8 -8
- package/src/elements/prompt-input.tsx +10 -10
- package/src/elements/prompt-suggestions.tsx +7 -4
- package/src/elements/reasoning.tsx +1 -1
- package/src/elements/register.ts +9 -0
- package/src/elements/resizable.globals.d.ts +36 -0
- package/src/elements/resizable.tsx +24 -13
- package/src/elements/response-stream.tsx +1 -1
- package/src/elements/scroll-button.tsx +1 -1
- package/src/elements/search.tsx +6 -1
- package/src/elements/segmented.tsx +5 -11
- package/src/elements/separator.tsx +1 -1
- package/src/elements/slot-text.ts +73 -0
- package/src/elements/slots.ts +67 -24
- package/src/elements/source.tsx +21 -22
- package/src/elements/switch.tsx +3 -3
- package/src/elements/tasks.tsx +5 -4
- package/src/elements/thread.tsx +36 -4
- package/src/elements/toast.tsx +3 -2
- package/src/elements/tool.tsx +1 -1
- package/src/elements/validate-messages.ts +76 -0
- package/src/elements/voice-input.tsx +7 -7
- package/src/elements/voice-output.tsx +4 -4
- package/src/index.ts +145 -6
- package/src/primitives/card-component-types.ts +63 -0
- package/src/primitives/card-data-types.ts +312 -0
- package/src/primitives/card-recovery.ts +13 -2
- package/src/primitives/card-registry.tsx +34 -23
- package/src/primitives/card-schemas/artifact.schema.json +92 -0
- package/src/primitives/card-schemas/embed.schema.json +0 -2
- package/src/primitives/card-schemas/link.schema.json +0 -2
- package/src/primitives/card-tags.ts +61 -0
- package/src/primitives/card-validate-cards.ts +255 -0
- package/src/primitives/card-validate-generator.testlib.ts +38 -0
- package/src/primitives/card-validate-schemas.ts +58 -0
- package/src/primitives/card-validate.ts +48 -20
- package/src/primitives/create-tween.ts +54 -2
- package/src/primitives/message-feedback.ts +3 -2
- package/src/primitives/use-audio-analysis.ts +20 -1
- package/src/primitives/use-sequencer.ts +20 -1
- package/src/schemas/from-tool-call.ts +151 -0
- package/src/schemas/index.ts +340 -0
- package/src/schemas/provider-subsets.ts +538 -0
- package/src/schemas/registry.ts +355 -0
- package/src/schemas/tool-defs.ts +492 -0
- package/src/solid.ts +150 -0
- package/src/state/index.ts +27 -1
- package/src/state/messages.ts +21 -4
- package/src/state/mock.ts +211 -0
- package/src/state/parts.ts +295 -0
- package/src/state/stream.ts +56 -58
- package/src/ui/collapsible.tsx +17 -5
- package/src/ui/dropdown.tsx +46 -12
- package/src/ui/overlay.tsx +10 -4
- package/src/wire/chunk.ts +201 -0
- package/src/wire/consume.ts +440 -0
- package/src/wire/encode.ts +689 -0
- package/src/wire/files.ts +342 -0
- package/src/wire/formats/anthropic.ts +267 -0
- package/src/wire/formats/openai.ts +187 -0
- package/src/wire/index.ts +73 -0
- package/src/wire/media-types.ts +344 -0
- package/src/wire/read.ts +152 -0
- package/src/wire/sink-helpers.ts +50 -0
- package/src/wire/sse.ts +140 -0
- package/dist/create-tween-hvpmSuwW.js +0 -7
- package/dist/elements/chunks/arrow-left-DQocUFvy.js +0 -1
- package/dist/elements/chunks/audio-visualizer-BqNy7dfm.js +0 -1
- package/dist/elements/chunks/card-BkHbp4BA.js +0 -1
- package/dist/elements/chunks/chat-thread-CkRN6pZ7.js +0 -1
- package/dist/elements/chunks/check-B9-gn6SS.js +0 -1
- package/dist/elements/chunks/chevron-down-BNsxc2hk.js +0 -1
- package/dist/elements/chunks/chevron-right-Hv00StBO.js +0 -1
- package/dist/elements/chunks/circle-0e3KJTDr.js +0 -1
- package/dist/elements/chunks/circle-check-BNAS0JEy.js +0 -1
- package/dist/elements/chunks/code-block-BFUHn6Va.js +0 -1
- package/dist/elements/chunks/context-Cb7k4F-v.js +0 -1
- package/dist/elements/chunks/conversation-list-CvoAkWhN.js +0 -1
- package/dist/elements/chunks/create-tween-oVvAIG8r.js +0 -7
- package/dist/elements/chunks/default-input-Cqqilg9t.js +0 -1
- package/dist/elements/chunks/define-D1din7Ld.js +0 -1
- package/dist/elements/chunks/hover-card-Cmm6Q53m.js +0 -1
- package/dist/elements/chunks/message-BqyXDN3-.js +0 -1
- package/dist/elements/chunks/message-feedback-Djr3z6Yb.js +0 -1
- package/dist/elements/chunks/message-square-vFAgbDaX.js +0 -1
- package/dist/elements/chunks/model-switcher-B94YAKsM.js +0 -1
- package/dist/elements/chunks/reasoning-DTpCGtnY.js +0 -1
- package/dist/elements/chunks/scroll-button-B0cH5fXf.js +0 -1
- package/dist/elements/chunks/skeleton-DSXW5Yya.js +0 -1
- package/dist/elements/chunks/slots--V19HwUb.js +0 -1
- package/dist/elements/chunks/textarea-CUwqAX0G.js +0 -1
- package/dist/elements/chunks/tooltip-CgBZCVKp.js +0 -1
- package/dist/elements/chunks/use-card-resolution-BSu_XHjH.js +0 -1
- package/dist/elements/chunks/x-GRUeeQFT.js +0 -1
- package/dist/host-embed.d.ts +0 -33
- package/dist/kai-provider.d.ts +0 -2
- package/dist/origin.d.ts +0 -5
- package/dist/provider-runtime.d.ts +0 -27
- package/dist/register-impl-DQyejdfb.js +0 -145
- package/dist/validate.d.ts +0 -4
- package/dist/version.d.ts +0 -4
- package/src/components/component-meta.json +0 -4550
- package/src/elements/card-media.jpg +0 -0
- package/src/elements/resizable.d.ts +0 -27
- package/src/elements/video-poster.jpg +0 -0
|
@@ -7,20 +7,70 @@ const openrouter: Integration = {
|
|
|
7
7
|
language: 'ts',
|
|
8
8
|
streamFormat: 'openai-sse',
|
|
9
9
|
envVars: ['OPENROUTER_API_KEY'],
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
// No per-framework templates: the handler below is web-standard, so the
|
|
11
|
+
// scaffolder wraps it in whatever the target framework routes with. This used
|
|
12
|
+
// to be a `next`-only entry that every other framework inherited verbatim.
|
|
13
|
+
routeTemplates: {},
|
|
14
|
+
webRoute: `async function chatHandler(request: Request): Promise<Response> {
|
|
15
|
+
// tools is undefined unless the front end sent one; JSON.stringify drops it,
|
|
16
|
+
// so the same handler serves a tool archetype and a plain chat.
|
|
17
|
+
const { model, messages, tools } = await readChatRequest(request);
|
|
18
|
+
|
|
13
19
|
const upstream = await fetch('https://openrouter.ai/api/v1/chat/completions', {
|
|
14
20
|
method: 'POST',
|
|
15
|
-
headers: {
|
|
16
|
-
|
|
21
|
+
headers: {
|
|
22
|
+
Authorization: \`Bearer \${process.env.OPENROUTER_API_KEY}\`,
|
|
23
|
+
'Content-Type': 'application/json',
|
|
24
|
+
},
|
|
25
|
+
body: JSON.stringify({ model, messages, tools, stream: true }),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// FORWARD THE STATUS. Returning 200 here is how a missing key turns into
|
|
29
|
+
// silence: the 401 body is JSON, it goes out labelled text/event-stream, the
|
|
30
|
+
// SSE reader finds no frame, and the turn resolves empty with nothing logged
|
|
31
|
+
// and no bubble. With the status intact readOpenAIStream throws a WireError
|
|
32
|
+
// carrying the provider's own message.
|
|
33
|
+
if (!upstream.ok) {
|
|
34
|
+
return new Response(await upstream.text(), {
|
|
35
|
+
status: upstream.status,
|
|
36
|
+
headers: {
|
|
37
|
+
'Content-Type': upstream.headers.get('content-type') ?? 'application/json',
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
if (!upstream.body) {
|
|
42
|
+
return new Response(JSON.stringify({ error: { message: 'The provider returned no body to stream.' } }), {
|
|
43
|
+
status: 502,
|
|
44
|
+
headers: { 'Content-Type': 'application/json' },
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return new Response(upstream.body, {
|
|
49
|
+
status: 200,
|
|
50
|
+
headers: {
|
|
51
|
+
'Content-Type': 'text/event-stream; charset=utf-8',
|
|
52
|
+
// no-transform stops a proxy buffering the stream into one blob.
|
|
53
|
+
'Cache-Control': 'no-cache, no-transform',
|
|
54
|
+
Connection: 'keep-alive',
|
|
55
|
+
},
|
|
17
56
|
});
|
|
18
|
-
return new Response(upstream.body, { headers: { 'Content-Type': 'text/event-stream' } });
|
|
19
57
|
}`,
|
|
20
|
-
|
|
21
|
-
|
|
58
|
+
streamMapping:
|
|
59
|
+
'OpenRouter returns OpenAI-format SSE. Pipe upstream.body straight to the browser; readOpenAIStream from @kitn.ai/ui/wire parses it, including tool calls and reasoning.',
|
|
22
60
|
runNote: 'Set OPENROUTER_API_KEY. Model ids are vendor/model, e.g. openai/gpt-4o.',
|
|
23
61
|
docsSlug: 'integrations/connect-any-model',
|
|
62
|
+
// The route forwards both, so a scaffold's editable consts reach the gateway.
|
|
63
|
+
forwardsFromClient: ['model', 'tools'],
|
|
64
|
+
// The handler puts the client's `tools` straight into an OpenAI-compatible
|
|
65
|
+
// /chat/completions body, unconverted — so the client must send OpenAI's own
|
|
66
|
+
// `{ type: 'function', function: { parameters } }` envelope.
|
|
67
|
+
clientToolFormat: 'openai',
|
|
68
|
+
// Nothing to install. The route is global `fetch` and imports no module.
|
|
69
|
+
deps: { npm: [], pip: [] },
|
|
70
|
+
// The route reads OPENROUTER_API_KEY into an `Authorization: Bearer` header.
|
|
71
|
+
keyExposure: 'needs-proxy',
|
|
72
|
+
// A remote HTTPS endpoint and a key. Nothing to install, nothing to start.
|
|
73
|
+
outOfBand: 'none',
|
|
24
74
|
};
|
|
25
75
|
|
|
26
76
|
export default openrouter;
|
|
@@ -8,33 +8,114 @@ const pi: Integration = {
|
|
|
8
8
|
streamFormat: 'native',
|
|
9
9
|
envVars: [],
|
|
10
10
|
routeTemplates: {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
//
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
11
|
+
// Express only, and genuinely so: the bridge spawns a local process, which a
|
|
12
|
+
// Worker or an edge runtime cannot do. Every other framework gets this under
|
|
13
|
+
// the scaffolder's "cannot host" warning, the same way pydantic-ai emits a
|
|
14
|
+
// whole FastAPI service.
|
|
15
|
+
express: `// server.ts
|
|
16
|
+
import express from 'express';
|
|
17
|
+
import { spawn } from 'node:child_process';
|
|
18
|
+
import type { OpenAIWireMessage } from '@kitn.ai/ui/wire';
|
|
19
|
+
|
|
20
|
+
const app = express();
|
|
21
|
+
app.use(express.json());
|
|
22
|
+
|
|
23
|
+
// POST /api/chat: bridge a Pi RPC session to the browser as SSE.
|
|
24
|
+
app.post('/api/chat', (req, res) => {
|
|
25
|
+
const { messages } = req.body as { messages: OpenAIWireMessage[] };
|
|
26
|
+
const last = messages.at(-1)?.content;
|
|
27
|
+
// \`content\` is a plain string until the turn carries an attachment, at which
|
|
28
|
+
// point it is an ARRAY of content parts. Pi's RPC mode takes a TEXT prompt and
|
|
29
|
+
// has no channel for a file, so an attachment is REFUSED here. Passing the
|
|
30
|
+
// array straight through would JSON.stringify an object graph into the prompt
|
|
31
|
+
// — no type error, no crash, just a model reading serialised noise.
|
|
32
|
+
if (Array.isArray(last) && last.some((part) => part.type !== 'text')) {
|
|
33
|
+
res.status(400).json({
|
|
34
|
+
error: {
|
|
35
|
+
message:
|
|
36
|
+
'This Pi bridge forwards a text prompt only and has no channel for an attachment. Extract the file content into the message text, or send it through a tool.',
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
return;
|
|
31
40
|
}
|
|
41
|
+
const prompt =
|
|
42
|
+
typeof last === 'string'
|
|
43
|
+
? last
|
|
44
|
+
: (last ?? []).map((part) => (part.type === 'text' ? part.text : '')).join('');
|
|
45
|
+
|
|
46
|
+
// Headers before the first frame: without text/event-stream the browser
|
|
47
|
+
// buffers the body and readOpenAIStream never sees a frame.
|
|
48
|
+
res.setHeader('Content-Type', 'text/event-stream; charset=utf-8');
|
|
49
|
+
// no-transform stops a proxy buffering the stream into one blob.
|
|
50
|
+
res.setHeader('Cache-Control', 'no-cache, no-transform');
|
|
51
|
+
res.setHeader('Connection', 'keep-alive');
|
|
52
|
+
|
|
53
|
+
// ONE PROCESS PER REQUEST. Pi in RPC mode holds conversation state, so a
|
|
54
|
+
// module-level process would leak one caller's turns into the next caller's
|
|
55
|
+
// reply and serialise every request behind a single stdin.
|
|
56
|
+
const pi = spawn('pi', ['--mode', 'rpc', '--no-session']);
|
|
57
|
+
|
|
58
|
+
// A spawn failure (pi not on PATH) arrives asynchronously, by which point the
|
|
59
|
+
// SSE headers are out and the status is spent, so report IN BAND:
|
|
60
|
+
// readOpenAIStream lands this on turn.error instead of leaving a blank bubble.
|
|
61
|
+
pi.on('error', (err) => {
|
|
62
|
+
res.write(\`data: \${JSON.stringify({ error: { message: err.message } })}\\n\\n\`);
|
|
63
|
+
res.write('data: [DONE]\\n\\n');
|
|
64
|
+
res.end();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Send the user's turn. Pi commands are { type, message }.
|
|
68
|
+
pi.stdin.write(JSON.stringify({ type: 'prompt', message: prompt }) + '\\n');
|
|
69
|
+
|
|
70
|
+
let buffer = '';
|
|
71
|
+
pi.stdout.on('data', (chunk: Buffer) => {
|
|
72
|
+
buffer += chunk.toString();
|
|
73
|
+
// Split on \\n rather than readline: readline also breaks on the Unicode
|
|
74
|
+
// separators U+2028/U+2029, which appear inside model output.
|
|
75
|
+
const lines = buffer.split('\\n');
|
|
76
|
+
// pop() is \`string | undefined\`, and the buffer has to stay a string.
|
|
77
|
+
buffer = lines.pop() ?? ''; // hold the partial line for the next chunk
|
|
78
|
+
for (const line of lines) {
|
|
79
|
+
if (!line) continue;
|
|
80
|
+
const event = JSON.parse(line);
|
|
81
|
+
const part = event.assistantMessageEvent;
|
|
82
|
+
if (event.type === 'message_update' && part?.type === 'text_delta') {
|
|
83
|
+
res.write(\`data: \${JSON.stringify({ choices: [{ delta: { content: part.delta } }] })}\\n\\n\`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
pi.on('close', () => { res.write('data: [DONE]\\n\\n'); res.end(); });
|
|
32
88
|
});
|
|
33
|
-
|
|
89
|
+
|
|
90
|
+
app.listen(3001, () => console.log('chat api: http://localhost:3001/api/chat'));
|
|
91
|
+
// The front end fetches a RELATIVE /api/chat, so proxy that path to this port
|
|
92
|
+
// from your dev server, or serve both from one origin.`,
|
|
34
93
|
},
|
|
35
|
-
streamMapping: "Pi runs as a local stdio process in RPC mode (pi --mode rpc --no-session). It emits newline-delimited JSON events on stdout. Map message_update events where assistantMessageEvent.type === 'text_delta' to data: {choices:[{delta:{content:part.delta}}]} SSE frames; send data: [DONE] on close. Split stdout on \\n (not readline, which breaks on Unicode separators U+2028/U+2029). Pi also emits thinking_delta
|
|
36
|
-
runNote: "Pi must be installed locally and available on PATH as 'pi'. No API key is required by the bridge itself; Pi uses its own credentials. Pi runs with full user permissions
|
|
94
|
+
streamMapping: "Pi runs as a local stdio process in RPC mode (pi --mode rpc --no-session). It emits newline-delimited JSON events on stdout. Map message_update events where assistantMessageEvent.type === 'text_delta' to data: {choices:[{delta:{content:part.delta}}]} SSE frames; send data: [DONE] on close. Split stdout on \\n (not readline, which breaks on Unicode separators U+2028/U+2029). Pi also emits thinking_delta and toolcall_* events; re-frame them onto delta.reasoning and delta.tool_calls in the same frames. readOpenAIStream from @kitn.ai/ui/wire parses it, including tool calls and reasoning.",
|
|
95
|
+
runNote: "Pi must be installed locally and available on PATH as 'pi'. No API key is required by the bridge itself; Pi uses its own credentials. Pi runs with full user permissions, so sandbox it before exposing to a public endpoint. See the RPC reference: https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/rpc.md",
|
|
37
96
|
docsSlug: 'integrations/harnesses',
|
|
97
|
+
// Nothing. Pi runs with its own credentials, model and tools; the bridge
|
|
98
|
+
// forwards a prompt, not a model id.
|
|
99
|
+
forwardsFromClient: [],
|
|
100
|
+
// `express` only. The bridge's other two imports are excluded by the rule:
|
|
101
|
+
// `node:child_process` is a builtin and '@kitn.ai/ui/wire' is the kit. Pi
|
|
102
|
+
// itself is NOT an npm dep of the app — runNote requires it on PATH, and the
|
|
103
|
+
// route reaches it with spawn('pi'), not an import.
|
|
104
|
+
deps: { npm: ['express'], pip: [] },
|
|
105
|
+
// No key anywhere: envVars is empty and Pi uses its own credentials on disk.
|
|
106
|
+
// This is 'needs-proxy' for the OTHER reason the flag covers — a server-only
|
|
107
|
+
// capability. The bridge calls spawn(), which no browser can do, and runNote
|
|
108
|
+
// warns that Pi runs with full user permissions and wants sandboxing before it
|
|
109
|
+
// is exposed. "No key" is not the same fact as "safe in the browser", and
|
|
110
|
+
// collapsing the two is exactly how this flag would be got wrong.
|
|
111
|
+
keyExposure: 'needs-proxy',
|
|
112
|
+
// Not 'local-server': nothing is listening in advance. The bridge spawns
|
|
113
|
+
// `pi --mode rpc` per request, so what the developer supplies is an EXECUTABLE
|
|
114
|
+
// on PATH ("Pi must be installed locally and available on PATH as 'pi'"), and
|
|
115
|
+
// "start the server first" would be the wrong instruction to print. Pi is not
|
|
116
|
+
// in `deps` for the same reason — the route reaches it with spawn(), not an
|
|
117
|
+
// import. The schema's SPAWNS_PROCESS net catches this one independently.
|
|
118
|
+
outOfBand: 'local-binary',
|
|
38
119
|
};
|
|
39
120
|
|
|
40
121
|
export default pi;
|
|
@@ -10,7 +10,7 @@ const pydanticAi: Integration = {
|
|
|
10
10
|
routeTemplates: {
|
|
11
11
|
fastapi: `# main.py
|
|
12
12
|
import json
|
|
13
|
-
from fastapi import FastAPI
|
|
13
|
+
from fastapi import FastAPI, HTTPException
|
|
14
14
|
from fastapi.middleware.cors import CORSMiddleware
|
|
15
15
|
from fastapi.responses import StreamingResponse
|
|
16
16
|
from pydantic import BaseModel
|
|
@@ -23,15 +23,41 @@ app.add_middleware(
|
|
|
23
23
|
CORSMiddleware, allow_origins=['*'], allow_methods=['*'], allow_headers=['*']
|
|
24
24
|
)
|
|
25
25
|
|
|
26
|
+
class ContentPart(BaseModel):
|
|
27
|
+
type: str
|
|
28
|
+
text: str | None = None
|
|
29
|
+
|
|
26
30
|
class Message(BaseModel):
|
|
27
31
|
role: str
|
|
28
|
-
|
|
32
|
+
# A plain string until the turn carries an attachment, at which point the
|
|
33
|
+
# wire sends an ARRAY of content parts. Declaring this \`str\` alone made a
|
|
34
|
+
# message with a file a 422 that named pydantic rather than the cause.
|
|
35
|
+
content: str | list[ContentPart] | None = None
|
|
29
36
|
|
|
30
37
|
class ChatRequest(BaseModel):
|
|
31
38
|
messages: list[Message]
|
|
32
39
|
|
|
40
|
+
def prompt_text(message: Message) -> str:
|
|
41
|
+
"""The text of a turn, refusing what this route cannot carry.
|
|
42
|
+
|
|
43
|
+
agent.run_stream() takes a TEXT prompt, so an image or a document has no
|
|
44
|
+
channel here. Dropping it would send the model a turn that silently lost the
|
|
45
|
+
user's file; a 400 that names the reason is the honest failure.
|
|
46
|
+
"""
|
|
47
|
+
if message.content is None:
|
|
48
|
+
return ''
|
|
49
|
+
if isinstance(message.content, str):
|
|
50
|
+
return message.content
|
|
51
|
+
if any(part.type != 'text' for part in message.content):
|
|
52
|
+
raise HTTPException(
|
|
53
|
+
status_code=400,
|
|
54
|
+
detail='This route forwards a text prompt only and has no channel for an attachment. '
|
|
55
|
+
'Extract the file content into the message text, or send it through a tool.',
|
|
56
|
+
)
|
|
57
|
+
return ''.join(part.text or '' for part in message.content)
|
|
58
|
+
|
|
33
59
|
async def openai_sse(messages: list[Message]):
|
|
34
|
-
prompt = messages[-1]
|
|
60
|
+
prompt = prompt_text(messages[-1]) if messages else ''
|
|
35
61
|
async with agent.run_stream(prompt) as result:
|
|
36
62
|
async for delta in result.stream_text(delta=True):
|
|
37
63
|
chunk = {'choices': [{'delta': {'content': delta}}]}
|
|
@@ -43,10 +69,40 @@ async def chat(req: ChatRequest):
|
|
|
43
69
|
return StreamingResponse(openai_sse(req.messages), media_type='text/event-stream')`,
|
|
44
70
|
},
|
|
45
71
|
streamMapping:
|
|
46
|
-
"Pydantic AI's agent.run_stream() yields text deltas via result.stream_text(delta=True). Each delta is re-framed as a data: {choices:[{delta:{content}}]} SSE line and the stream closes with data: [DONE].
|
|
72
|
+
"Pydantic AI's agent.run_stream() yields text deltas via result.stream_text(delta=True). Each delta is re-framed as a data: {choices:[{delta:{content}}]} SSE line and the stream closes with data: [DONE]. readOpenAIStream from @kitn.ai/ui/wire parses tool calls and reasoning too, but stream_text() yields text only: iterate the run's events instead and re-frame its tool-call events onto delta.tool_calls to fill kai-tool.",
|
|
73
|
+
// No install list here: `deps` below is the one, and the scaffolder emits it.
|
|
74
|
+
// This sentence used to open "Install: pip install pydantic-ai fastapi
|
|
75
|
+
// uvicorn." — three of the four packages, missing the `pydantic` its own route
|
|
76
|
+
// imports on the next line.
|
|
47
77
|
runNote:
|
|
48
|
-
'
|
|
78
|
+
'Set OPENAI_API_KEY. Run: uvicorn main:app --reload (default port 8000). Point kai-chat at http://localhost:8000/api/chat.',
|
|
49
79
|
docsSlug: 'integrations/pydantic-ai',
|
|
80
|
+
// Nothing. Agent('openai:gpt-4o') pins the model and the agent registers its
|
|
81
|
+
// own tools, both server-side.
|
|
82
|
+
forwardsFromClient: [],
|
|
83
|
+
// The only python integration, so the only non-empty `pip`. Three of the four
|
|
84
|
+
// are the route's own imports (`pydantic_ai` is imported under its module
|
|
85
|
+
// name and installed under its hyphenated one); `uvicorn` is the ASGI server
|
|
86
|
+
// the run note starts, which nothing imports and without which the app cannot
|
|
87
|
+
// run. That asymmetry is why the pip guard checks imports ⊆ declared and not
|
|
88
|
+
// the reverse.
|
|
89
|
+
deps: { npm: [], pip: ['pydantic-ai', 'fastapi', 'pydantic', 'uvicorn'] },
|
|
90
|
+
// Agent('openai:gpt-4o') reads OPENAI_API_KEY inside the python process.
|
|
91
|
+
//
|
|
92
|
+
// Worth being precise about, because this one looks like an exception: the
|
|
93
|
+
// FastAPI app sets CORS to allow_origins=['*'] and the browser fetches it
|
|
94
|
+
// DIRECTLY on :8000, with no JS proxy in front. That still is not
|
|
95
|
+
// 'frontend-safe' — the flag asks where the SECRET lives, and it lives in the
|
|
96
|
+
// python process. The FastAPI service IS the server hop.
|
|
97
|
+
keyExposure: 'needs-proxy',
|
|
98
|
+
// The emitted backend is a FastAPI service: a python interpreter, the four
|
|
99
|
+
// `deps.pip` packages, and `uvicorn main:app` on its own port — none of which a
|
|
100
|
+
// node toolchain provides or starts. It also needs OPENAI_API_KEY, so this is
|
|
101
|
+
// the entry that proves the groups are not mutually exclusive: a runtime
|
|
102
|
+
// prerequisite and a key at once. The prompt should lead with the runtime,
|
|
103
|
+
// because a key is useless until the service runs. The schema's `language ===
|
|
104
|
+
// 'python'` net catches this one independently.
|
|
105
|
+
outOfBand: 'language-runtime',
|
|
50
106
|
};
|
|
51
107
|
|
|
52
108
|
export default pydanticAi;
|
|
@@ -7,38 +7,145 @@ const vercelAiSdk: Integration = {
|
|
|
7
7
|
language: 'ts',
|
|
8
8
|
streamFormat: 'ai-sdk',
|
|
9
9
|
envVars: ['AI_GATEWAY_API_KEY'],
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
// No per-framework templates: the handler below is web-standard, so the
|
|
11
|
+
// scaffolder wraps it in the target framework's own route declaration.
|
|
12
|
+
routeTemplates: {},
|
|
13
|
+
webRoute: `import { streamText } from 'ai';
|
|
14
|
+
import type { ModelMessage, AssistantContent, UserContent, FilePart, ToolResultPart } from 'ai';
|
|
13
15
|
|
|
14
|
-
export const maxDuration = 30
|
|
16
|
+
// Next.js only: add \`export const maxDuration = 30\` to the route file to allow
|
|
17
|
+
// long streaming responses. It is a Next route-segment config, not part of the
|
|
18
|
+
// handler, so it lives in the file rather than in here.
|
|
15
19
|
|
|
16
|
-
|
|
17
|
-
|
|
20
|
+
/**
|
|
21
|
+
* One attachment to an AI SDK FilePart.
|
|
22
|
+
*
|
|
23
|
+
* \`data\` takes the BARE shorthand — a base64 string, or a URL object for a
|
|
24
|
+
* remote file — rather than the newer tagged \`{ type: 'data' | 'url' }\` form.
|
|
25
|
+
* Both are accepted by the installed SDK; the shorthand is also what AI SDK v5
|
|
26
|
+
* and v6 accept, so this route keeps compiling if the app pins an older \`ai\`.
|
|
27
|
+
*
|
|
28
|
+
* Images arrive as \`image_url\` and become FileParts too. \`ImagePart\` still
|
|
29
|
+
* exists but is deprecated in favour of exactly this.
|
|
30
|
+
*/
|
|
31
|
+
function toFilePart(part: Extract<WirePart, { kind: 'file' }>): FilePart {
|
|
32
|
+
return {
|
|
33
|
+
type: 'file',
|
|
34
|
+
mediaType: part.mediaType,
|
|
35
|
+
...(part.filename === undefined ? {} : { filename: part.filename }),
|
|
36
|
+
data: part.source.type === 'data' ? part.source.data : new URL(part.source.url),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// The kit's wire format is OpenAI-shaped (tool_calls on the assistant message,
|
|
41
|
+
// a flat content string on the tool message). The AI SDK's ModelMessage is not:
|
|
42
|
+
// a tool call is a CONTENT PART on the assistant message, and a tool result is a
|
|
43
|
+
// tagged output union, not a bare string. Converting rather than casting is the
|
|
44
|
+
// point — a cast would compile but hand the SDK the wrong shape at runtime.
|
|
45
|
+
function toModelMessages(messages: ChatRequestBody['messages']): ModelMessage[] {
|
|
46
|
+
return messages.map((message): ModelMessage => {
|
|
47
|
+
switch (message.role) {
|
|
48
|
+
// A system prompt is text-only on every wire, so the array form collapses.
|
|
49
|
+
case 'system':
|
|
50
|
+
return { role: 'system', content: wireText(message.content) };
|
|
51
|
+
case 'user': {
|
|
52
|
+
const parts = wireParts(message.content);
|
|
53
|
+
// Plain string for an ordinary turn: identical to what this route sent
|
|
54
|
+
// before attachments existed, and the shape the SDK docs lead with.
|
|
55
|
+
if (parts.every((p) => p.kind === 'text')) {
|
|
56
|
+
return { role: 'user', content: wireText(message.content) };
|
|
57
|
+
}
|
|
58
|
+
const content: UserContent = parts.map((p) =>
|
|
59
|
+
p.kind === 'text' ? { type: 'text' as const, text: p.text } : toFilePart(p),
|
|
60
|
+
);
|
|
61
|
+
return { role: 'user', content };
|
|
62
|
+
}
|
|
63
|
+
case 'tool': {
|
|
64
|
+
const result: ToolResultPart = {
|
|
65
|
+
type: 'tool-result',
|
|
66
|
+
toolCallId: message.tool_call_id ?? '',
|
|
67
|
+
toolName: message.name ?? '',
|
|
68
|
+
output: { type: 'text', value: wireText(message.content) },
|
|
69
|
+
};
|
|
70
|
+
return { role: 'tool', content: [result] };
|
|
71
|
+
}
|
|
72
|
+
case 'assistant': {
|
|
73
|
+
const text = wireText(message.content);
|
|
74
|
+
if (!message.tool_calls?.length) return { role: 'assistant', content: text };
|
|
75
|
+
const content: AssistantContent = [];
|
|
76
|
+
if (text) content.push({ type: 'text', text });
|
|
77
|
+
for (const call of message.tool_calls) {
|
|
78
|
+
content.push({
|
|
79
|
+
type: 'tool-call',
|
|
80
|
+
toolCallId: call.id,
|
|
81
|
+
toolName: call.function.name,
|
|
82
|
+
input: JSON.parse(call.function.arguments),
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
return { role: 'assistant', content };
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function chatHandler(request: Request): Promise<Response> {
|
|
92
|
+
const { messages } = await readChatRequest(request);
|
|
18
93
|
|
|
19
94
|
const result = streamText({
|
|
20
95
|
model: 'openai/gpt-4o', // AI Gateway id; needs AI_GATEWAY_API_KEY
|
|
21
|
-
messages,
|
|
96
|
+
messages: toModelMessages(messages),
|
|
22
97
|
});
|
|
23
98
|
|
|
24
99
|
const encoder = new TextEncoder();
|
|
25
100
|
const sse = new ReadableStream({
|
|
26
101
|
async start(controller) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
102
|
+
try {
|
|
103
|
+
for await (const delta of result.textStream) {
|
|
104
|
+
const chunk = { choices: [{ delta: { content: delta } }] };
|
|
105
|
+
controller.enqueue(encoder.encode(\`data: \${JSON.stringify(chunk)}\\n\\n\`));
|
|
106
|
+
}
|
|
107
|
+
} catch (err) {
|
|
108
|
+
// The status is spent by the time the SDK fails — the headers went out
|
|
109
|
+
// with the first byte — so report it IN BAND. readOpenAIStream lands
|
|
110
|
+
// this on turn.error and keeps whatever streamed before it. Without it a
|
|
111
|
+
// failed key is an empty bubble and nothing in the console.
|
|
112
|
+
const message = err instanceof Error ? err.message : 'Model stream failed';
|
|
113
|
+
controller.enqueue(encoder.encode(\`data: \${JSON.stringify({ error: { message } })}\\n\\n\`));
|
|
30
114
|
}
|
|
31
115
|
controller.enqueue(encoder.encode('data: [DONE]\\n\\n'));
|
|
32
116
|
controller.close();
|
|
33
117
|
},
|
|
34
118
|
});
|
|
35
119
|
|
|
36
|
-
return new Response(sse, {
|
|
120
|
+
return new Response(sse, {
|
|
121
|
+
status: 200,
|
|
122
|
+
headers: {
|
|
123
|
+
'Content-Type': 'text/event-stream; charset=utf-8',
|
|
124
|
+
// no-transform stops a proxy buffering the stream into one blob.
|
|
125
|
+
'Cache-Control': 'no-cache, no-transform',
|
|
126
|
+
Connection: 'keep-alive',
|
|
127
|
+
},
|
|
128
|
+
});
|
|
37
129
|
}`,
|
|
38
|
-
},
|
|
39
|
-
streamMapping: "The Vercel AI SDK's toUIMessageStreamResponse() and toTextStreamResponse() don't emit OpenAI-format SSE. Wrap result.textStream manually: iterate text deltas and emit data: {choices:[{delta:{content}}]} frames, closing with data: [DONE]. The kai-chat SSE reader handles it.",
|
|
130
|
+
streamMapping: "The Vercel AI SDK's toUIMessageStreamResponse() and toTextStreamResponse() don't emit OpenAI-format SSE. Wrap result.textStream manually: iterate text deltas and emit data: {choices:[{delta:{content}}]} frames, closing with data: [DONE]. readOpenAIStream from @kitn.ai/ui/wire parses tool calls and reasoning too, but textStream carries neither: it is text deltas only. Switch to result.fullStream, which yields typed parts, and re-frame its tool-call and reasoning parts onto delta.tool_calls and delta.reasoning to get them.",
|
|
40
131
|
runNote: 'Set AI_GATEWAY_API_KEY for the AI Gateway (string model id form: creator/model-name). For direct provider access, import its provider package (e.g. @ai-sdk/openai) and set the corresponding key (e.g. OPENAI_API_KEY).',
|
|
41
132
|
docsSlug: 'integrations/vercel-ai-sdk',
|
|
133
|
+
// Nothing. The route pins model: 'openai/gpt-4o' in the streamText() call and
|
|
134
|
+
// defines any tools there too, so neither belongs in the front end.
|
|
135
|
+
forwardsFromClient: [],
|
|
136
|
+
// `ai` only. A direct provider (e.g. @ai-sdk/openai) is the alternative path
|
|
137
|
+
// described in runNote, not what this route imports, so it is not listed: the
|
|
138
|
+
// rule is what the emitted code actually imports.
|
|
139
|
+
deps: { npm: ['ai'], pip: [] },
|
|
140
|
+
// Grep the route for a key and you find NOTHING — no header, no process.env.
|
|
141
|
+
// The AI SDK reads AI_GATEWAY_API_KEY out of the environment itself, inside
|
|
142
|
+
// streamText(). This is the case that makes inference from route source
|
|
143
|
+
// unsafe and the declaration necessary; `envVars` is what the schema's safety
|
|
144
|
+
// net can still see.
|
|
145
|
+
keyExposure: 'needs-proxy',
|
|
146
|
+
// The AI Gateway is a remote HTTPS endpoint reached through the `ai` package,
|
|
147
|
+
// which is an ordinary npm dependency (see `deps`). Nothing to start.
|
|
148
|
+
outOfBand: 'none',
|
|
42
149
|
};
|
|
43
150
|
|
|
44
151
|
export default vercelAiSdk;
|