@kitn.ai/ui 0.20.1 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -45
- package/dist/components/artifact-card.d.ts +51 -0
- package/dist/components/artifact.d.ts +1 -1
- package/dist/components/attachments.d.ts +2 -2
- package/dist/components/audio-visualizer/index.d.ts +3 -3
- package/dist/components/audio-visualizer/labs/lab-choreography.d.ts +59 -0
- package/dist/components/audio-visualizer/labs/lab-shaders.d.ts +22 -0
- package/dist/components/audio-visualizer/labs/lab-visualizer.d.ts +18 -0
- package/dist/components/audio-visualizer/variant-aurora.d.ts +2 -2
- package/dist/components/audio-visualizer/variant-bar.d.ts +2 -2
- package/dist/components/audio-visualizer/variant-custom.d.ts +2 -2
- package/dist/components/audio-visualizer/variant-grid.d.ts +1 -1
- package/dist/components/audio-visualizer/variant-radial.d.ts +1 -1
- package/dist/components/audio-visualizer/variant-wave.d.ts +1 -1
- package/dist/components/card-fallback.d.ts +12 -2
- package/dist/components/card-renderer.d.ts +63 -2
- package/dist/components/chat-container.d.ts +3 -0
- package/dist/components/chat-scope-picker.d.ts +2 -2
- package/dist/components/chat-thread.d.ts +68 -18
- package/dist/components/choice-card.d.ts +3 -28
- package/dist/components/composer-dom.d.ts +1 -1
- package/dist/components/composer-highlight.d.ts +1 -1
- package/dist/components/composer-history.d.ts +1 -1
- package/dist/components/composer.d.ts +1 -1
- package/dist/components/confirm-card.d.ts +3 -18
- package/dist/components/context.d.ts +8 -4
- package/dist/components/conversation-item.d.ts +1 -1
- package/dist/components/conversation-list.d.ts +4 -3
- package/dist/components/embed.d.ts +3 -3
- package/dist/components/file-tree.d.ts +11 -22
- package/dist/components/form-widgets.d.ts +1 -1
- package/dist/components/form.d.ts +3 -44
- package/dist/components/link-preview.d.ts +3 -3
- package/dist/components/loader.d.ts +30 -48
- package/dist/components/message-skills.d.ts +4 -0
- package/dist/components/message.d.ts +84 -21
- package/dist/components/model-switcher.d.ts +2 -2
- package/dist/components/prompt-input.d.ts +2 -2
- package/dist/components/response-compare-types.d.ts +11 -2
- package/dist/components/response-compare.d.ts +3 -3
- package/dist/components/source.d.ts +12 -3
- package/dist/components/tasks-card.d.ts +3 -31
- package/dist/components/thread.d.ts +17 -5
- package/dist/components/toast.d.ts +2 -2
- package/dist/components/tool-classify.d.ts +8 -0
- package/dist/components/tool-types.d.ts +29 -1
- package/dist/components/tool.d.ts +3 -3
- package/dist/components/use-card-resolution.d.ts +1 -1
- package/dist/create-tween-D3Y5qiij.js +338 -0
- package/dist/create-tween-DGKY1FDY.js +341 -0
- package/dist/create-tween-paho5Rh-.js +7 -0
- package/dist/custom-elements.json +1681 -454
- package/dist/elements/agent-card.js +1 -1
- package/dist/elements/artifact.js +1 -1
- package/dist/elements/attachments.js +1 -1
- package/dist/elements/audio-visualizer.js +1 -1
- package/dist/elements/avatar.js +1 -1
- package/dist/elements/badge.js +1 -1
- package/dist/elements/button.js +1 -1
- package/dist/elements/card.js +1 -1
- package/dist/elements/cards.js +1 -1
- package/dist/elements/chain-of-thought.js +1 -1
- package/dist/elements/chat-scope-picker.js +1 -1
- package/dist/elements/chat-types.d.ts +70 -10
- package/dist/elements/chat-workspace.js +1 -1
- package/dist/elements/chat.js +1 -1
- package/dist/elements/checkpoint.js +1 -1
- package/dist/elements/choice.js +1 -1
- package/dist/elements/chunks/{Icon-Tj58ES_d.js → Icon-C-2LvGDG.js} +1 -1
- package/dist/elements/chunks/{action-icons-ns1Fnedj.js → action-icons-CaYoImEk.js} +1 -1
- package/dist/elements/chunks/arrow-left-Bb46H7w9.js +1 -0
- package/dist/elements/chunks/artifact-BBIscOxJ.js +1 -0
- package/dist/elements/chunks/{attachments-mCB5F1Fq.js → attachments-BB56zoji.js} +1 -1
- package/dist/elements/chunks/audio-visualizer-BxOx4THz.js +1 -0
- package/dist/elements/chunks/{badge-B4Zuajl2.js → badge-SxLSZ19R.js} +1 -1
- package/dist/elements/chunks/{button-ZWNif3--.js → button-CmkUkz6c.js} +1 -1
- package/dist/elements/chunks/card-renderer-Db4ffkWv.js +1 -0
- package/dist/elements/chunks/card-tags-D8lZ-C_U.js +1 -0
- package/dist/elements/chunks/chat-thread-B3Ks-cKr.js +1 -0
- package/dist/elements/chunks/check-C8desc_h.js +1 -0
- package/dist/elements/chunks/chevron-down-DKtc2HNB.js +1 -0
- package/dist/elements/chunks/chevron-right-Dw4GPhwL.js +1 -0
- package/dist/elements/chunks/choice-card-Q9cYYDyM.js +1 -0
- package/dist/elements/chunks/circle-BeokBsvB.js +1 -0
- package/dist/elements/chunks/circle-check-wJSqt859.js +1 -0
- package/dist/elements/chunks/{circle-x-B7faJFlY.js → circle-x-D0MEfxOM.js} +1 -1
- package/dist/elements/chunks/code-block-C8VzAraE.js +1 -0
- package/dist/elements/chunks/{collapsible-DM1Ckd4e.js → collapsible-Cvj61obE.js} +1 -1
- package/dist/elements/chunks/{composer-BguS0QL_.js → composer-D86ztyL0.js} +2 -2
- package/dist/elements/chunks/confirm-card-BdyDAGLw.js +1 -0
- package/dist/elements/chunks/context-WHDl-xSx.js +1 -0
- package/dist/elements/chunks/conversation-list-CdR14_Y4.js +1 -0
- package/dist/elements/chunks/create-tween-C_A_B638.js +7 -0
- package/dist/elements/chunks/{default-input-Cqqilg9t.js → default-input-CSZqUM81.js} +1 -1
- package/dist/elements/chunks/define-BzPnNsXg.js +1 -0
- package/dist/elements/chunks/{disclosure-CIZ5c8nO.js → disclosure-DVKsoc1F.js} +1 -1
- package/dist/elements/chunks/{download-Dsbg3ste.js → download-BjA54IMJ.js} +1 -1
- package/dist/elements/chunks/{dropdown-BDd1-Zgi.js → dropdown-BjVVHWXH.js} +1 -1
- package/dist/elements/chunks/{ellipsis-Dw6qrss-.js → ellipsis-BNedmqfi.js} +1 -1
- package/dist/elements/chunks/embed-DQsl4XEq.js +1 -0
- package/dist/elements/chunks/{external-link-Vt74zAoL.js → external-link-Di-dw2CU.js} +1 -1
- package/dist/elements/chunks/{file-text-CZrZF6_v.js → file-text-B3sAODCV.js} +1 -1
- package/dist/elements/chunks/{file-tree-DF4okKyo.js → file-tree-Dgto-bMn.js} +1 -1
- package/dist/elements/chunks/{folder-glP5rha4.js → folder-D6rMadFV.js} +1 -1
- package/dist/elements/chunks/form-CILxvhQ7.js +1 -0
- package/dist/elements/chunks/hover-card-BtiF4XiU.js +1 -0
- package/dist/elements/chunks/{icon-BLb8beoi.js → icon-DKkmze3y.js} +1 -1
- package/dist/elements/chunks/{info-CIsv92JK.js → info-CWRjZkuy.js} +1 -1
- package/dist/elements/chunks/{input-Dds1kFyt.js → input--QJBK6BA.js} +1 -1
- package/dist/elements/chunks/{kbd-De_xbgi9.js → kbd-C3zuKh8R.js} +1 -1
- package/dist/elements/chunks/{link-i4r_k7z4.js → link-BL5JA7Q0.js} +1 -1
- package/dist/elements/chunks/link-preview-CU37bbXh.js +1 -0
- package/dist/elements/chunks/{loader-BmQH4s3V.js → loader-XmpyxyRH.js} +1 -1
- package/dist/elements/chunks/{markdown-Zi21WhUz.js → markdown-3Gv5UZ_o.js} +1 -1
- package/dist/elements/chunks/message-CnBA2OUt.js +1 -0
- package/dist/elements/chunks/message-X5NZbq-W.js +1 -0
- package/dist/elements/chunks/message-square-CnpAm_P0.js +1 -0
- package/dist/elements/chunks/{minimize-2--ifeCGrl.js → minimize-2-DwyrUTcM.js} +1 -1
- package/dist/elements/chunks/{model-switcher-B94YAKsM.js → model-switcher-CclaJEHt.js} +1 -1
- package/dist/elements/chunks/{overlay-kD8dwRv_.js → overlay-Cepd-KcS.js} +1 -1
- package/dist/elements/chunks/{paperclip-q21dYOSK.js → paperclip-CdwKjrlu.js} +1 -1
- package/dist/elements/chunks/{progress-bar-BrAIIaaJ.js → progress-bar-DMVXBD3b.js} +1 -1
- package/dist/elements/chunks/{prompt-suggestion-Bjn2K-fR.js → prompt-suggestion-Di_WZE_5.js} +1 -1
- package/dist/elements/chunks/reasoning-D0jMch5u.js +1 -0
- package/dist/elements/chunks/{resizable-Dp4JExql.js → resizable-1znbL1Gv.js} +1 -1
- package/dist/elements/chunks/{rotate-cw-CFWWkpj2.js → rotate-cw-DVhhgx8n.js} +1 -1
- package/dist/elements/chunks/{scroll-area-Bb0kHH1j.js → scroll-area-Bs4bFS1P.js} +1 -1
- package/dist/elements/chunks/scroll-button-Bet5NLc4.js +1 -0
- package/dist/elements/chunks/{separator-9kRgr1X9.js → separator-C5Gh8qGC.js} +1 -1
- package/dist/elements/chunks/{settings-B8lmec4D.js → settings-5vmEFg0K.js} +1 -1
- package/dist/elements/chunks/{settings-group-I0hEuXiN.js → settings-group-BWLB38kz.js} +1 -1
- package/dist/elements/chunks/{share-C3lTiYGt.js → share-BZ3G_DaB.js} +1 -1
- package/dist/elements/chunks/skeleton-DafPJQTp.js +1 -0
- package/dist/elements/chunks/slots-CIw9RlAe.js +1 -0
- package/dist/elements/chunks/source-D_qj_6uJ.js +1 -0
- package/dist/elements/chunks/{star-CSJtGxgv.js → star-Cuv7j2IE.js} +1 -1
- package/dist/elements/chunks/{store-CHyW95TP.js → store-C1StPHfW.js} +1 -1
- package/dist/elements/chunks/tasks-card-EyoBZfpA.js +1 -0
- package/dist/elements/chunks/{text-shimmer-Cn9TmNpI.js → text-shimmer-D9gRSh34.js} +1 -1
- package/dist/elements/chunks/textarea-DuhtHfcm.js +1 -0
- package/dist/elements/chunks/{thumbs-up-PNVVThEF.js → thumbs-up-B9hwv43w.js} +1 -1
- package/dist/elements/chunks/{toast-store-DKbQn3Wn.js → toast-store-Bm3DUEDe.js} +1 -1
- package/dist/elements/chunks/{tool-DjTRpIpl.js → tool-CDtW7L0z.js} +1 -1
- package/dist/elements/chunks/tooltip-eB226mGK.js +1 -0
- package/dist/elements/chunks/{triangle-alert-DsHTHCPm.js → triangle-alert-C1SNzTqK.js} +1 -1
- package/dist/elements/chunks/use-card-resolution-YZh2MvI5.js +1 -0
- package/dist/elements/chunks/{variant-aurora-Dc4nQUia.js → variant-aurora-Cj1F7D0l.js} +2 -2
- package/dist/elements/chunks/{variant-custom-y_3kVQhS.js → variant-custom-T9gpt7QE.js} +1 -1
- package/dist/elements/chunks/{variant-wave-D5OR1-9Z.js → variant-wave-CxU9z-TE.js} +2 -2
- package/dist/elements/chunks/{video-B7vmenoC.js → video-CK4R7Rus.js} +1 -1
- package/dist/elements/chunks/x-D1qx0aLd.js +1 -0
- package/dist/elements/coachmark.js +1 -1
- package/dist/elements/code-block.js +1 -1
- package/dist/elements/command.js +1 -1
- package/dist/elements/compare.js +1 -1
- package/dist/elements/composer.js +1 -1
- package/dist/elements/confirm-card.js +1 -1
- package/dist/elements/context-meter.js +1 -1
- package/dist/elements/conversation-list.js +1 -1
- package/dist/elements/default-input.d.ts +7 -7
- package/dist/elements/define.d.ts +1 -0
- package/dist/elements/dialog.js +1 -1
- package/dist/elements/disclosure.d.ts +1 -1
- package/dist/elements/editable-label.js +1 -1
- package/dist/elements/element-data-types.d.ts +107 -0
- package/dist/elements/embed.js +1 -1
- package/dist/elements/empty.js +1 -1
- package/dist/elements/feedback-bar.js +1 -1
- package/dist/elements/file-tree.js +1 -1
- package/dist/elements/file-upload.js +1 -1
- package/dist/elements/form.js +1 -1
- package/dist/elements/hover-card.js +1 -1
- package/dist/elements/icon.js +1 -1
- package/dist/elements/image.js +1 -1
- package/dist/elements/input.js +1 -1
- package/dist/elements/kbd.js +1 -1
- package/dist/elements/link-preview.js +1 -1
- package/dist/elements/loader.js +1 -1
- package/dist/elements/markdown.js +1 -1
- package/dist/elements/menu.js +1 -1
- package/dist/elements/message-skills.js +1 -1
- package/dist/elements/message.js +1 -1
- package/dist/elements/model-switcher.js +1 -1
- package/dist/elements/nav.js +1 -1
- package/dist/elements/notice.js +1 -1
- package/dist/elements/pane-group.js +1 -1
- package/dist/elements/pane.js +1 -1
- package/dist/elements/popover.js +1 -1
- package/dist/elements/progress-bar.js +1 -1
- package/dist/elements/prompt-dock.js +1 -1
- package/dist/elements/prompt-input.js +1 -1
- package/dist/elements/prompt-suggestions.js +1 -1
- package/dist/elements/reasoning.js +1 -1
- package/dist/elements/register.d.ts +8 -6
- package/dist/elements/remote.js +1 -1
- package/dist/elements/resizable.js +1 -1
- package/dist/elements/response-stream.js +2 -2
- package/dist/elements/screen.js +1 -1
- package/dist/elements/scroll-area.js +1 -1
- package/dist/elements/scroll-button.js +1 -1
- package/dist/elements/search.js +1 -1
- package/dist/elements/segmented.js +1 -1
- package/dist/elements/separator.js +1 -1
- package/dist/elements/setting-item.js +1 -1
- package/dist/elements/settings-group.js +1 -1
- package/dist/elements/skeleton.js +1 -1
- package/dist/elements/slots.d.ts +16 -0
- package/dist/elements/source.js +1 -1
- package/dist/elements/status.js +1 -1
- package/dist/elements/switch.js +1 -1
- package/dist/elements/tabs.js +1 -1
- package/dist/elements/tasks.js +1 -1
- package/dist/elements/text-shimmer.js +1 -1
- package/dist/elements/thinking-bar.js +1 -1
- package/dist/elements/thread.js +1 -1
- package/dist/elements/toast.js +1 -1
- package/dist/elements/tool.js +1 -1
- package/dist/elements/tooltip.js +1 -1
- package/dist/elements/validate-messages.d.ts +25 -0
- package/dist/elements/voice-input.js +1 -1
- package/dist/elements/voice-output.js +1 -1
- package/dist/elements.d.ts +2725 -214
- package/dist/index.d.ts +163 -125
- package/dist/index.js +6336 -5802
- package/dist/index.server.d.ts +5 -0
- package/dist/index.server.js +5446 -4937
- package/dist/kai-provider.es.d.ts +5 -0
- package/dist/kai-provider.es.js +79 -77
- package/dist/kai.es.d.ts +5 -0
- package/dist/kai.es.js +1 -1
- package/dist/llms/llms-full.txt +762 -148
- package/dist/llms/llms.txt +57 -5
- package/dist/mcp.es.js +4010 -770
- package/dist/primitives/card-component-types.d.ts +11 -0
- package/dist/primitives/card-data-types.d.ts +196 -0
- package/dist/primitives/card-host.d.ts +1 -1
- package/dist/primitives/card-recovery.d.ts +3 -3
- package/dist/primitives/card-registry.d.ts +4 -14
- package/dist/primitives/card-resolution.d.ts +1 -1
- package/dist/primitives/card-routing.d.ts +1 -1
- package/dist/primitives/card-tags.d.ts +11 -0
- package/dist/primitives/card-validate-cards.d.ts +57 -0
- package/dist/primitives/card-validate-generator.testlib.d.ts +20 -0
- package/dist/primitives/card-validate-schemas.d.ts +17 -0
- package/dist/primitives/card-validate.d.ts +24 -0
- package/dist/primitives/create-kai-chat.d.ts +3 -3
- package/dist/primitives/embed-providers.d.ts +1 -1
- package/dist/primitives/link-preview.d.ts +1 -1
- package/dist/primitives/message-feedback.d.ts +2 -2
- package/dist/primitives/toast-store.d.ts +1 -1
- package/dist/react/index.d.ts +717 -299
- package/dist/react/use-kai-chat.d.ts +8 -4
- package/dist/react.d.ts +5 -0
- package/dist/react.js +417 -316
- package/dist/register-impl-bzrZ44ft.js +145 -0
- package/dist/remote/host-embed.d.ts +1 -1
- package/dist/remote/index.d.ts +2 -2
- package/dist/remote/provider-runtime.d.ts +2 -2
- package/dist/remote/provider.d.ts +2 -2
- package/dist/remote/validate.d.ts +1 -1
- package/dist/remote/wire.d.ts +1 -1
- package/dist/schemas/artifact.schema.json +92 -0
- package/dist/schemas/embed.schema.json +0 -2
- package/dist/schemas/from-tool-call.d.ts +48 -0
- package/dist/schemas/index.d.ts +48 -0
- package/dist/schemas/link.schema.json +0 -2
- package/dist/schemas/provider-subsets.d.ts +136 -0
- package/dist/schemas/registry.d.ts +140 -0
- package/dist/schemas/tool-defs.d.ts +121 -0
- package/dist/schemas.d.ts +5 -0
- package/dist/schemas.js +717 -0
- package/dist/solid-BCJUQqwS.js +22750 -0
- package/dist/solid-BdENBcEq.js +20108 -0
- package/dist/solid.d.ts +66 -0
- package/dist/solid.js +261 -0
- package/dist/solid.server.d.ts +5 -0
- package/dist/solid.server.js +261 -0
- package/dist/state/index.d.ts +14 -4
- package/dist/state/messages.d.ts +8 -3
- package/dist/state/mock.d.ts +46 -0
- package/dist/state/parts.d.ts +85 -0
- package/dist/state/stream.d.ts +15 -9
- package/dist/state.d.ts +5 -0
- package/dist/state.js +253 -84
- package/dist/ui/action-icons.d.ts +1 -1
- package/dist/ui/collapsible.d.ts +11 -6
- package/dist/ui/dropdown.d.ts +47 -42
- package/dist/ui/pane-group.d.ts +1 -1
- package/dist/variant-aurora-1wMcXRUS.js +381 -0
- package/dist/variant-aurora-BKHHiOgL.js +391 -0
- package/dist/{variant-aurora-DVtGqVH6.js → variant-aurora-DOb_o4pC.js} +1 -1
- package/dist/{variant-custom-Dif-IV4u.js → variant-custom-CJGB0Kbw.js} +1 -1
- package/dist/variant-custom-Co0CkXJF.js +200 -0
- package/dist/variant-custom-DWSSgmd2.js +170 -0
- package/dist/variant-wave-C3-45cL6.js +218 -0
- package/dist/{variant-wave-DbfNdvSi.js → variant-wave-CEODq-9q.js} +1 -1
- package/dist/variant-wave-jdXBnCUZ.js +208 -0
- package/dist/wire/chunk.d.ts +171 -0
- package/dist/wire/consume.d.ts +26 -0
- package/dist/wire/encode.d.ts +148 -0
- package/dist/wire/formats/anthropic.d.ts +2 -0
- package/dist/wire/formats/openai.d.ts +5 -0
- package/dist/wire/index.d.ts +14 -0
- package/dist/wire/read.d.ts +26 -0
- package/dist/wire/sink-helpers.d.ts +21 -0
- package/dist/wire/sse.d.ts +33 -0
- package/dist/wire.d.ts +5 -53
- package/dist/wire.js +898 -0
- package/frameworks/react/index.tsx +183 -154
- package/frameworks/react/runtime.tsx +21 -1
- package/frameworks/react/use-kai-chat.tsx +11 -1
- package/llms-full.txt +762 -148
- package/llms.txt +57 -5
- package/package.json +61 -8
- package/src/agent-tooling/archetypes.ts +96 -0
- package/src/agent-tooling/integrations/anthropic.ts +412 -0
- package/src/agent-tooling/integrations/cloudflare.ts +136 -52
- package/src/agent-tooling/integrations/langgraph.ts +67 -13
- package/src/agent-tooling/integrations/mastra.ts +120 -12
- package/src/agent-tooling/integrations/mock.ts +43 -8
- package/src/agent-tooling/integrations/ollama.ts +61 -24
- package/src/agent-tooling/integrations/openai.ts +87 -0
- package/src/agent-tooling/integrations/openrouter.ts +56 -8
- package/src/agent-tooling/integrations/pi.ts +79 -23
- package/src/agent-tooling/integrations/pydantic-ai.ts +24 -2
- package/src/agent-tooling/integrations/vercel-ai-sdk.ts +84 -13
- package/src/agent-tooling/mcp/manifest.ts +206 -25
- package/src/agent-tooling/mcp/tools/debug.ts +13 -7
- package/src/agent-tooling/mcp/tools/reference.ts +285 -6
- package/src/agent-tooling/mcp/tools/scaffold.ts +3345 -675
- package/src/agent-tooling/registry.ts +17 -0
- package/src/agent-tooling/types.ts +229 -1
- package/src/components/artifact-card.tsx +120 -0
- package/src/components/audio-visualizer/index.tsx +9 -0
- package/src/components/audio-visualizer/labs/lab-choreography.ts +187 -0
- package/src/components/audio-visualizer/labs/lab-shaders.ts +363 -0
- package/src/components/audio-visualizer/labs/lab-visualizer.tsx +102 -0
- package/src/components/audio-visualizer/shader-canvas.tsx +31 -1
- package/src/components/card-fallback.tsx +42 -10
- package/src/components/card-renderer.tsx +146 -9
- package/src/components/chat-container.tsx +4 -0
- package/src/components/chat-thread.tsx +118 -51
- package/src/components/choice-card.tsx +17 -29
- package/src/components/composer.tsx +12 -2
- package/src/components/confirm-card.tsx +24 -20
- package/src/components/context.tsx +12 -4
- package/src/components/conversation-list.tsx +18 -2
- package/src/components/file-tree.tsx +11 -22
- package/src/components/file-upload.tsx +14 -8
- package/src/components/form.tsx +14 -47
- package/src/components/loader.tsx +39 -12
- package/src/components/message-skills.tsx +4 -0
- package/src/components/message.tsx +294 -46
- package/src/components/reasoning.tsx +92 -5
- package/src/components/response-compare-types.ts +25 -0
- package/src/components/response-compare.tsx +4 -9
- package/src/components/source.tsx +50 -24
- package/src/components/tasks-card.tsx +10 -33
- package/src/components/thread.tsx +84 -42
- package/src/components/toast.tsx +11 -5
- package/src/components/tool-classify.ts +26 -0
- package/src/components/tool-types.ts +31 -1
- package/src/elements/agent-card.tsx +11 -11
- package/src/elements/artifact.tsx +15 -7
- package/src/elements/attachments.tsx +6 -4
- package/src/elements/avatar.tsx +1 -1
- package/src/elements/button.tsx +3 -3
- package/src/elements/card.tsx +2 -2
- package/src/elements/cards.tsx +109 -14
- package/src/elements/chain-of-thought.tsx +14 -10
- package/src/elements/chat-scope-picker.tsx +9 -7
- package/src/elements/chat-types.ts +59 -6
- package/src/elements/chat-workspace.tsx +50 -9
- package/src/elements/chat.tsx +64 -3
- package/src/elements/choice.tsx +8 -8
- package/src/elements/coachmark.tsx +1 -1
- package/src/elements/command.tsx +8 -23
- package/src/elements/compare.tsx +7 -6
- package/src/elements/compiled.css +1 -1
- package/src/elements/composer.tsx +8 -8
- package/src/elements/confirm-card.tsx +9 -9
- package/src/elements/context-meter.tsx +4 -18
- package/src/elements/conversation-list.tsx +14 -9
- package/src/elements/default-input.tsx +3 -3
- package/src/elements/define.tsx +178 -43
- package/src/elements/editable-label.tsx +17 -2
- package/src/elements/element-data-types.ts +112 -0
- package/src/elements/element-meta.json +1596 -304
- package/src/elements/element-types.d.ts +2636 -211
- package/src/elements/feedback-bar.tsx +1 -1
- package/src/elements/file-tree.tsx +1 -1
- package/src/elements/file-upload.tsx +1 -1
- package/src/elements/form.tsx +14 -6
- package/src/elements/input.tsx +1 -1
- package/src/elements/kbd.tsx +1 -1
- package/src/elements/menu.tsx +10 -29
- package/src/elements/message-skills.tsx +7 -9
- package/src/elements/message.tsx +200 -32
- package/src/elements/model-switcher.tsx +5 -3
- package/src/elements/pane.tsx +1 -1
- package/src/elements/prompt-dock.tsx +8 -8
- package/src/elements/prompt-input.tsx +10 -10
- package/src/elements/prompt-suggestions.tsx +7 -4
- package/src/elements/reasoning.tsx +1 -1
- package/src/elements/register.ts +9 -0
- package/src/elements/resizable.globals.d.ts +36 -0
- package/src/elements/resizable.tsx +24 -13
- package/src/elements/response-stream.tsx +1 -1
- package/src/elements/scroll-button.tsx +1 -1
- package/src/elements/search.tsx +6 -1
- package/src/elements/segmented.tsx +5 -11
- package/src/elements/separator.tsx +1 -1
- package/src/elements/slots.ts +57 -24
- package/src/elements/source.tsx +21 -22
- package/src/elements/switch.tsx +3 -3
- package/src/elements/tasks.tsx +5 -4
- package/src/elements/thread.tsx +36 -4
- package/src/elements/toast.tsx +3 -2
- package/src/elements/tool.tsx +1 -1
- package/src/elements/validate-messages.ts +76 -0
- package/src/elements/voice-input.tsx +7 -7
- package/src/elements/voice-output.tsx +4 -4
- package/src/index.ts +141 -6
- package/src/primitives/card-component-types.ts +63 -0
- package/src/primitives/card-data-types.ts +312 -0
- package/src/primitives/card-recovery.ts +13 -2
- package/src/primitives/card-registry.tsx +34 -23
- package/src/primitives/card-schemas/artifact.schema.json +92 -0
- package/src/primitives/card-schemas/embed.schema.json +0 -2
- package/src/primitives/card-schemas/link.schema.json +0 -2
- package/src/primitives/card-tags.ts +61 -0
- package/src/primitives/card-validate-cards.ts +255 -0
- package/src/primitives/card-validate-generator.testlib.ts +38 -0
- package/src/primitives/card-validate-schemas.ts +58 -0
- package/src/primitives/card-validate.ts +48 -20
- package/src/primitives/create-tween.ts +54 -2
- package/src/primitives/message-feedback.ts +3 -2
- package/src/primitives/use-audio-analysis.ts +20 -1
- package/src/primitives/use-sequencer.ts +20 -1
- package/src/schemas/from-tool-call.ts +151 -0
- package/src/schemas/index.ts +340 -0
- package/src/schemas/provider-subsets.ts +538 -0
- package/src/schemas/registry.ts +355 -0
- package/src/schemas/tool-defs.ts +492 -0
- package/src/solid.ts +150 -0
- package/src/state/index.ts +27 -1
- package/src/state/messages.ts +21 -4
- package/src/state/mock.ts +211 -0
- package/src/state/parts.ts +295 -0
- package/src/state/stream.ts +56 -58
- package/src/ui/collapsible.tsx +17 -5
- package/src/ui/dropdown.tsx +46 -12
- package/src/ui/overlay.tsx +10 -4
- package/src/wire/chunk.ts +201 -0
- package/src/wire/consume.ts +440 -0
- package/src/wire/encode.ts +474 -0
- package/src/wire/formats/anthropic.ts +267 -0
- package/src/wire/formats/openai.ts +187 -0
- package/src/wire/index.ts +52 -0
- package/src/wire/read.ts +152 -0
- package/src/wire/sink-helpers.ts +50 -0
- package/src/wire/sse.ts +140 -0
- package/dist/create-tween-hvpmSuwW.js +0 -7
- package/dist/elements/chunks/arrow-left-DQocUFvy.js +0 -1
- package/dist/elements/chunks/audio-visualizer-BqNy7dfm.js +0 -1
- package/dist/elements/chunks/card-BkHbp4BA.js +0 -1
- package/dist/elements/chunks/chat-thread-CkRN6pZ7.js +0 -1
- package/dist/elements/chunks/check-B9-gn6SS.js +0 -1
- package/dist/elements/chunks/chevron-down-BNsxc2hk.js +0 -1
- package/dist/elements/chunks/chevron-right-Hv00StBO.js +0 -1
- package/dist/elements/chunks/circle-0e3KJTDr.js +0 -1
- package/dist/elements/chunks/circle-check-BNAS0JEy.js +0 -1
- package/dist/elements/chunks/code-block-BFUHn6Va.js +0 -1
- package/dist/elements/chunks/context-Cb7k4F-v.js +0 -1
- package/dist/elements/chunks/conversation-list-CvoAkWhN.js +0 -1
- package/dist/elements/chunks/create-tween-oVvAIG8r.js +0 -7
- package/dist/elements/chunks/define-D1din7Ld.js +0 -1
- package/dist/elements/chunks/hover-card-Cmm6Q53m.js +0 -1
- package/dist/elements/chunks/message-BqyXDN3-.js +0 -1
- package/dist/elements/chunks/message-feedback-Djr3z6Yb.js +0 -1
- package/dist/elements/chunks/message-square-vFAgbDaX.js +0 -1
- package/dist/elements/chunks/reasoning-DTpCGtnY.js +0 -1
- package/dist/elements/chunks/scroll-button-B0cH5fXf.js +0 -1
- package/dist/elements/chunks/skeleton-DSXW5Yya.js +0 -1
- package/dist/elements/chunks/slots--V19HwUb.js +0 -1
- package/dist/elements/chunks/textarea-CUwqAX0G.js +0 -1
- package/dist/elements/chunks/tooltip-CgBZCVKp.js +0 -1
- package/dist/elements/chunks/use-card-resolution-BSu_XHjH.js +0 -1
- package/dist/elements/chunks/x-GRUeeQFT.js +0 -1
- package/dist/host-embed.d.ts +0 -33
- package/dist/kai-provider.d.ts +0 -2
- package/dist/origin.d.ts +0 -5
- package/dist/provider-runtime.d.ts +0 -27
- package/dist/register-impl-DQyejdfb.js +0 -145
- package/dist/validate.d.ts +0 -4
- package/dist/version.d.ts +0 -4
- package/src/components/component-meta.json +0 -4550
- package/src/elements/card-media.jpg +0 -0
- package/src/elements/resizable.d.ts +0 -27
- package/src/elements/video-poster.jpg +0 -0
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
// ChatMessage[] back onto the wire, so a host can run a multi-round tool loop in
|
|
2
|
+
// about fifteen lines of its own code. The kit never calls a consumer's function
|
|
3
|
+
// and never drives the loop; these two functions are the whole contribution.
|
|
4
|
+
//
|
|
5
|
+
// No provider SDK, no fetch, no DOM. Pure functions over the content model.
|
|
6
|
+
import type { ChatMessage, MessagePart } from '../elements/chat-types';
|
|
7
|
+
import type { ToolPart } from '../components/tool-types';
|
|
8
|
+
|
|
9
|
+
export interface OpenAIToolCall {
|
|
10
|
+
id: string;
|
|
11
|
+
type: 'function';
|
|
12
|
+
function: { name: string; arguments: string };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** One `reasoning_details` entry. Provider-owned open shape, kept as a record
|
|
16
|
+
* for the same reason `AnthropicContentBlock` is: an opaque entry has to pass
|
|
17
|
+
* through UNTOUCHED, and a closed type would be a list of the fields we happen
|
|
18
|
+
* to have seen. */
|
|
19
|
+
export type OpenAIReasoningDetail = Record<string, unknown>;
|
|
20
|
+
|
|
21
|
+
export interface OpenAIWireMessage {
|
|
22
|
+
role: 'system' | 'user' | 'assistant' | 'tool';
|
|
23
|
+
content: string | null;
|
|
24
|
+
tool_calls?: OpenAIToolCall[];
|
|
25
|
+
tool_call_id?: string;
|
|
26
|
+
name?: string;
|
|
27
|
+
/** Only ever present when `toOpenAIMessages` was asked for it. See
|
|
28
|
+
* `OpenAIEncodeOptions.reasoning`. */
|
|
29
|
+
reasoning_details?: OpenAIReasoningDetail[];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface OpenAIEncodeOptions {
|
|
33
|
+
/**
|
|
34
|
+
* Whether to send the assistant's own reasoning back with the thread.
|
|
35
|
+
*
|
|
36
|
+
* DEFAULT `'omit'`, and that default is a measurement, not caution. Omitting
|
|
37
|
+
* reasoning is accepted by every configuration tested -- five live omission
|
|
38
|
+
* trials plus 28 recorded live requests per configuration across the spike's
|
|
39
|
+
* conformance sweep, zero 400s -- so the path that ships today demonstrably
|
|
40
|
+
* works, while including reasoning cost about 25% more prompt tokens per round
|
|
41
|
+
* when measured (665 -> 834 on a two-round loop). A library does not get to
|
|
42
|
+
* raise every consumer's bill and add a new provider-validation surface as a
|
|
43
|
+
* side effect of a bug fix.
|
|
44
|
+
*
|
|
45
|
+
* `'include'` is for a multi-round TOOL loop, which is where OpenRouter says it
|
|
46
|
+
* pays: "when you post tool results, including the original reasoning ensures
|
|
47
|
+
* the model can continue its reasoning from where it left off". Measured
|
|
48
|
+
* accepted (HTTP 200) for a signed Anthropic block and for an OpenAI encrypted
|
|
49
|
+
* block, over the OpenAI-compatible wire.
|
|
50
|
+
*
|
|
51
|
+
* The Anthropic wire has no such knob because it has no such choice: a filtered
|
|
52
|
+
* or rebuilt thinking block there is a hard 400.
|
|
53
|
+
*/
|
|
54
|
+
reasoning?: 'omit' | 'include';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Anthropic content blocks are an open, provider-owned union. Keeping them as
|
|
58
|
+
* records is what lets a verbatim `thinking` payload pass through UNTOUCHED,
|
|
59
|
+
* which is the entire point of this encoder. */
|
|
60
|
+
export type AnthropicContentBlock = Record<string, unknown>;
|
|
61
|
+
|
|
62
|
+
export interface AnthropicWireMessage {
|
|
63
|
+
role: 'user' | 'assistant';
|
|
64
|
+
content: AnthropicContentBlock[];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** A message cannot be encoded without losing something the provider will reject.
|
|
68
|
+
* Thrown at encode time, on purpose: a throw here beats a 400 at request time,
|
|
69
|
+
* because here you still know which message and which part caused it. */
|
|
70
|
+
export class WireEncodeError extends Error {
|
|
71
|
+
readonly messageId: string;
|
|
72
|
+
readonly partIndex: number;
|
|
73
|
+
|
|
74
|
+
constructor(message: string, messageId: string, partIndex: number) {
|
|
75
|
+
super(message);
|
|
76
|
+
// Restores the prototype chain when this class is DOWNLEVELLED to ES5 by a
|
|
77
|
+
// consumer's build, exactly as WireError does. Without it
|
|
78
|
+
// `err instanceof WireEncodeError` is false there and the documented way to
|
|
79
|
+
// catch an unencodable history stops working.
|
|
80
|
+
Object.setPrototypeOf(this, WireEncodeError.prototype);
|
|
81
|
+
this.name = 'WireEncodeError';
|
|
82
|
+
this.messageId = messageId;
|
|
83
|
+
this.partIndex = partIndex;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
type TextPart = Extract<MessagePart, { type: 'text' }>;
|
|
88
|
+
|
|
89
|
+
/** A tool that can be echoed back: it has the provider's own call id AND a
|
|
90
|
+
* result. Narrowing `toolCallId` to `string` here is what removes every
|
|
91
|
+
* non-null assertion downstream. */
|
|
92
|
+
type SettledTool = ToolPart & { toolCallId: string };
|
|
93
|
+
|
|
94
|
+
const isTextPart = (p: MessagePart): p is TextPart => p.type === 'text';
|
|
95
|
+
|
|
96
|
+
const textOf = (parts: MessagePart[]): string =>
|
|
97
|
+
parts
|
|
98
|
+
.filter(isTextPart)
|
|
99
|
+
.map((p) => p.text)
|
|
100
|
+
.join('');
|
|
101
|
+
|
|
102
|
+
/** A tool is encodable only once it has a RESULT. Both APIs require every echoed
|
|
103
|
+
* tool call to have exactly one matching result, so a call with no answer yet is
|
|
104
|
+
* skipped entirely rather than sent half-formed. In a real loop the host
|
|
105
|
+
* executes tools before re-encoding, so this only drops genuinely pending work.
|
|
106
|
+
*
|
|
107
|
+
* A tool with no `toolCallId` is skipped for the same reason: there is no id to
|
|
108
|
+
* correlate a result with, and synthesising one (`call_0`) produces a request
|
|
109
|
+
* the provider rejects. */
|
|
110
|
+
const isSettled = (tool: ToolPart): tool is SettledTool =>
|
|
111
|
+
typeof tool.toolCallId === 'string' &&
|
|
112
|
+
tool.toolCallId !== '' &&
|
|
113
|
+
(tool.output !== undefined || tool.errorText !== undefined);
|
|
114
|
+
|
|
115
|
+
/** The argument text to echo. `rawInput` is the raw accumulated fragments and is
|
|
116
|
+
* preferred: providers validate an echoed tool block against what they emitted,
|
|
117
|
+
* and re-stringifying a parse changes key order and whitespace. An EMPTY
|
|
118
|
+
* `rawInput` is not valid JSON, so it falls through to the parsed snapshot the
|
|
119
|
+
* same way an absent one does. */
|
|
120
|
+
function argumentsOf(tool: ToolPart): string {
|
|
121
|
+
if (tool.rawInput !== undefined && tool.rawInput !== '') return tool.rawInput;
|
|
122
|
+
return tool.input !== undefined ? JSON.stringify(tool.input) : '{}';
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const toolCallOf = (tool: SettledTool): OpenAIToolCall => ({
|
|
126
|
+
id: tool.toolCallId,
|
|
127
|
+
type: 'function',
|
|
128
|
+
function: { name: tool.type, arguments: argumentsOf(tool) },
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
/** `errorText` WINS over `output` when a part somehow carries both, on both
|
|
132
|
+
* wires. A host that reported a failure after a partial result meant the
|
|
133
|
+
* failure, and echoing a half-written success back to the model is the version
|
|
134
|
+
* of this that produces confidently wrong follow-up work. */
|
|
135
|
+
const toolResultOf = (tool: SettledTool): OpenAIWireMessage => ({
|
|
136
|
+
role: 'tool',
|
|
137
|
+
tool_call_id: tool.toolCallId,
|
|
138
|
+
name: tool.type,
|
|
139
|
+
content: tool.errorText ?? JSON.stringify(tool.output),
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
type ReasoningPart = Extract<MessagePart, { type: 'reasoning' }>;
|
|
143
|
+
|
|
144
|
+
const isRecord = (v: unknown): v is Record<string, unknown> =>
|
|
145
|
+
typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* The provider's own `reasoning_details` entry for this block, if the part came
|
|
149
|
+
* from the OpenAI wire at all.
|
|
150
|
+
*
|
|
151
|
+
* WHAT THIS IS NOT: the block. `part.raw` holds the LAST delta that touched this
|
|
152
|
+
* part, because `appendReasoningPart` resolves `raw` last-write-wins, and every
|
|
153
|
+
* reasoning-carrying provider streams its blocks in fragments. So this entry is
|
|
154
|
+
* read for its SHAPE -- type, format, id, index, and an opaque payload if it has
|
|
155
|
+
* one -- and never for its text.
|
|
156
|
+
*
|
|
157
|
+
* A delta carrying several entries at once is rare but legal, so the entry is
|
|
158
|
+
* matched on the part's own block index first; that is the only correlator there
|
|
159
|
+
* is. The last one wins otherwise, which is the entry `raw` was captured from.
|
|
160
|
+
*/
|
|
161
|
+
function detailOf(part: ReasoningPart): Record<string, unknown> | undefined {
|
|
162
|
+
if (part.raw?.source !== 'openai.reasoning_details') return undefined;
|
|
163
|
+
const payload = part.raw.payload;
|
|
164
|
+
if (!Array.isArray(payload)) return undefined;
|
|
165
|
+
const records = payload.filter(isRecord);
|
|
166
|
+
const matched = records.filter((d) => d.index === part.index);
|
|
167
|
+
const pool = matched.length > 0 ? matched : records;
|
|
168
|
+
return pool[pool.length - 1];
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* ONE reasoning part to ONE `reasoning_details` entry, or nothing.
|
|
173
|
+
*
|
|
174
|
+
* Three cases, and every one of them is in a recorded fixture:
|
|
175
|
+
*
|
|
176
|
+
* 1. OPAQUE (`reasoning.encrypted`, `openai/gpt-5.4-mini`). The `data` blob is
|
|
177
|
+
* the block; there is no text and no signature to rebuild it from, and it
|
|
178
|
+
* arrives whole in a single frame, so `raw` really is the block here. It goes
|
|
179
|
+
* back BY REFERENCE, untouched.
|
|
180
|
+
*
|
|
181
|
+
* 2. SIGNED (`reasoning.text` with a signature, an Anthropic model over this
|
|
182
|
+
* wire). REASSEMBLED from `part.text` and `part.signature`, never echoed.
|
|
183
|
+
* Echoing `raw` sends the final frame, which carries the signature and NO
|
|
184
|
+
* text -- measured: of 85 reasoning frames in one turn, only the last has the
|
|
185
|
+
* signature and it has no text. The signature is the load-bearing field:
|
|
186
|
+
* stripping it is a hard 400 (`Invalid signature in thinking block`) from all
|
|
187
|
+
* four providers OpenRouter tried, while mutating the text was accepted.
|
|
188
|
+
*
|
|
189
|
+
* 3. NEITHER (`deepseek` `format: "unknown"`, and the `reasoning.summary`
|
|
190
|
+
* entries that ride alongside gpt-5.4-mini's encrypted block). SKIPPED.
|
|
191
|
+
* Nothing measured supports sending these: the two accepted round-trips were
|
|
192
|
+
* a signed block and an encrypted one, and every measured 400 was a provider
|
|
193
|
+
* failing to verify a block it was handed. Omission, by contrast, is proven
|
|
194
|
+
* accepted everywhere. `format: "unknown"` is OpenRouter saying it could not
|
|
195
|
+
* attribute the block to a round-trippable format, and re-sending a summary
|
|
196
|
+
* OpenAI itself treats as a display artifact spends tokens on nothing.
|
|
197
|
+
*
|
|
198
|
+
* On the sequence rule -- "the entire sequence of consecutive reasoning blocks
|
|
199
|
+
* must match the outputs generated by the model" -- case 3 is the one place this
|
|
200
|
+
* is READ rather than proven. It holds up because verifiability is a
|
|
201
|
+
* per-configuration property in all 40 recorded reasoning turns, never a
|
|
202
|
+
* per-block one: Haiku signs every block, deepseek signs none, gpt-5.4-mini
|
|
203
|
+
* signs none but carries `data`. So the skip never filters one block out of a
|
|
204
|
+
* run of comparable ones. For gpt-5.4-mini it drops the summary at block index 0
|
|
205
|
+
* and keeps the encrypted carrier at index 1, which is exactly the single-entry
|
|
206
|
+
* payload the live probe sent and got a 200 for.
|
|
207
|
+
*/
|
|
208
|
+
function reasoningDetailOf(part: ReasoningPart): OpenAIReasoningDetail | undefined {
|
|
209
|
+
const detail = detailOf(part);
|
|
210
|
+
if (!detail) return undefined;
|
|
211
|
+
|
|
212
|
+
if (detail.type === 'reasoning.encrypted' || typeof detail.data === 'string') return detail;
|
|
213
|
+
|
|
214
|
+
if (typeof part.signature !== 'string' || part.signature === '') return undefined;
|
|
215
|
+
|
|
216
|
+
const type = typeof detail.type === 'string' ? detail.type : 'reasoning.text';
|
|
217
|
+
// The readable field is named for the entry type: `summary` entries carry
|
|
218
|
+
// `summary`, everything else carries `text`.
|
|
219
|
+
const textKey = type === 'reasoning.summary' ? 'summary' : 'text';
|
|
220
|
+
const out: OpenAIReasoningDetail = { type, [textKey]: part.text, signature: part.signature };
|
|
221
|
+
if (typeof detail.format === 'string') out.format = detail.format;
|
|
222
|
+
if (typeof detail.id === 'string') out.id = detail.id;
|
|
223
|
+
if (detail.index !== undefined) out.index = detail.index;
|
|
224
|
+
return out;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* ChatMessage[] to an OpenAI chat-completions `messages` array.
|
|
229
|
+
*
|
|
230
|
+
* ONE ChatMessage CAN BECOME SEVERAL WIRE MESSAGES. The kit streams a whole
|
|
231
|
+
* assistant turn into a single message, so text, a tool call and the model's
|
|
232
|
+
* answer to that call all live in one `parts` array. The OpenAI wire has no such
|
|
233
|
+
* shape: a `role:'tool'` result must sit between the assistant message that
|
|
234
|
+
* announced the call and whatever the model said afterwards. So the turn is
|
|
235
|
+
* SPLIT at each tool boundary, into
|
|
236
|
+
*
|
|
237
|
+
* assistant(pre-tool text + tool_calls) -> tool(result)... -> assistant(answer)
|
|
238
|
+
*
|
|
239
|
+
* Flattening instead would put the model's answer BEFORE the result it was based
|
|
240
|
+
* on. No endpoint rejects that, which is exactly why it is worth spelling out:
|
|
241
|
+
* it quietly degrades every later round of a tool loop.
|
|
242
|
+
*
|
|
243
|
+
* Consecutive tool parts stay in ONE assistant message, because parallel calls
|
|
244
|
+
* are announced together and their results follow together.
|
|
245
|
+
*
|
|
246
|
+
* A turn that encodes to nothing is SKIPPED, never sent as `{ content: null }`
|
|
247
|
+
* with no `tool_calls`: OpenAI treats `content` as required unless `tool_calls`
|
|
248
|
+
* is present, and strict-compatible endpoints reject it.
|
|
249
|
+
*
|
|
250
|
+
* REASONING IS OPT-IN, and off by default. OpenRouter's OpenAI-compatible
|
|
251
|
+
* endpoint does have a channel on the way back in -- `reasoning_details` on the
|
|
252
|
+
* assistant message -- and `{ reasoning: 'include' }` uses it, one entry per
|
|
253
|
+
* reasoning part, in part order, reassembled by `reasoningDetailOf` rather than
|
|
254
|
+
* echoed out of `part.raw`. Read that function for which blocks make it and why.
|
|
255
|
+
* The default omits, because omitting is measured-accepted everywhere and costs
|
|
256
|
+
* about 25% fewer prompt tokens per round; see `OpenAIEncodeOptions.reasoning`.
|
|
257
|
+
*
|
|
258
|
+
* Reasoning alone still encodes to NOTHING. A block is content the model already
|
|
259
|
+
* produced, not a reason to send a turn, so a message carrying reasoning and no
|
|
260
|
+
* text and no settled tool is skipped exactly as before, rather than becoming
|
|
261
|
+
* `{ content: null }` with no `tool_calls`.
|
|
262
|
+
*
|
|
263
|
+
* `card`, `source` and `file` parts are never encoded; they are kit-side. File
|
|
264
|
+
* attachments are a documented v1 limitation, which is why a user turn carrying
|
|
265
|
+
* only an attachment encodes to nothing and is skipped.
|
|
266
|
+
*/
|
|
267
|
+
export function toOpenAIMessages(
|
|
268
|
+
messages: ChatMessage[],
|
|
269
|
+
options: OpenAIEncodeOptions = {},
|
|
270
|
+
): OpenAIWireMessage[] {
|
|
271
|
+
const includeReasoning = options.reasoning === 'include';
|
|
272
|
+
const out: OpenAIWireMessage[] = [];
|
|
273
|
+
|
|
274
|
+
for (const message of messages) {
|
|
275
|
+
if (message.role === 'user') {
|
|
276
|
+
const content = textOf(message.parts);
|
|
277
|
+
if (content !== '') out.push({ role: 'user', content });
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
let text = '';
|
|
282
|
+
let pending: SettledTool[] = [];
|
|
283
|
+
let reasoning: OpenAIReasoningDetail[] = [];
|
|
284
|
+
|
|
285
|
+
/** Emit the assistant message for everything buffered so far, then the
|
|
286
|
+
* result message for each call it announced, in the same order.
|
|
287
|
+
*
|
|
288
|
+
* The early return leaves any buffered reasoning where it is on purpose:
|
|
289
|
+
* nothing was emitted, so those blocks still belong to a message that has
|
|
290
|
+
* not been written yet. In practice it is unreachable with reasoning
|
|
291
|
+
* buffered, because the only mid-loop `flush()` runs with `pending`
|
|
292
|
+
* non-empty. */
|
|
293
|
+
const flush = (): void => {
|
|
294
|
+
if (text === '' && pending.length === 0) return;
|
|
295
|
+
out.push({
|
|
296
|
+
role: 'assistant',
|
|
297
|
+
content: text === '' ? null : text,
|
|
298
|
+
...(reasoning.length > 0 ? { reasoning_details: reasoning } : {}),
|
|
299
|
+
...(pending.length > 0 ? { tool_calls: pending.map(toolCallOf) } : {}),
|
|
300
|
+
});
|
|
301
|
+
for (const tool of pending) out.push(toolResultOf(tool));
|
|
302
|
+
text = '';
|
|
303
|
+
pending = [];
|
|
304
|
+
reasoning = [];
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
for (const part of message.parts) {
|
|
308
|
+
if (part.type === 'text') {
|
|
309
|
+
// Text that arrives AFTER a call is the model's answer to it, so the
|
|
310
|
+
// call and its result have to be on the wire before this text is.
|
|
311
|
+
if (pending.length > 0) flush();
|
|
312
|
+
text += part.text;
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
if (part.type === 'reasoning') {
|
|
316
|
+
if (!includeReasoning) continue;
|
|
317
|
+
// A block after a call belongs to the round that READ the result, for the
|
|
318
|
+
// same reason text does. Same rule as `toAnthropicMessages`.
|
|
319
|
+
if (pending.length > 0) flush();
|
|
320
|
+
const detail = reasoningDetailOf(part);
|
|
321
|
+
if (detail) reasoning.push(detail);
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
if (part.type === 'tool' && isSettled(part.tool)) pending.push(part.tool);
|
|
325
|
+
}
|
|
326
|
+
flush();
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return out;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* ChatMessage[] to an Anthropic Messages `messages` array. THE ROUND-TRIP
|
|
334
|
+
* ENCODER.
|
|
335
|
+
*
|
|
336
|
+
* A reasoning block is emitted as `part.raw.payload` verbatim and is NEVER
|
|
337
|
+
* rebuilt from `text` plus `signature`: Anthropic returns 400 if a thinking
|
|
338
|
+
* block in the most recent assistant message is modified, reordered, filtered or
|
|
339
|
+
* reconstructed. A reasoning part with no `raw`, or with a `raw` captured from
|
|
340
|
+
* some other format, therefore THROWS rather than silently producing a request
|
|
341
|
+
* that will fail.
|
|
342
|
+
*
|
|
343
|
+
* Block order follows part order, which follows stream order, with no filtering,
|
|
344
|
+
* because the API validates order too. An empty-text reasoning part (an omitted
|
|
345
|
+
* or redacted block) is still emitted: the docs require sending back every block
|
|
346
|
+
* "including any blocks with empty thinking fields".
|
|
347
|
+
*
|
|
348
|
+
* ONE ChatMessage CAN BECOME SEVERAL WIRE MESSAGES, for the same reason as
|
|
349
|
+
* `toOpenAIMessages`: the kit streams a whole assistant turn into one message, so
|
|
350
|
+
* the tool call and the model's answer to it share a `parts` array, but Anthropic
|
|
351
|
+
* carries the result in a SEPARATE user message that has to sit between them. So
|
|
352
|
+
* the turn is SPLIT at each tool boundary, into
|
|
353
|
+
*
|
|
354
|
+
* assistant(pre-tool blocks + tool_use) -> user(tool_result)... -> assistant(answer)
|
|
355
|
+
*
|
|
356
|
+
* Flattening instead puts the model's answer BEFORE the result it was based on,
|
|
357
|
+
* and strands every later round's thinking block in the first assistant message.
|
|
358
|
+
* Consecutive tool parts stay in ONE assistant message, because parallel calls are
|
|
359
|
+
* announced together and their results come back together.
|
|
360
|
+
*
|
|
361
|
+
* Adjacent user messages are MERGED. The API combines consecutive same-role turns
|
|
362
|
+
* itself rather than rejecting them, so this is not what stands between you and a
|
|
363
|
+
* 400; it is emitted anyway because the tool-result turn and a following user turn
|
|
364
|
+
* are one turn, several OpenAI-compatible Anthropic proxies do enforce strict
|
|
365
|
+
* alternation, and the merged form is what the models are trained on. Ordering is
|
|
366
|
+
* safe by construction: `results` is only non-empty when `blocks` is, so a
|
|
367
|
+
* tool_result message always follows its assistant message and can never be
|
|
368
|
+
* appended after a plain user turn.
|
|
369
|
+
*
|
|
370
|
+
* Asymmetry worth knowing: `tool_use.input` is a parsed OBJECT on this wire, not
|
|
371
|
+
* a string, so it uses `input` and not `rawInput`. Only thinking blocks carry a
|
|
372
|
+
* verbatim requirement.
|
|
373
|
+
*/
|
|
374
|
+
export function toAnthropicMessages(messages: ChatMessage[]): AnthropicWireMessage[] {
|
|
375
|
+
const out: AnthropicWireMessage[] = [];
|
|
376
|
+
|
|
377
|
+
/** Append to the trailing user message when there is one, so the wire never
|
|
378
|
+
* carries two user turns in a row. */
|
|
379
|
+
const pushUser = (content: AnthropicContentBlock[]): void => {
|
|
380
|
+
const last = out[out.length - 1];
|
|
381
|
+
if (last?.role === 'user') last.content = [...last.content, ...content];
|
|
382
|
+
else out.push({ role: 'user', content });
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
for (const message of messages) {
|
|
386
|
+
if (message.role === 'user') {
|
|
387
|
+
const userBlocks = message.parts
|
|
388
|
+
.filter(isTextPart)
|
|
389
|
+
.filter((p) => p.text !== '')
|
|
390
|
+
.map<AnthropicContentBlock>((p) => ({ type: 'text', text: p.text }));
|
|
391
|
+
if (userBlocks.length > 0) pushUser(userBlocks);
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
let blocks: AnthropicContentBlock[] = [];
|
|
396
|
+
let results: AnthropicContentBlock[] = [];
|
|
397
|
+
|
|
398
|
+
/** Emit the assistant message built so far, then the user message carrying the
|
|
399
|
+
* results of the calls it announced. */
|
|
400
|
+
const flush = (): void => {
|
|
401
|
+
if (blocks.length > 0) out.push({ role: 'assistant', content: blocks });
|
|
402
|
+
// Anthropic carries tool results in the FOLLOWING user message.
|
|
403
|
+
if (results.length > 0) pushUser(results);
|
|
404
|
+
blocks = [];
|
|
405
|
+
results = [];
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
message.parts.forEach((part, partIndex) => {
|
|
409
|
+
switch (part.type) {
|
|
410
|
+
case 'reasoning': {
|
|
411
|
+
if (!part.raw) {
|
|
412
|
+
throw new WireEncodeError(
|
|
413
|
+
`Cannot encode reasoning part ${partIndex} of message "${message.id}": it has no \`raw\` payload, and Anthropic requires a thinking block to be echoed back verbatim. Rebuilding one from text plus signature is the documented 400. Produce reasoning parts with readAnthropicStream, which attaches the provider's own block, and keep \`raw\` when you persist a message.`,
|
|
414
|
+
message.id,
|
|
415
|
+
partIndex,
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
if (!part.raw.source.startsWith('anthropic.')) {
|
|
419
|
+
throw new WireEncodeError(
|
|
420
|
+
`Cannot encode reasoning part ${partIndex} of message "${message.id}": its \`raw\` came from "${part.raw.source}", not from the Anthropic Messages format. Only a payload tagged \`anthropic.\` can be echoed back verbatim. If you are talking to an Anthropic model through an OpenAI-compatible endpoint, use toOpenAIMessages.`,
|
|
421
|
+
message.id,
|
|
422
|
+
partIndex,
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
// A thinking block after a call belongs to the round that READ the
|
|
426
|
+
// result, so the result has to be on the wire before it is.
|
|
427
|
+
if (results.length > 0) flush();
|
|
428
|
+
blocks.push(part.raw.payload as AnthropicContentBlock);
|
|
429
|
+
break;
|
|
430
|
+
}
|
|
431
|
+
case 'text': {
|
|
432
|
+
if (part.text === '') break;
|
|
433
|
+
// Text after a call is the model's answer to it. Same reason.
|
|
434
|
+
if (results.length > 0) flush();
|
|
435
|
+
blocks.push({ type: 'text', text: part.text });
|
|
436
|
+
break;
|
|
437
|
+
}
|
|
438
|
+
case 'tool': {
|
|
439
|
+
const tool = part.tool;
|
|
440
|
+
if (!isSettled(tool)) break;
|
|
441
|
+
blocks.push({
|
|
442
|
+
type: 'tool_use',
|
|
443
|
+
id: tool.toolCallId,
|
|
444
|
+
name: tool.type,
|
|
445
|
+
input: tool.input ?? {},
|
|
446
|
+
});
|
|
447
|
+
results.push(
|
|
448
|
+
tool.errorText !== undefined
|
|
449
|
+
? {
|
|
450
|
+
type: 'tool_result',
|
|
451
|
+
tool_use_id: tool.toolCallId,
|
|
452
|
+
is_error: true,
|
|
453
|
+
content: tool.errorText,
|
|
454
|
+
}
|
|
455
|
+
: {
|
|
456
|
+
type: 'tool_result',
|
|
457
|
+
tool_use_id: tool.toolCallId,
|
|
458
|
+
content: JSON.stringify(tool.output),
|
|
459
|
+
},
|
|
460
|
+
);
|
|
461
|
+
break;
|
|
462
|
+
}
|
|
463
|
+
default:
|
|
464
|
+
// card, source and file are kit-side and have no wire representation
|
|
465
|
+
// in v1. File attachments in particular are a known limitation.
|
|
466
|
+
break;
|
|
467
|
+
}
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
flush();
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
return out;
|
|
474
|
+
}
|