@kitn.ai/ui 0.21.0 → 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/dist/components/chat-thread.d.ts +9 -0
- package/dist/{create-tween-D3Y5qiij.js → create-tween-COnPwAQN.js} +1 -1
- package/dist/{create-tween-DGKY1FDY.js → create-tween-DmTUaXWE.js} +1 -1
- package/dist/{create-tween-paho5Rh-.js → create-tween-qs-jjvNA.js} +1 -1
- package/dist/custom-elements.json +43 -7
- 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-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-C-2LvGDG.js → Icon-CHG5z5L7.js} +1 -1
- package/dist/elements/chunks/{action-icons-CaYoImEk.js → action-icons-BAtCEEgg.js} +1 -1
- package/dist/elements/chunks/arrow-left-CFPTeL1Y.js +1 -0
- package/dist/elements/chunks/{artifact-BBIscOxJ.js → artifact-fBZOE4SO.js} +1 -1
- package/dist/elements/chunks/{attachments-BB56zoji.js → attachments-DpYMe5VQ.js} +1 -1
- package/dist/elements/chunks/{audio-visualizer-BxOx4THz.js → audio-visualizer-DFwzAcuL.js} +1 -1
- package/dist/elements/chunks/{badge-SxLSZ19R.js → badge-D6zodm6E.js} +1 -1
- package/dist/elements/chunks/{button-CmkUkz6c.js → button-C42vrGvO.js} +1 -1
- package/dist/elements/chunks/{card-renderer-Db4ffkWv.js → card-renderer-DMZsSuvU.js} +1 -1
- package/dist/elements/chunks/{chat-thread-B3Ks-cKr.js → chat-thread-CNNEhol-.js} +1 -1
- 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-Q9cYYDyM.js → choice-card-YoWjpsFV.js} +1 -1
- 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-D0MEfxOM.js → circle-x-QIxXda6k.js} +1 -1
- package/dist/elements/chunks/{code-block-C8VzAraE.js → code-block-CqeugReY.js} +1 -1
- package/dist/elements/chunks/{collapsible-Cvj61obE.js → collapsible-sTv-_5lF.js} +1 -1
- package/dist/elements/chunks/{composer-D86ztyL0.js → composer-ldQyeVEB.js} +1 -1
- package/dist/elements/chunks/{confirm-card-BdyDAGLw.js → confirm-card-C0RNOwfn.js} +1 -1
- 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-C_A_B638.js → create-tween-V4TXZ93X.js} +1 -1
- package/dist/elements/chunks/default-input-DeeRSDHc.js +1 -0
- package/dist/elements/chunks/{define-BzPnNsXg.js → define-lE-QvQFn.js} +1 -1
- package/dist/elements/chunks/{disclosure-DVKsoc1F.js → disclosure-DZSa9jog.js} +1 -1
- package/dist/elements/chunks/{download-BjA54IMJ.js → download-B2ZmnGqW.js} +1 -1
- package/dist/elements/chunks/{dropdown-BjVVHWXH.js → dropdown-R3B7cSRE.js} +1 -1
- package/dist/elements/chunks/{ellipsis-BNedmqfi.js → ellipsis-BmxceY4l.js} +1 -1
- package/dist/elements/chunks/{embed-DQsl4XEq.js → embed-BVrtTZPo.js} +1 -1
- package/dist/elements/chunks/{external-link-Di-dw2CU.js → external-link-BbdR7YO7.js} +1 -1
- package/dist/elements/chunks/{file-text-B3sAODCV.js → file-text-D7dijo1A.js} +1 -1
- package/dist/elements/chunks/{file-tree-Dgto-bMn.js → file-tree-0upJ5jeb.js} +1 -1
- package/dist/elements/chunks/{folder-D6rMadFV.js → folder-D8z58t7B.js} +1 -1
- package/dist/elements/chunks/{form-CILxvhQ7.js → form-p5BKJNw5.js} +1 -1
- package/dist/elements/chunks/{hover-card-BtiF4XiU.js → hover-card-CN9s2md5.js} +1 -1
- package/dist/elements/chunks/{icon-DKkmze3y.js → icon-CNiKP37E.js} +1 -1
- package/dist/elements/chunks/{info-CWRjZkuy.js → info-BSnuJiHI.js} +1 -1
- package/dist/elements/chunks/{input--QJBK6BA.js → input-BauDmzxX.js} +1 -1
- package/dist/elements/chunks/{kbd-C3zuKh8R.js → kbd-BlHJaP9b.js} +1 -1
- package/dist/elements/chunks/{link-BL5JA7Q0.js → link-DEi5iUWC.js} +1 -1
- package/dist/elements/chunks/{link-preview-CU37bbXh.js → link-preview-BaHyM6W4.js} +1 -1
- package/dist/elements/chunks/{loader-XmpyxyRH.js → loader-CmJG_Fkq.js} +1 -1
- package/dist/elements/chunks/{markdown-3Gv5UZ_o.js → markdown-D2jaMpCN.js} +1 -1
- package/dist/elements/chunks/{message-X5NZbq-W.js → message-C5UfXZ2Z.js} +1 -1
- package/dist/elements/chunks/{message-CnBA2OUt.js → message-CXWLmmLn.js} +1 -1
- package/dist/elements/chunks/message-square-hXylwsy0.js +1 -0
- package/dist/elements/chunks/{minimize-2-DwyrUTcM.js → minimize-2-C9ytZFMU.js} +1 -1
- package/dist/elements/chunks/model-switcher-CivGfD7-.js +1 -0
- package/dist/elements/chunks/{overlay-Cepd-KcS.js → overlay-DJL_sbOm.js} +1 -1
- package/dist/elements/chunks/{paperclip-CdwKjrlu.js → paperclip-CFaB8naM.js} +1 -1
- package/dist/elements/chunks/{progress-bar-DMVXBD3b.js → progress-bar-Br5SksfR.js} +1 -1
- package/dist/elements/chunks/{prompt-suggestion-Di_WZE_5.js → prompt-suggestion-CMIvtAhU.js} +1 -1
- package/dist/elements/chunks/{reasoning-D0jMch5u.js → reasoning-DpZcNzLq.js} +1 -1
- package/dist/elements/chunks/{resizable-1znbL1Gv.js → resizable-Q8i3T9a_.js} +1 -1
- package/dist/elements/chunks/{rotate-cw-DVhhgx8n.js → rotate-cw-B5IHCBFV.js} +1 -1
- package/dist/elements/chunks/{scroll-area-Bs4bFS1P.js → scroll-area-eLeydBje.js} +1 -1
- package/dist/elements/chunks/{scroll-button-Bet5NLc4.js → scroll-button-DWbz0yA1.js} +1 -1
- package/dist/elements/chunks/{separator-C5Gh8qGC.js → separator-Xic78AD_.js} +1 -1
- package/dist/elements/chunks/{settings-5vmEFg0K.js → settings-B8H51pAx.js} +1 -1
- package/dist/elements/chunks/{settings-group-BWLB38kz.js → settings-group-CsG9eYLe.js} +1 -1
- package/dist/elements/chunks/{share-BZ3G_DaB.js → share-CNTx0z__.js} +1 -1
- package/dist/elements/chunks/{skeleton-DafPJQTp.js → skeleton-CLlHTcMI.js} +1 -1
- package/dist/elements/chunks/{source-D_qj_6uJ.js → source-BoVcU5ok.js} +1 -1
- package/dist/elements/chunks/{star-Cuv7j2IE.js → star-CxQVTVhI.js} +1 -1
- package/dist/elements/chunks/{store-C1StPHfW.js → store-CLtS5CUG.js} +1 -1
- package/dist/elements/chunks/{tasks-card-EyoBZfpA.js → tasks-card-CzlkLmUt.js} +1 -1
- package/dist/elements/chunks/{text-shimmer-D9gRSh34.js → text-shimmer-D5NhpIQ1.js} +1 -1
- package/dist/elements/chunks/{textarea-DuhtHfcm.js → textarea-x5Uxsmtm.js} +1 -1
- package/dist/elements/chunks/{thumbs-up-B9hwv43w.js → thumbs-up-UobcJnx9.js} +1 -1
- package/dist/elements/chunks/{toast-store-Bm3DUEDe.js → toast-store-BOfs9eXq.js} +1 -1
- package/dist/elements/chunks/{tool-CDtW7L0z.js → tool-Be6Dh_dt.js} +1 -1
- package/dist/elements/chunks/{tooltip-eB226mGK.js → tooltip-RxMxyCLF.js} +1 -1
- package/dist/elements/chunks/{triangle-alert-C1SNzTqK.js → triangle-alert-C4yxH2XN.js} +1 -1
- package/dist/elements/chunks/{use-card-resolution-YZh2MvI5.js → use-card-resolution-jva9ehH6.js} +1 -1
- package/dist/elements/chunks/{variant-aurora-Cj1F7D0l.js → variant-aurora-Dx4VRcjS.js} +1 -1
- package/dist/elements/chunks/{variant-custom-T9gpt7QE.js → variant-custom-Y1ZTWgEa.js} +1 -1
- package/dist/elements/chunks/{variant-wave-CxU9z-TE.js → variant-wave-B9gml9Cg.js} +1 -1
- package/dist/elements/chunks/{video-CK4R7Rus.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 +27 -0
- package/dist/elements/dialog.js +1 -1
- package/dist/elements/editable-label.js +1 -1
- 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/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/slot-text.d.ts +51 -0
- package/dist/elements/slots.d.ts +5 -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/voice-input.js +1 -1
- package/dist/elements/voice-output.js +1 -1
- package/dist/elements.d.ts +12 -6
- package/dist/index.js +1535 -1487
- package/dist/index.server.js +2330 -2296
- package/dist/kai.es.js +1 -1
- package/dist/llms/llms-full.txt +12 -3
- package/dist/mcp.es.js +809 -75
- package/dist/react/index.d.ts +13 -3
- package/dist/react.js +14 -14
- package/dist/register-impl-AxIlqjo7.js +145 -0
- package/dist/{solid-BCJUQqwS.js → solid-BGYSJyHG.js} +4901 -4691
- package/dist/{solid-BdENBcEq.js → solid-Dt2nEwfl.js} +4406 -4320
- package/dist/solid.js +1 -1
- package/dist/solid.server.js +1 -1
- package/dist/{variant-aurora-DOb_o4pC.js → variant-aurora-B44X69AG.js} +1 -1
- package/dist/{variant-aurora-BKHHiOgL.js → variant-aurora-BRhV8Flk.js} +2 -2
- package/dist/{variant-aurora-1wMcXRUS.js → variant-aurora-Ds5ou9BH.js} +2 -2
- package/dist/{variant-custom-DWSSgmd2.js → variant-custom-B20FhtYj.js} +2 -2
- package/dist/{variant-custom-CJGB0Kbw.js → variant-custom-BvA1Schl.js} +1 -1
- package/dist/{variant-custom-Co0CkXJF.js → variant-custom-D6Rtvneb.js} +2 -2
- package/dist/{variant-wave-CEODq-9q.js → variant-wave-BUQtkryq.js} +1 -1
- package/dist/{variant-wave-C3-45cL6.js → variant-wave-BVYnqy9L.js} +2 -2
- package/dist/{variant-wave-jdXBnCUZ.js → variant-wave-DXC6_LcH.js} +2 -2
- package/dist/wire/encode.d.ts +79 -6
- package/dist/wire/files.d.ts +116 -0
- package/dist/wire/index.d.ts +3 -1
- package/dist/wire/media-types.d.ts +106 -0
- package/dist/wire.js +596 -368
- package/frameworks/react/index.tsx +9 -5
- package/llms-full.txt +12 -3
- package/package.json +2 -1
- package/src/agent-tooling/archetypes.ts +43 -12
- package/src/agent-tooling/integrations/anthropic.ts +29 -5
- package/src/agent-tooling/integrations/cloudflare.ts +5 -0
- package/src/agent-tooling/integrations/langgraph.ts +13 -0
- package/src/agent-tooling/integrations/mastra.ts +46 -5
- package/src/agent-tooling/integrations/mock.ts +4 -0
- package/src/agent-tooling/integrations/ollama.ts +7 -0
- package/src/agent-tooling/integrations/openai.ts +2 -0
- package/src/agent-tooling/integrations/openrouter.ts +2 -0
- package/src/agent-tooling/integrations/pi.ts +26 -1
- package/src/agent-tooling/integrations/pydantic-ai.ts +37 -3
- package/src/agent-tooling/integrations/vercel-ai-sdk.ts +42 -6
- package/src/agent-tooling/mcp/tools/scaffold.ts +853 -32
- package/src/agent-tooling/registry.ts +57 -0
- package/src/agent-tooling/types.ts +132 -0
- package/src/components/chat-thread.tsx +10 -1
- package/src/components/context.tsx +18 -12
- package/src/components/conversation-item.tsx +6 -0
- package/src/components/model-switcher.tsx +38 -8
- package/src/components/toast.tsx +6 -1
- package/src/elements/button.tsx +26 -3
- package/src/elements/chat.tsx +28 -2
- package/src/elements/default-input.tsx +128 -5
- package/src/elements/element-meta.json +30 -4
- package/src/elements/element-types.d.ts +12 -6
- package/src/elements/menu.tsx +30 -3
- package/src/elements/slot-text.ts +73 -0
- package/src/elements/slots.ts +10 -0
- package/src/index.ts +9 -5
- package/src/wire/encode.ts +229 -14
- package/src/wire/files.ts +342 -0
- package/src/wire/index.ts +21 -0
- package/src/wire/media-types.ts +344 -0
- package/dist/elements/chunks/arrow-left-Bb46H7w9.js +0 -1
- package/dist/elements/chunks/check-C8desc_h.js +0 -1
- package/dist/elements/chunks/chevron-down-DKtc2HNB.js +0 -1
- package/dist/elements/chunks/chevron-right-Dw4GPhwL.js +0 -1
- package/dist/elements/chunks/circle-BeokBsvB.js +0 -1
- package/dist/elements/chunks/circle-check-wJSqt859.js +0 -1
- package/dist/elements/chunks/context-WHDl-xSx.js +0 -1
- package/dist/elements/chunks/conversation-list-CdR14_Y4.js +0 -1
- package/dist/elements/chunks/default-input-CSZqUM81.js +0 -1
- package/dist/elements/chunks/message-square-CnpAm_P0.js +0 -1
- package/dist/elements/chunks/model-switcher-CclaJEHt.js +0 -1
- package/dist/elements/chunks/x-D1qx0aLd.js +0 -1
- package/dist/register-impl-bzrZ44ft.js +0 -145
|
@@ -203,7 +203,7 @@ export interface ButtonProps extends WebComponentProps {
|
|
|
203
203
|
icon?: string;
|
|
204
204
|
/** Trailing icon, after the label (e.g. `"chevron-down"` for a menu affordance). */
|
|
205
205
|
iconTrailing?: string;
|
|
206
|
-
/** Accessible name. REQUIRED for icon-only buttons (no visible text); ignored when you slot visible text, which already names the button. */
|
|
206
|
+
/** Accessible name. REQUIRED for icon-only buttons (no visible text); ignored when you slot visible text, which already names the button. An `aria-label` on top of visible text REPLACES that name rather than adding to it, so a button reading "Save" that answers to "Submit" is unusable by speech input (WCAG 2.5.3, Label in Name). The visible text wins. An `icon` / `icon-sm` size hides the slot, which makes the button icon-only whatever you slotted, so `label` is what names it there. */
|
|
207
207
|
label?: string;
|
|
208
208
|
/** Disable the button (non-interactive, dimmed). */
|
|
209
209
|
disabled?: boolean;
|
|
@@ -353,6 +353,8 @@ export interface ChatProps extends WebComponentProps {
|
|
|
353
353
|
kindIcons?: Record<string, string>;
|
|
354
354
|
/** Whether each message's action bar is always visible (`'always'`, default) or only revealed on hover of that message row (`'hover'`). */
|
|
355
355
|
actionsReveal?: "always" | "hover";
|
|
356
|
+
/** Which attachment media types the user may stage, in HTML `accept` syntax: `<kai-chat accept="image/*,application/pdf">`. A plain string, so unlike `messages` it DOES work as an attribute. Omitted means no filter. MEDIA TYPES ONLY -- exact (`image/png`) or subtype wildcard (`text/*`). HTML allows a file extension here and this does not: `accept=".py"` THROWS with the entry named, rather than silently resolving to a picker that accepts nothing. It can only NARROW what the kit can already encode: `accept="image/*"` resolves to the four image formats both APIs take, not to every image type the OS offers. Pass the SAME string to `toOpenAIMessages(msgs, { accept })` and the picker and the wire cannot disagree -- both resolve it through `resolveMediaPolicy` against one declaration. That declaration is readable as `encodableMediaTypes()` from `@kitn.ai/ui/wire`, if you would rather build your own picker than use this prop. */
|
|
357
|
+
accept?: string;
|
|
356
358
|
/** The full message thread to render, newest last. Each entry carries its role, ordered `parts`, and optional actions/avatar/feedback. Set as a JS property (`el.messages = [...]`); a NEW array reference per streaming chunk re-renders (mutating in place does not). Omit for an empty thread. Re-declared here (rather than inherited from `ChatThreadProps`) because the ELEMENT registers a `[]` default and renders the empty state without it, while the SolidJS `<ChatThread>` component still requires it. The facade hands it a validated array either way. Matches `<kai-thread>`. */
|
|
357
359
|
messages?: { id: string; role: "user" | "assistant"; parts: ({ type: "text"; text: string; raw?: { source: string; payload: unknown } } | { type: "reasoning"; text: string; label?: string; index?: number; streamId?: string; signature?: string; raw?: { source: string; payload: unknown } } | { type: "tool"; tool: { type: string; kind?: "command" | "file-change" | "search" | "fetch" | "mcp" | "image" | "generic"; state: "input-streaming" | "input-available" | "output-available" | "output-error"; input?: Record<string, unknown>; rawInput?: string; output?: Record<string, unknown>; toolCallId?: string; errorText?: string; raw?: { source: string; payload: unknown } }; raw?: { source: string; payload: unknown } } | { type: "card"; envelope: { type: string; id: string; data: unknown; title?: string; resolution?: { kind: "action"; action: string; payload?: unknown; at?: string } | { kind: "submit"; data: unknown; at?: string } | { kind: "dismissed"; at?: string } | { kind: "expired"; reason?: string; at?: string } }; raw?: { source: string; payload: unknown } } | { type: "source"; source: { id?: string; url?: string; title?: string; snippet?: string; index?: number }; raw?: { source: string; payload: unknown } } | { type: "file"; attachment: { id: string; type: "file" | "source-document"; filename?: string; mediaType?: string; url?: string; title?: string }; raw?: { source: string; payload: unknown } })[]; actions?: ("copy" | "like" | "dislike" | "regenerate" | "edit" | { id: string; label: string; icon?: string; tooltip?: string })[]; avatar?: { src?: string; fallback?: string; alt?: string }; feedback?: "like" | "dislike" }[];
|
|
358
360
|
/** Optional card type -> custom-element tag overrides/additions for `card` parts (merged over the built-ins). Property: `el.cardTypes`. Typed as a plain string map (not the `CardTagMap` alias) so the generated React wrapper inlines it instead of emitting an unresolved named type. */
|
|
@@ -361,6 +363,8 @@ export interface ChatProps extends WebComponentProps {
|
|
|
361
363
|
cardSchemas?: Record<string, object>;
|
|
362
364
|
/** The staged attachments changed (file added or removed). Carries the full current list so a consumer can react in real time. */
|
|
363
365
|
onAttachmentsChange?: (event: CustomEvent<{ attachments: { id: string; type: "file" | "source-document"; filename?: undefined | string; mediaType?: undefined | string; url?: undefined | string; title?: undefined | string }[] }>) => void;
|
|
366
|
+
/** One or more picked files were refused because `accept` excluded them. The element renders NO message of its own: it reports the facts (name, media type, whether the kit could have sent it) and what the user should see is the application's call. Only ever fires when `accept` is set. */
|
|
367
|
+
onAttachmentsRejected?: (event: CustomEvent<{ rejected: { filename: string; mediaType: string; reason: "filtered" | "unsupported" }[] }>) => void;
|
|
364
368
|
/** An action button on a message was clicked. `action` is the built-in name or custom id. `state` is present only for the toggleable feedback votes: `'on'` when a like/dislike is set, `'off'` when re-tapped to clear. */
|
|
365
369
|
onMessageAction?: (event: CustomEvent<{ messageId: string; action: string; state?: undefined | "on" | "off" }>) => void;
|
|
366
370
|
/** The header model switcher changed. */
|
|
@@ -379,8 +383,8 @@ export interface ChatProps extends WebComponentProps {
|
|
|
379
383
|
|
|
380
384
|
export const Chat = /*#__PURE__*/ createWebComponent<ChatProps>(
|
|
381
385
|
'kai-chat',
|
|
382
|
-
["theme","search","value","placeholder","loading","suggestions","suggestionMode","persistSuggestions","proseSize","codeTheme","codeHighlight","chatTitle","models","currentModel","context","scrollButton","headerStart","headerEnd","headerFull","sidebar","empty","composer","composerActions","footer","voice","triggers","kindIcons","actionsReveal","messages","cardTypes","cardSchemas"],
|
|
383
|
-
{ onAttachmentsChange: 'kai-attachments-change', onMessageAction: 'kai-message-action', onModelChange: 'kai-model-change', onSearch: 'kai-search', onSubmit: 'kai-submit', onSuggestionClick: 'kai-suggestion-click', onValueChange: 'kai-value-change', onVoice: 'kai-voice' },
|
|
386
|
+
["theme","search","value","placeholder","loading","suggestions","suggestionMode","persistSuggestions","proseSize","codeTheme","codeHighlight","chatTitle","models","currentModel","context","scrollButton","headerStart","headerEnd","headerFull","sidebar","empty","composer","composerActions","footer","voice","triggers","kindIcons","actionsReveal","accept","messages","cardTypes","cardSchemas"],
|
|
387
|
+
{ onAttachmentsChange: 'kai-attachments-change', onAttachmentsRejected: 'kai-attachments-rejected', onMessageAction: 'kai-message-action', onModelChange: 'kai-model-change', onSearch: 'kai-search', onSubmit: 'kai-submit', onSuggestionClick: 'kai-suggestion-click', onValueChange: 'kai-value-change', onVoice: 'kai-voice' },
|
|
384
388
|
() => import('@kitn.ai/ui/elements/chat'),
|
|
385
389
|
);
|
|
386
390
|
|
|
@@ -973,11 +977,11 @@ export interface MenuProps extends WebComponentProps {
|
|
|
973
977
|
placement?: string;
|
|
974
978
|
/** Built-in trigger: leading icon (a named icon like `"plus"`, an image URL/data-URI, or text). Use this instead of slotting `slot="trigger"` for the common case; a slotted trigger overrides it. */
|
|
975
979
|
triggerIcon?: string;
|
|
976
|
-
/** Built-in trigger: a text label (e.g. `"High"`). */
|
|
980
|
+
/** Built-in trigger: a text label (e.g. `"High"`). This is the trigger's VISIBLE text, so it is also its accessible name, and `label` does not override it: an accessible name that does not contain the visible text is unreachable by speech input, which is what WCAG 2.5.3 (Label in Name) exists for. A slotted `slot="trigger"` replaces this built-in trigger entirely and is named differently; see `label`. */
|
|
977
981
|
triggerLabel?: string;
|
|
978
982
|
/** Built-in trigger: a trailing icon (e.g. `"chevron-down"` for a select look). */
|
|
979
983
|
triggerIconTrailing?: string;
|
|
980
|
-
/** Accessible name for
|
|
984
|
+
/** Accessible name for a trigger with no visible label. Ignored when `triggerLabel` is set, which is already the visible name. It DOES name a slotted `slot="trigger"`, and that is a difference in what the two slots MEAN, not a limitation. `<kai-button>`'s slot IS the button's label, so text slotted there is the name and `label` steps aside. This slot is VISUAL content, a `+` or an `<svg>`, with the name supplied separately: decoration beside a name, never a second name competing with one. So `label` names the trigger here by design. Slotting a real WORD rather than a glyph makes that word a visible label, and an accessible name has to contain the visible text. Then either drop `label` or make it contain the word you slotted. */
|
|
981
985
|
label?: string;
|
|
982
986
|
/** Drive/observe open state (Shoelace-style: settable + reflected to the `open` attribute, the menu still self-manages on click/keyboard). Set `el.open = true`, or `<kai-menu open>`; listen for `kai-open-change`. */
|
|
983
987
|
open?: boolean;
|
package/llms-full.txt
CHANGED
|
@@ -449,7 +449,7 @@ _No events._
|
|
|
449
449
|
| `size` | `size` | `undefined \| "sm" \| "md" \| "lg" \| "icon" \| "icon-sm"` | Size token. `icon` / `icon-sm` are square (for icon-only buttons); `sm` / `md` / `lg` size text buttons. Defaults to `md`. |
|
|
450
450
|
| `icon` | `icon` | `undefined \| string` | Leading icon: a named icon (e.g. `"mic"`, `"plus"`), an image URL/data-URI, or plain text. Renders before any slotted label. |
|
|
451
451
|
| `iconTrailing` | `icon-trailing` | `undefined \| string` | Trailing icon, after the label (e.g. `"chevron-down"` for a menu affordance). |
|
|
452
|
-
| `label` | `label` | `undefined \| string` | Accessible name. REQUIRED for icon-only buttons (no visible text); ignored when you slot visible text, which already names the button. |
|
|
452
|
+
| `label` | `label` | `undefined \| string` | Accessible name. REQUIRED for icon-only buttons (no visible text); ignored when you slot visible text, which already names the button. An `aria-label` on top of visible text REPLACES that name rather than adding to it, so a button reading "Save" that answers to "Submit" is unusable by speech input (WCAG 2.5.3, Label in Name). The visible text wins. An `icon` / `icon-sm` size hides the slot, which makes the button icon-only whatever you slotted, so `label` is what names it there. |
|
|
453
453
|
| `disabled` | `disabled` | `undefined \| false \| true` | Disable the button (non-interactive, dimmed). |
|
|
454
454
|
| `full` | `full` | `undefined \| false \| true` | Stretch the button to the full width of its container (a block button), e.g. a card CTA or a stacked action. Attribute: `full`. |
|
|
455
455
|
| `align` | `align` | `undefined \| "start" \| "center" \| "end"` | Justify the button's content: `start`, `center` (default), or `end`. Combine with `full` for a full-width, left-aligned button. |
|
|
@@ -629,6 +629,7 @@ _No events._
|
|
|
629
629
|
| `triggers` | — | `undefined \| { char: string; kind: string; items?: undefined \| { id: string; label: string; icon?: undefined \| string; description?: undefined \| string; group?: undefined \| string; kind?: undefined \| string; promptText?: undefined \| string; data?: undefined \| Record<string, unknown> }[] }[]` | Rich entity triggers. Each `{ char, kind, items }` opens a caret-anchored menu that inserts an atomic pill (`/` skills, `@` agents/plugins). Set as a JS property; forwarded to the input. |
|
|
630
630
|
| `kindIcons` | — | `undefined \| Record<string, string>` | Default icon per entity kind (kind → image src) for pills/menu items. |
|
|
631
631
|
| `actionsReveal` | `actions-reveal` | `undefined \| "always" \| "hover"` | Whether each message's action bar is always visible (`'always'`, default) or only revealed on hover of that message row (`'hover'`). |
|
|
632
|
+
| `accept` | `accept` | `undefined \| string` | Which attachment media types the user may stage, in HTML `accept` syntax: `<kai-chat accept="image/*,application/pdf">`. A plain string, so unlike `messages` it DOES work as an attribute. Omitted means no filter. MEDIA TYPES ONLY -- exact (`image/png`) or subtype wildcard (`text/*`). HTML allows a file extension here and this does not: `accept=".py"` THROWS with the entry named, rather than silently resolving to a picker that accepts nothing. It can only NARROW what the kit can already encode: `accept="image/*"` resolves to the four image formats both APIs take, not to every image type the OS offers. Pass the SAME string to `toOpenAIMessages(msgs, { accept })` and the picker and the wire cannot disagree -- both resolve it through `resolveMediaPolicy` against one declaration. That declaration is readable as `encodableMediaTypes()` from `@kitn.ai/ui/wire`, if you would rather build your own picker than use this prop. |
|
|
632
633
|
| `messages` | — | `undefined \| { id: string; role: "user" \| "assistant"; parts: ({ type: "text"; text: string; raw?: undefined \| { source: string; payload: unknown } } \| { type: "reasoning"; text: string; label?: undefined \| string; index?: undefined \| number; streamId?: undefined \| string; signature?: undefined \| string; raw?: undefined \| { source: string; payload: unknown } } \| { type: "tool"; tool: { type: string; kind?: undefined \| "command" \| "file-change" \| "search" \| "fetch" \| "mcp" \| "image" \| "generic"; state: "input-streaming" \| "input-available" \| "output-available" \| "output-error"; input?: undefined \| Record<string, unknown>; rawInput?: undefined \| string; output?: undefined \| Record<string, unknown>; toolCallId?: undefined \| string; errorText?: undefined \| string; raw?: undefined \| { source: string; payload: unknown } }; raw?: undefined \| { source: string; payload: unknown } } \| { type: "card"; envelope: { type: string; id: string; data: unknown; title?: undefined \| string; resolution?: undefined \| { kind: "action"; action: string; payload?: unknown; at?: undefined \| string } \| { kind: "submit"; data: unknown; at?: undefined \| string } \| { kind: "dismissed"; at?: undefined \| string } \| { kind: "expired"; reason?: undefined \| string; at?: undefined \| string } }; raw?: undefined \| { source: string; payload: unknown } } \| { type: "source"; source: { id?: undefined \| string; url?: undefined \| string; title?: undefined \| string; snippet?: undefined \| string; index?: undefined \| number }; raw?: undefined \| { source: string; payload: unknown } } \| { type: "file"; attachment: { id: string; type: "file" \| "source-document"; filename?: undefined \| string; mediaType?: undefined \| string; url?: undefined \| string; title?: undefined \| string }; raw?: undefined \| { source: string; payload: unknown } })[]; actions?: undefined \| ("copy" \| "like" \| "dislike" \| "regenerate" \| "edit" \| { id: string; label: string; icon?: undefined \| string; tooltip?: undefined \| string })[]; avatar?: undefined \| { src?: undefined \| string; fallback?: undefined \| string; alt?: undefined \| string }; feedback?: undefined \| "like" \| "dislike" }[]` | The full message thread to render, newest last. Each entry carries its role, ordered `parts`, and optional actions/avatar/feedback. Set as a JS property (`el.messages = [...]`); a NEW array reference per streaming chunk re-renders (mutating in place does not). Omit for an empty thread. Re-declared here (rather than inherited from `ChatThreadProps`) because the ELEMENT registers a `[]` default and renders the empty state without it, while the SolidJS `<ChatThread>` component still requires it. The facade hands it a validated array either way. Matches `<kai-thread>`. |
|
|
633
634
|
| `cardTypes` | — | `undefined \| Record<string, string>` | Optional card type -> custom-element tag overrides/additions for `card` parts (merged over the built-ins). Property: `el.cardTypes`. Typed as a plain string map (not the `CardTagMap` alias) so the generated React wrapper inlines it instead of emitting an unresolved named type. |
|
|
634
635
|
| `cardSchemas` | — | `undefined \| Record<string, object>` | JSON Schemas for the card types this app renders, keyed by envelope type. The companion of `cardTypes`, which says what DRAWS a card while this says what a VALID one looks like. An OBJECT, so it is a JS property only: `el.cardSchemas = { 'pricing-table': pricingSchema }`, never an attribute. `createCardRegistry(...).validationSchemas` is exactly this shape. Without it the kit validates its own seven built-ins and leaves your own card type, the one your app actually cares about, as the only unchecked thing on screen. A schema here WINS over a built-in of the same name. Typed `Record<string, object>` rather than `Record<string, JsonSchema>` deliberately: an imported `.json` schema widens `"type"` to `string`, and an authored one carries `$schema`/`title`/`description`/`additionalProperties`, so the tighter type would reject both of the normal ways to supply one. |
|
|
@@ -638,6 +639,7 @@ _No events._
|
|
|
638
639
|
| Event | `detail` type | Description |
|
|
639
640
|
|---|---|---|
|
|
640
641
|
| `kai-attachments-change` | `CustomEvent<{ attachments: { id: string; type: "file" \| "source-document"; filename?: undefined \| string; mediaType?: undefined \| string; url?: undefined \| string; title?: undefined \| string }[] }>` | The staged attachments changed (file added or removed). Carries the full current list so a consumer can react in real time. |
|
|
642
|
+
| `kai-attachments-rejected` | `CustomEvent<{ rejected: { filename: string; mediaType: string; reason: "filtered" \| "unsupported" }[] }>` | One or more picked files were refused because `accept` excluded them. The element renders NO message of its own: it reports the facts (name, media type, whether the kit could have sent it) and what the user should see is the application's call. Only ever fires when `accept` is set. |
|
|
641
643
|
| `kai-message-action` | `CustomEvent<{ messageId: string; action: string; state?: undefined \| "on" \| "off" }>` | An action button on a message was clicked. `action` is the built-in name or custom id. `state` is present only for the toggleable feedback votes: `'on'` when a like/dislike is set, `'off'` when re-tapped to clear. |
|
|
642
644
|
| `kai-model-change` | `CustomEvent<{ modelId: string }>` | The header model switcher changed. |
|
|
643
645
|
| `kai-search` | `CustomEvent<Record<string, never>>` | The Search button was clicked. |
|
|
@@ -951,6 +953,13 @@ _No events._
|
|
|
951
953
|
|---|---|---|
|
|
952
954
|
| `kai-threshold-change` | `CustomEvent<{ level: "ok" \| "warn" \| "danger" }>` | Fires when the computed severity level changes (ok → warn → danger or back). `detail.level` is `'ok'`, `'warn'`, or `'danger'`. |
|
|
953
955
|
|
|
956
|
+
**Styleable parts** (restyle from outside via `kai-context::part(name)`):
|
|
957
|
+
|
|
958
|
+
| Part | Description |
|
|
959
|
+
|---|---|
|
|
960
|
+
| `::part(track)` | The usage meter track inside the hover-card breakdown. Carries `role="progressbar"` and is in the DOM only while the card is open. Restyle its height, radius, or background from outside. — `kai-context::part(track) { height: 0.5rem }` |
|
|
961
|
+
| `::part(fill)` | The used-tokens portion of that meter. Its width follows `usedTokens / maxTokens`; its default color is the severity hue picked by `warnThreshold` / `dangerThreshold`, so recoloring it from outside replaces that signal. — `kai-context::part(fill) { background: var(--color-tool-blue) }` |
|
|
962
|
+
|
|
954
963
|
---
|
|
955
964
|
|
|
956
965
|
### `kai-conversations` / `Conversations`
|
|
@@ -1450,9 +1459,9 @@ _No events._
|
|
|
1450
1459
|
| `items` | — | `undefined \| { id?: undefined \| string; label?: undefined \| string; icon?: undefined \| string; shortcut?: undefined \| string; checked?: undefined \| false \| true; radioGroup?: undefined \| string; disabled?: undefined \| false \| true; separator?: undefined \| false \| true; heading?: undefined \| false \| true; items?: undefined \| Record<string, unknown>[] }[]` | Tree of menu items. Set as a JS property, not an HTML attribute. |
|
|
1451
1460
|
| `placement` | `placement` | `undefined \| string` | Optional placement hint (unused by the underlying Dropdown which always positions bottom-start, kept for future extension). |
|
|
1452
1461
|
| `triggerIcon` | `trigger-icon` | `undefined \| string` | Built-in trigger: leading icon (a named icon like `"plus"`, an image URL/data-URI, or text). Use this instead of slotting `slot="trigger"` for the common case; a slotted trigger overrides it. |
|
|
1453
|
-
| `triggerLabel` | `trigger-label` | `undefined \| string` | Built-in trigger: a text label (e.g. `"High"`). |
|
|
1462
|
+
| `triggerLabel` | `trigger-label` | `undefined \| string` | Built-in trigger: a text label (e.g. `"High"`). This is the trigger's VISIBLE text, so it is also its accessible name, and `label` does not override it: an accessible name that does not contain the visible text is unreachable by speech input, which is what WCAG 2.5.3 (Label in Name) exists for. A slotted `slot="trigger"` replaces this built-in trigger entirely and is named differently; see `label`. |
|
|
1454
1463
|
| `triggerIconTrailing` | `trigger-icon-trailing` | `undefined \| string` | Built-in trigger: a trailing icon (e.g. `"chevron-down"` for a select look). |
|
|
1455
|
-
| `label` | `label` | `undefined \| string` | Accessible name for
|
|
1464
|
+
| `label` | `label` | `undefined \| string` | Accessible name for a trigger with no visible label. Ignored when `triggerLabel` is set, which is already the visible name. It DOES name a slotted `slot="trigger"`, and that is a difference in what the two slots MEAN, not a limitation. `<kai-button>`'s slot IS the button's label, so text slotted there is the name and `label` steps aside. This slot is VISUAL content, a `+` or an `<svg>`, with the name supplied separately: decoration beside a name, never a second name competing with one. So `label` names the trigger here by design. Slotting a real WORD rather than a glyph makes that word a visible label, and an accessible name has to contain the visible text. Then either drop `label` or make it contain the word you slotted. |
|
|
1456
1465
|
| `open` | `open` | `undefined \| false \| true` | Drive/observe open state (Shoelace-style: settable + reflected to the `open` attribute, the menu still self-manages on click/keyboard). Set `el.open = true`, or `<kai-menu open>`; listen for `kai-open-change`. |
|
|
1457
1466
|
| `defaultOpen` | `default-open` | `undefined \| false \| true` | Initial open state on mount (uncontrolled seed). |
|
|
1458
1467
|
| `disabled` | `disabled` | `undefined \| false \| true` | Disable the trigger: click/keyboard and `show()` no longer open the menu. |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kitn.ai/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.0",
|
|
4
4
|
"nx": {
|
|
5
5
|
"name": "ui",
|
|
6
6
|
"targets": {
|
|
@@ -177,6 +177,7 @@
|
|
|
177
177
|
"verify:ssr:imports": "node scripts/verify-ssr-imports.mjs",
|
|
178
178
|
"verify:ssr:render": "node scripts/verify-ssr-render.mjs",
|
|
179
179
|
"verify:pack": "node scripts/verify-pack-weight.mjs",
|
|
180
|
+
"verify:starters": "node scripts/verify-starters.mjs",
|
|
180
181
|
"test": "vitest run",
|
|
181
182
|
"test:react": "vitest run --config vitest.react.config.ts",
|
|
182
183
|
"test:storybook": "vitest run --project=storybook",
|
|
@@ -55,6 +55,34 @@ export const archetypes: Archetype[] = [
|
|
|
55
55
|
defaultPlacement: 'full-page',
|
|
56
56
|
docsSlug: 'examples/voice-assistant',
|
|
57
57
|
},
|
|
58
|
+
/**
|
|
59
|
+
* THE PRESET THAT EXISTS TO MAKE A CAPABILITY REACHABLE, not to name a product
|
|
60
|
+
* shape someone asked for.
|
|
61
|
+
*
|
|
62
|
+
* `kai-file-upload` and `kai-attachments` were registered elements that NO
|
|
63
|
+
* preset composed, and `listCapabilityGroups` derives its answer from this
|
|
64
|
+
* table — so the attachments capability reached neither `listSurfaceProbes`
|
|
65
|
+
* nor `verify:scaffold`, and `renderSurface` had no branch that emitted either
|
|
66
|
+
* tag. A caller could pass them in `components` (the axis takes any list) and
|
|
67
|
+
* get two bare `<kai-file-upload></kai-file-upload>` / `<kai-attachments>`
|
|
68
|
+
* siblings with nothing wired to them: on screen, inert, silent.
|
|
69
|
+
*
|
|
70
|
+
* The pair is ONE capability for the same reason `kai-artifact` +
|
|
71
|
+
* `kai-resizable` are: a dropzone with no list is a black hole, and a list
|
|
72
|
+
* with no dropzone can never fill. `hasAttachments` in scaffold.ts requires
|
|
73
|
+
* both for exactly that reason.
|
|
74
|
+
*
|
|
75
|
+
* The components match the create-kai spec's own feature table
|
|
76
|
+
* (`attachments` -> `kai-file-upload`, `kai-attachments`), so the CLI's
|
|
77
|
+
* multi-select and this preset name the same surface rather than two.
|
|
78
|
+
*/
|
|
79
|
+
{
|
|
80
|
+
id: 'attachments',
|
|
81
|
+
title: 'File attachments',
|
|
82
|
+
components: ['kai-chat', 'kai-file-upload', 'kai-attachments'],
|
|
83
|
+
defaultPlacement: 'full-page',
|
|
84
|
+
docsSlug: 'examples/attachments',
|
|
85
|
+
},
|
|
58
86
|
];
|
|
59
87
|
|
|
60
88
|
/** The component every surface composes around, so never a capability by itself. */
|
|
@@ -71,9 +99,10 @@ export interface CapabilityGroup {
|
|
|
71
99
|
* The distinct capabilities the presets know about, DERIVED from them.
|
|
72
100
|
*
|
|
73
101
|
* A capability is one preset's components minus `kai-chat`, deduped across the
|
|
74
|
-
* catalog. Today that is
|
|
75
|
-
* `voice-input` — and the
|
|
76
|
-
* and `support-widget` both add nothing (they differ
|
|
102
|
+
* catalog. Today that is five: `sources`, `tool+reasoning`, `artifact+resizable`,
|
|
103
|
+
* `voice-input`, `file-upload+attachments` — and the seven presets collapse onto
|
|
104
|
+
* them because `drop-in-chat` and `support-widget` both add nothing (they differ
|
|
105
|
+
* only in `defaultPlacement`).
|
|
77
106
|
*
|
|
78
107
|
* Derived rather than listed for the reason the verify script's other axes are:
|
|
79
108
|
* a hand-written list would have to be edited by the same hand that adds a
|
|
@@ -83,9 +112,11 @@ export interface CapabilityGroup {
|
|
|
83
112
|
*
|
|
84
113
|
* The grouping is real and not an artifact of the derivation: `kai-tool` and
|
|
85
114
|
* `kai-reasoning` are one capability because a tool panel with no reasoning
|
|
86
|
-
* disclosure is not a surface anyone asks for,
|
|
115
|
+
* disclosure is not a surface anyone asks for, `kai-artifact` /
|
|
87
116
|
* `kai-resizable` are one because `isWorkspace` only fires when BOTH are present
|
|
88
|
-
* — split them and the renderer emits a bare artifact with no pane to put it in
|
|
117
|
+
* — split them and the renderer emits a bare artifact with no pane to put it in
|
|
118
|
+
* — and `kai-file-upload` / `kai-attachments` are one because `hasAttachments`
|
|
119
|
+
* fires the same way, for the same kind of reason.
|
|
89
120
|
*/
|
|
90
121
|
export function listCapabilityGroups(): CapabilityGroup[] {
|
|
91
122
|
const seen = new Map<string, CapabilityGroup>();
|
|
@@ -109,14 +140,14 @@ export interface SurfaceProbe {
|
|
|
109
140
|
* The surface axis for `verify:scaffold`: each capability ALONE, plus none, plus
|
|
110
141
|
* ALL of them.
|
|
111
142
|
*
|
|
112
|
-
* WHY THESE AND NOT THE PRESETS. The presets are
|
|
113
|
-
* components lists (`support-widget` repeats `drop-in-chat`'s, and the
|
|
114
|
-
* holds `placement` at one value, so that pair compiled the same types
|
|
115
|
-
* More importantly they cover no combination at all — every preset is
|
|
116
|
-
* capability — so the surfaces the multi-select makes reachable,
|
|
117
|
-
* them, had nothing compiling them.
|
|
143
|
+
* WHY THESE AND NOT THE PRESETS. The presets are seven cells covering six
|
|
144
|
+
* distinct components lists (`support-widget` repeats `drop-in-chat`'s, and the
|
|
145
|
+
* matrix holds `placement` at one value, so that pair compiled the same types
|
|
146
|
+
* twice). More importantly they cover no combination at all — every preset is
|
|
147
|
+
* exactly one capability — so the surfaces the multi-select makes reachable,
|
|
148
|
+
* which is most of them, had nothing compiling them.
|
|
118
149
|
*
|
|
119
|
-
* WHY NOT THE POWER SET. 2^
|
|
150
|
+
* WHY NOT THE POWER SET. 2^5 x 11 integrations x 8 frameworks is 2816 cells to
|
|
120
151
|
* exercise no new branch. The renderers compose capabilities INDEPENDENTLY: each
|
|
121
152
|
* companion tag is emitted on its own terms, and the one multi-component
|
|
122
153
|
* predicate (`isWorkspace`) reads components from within a single group. So
|
|
@@ -35,10 +35,19 @@ const anthropic: Integration = {
|
|
|
35
35
|
// No per-framework templates: the handler below is web-standard, so the
|
|
36
36
|
// scaffolder wraps it in whatever the target framework routes with.
|
|
37
37
|
routeTemplates: {},
|
|
38
|
-
webRoute: `/**
|
|
38
|
+
webRoute: `/** Where an image or document's bytes come from. Anthropic takes both forms
|
|
39
|
+
* for both block types, which is why this route has no gap where the
|
|
40
|
+
* OpenAI-shaped one does: a chat-completions \`file\` part is base64-only. */
|
|
41
|
+
type AnthropicSource =
|
|
42
|
+
| { type: 'base64'; media_type: string; data: string }
|
|
43
|
+
| { type: 'url'; url: string };
|
|
44
|
+
|
|
45
|
+
/** One Anthropic content block. Open union on the wire; these are the ones the
|
|
39
46
|
* scaffold's own thread can produce. */
|
|
40
47
|
type AnthropicBlock =
|
|
41
48
|
| { type: 'text'; text: string }
|
|
49
|
+
| { type: 'image'; source: AnthropicSource }
|
|
50
|
+
| { type: 'document'; source: AnthropicSource }
|
|
42
51
|
| { type: 'tool_use'; id: string; name: string; input: unknown }
|
|
43
52
|
| { type: 'tool_result'; tool_use_id: string; content: string };
|
|
44
53
|
|
|
@@ -121,12 +130,24 @@ function toAnthropicBody(messages: ChatRequestBody['messages']): {
|
|
|
121
130
|
switch (message.role) {
|
|
122
131
|
case 'system': {
|
|
123
132
|
// TOP-LEVEL, not a message. Several system turns concatenate.
|
|
124
|
-
const text = message.content
|
|
133
|
+
const text = wireText(message.content);
|
|
125
134
|
system = system === undefined ? text : \`\${system}\\n\\n\${text}\`;
|
|
126
135
|
break;
|
|
127
136
|
}
|
|
128
137
|
case 'user': {
|
|
129
|
-
|
|
138
|
+
// Attachments ride HERE, as image and document blocks beside the text,
|
|
139
|
+
// in the order the thread authored them.
|
|
140
|
+
const blocks = wireParts(message.content).map((part): AnthropicBlock => {
|
|
141
|
+
if (part.kind === 'text') return { type: 'text', text: part.text };
|
|
142
|
+
const source: AnthropicSource =
|
|
143
|
+
part.source.type === 'data'
|
|
144
|
+
? { type: 'base64', media_type: part.mediaType, data: part.source.data }
|
|
145
|
+
: { type: 'url', url: part.source.url };
|
|
146
|
+
// \`image/png\` and the bare top-level \`image\` both mean an image; a URL
|
|
147
|
+
// source reports only the segment because the wire carries no more.
|
|
148
|
+
return part.mediaType.startsWith('image') ? { type: 'image', source } : { type: 'document', source };
|
|
149
|
+
});
|
|
150
|
+
if (blocks.length > 0) pushUser(blocks);
|
|
130
151
|
break;
|
|
131
152
|
}
|
|
132
153
|
case 'tool': {
|
|
@@ -135,14 +156,15 @@ function toAnthropicBody(messages: ChatRequestBody['messages']): {
|
|
|
135
156
|
{
|
|
136
157
|
type: 'tool_result',
|
|
137
158
|
tool_use_id: message.tool_call_id ?? '',
|
|
138
|
-
content: message.content
|
|
159
|
+
content: wireText(message.content),
|
|
139
160
|
},
|
|
140
161
|
]);
|
|
141
162
|
break;
|
|
142
163
|
}
|
|
143
164
|
case 'assistant': {
|
|
144
165
|
const content: AnthropicBlock[] = [];
|
|
145
|
-
|
|
166
|
+
const text = wireText(message.content);
|
|
167
|
+
if (text) content.push({ type: 'text', text });
|
|
146
168
|
for (const call of message.tool_calls ?? []) {
|
|
147
169
|
content.push({
|
|
148
170
|
type: 'tool_use',
|
|
@@ -407,6 +429,8 @@ async function chatHandler(request: Request): Promise<Response> {
|
|
|
407
429
|
// header is NOT `Authorization`, which is why the schema's detector matches
|
|
408
430
|
// both spellings.
|
|
409
431
|
keyExposure: 'needs-proxy',
|
|
432
|
+
// A remote HTTPS endpoint and a key. Nothing to install, nothing to start.
|
|
433
|
+
outOfBand: 'none',
|
|
410
434
|
};
|
|
411
435
|
|
|
412
436
|
export default anthropic;
|
|
@@ -167,6 +167,11 @@ export default {
|
|
|
167
167
|
// the `env.AI` binding and holds no token in code at all, but a binding is a
|
|
168
168
|
// Worker capability, not a browser one. Either way a server is required.
|
|
169
169
|
keyExposure: 'needs-proxy',
|
|
170
|
+
// Workers AI is a remote HTTPS endpoint (the REST route) or a platform binding
|
|
171
|
+
// (the worker route) — either way there is nothing on the developer's machine
|
|
172
|
+
// to install or run. `wrangler` is a devDependency of a Worker app, not an
|
|
173
|
+
// out-of-band prerequisite of this integration.
|
|
174
|
+
outOfBand: 'none',
|
|
170
175
|
};
|
|
171
176
|
|
|
172
177
|
export default cloudflare;
|
|
@@ -109,6 +109,19 @@ async function chatHandler(request: Request): Promise<Response> {
|
|
|
109
109
|
// No key appears in the route: `new ChatOpenAI(...)` reads OPENAI_API_KEY from
|
|
110
110
|
// the environment itself. Same invisible-key shape as vercel-ai-sdk.
|
|
111
111
|
keyExposure: 'needs-proxy',
|
|
112
|
+
// 'none', and this is the entry most likely to be "corrected" to something
|
|
113
|
+
// else, so the reason is here rather than assumed. The create-kai spec lists
|
|
114
|
+
// LangGraph under "Bring a server or runtime". That is WRONG for this route:
|
|
115
|
+
// the graph is built and compiled IN PROCESS above (`createReactAgent` over a
|
|
116
|
+
// `new ChatOpenAI(...)`), so there is no LangGraph server, no port and no
|
|
117
|
+
// second process — the four packages in `deps` are the whole install and
|
|
118
|
+
// `runNote` asks for a key and nothing else.
|
|
119
|
+
//
|
|
120
|
+
// LangGraph Platform / `langgraph dev` IS a server, and an integration that
|
|
121
|
+
// talked to one over HTTP would be 'local-server'. That is a different route
|
|
122
|
+
// than the one above, and if it is ever added it must be added as its own
|
|
123
|
+
// catalog entry rather than by changing this line.
|
|
124
|
+
outOfBand: 'none',
|
|
112
125
|
};
|
|
113
126
|
|
|
114
127
|
export default langgraph;
|
|
@@ -35,17 +35,43 @@ async function chatHandler(request: Request): Promise<Response> {
|
|
|
35
35
|
// is never null. A 'tool' wire message is OUR client-side tool bookkeeping;
|
|
36
36
|
// this agent owns its tools server-side (forwardsFromClient is empty), so it
|
|
37
37
|
// has no schema for that shape and the entry is dropped rather than guessed.
|
|
38
|
-
|
|
38
|
+
//
|
|
39
|
+
// Only the USER role takes content parts: a system prompt is text by
|
|
40
|
+
// definition, and an assistant turn here is replayed history, not a new
|
|
41
|
+
// upload. \`data\` uses the bare base64-or-URL shorthand, which every AI SDK
|
|
42
|
+
// major Mastra accepts understands.
|
|
43
|
+
type MastraFilePart = { type: 'file'; data: string | URL; mediaType: string; filename?: string };
|
|
44
|
+
type MastraUserContent = string | Array<{ type: 'text'; text: string } | MastraFilePart>;
|
|
45
|
+
type MastraMessage = { role: 'system'; content: string } | { role: 'user'; content: MastraUserContent } | { role: 'assistant'; content: string };
|
|
39
46
|
const mastraMessages: MastraMessage[] = [];
|
|
40
47
|
for (const m of messages) {
|
|
41
|
-
const content = m.content ?? '';
|
|
42
48
|
// Each branch constructs a literal with ONE fixed role, not m.role (which is
|
|
43
49
|
// still typed as the 4-way union): a union-VALUED field on a single object
|
|
44
50
|
// does not structurally match a union of role-discriminated objects, so
|
|
45
51
|
// widening back to m.role here would reintroduce the original TS2345.
|
|
46
|
-
if (m.role === 'system') mastraMessages.push({ role: 'system', content });
|
|
47
|
-
else if (m.role === '
|
|
48
|
-
else if (m.role === '
|
|
52
|
+
if (m.role === 'system') mastraMessages.push({ role: 'system', content: wireText(m.content) });
|
|
53
|
+
else if (m.role === 'assistant') mastraMessages.push({ role: 'assistant', content: wireText(m.content) });
|
|
54
|
+
else if (m.role === 'user') {
|
|
55
|
+
const parts = wireParts(m.content);
|
|
56
|
+
// Plain string unless the turn actually carries an attachment.
|
|
57
|
+
if (parts.every((p) => p.kind === 'text')) {
|
|
58
|
+
mastraMessages.push({ role: 'user', content: wireText(m.content) });
|
|
59
|
+
} else {
|
|
60
|
+
mastraMessages.push({
|
|
61
|
+
role: 'user',
|
|
62
|
+
content: parts.map((p) =>
|
|
63
|
+
p.kind === 'text'
|
|
64
|
+
? { type: 'text' as const, text: p.text }
|
|
65
|
+
: {
|
|
66
|
+
type: 'file' as const,
|
|
67
|
+
mediaType: p.mediaType,
|
|
68
|
+
...(p.filename === undefined ? {} : { filename: p.filename }),
|
|
69
|
+
data: p.source.type === 'data' ? p.source.data : new URL(p.source.url),
|
|
70
|
+
},
|
|
71
|
+
),
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
49
75
|
}
|
|
50
76
|
|
|
51
77
|
let agentStream: Awaited<ReturnType<ReturnType<typeof mastra.getAgent>['stream']>>;
|
|
@@ -130,6 +156,21 @@ async function chatHandler(request: Request): Promise<Response> {
|
|
|
130
156
|
// no key leaks. The conservative direction is the cheap one: a needless server
|
|
131
157
|
// hop costs a process, the other error costs the endpoint.
|
|
132
158
|
keyExposure: 'needs-proxy',
|
|
159
|
+
// THE JUDGEMENT CALL FOR THIS FIELD, exactly as keyExposure above is, and for
|
|
160
|
+
// a related reason: no automatic check can decide it. MASTRA_URL is a base URL
|
|
161
|
+
// for a server this integration does not ship and cannot start; the route's
|
|
162
|
+
// only mention of loopback is inside a `throw new Error(...)` string, which
|
|
163
|
+
// LOOPBACK_FETCH deliberately does not match (a net that fired on prose would
|
|
164
|
+
// be right here by accident and wrong elsewhere). So 'none' would parse
|
|
165
|
+
// cleanly.
|
|
166
|
+
//
|
|
167
|
+
// It is 'local-server' on what the integration actually requires: a Mastra
|
|
168
|
+
// server has to be reachable at MASTRA_URL before the first message works,
|
|
169
|
+
// `mastra dev` is how a developer gets one (port 4111), and `@mastra/client-js`
|
|
170
|
+
// is a CLIENT for it, not the server itself. Printing "start your Mastra
|
|
171
|
+
// server" costs a line; omitting it produces a scaffold that fetches a URL
|
|
172
|
+
// nothing answers.
|
|
173
|
+
outOfBand: 'local-server',
|
|
133
174
|
};
|
|
134
175
|
|
|
135
176
|
export default mastra;
|
|
@@ -64,6 +64,10 @@ const mock: Integration = {
|
|
|
64
64
|
// the one place where "declares nothing" genuinely means safe — which is
|
|
65
65
|
// precisely why it still has to SAY so rather than be left blank.
|
|
66
66
|
keyExposure: 'frontend-safe',
|
|
67
|
+
// Nothing at all, and here that is the literal truth rather than a shorthand:
|
|
68
|
+
// there is no route, no upstream and no process. This is the "No backend"
|
|
69
|
+
// group of create-kai's gateway prompt all by itself.
|
|
70
|
+
outOfBand: 'none',
|
|
67
71
|
};
|
|
68
72
|
|
|
69
73
|
export default mock;
|
|
@@ -79,6 +79,13 @@ const ollama: Integration = {
|
|
|
79
79
|
// the browser-direct path (set OLLAMA_ORIGINS to allow the page origin), which
|
|
80
80
|
// is the path this value unlocks.
|
|
81
81
|
keyExposure: 'frontend-safe',
|
|
82
|
+
// The route fetches http://localhost:11434, so Ollama must ALREADY be running:
|
|
83
|
+
// `ollama serve`, then `ollama pull <model>` for the model the route pins.
|
|
84
|
+
// Nothing in package.json installs or starts it — `deps.npm` is empty because
|
|
85
|
+
// the route is bare `fetch`, which is exactly why this cannot be inferred from
|
|
86
|
+
// the dependency list. The schema's LOOPBACK_FETCH net catches this one
|
|
87
|
+
// independently, so a later edit to 'none' fails at the catalog boundary.
|
|
88
|
+
outOfBand: 'local-server',
|
|
82
89
|
};
|
|
83
90
|
|
|
84
91
|
export default ollama;
|
|
@@ -82,6 +82,8 @@ const openai: Integration = {
|
|
|
82
82
|
// The route reads OPENAI_API_KEY and puts it in an `Authorization: Bearer`
|
|
83
83
|
// header. A static bundle carrying that is a published key.
|
|
84
84
|
keyExposure: 'needs-proxy',
|
|
85
|
+
// A remote HTTPS endpoint and a key. Nothing to install, nothing to start.
|
|
86
|
+
outOfBand: 'none',
|
|
85
87
|
};
|
|
86
88
|
|
|
87
89
|
export default openai;
|
|
@@ -69,6 +69,8 @@ const openrouter: Integration = {
|
|
|
69
69
|
deps: { npm: [], pip: [] },
|
|
70
70
|
// The route reads OPENROUTER_API_KEY into an `Authorization: Bearer` header.
|
|
71
71
|
keyExposure: 'needs-proxy',
|
|
72
|
+
// A remote HTTPS endpoint and a key. Nothing to install, nothing to start.
|
|
73
|
+
outOfBand: 'none',
|
|
72
74
|
};
|
|
73
75
|
|
|
74
76
|
export default openrouter;
|
|
@@ -23,7 +23,25 @@ app.use(express.json());
|
|
|
23
23
|
// POST /api/chat: bridge a Pi RPC session to the browser as SSE.
|
|
24
24
|
app.post('/api/chat', (req, res) => {
|
|
25
25
|
const { messages } = req.body as { messages: OpenAIWireMessage[] };
|
|
26
|
-
const
|
|
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;
|
|
40
|
+
}
|
|
41
|
+
const prompt =
|
|
42
|
+
typeof last === 'string'
|
|
43
|
+
? last
|
|
44
|
+
: (last ?? []).map((part) => (part.type === 'text' ? part.text : '')).join('');
|
|
27
45
|
|
|
28
46
|
// Headers before the first frame: without text/event-stream the browser
|
|
29
47
|
// buffers the body and readOpenAIStream never sees a frame.
|
|
@@ -91,6 +109,13 @@ app.listen(3001, () => console.log('chat api: http://localhost:3001/api/chat'));
|
|
|
91
109
|
// is exposed. "No key" is not the same fact as "safe in the browser", and
|
|
92
110
|
// collapsing the two is exactly how this flag would be got wrong.
|
|
93
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',
|
|
94
119
|
};
|
|
95
120
|
|
|
96
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}}]}
|
|
@@ -69,6 +95,14 @@ async def chat(req: ChatRequest):
|
|
|
69
95
|
// 'frontend-safe' — the flag asks where the SECRET lives, and it lives in the
|
|
70
96
|
// python process. The FastAPI service IS the server hop.
|
|
71
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',
|
|
72
106
|
};
|
|
73
107
|
|
|
74
108
|
export default pydanticAi;
|