@tangle-network/agent-app 0.45.65 → 0.46.1

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.
Files changed (39) hide show
  1. package/dist/assistant/index.js +3 -3
  2. package/dist/chat-react/index.js +2 -2
  3. package/dist/chunk-2RTYQU4W.js +441 -0
  4. package/dist/chunk-2RTYQU4W.js.map +1 -0
  5. package/dist/{chunk-23J72UUA.js → chunk-DINGA2MO.js} +94 -560
  6. package/dist/chunk-DINGA2MO.js.map +1 -0
  7. package/dist/{chunk-AWM4H5XR.js → chunk-MBKNKAN2.js} +6 -4
  8. package/dist/{chunk-AWM4H5XR.js.map → chunk-MBKNKAN2.js.map} +1 -1
  9. package/dist/chunk-YDOQE47G.js +518 -0
  10. package/dist/chunk-YDOQE47G.js.map +1 -0
  11. package/dist/stories/studio/fixtures.d.ts +0 -4
  12. package/dist/studio/generation.d.ts +16 -74
  13. package/dist/studio/index.d.ts +2 -2
  14. package/dist/studio/index.js +27 -25
  15. package/dist/studio/model-options.d.ts +61 -0
  16. package/dist/studio-react/composer-option-controls.d.ts +140 -0
  17. package/dist/studio-react/generation-notice.d.ts +18 -0
  18. package/dist/studio-react/index.d.ts +6 -12
  19. package/dist/studio-react/index.js +902 -827
  20. package/dist/studio-react/index.js.map +1 -1
  21. package/dist/studio-react/studio-composer.d.ts +34 -0
  22. package/dist/studio-react/studio-workspace.d.ts +2 -4
  23. package/dist/studio-react/studio.css +51 -0
  24. package/dist/theme/tokens.css +18 -0
  25. package/dist/web-react/index.js +11 -11
  26. package/package.json +1 -1
  27. package/dist/chunk-23J72UUA.js.map +0 -1
  28. package/dist/chunk-HSBJB46C.js +0 -305
  29. package/dist/chunk-HSBJB46C.js.map +0 -1
  30. package/dist/chunk-MLG6XKPV.js +0 -44
  31. package/dist/chunk-MLG6XKPV.js.map +0 -1
  32. package/dist/studio-react/avatar-composer.d.ts +0 -8
  33. package/dist/studio-react/composer-hero.d.ts +0 -12
  34. package/dist/studio-react/composer-shell.d.ts +0 -18
  35. package/dist/studio-react/image-composer.d.ts +0 -8
  36. package/dist/studio-react/publish-package-composer.d.ts +0 -18
  37. package/dist/studio-react/speech-composer.d.ts +0 -4
  38. package/dist/studio-react/transcription-composer.d.ts +0 -12
  39. package/dist/studio-react/video-composer.d.ts +0 -10
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/web-react/provider-logo.tsx","../src/web-react/controls.tsx"],"sourcesContent":["/**\n * Provider brand marks — real logo path data (simple-icons / SVG Logos, both\n * CC0) inlined so the picker shows actual provider identity instead of\n * colored-initial monograms. Providers without a usable mark fall back to a\n * tinted monogram chip. Aliases (z-ai/zai, moonshot/moonshotai, deepseek_ai)\n * normalize to one entry.\n */\n\nimport type { ReactNode } from 'react'\n\ninterface LogoDef {\n viewBox: string\n fill: string\n paths: string[]\n}\n\nconst LOGOS: Record<string, LogoDef> = {\n anthropic: { viewBox: '0 0 24 24', fill: '#D97757', paths: [\"M17.3041 3.541h-3.6718l6.696 16.918H24Zm-10.6082 0L0 20.459h3.7442l1.3693-3.5527h7.0052l1.3693 3.5528h3.7442L10.5363 3.5409Zm-.3712 10.2232 2.2914-5.9456 2.2914 5.9456Z\"] },\n google: { viewBox: '0 0 24 24', fill: '#8E75B2', paths: [\"M11.04 19.32Q12 21.51 12 24q0-2.49.93-4.68.96-2.19 2.58-3.81t3.81-2.55Q21.51 12 24 12q-2.49 0-4.68-.93a12.3 12.3 0 0 1-3.81-2.58 12.3 12.3 0 0 1-2.58-3.81Q12 2.49 12 0q0 2.49-.96 4.68-.93 2.19-2.55 3.81a12.3 12.3 0 0 1-3.81 2.58Q2.49 12 0 12q2.49 0 4.68.96 2.19.93 3.81 2.55t2.55 3.81\"] },\n deepseek: { viewBox: '0 0 24 24', fill: '#4D6BFE', paths: [\"M23.748 4.651c-.254-.124-.364.113-.512.233-.051.04-.094.09-.137.137-.372.397-.806.657-1.373.626-.829-.046-1.537.214-2.163.848-.133-.782-.575-1.248-1.247-1.548-.352-.155-.708-.311-.955-.65-.172-.24-.219-.509-.305-.774-.055-.16-.11-.323-.293-.35-.2-.031-.278.136-.356.276-.313.572-.434 1.202-.422 1.84.027 1.436.633 2.58 1.838 3.393.137.094.172.187.129.323-.082.28-.18.553-.266.833-.055.179-.137.218-.328.14a5.5 5.5 0 0 1-1.737-1.179c-.857-.828-1.631-1.743-2.597-2.46a12 12 0 0 0-.689-.47c-.985-.957.13-1.743.387-1.836.27-.098.094-.433-.778-.428-.872.003-1.67.295-2.687.685a3 3 0 0 1-.465.136 9.6 9.6 0 0 0-2.883-.101c-1.885.21-3.39 1.1-4.497 2.622C.082 8.776-.231 10.854.152 13.02c.403 2.284 1.568 4.175 3.36 5.653 1.857 1.533 3.997 2.284 6.438 2.14 1.482-.085 3.132-.284 4.994-1.86.47.234.962.328 1.78.398.629.058 1.235-.031 1.705-.129.735-.155.684-.836.418-.961-2.155-1.004-1.682-.595-2.112-.926 1.095-1.295 2.768-3.598 3.284-6.733.05-.346.115-.834.108-1.114-.004-.171.035-.238.23-.257a4.2 4.2 0 0 0 1.545-.475c1.397-.763 1.96-2.016 2.093-3.517.02-.23-.004-.467-.247-.588M11.58 18.168c-2.088-1.642-3.101-2.183-3.52-2.16-.39.024-.32.472-.234.763.09.288.207.487.371.74.114.167.192.416-.113.603-.673.416-1.842-.14-1.897-.168-1.361-.801-2.5-1.86-3.301-3.306-.775-1.393-1.225-2.888-1.299-4.482-.02-.385.094-.522.477-.592a4.7 4.7 0 0 1 1.53-.038c2.131.311 3.946 1.264 5.467 2.774.868.86 1.525 1.887 2.202 2.89.72 1.066 1.494 2.082 2.48 2.915.348.291.626.513.892.677-.802.09-2.14.109-3.055-.615zm1.001-6.44a.306.306 0 0 1 .415-.287.3.3 0 0 1 .113.074.3.3 0 0 1 .086.214c0 .17-.136.307-.308.307a.303.303 0 0 1-.306-.307m3.11 1.596c-.2.081-.4.151-.591.16a1.25 1.25 0 0 1-.798-.254c-.274-.23-.47-.358-.551-.758a1.7 1.7 0 0 1 .015-.588c.07-.327-.007-.537-.238-.727-.188-.156-.426-.199-.689-.199a.6.6 0 0 1-.254-.078.253.253 0 0 1-.114-.358 1 1 0 0 1 .192-.21c.356-.202.767-.136 1.146.016.352.144.618.408 1.001.782.392.451.462.576.685.915.176.264.336.536.446.848.066.194-.02.353-.25.45\"] },\n mistral: { viewBox: '0 0 24 24', fill: '#FA520F', paths: [\"M17.143 3.429v3.428h-3.429v3.429h-3.428V6.857H6.857V3.43H3.43v13.714H0v3.428h10.286v-3.428H6.857v-3.429h3.429v3.429h3.429v-3.429h3.428v3.429h-3.428v3.428H24v-3.428h-3.43V3.429z\"] },\n xai: { viewBox: '0 0 24 24', fill: '#000000', paths: [\"M14.234 10.162 22.977 0h-2.072l-7.591 8.824L7.251 0H.258l9.168 13.343L.258 24H2.33l8.016-9.318L16.749 24h6.993zm-2.837 3.299-.929-1.329L3.076 1.56h3.182l5.965 8.532.929 1.329 7.754 11.09h-3.182z\"] },\n nvidia: { viewBox: '0 0 24 24', fill: '#76B900', paths: [\"M8.948 8.798v-1.43a6.7 6.7 0 0 1 .424-.018c3.922-.124 6.493 3.374 6.493 3.374s-2.774 3.851-5.75 3.851c-.398 0-.787-.062-1.158-.185v-4.346c1.528.185 1.837.857 2.747 2.385l2.04-1.714s-1.492-1.952-4-1.952a6.016 6.016 0 0 0-.796.035m0-4.735v2.138l.424-.027c5.45-.185 9.01 4.47 9.01 4.47s-4.08 4.964-8.33 4.964c-.37 0-.733-.035-1.095-.097v1.325c.3.035.61.062.91.062 3.957 0 6.82-2.023 9.593-4.408.459.371 2.34 1.263 2.73 1.652-2.633 2.208-8.772 3.984-12.253 3.984-.335 0-.653-.018-.971-.053v1.864H24V4.063zm0 10.326v1.131c-3.657-.654-4.673-4.46-4.673-4.46s1.758-1.944 4.673-2.262v1.237H8.94c-1.528-.186-2.73 1.245-2.73 1.245s.68 2.412 2.739 3.11M2.456 10.9s2.164-3.197 6.5-3.533V6.201C4.153 6.59 0 10.653 0 10.653s2.35 6.802 8.948 7.42v-1.237c-4.84-.6-6.492-5.936-6.492-5.936z\"] },\n meta: { viewBox: '0 0 24 24', fill: '#0467DF', paths: [\"M6.915 4.03c-1.968 0-3.683 1.28-4.871 3.113C.704 9.208 0 11.883 0 14.449c0 .706.07 1.369.21 1.973a6.624 6.624 0 0 0 .265.86 5.297 5.297 0 0 0 .371.761c.696 1.159 1.818 1.927 3.593 1.927 1.497 0 2.633-.671 3.965-2.444.76-1.012 1.144-1.626 2.663-4.32l.756-1.339.186-.325c.061.1.121.196.183.3l2.152 3.595c.724 1.21 1.665 2.556 2.47 3.314 1.046.987 1.992 1.22 3.06 1.22 1.075 0 1.876-.355 2.455-.843a3.743 3.743 0 0 0 .81-.973c.542-.939.861-2.127.861-3.745 0-2.72-.681-5.357-2.084-7.45-1.282-1.912-2.957-2.93-4.716-2.93-1.047 0-2.088.467-3.053 1.308-.652.57-1.257 1.29-1.82 2.05-.69-.875-1.335-1.547-1.958-2.056-1.182-.966-2.315-1.303-3.454-1.303zm10.16 2.053c1.147 0 2.188.758 2.992 1.999 1.132 1.748 1.647 4.195 1.647 6.4 0 1.548-.368 2.9-1.839 2.9-.58 0-1.027-.23-1.664-1.004-.496-.601-1.343-1.878-2.832-4.358l-.617-1.028a44.908 44.908 0 0 0-1.255-1.98c.07-.109.141-.224.211-.327 1.12-1.667 2.118-2.602 3.358-2.602zm-10.201.553c1.265 0 2.058.791 2.675 1.446.307.327.737.871 1.234 1.579l-1.02 1.566c-.757 1.163-1.882 3.017-2.837 4.338-1.191 1.649-1.81 1.817-2.486 1.817-.524 0-1.038-.237-1.383-.794-.263-.426-.464-1.13-.464-2.046 0-2.221.63-4.535 1.66-6.088.454-.687.964-1.226 1.533-1.533a2.264 2.264 0 0 1 1.088-.285z\"] },\n moonshotai: { viewBox: '0 0 24 24', fill: '#16191E', paths: [\"m1.053 16.91 9.538 2.55a21 20.981 0 0 0 .06 2.031l5.956 1.592a12 11.99 0 0 1-15.554-6.172m-1.02-5.79 11.352 3.035a21 20.981 0 0 0-.469 2.01l10.817 2.89a12 11.99 0 0 1-1.845 2.004L.658 15.918a12 11.99 0 0 1-.625-4.796m1.593-5.146L13.573 9.17a21 20.981 0 0 0-1.01 1.874l11.297 3.02a21 20.981 0 0 1-.67 2.362l-11.55-3.087L.125 10.26a12 11.99 0 0 1 1.499-4.285ZM6.067 1.58l11.285 3.016a21 20.981 0 0 0-1.688 1.719l7.824 2.091a21 20.981 0 0 1 .513 2.664L2.107 5.218a12 11.99 0 0 1 3.96-3.638M21.68 4.866 7.222 1.003A12 11.99 0 0 1 21.68 4.866\"] },\n openai: { viewBox: '0 0 256 260', fill: '#10A37F', paths: [\"M239.184 106.203a64.72 64.72 0 0 0-5.576-53.103C219.452 28.459 191 15.784 163.213 21.74A65.586 65.586 0 0 0 52.096 45.22a64.72 64.72 0 0 0-43.23 31.36c-14.31 24.602-11.061 55.634 8.033 76.74a64.67 64.67 0 0 0 5.525 53.102c14.174 24.65 42.644 37.324 70.446 31.36a64.72 64.72 0 0 0 48.754 21.744c28.481.025 53.714-18.361 62.414-45.481a64.77 64.77 0 0 0 43.229-31.36c14.137-24.558 10.875-55.423-8.083-76.483m-97.56 136.338a48.4 48.4 0 0 1-31.105-11.255l1.535-.87l51.67-29.825a8.6 8.6 0 0 0 4.247-7.367v-72.85l21.845 12.636c.218.111.37.32.409.563v60.367c-.056 26.818-21.783 48.545-48.601 48.601M37.158 197.93a48.35 48.35 0 0 1-5.781-32.589l1.534.921l51.722 29.826a8.34 8.34 0 0 0 8.441 0l63.181-36.425v25.221a.87.87 0 0 1-.358.665l-52.335 30.184c-23.257 13.398-52.97 5.431-66.404-17.803M23.549 85.38a48.5 48.5 0 0 1 25.58-21.333v61.39a8.29 8.29 0 0 0 4.195 7.316l62.874 36.272l-21.845 12.636a.82.82 0 0 1-.767 0L41.353 151.53c-23.211-13.454-31.171-43.144-17.804-66.405zm179.466 41.695l-63.08-36.63L161.73 77.86a.82.82 0 0 1 .768 0l52.233 30.184a48.6 48.6 0 0 1-7.316 87.635v-61.391a8.54 8.54 0 0 0-4.4-7.213m21.742-32.69l-1.535-.922l-51.619-30.081a8.39 8.39 0 0 0-8.492 0L99.98 99.808V74.587a.72.72 0 0 1 .307-.665l52.233-30.133a48.652 48.652 0 0 1 72.236 50.391zM88.061 139.097l-21.845-12.585a.87.87 0 0 1-.41-.614V65.685a48.652 48.652 0 0 1 79.757-37.346l-1.535.87l-51.67 29.825a8.6 8.6 0 0 0-4.246 7.367zm11.868-25.58L128.067 97.3l28.188 16.218v32.434l-28.086 16.218l-28.188-16.218z\"] },\n}\n\nconst ALIASES: Record<string, string> = {\n moonshot: 'moonshotai',\n deepseek_ai: 'deepseek',\n 'x-ai': 'xai',\n 'meta-llama': 'meta',\n}\n\nconst MONOGRAM: Record<string, { bg: string; fg: string }> = {\n cohere: { bg: '#fae8ff', fg: '#c026d3' },\n groq: { bg: '#fce7f3', fg: '#db2777' },\n cerebras: { bg: '#ccfbf1', fg: '#0d9488' },\n zai: { bg: '#ede9fe', fg: '#7c3aed' },\n 'z-ai': { bg: '#ede9fe', fg: '#7c3aed' },\n tuner: { bg: '#dbeafe', fg: '#2563eb' },\n}\n\nexport interface ProviderLogoProps {\n provider?: string\n size?: number\n}\n\n/** Real brand mark when we have one; tinted monogram otherwise. */\nexport function ProviderLogo({ provider, size = 16 }: ProviderLogoProps): ReactNode {\n const key = ALIASES[provider ?? ''] ?? provider ?? ''\n const logo = LOGOS[key]\n if (logo) {\n return (\n <svg width={size} height={size} viewBox={logo.viewBox} role=\"img\" aria-label={key}>\n {logo.paths.map((d, i) => (\n <path key={i} d={d} fill={logo.fill} />\n ))}\n </svg>\n )\n }\n // Unknown providers fall back to a neutral monogram drawn from theme tokens\n // (secondary tile, muted glyph) — the previous hardcoded grays (#f3f4f6 /\n // #6b7280) were tuned for light mode and read as a washed-out hole on dark.\n // `style` rather than the `fill` attribute so the var() references resolve.\n const mono = MONOGRAM[key]\n return (\n <svg width={size} height={size} viewBox=\"0 0 16 16\" role=\"img\" aria-label={key || 'model'}>\n <rect width=\"16\" height=\"16\" rx=\"4\" style={{ fill: mono ? mono.bg : 'hsl(var(--secondary))' }} />\n <text x=\"8\" y=\"11.6\" textAnchor=\"middle\" style={{ fill: mono ? mono.fg : 'hsl(var(--muted-foreground))' }} fontSize=\"9\" fontWeight=\"700\" fontFamily=\"system-ui, sans-serif\">\n {(key || '?').charAt(0).toUpperCase()}\n </text>\n </svg>\n )\n}\n","/**\n * Shared chat-shell control primitives — the LEAF that both the web-react barrel\n * (`./index`) and the composer children (`./agent-session-controls`,\n * `./seat-paywall`) import directly, so neither child has to reach back through\n * the barrel (which would re-create an import cycle). The barrel re-exports the\n * public names (`usePopover`, `usePending`, `ModelPicker`, `EffortPicker`, …)\n * unchanged, so the published export surface is identical.\n *\n * Styling contract matches the rest of `web-react`: Tailwind classes against the\n * shared design tokens; the glyphs are inline SVGs, no icon-library dependency.\n */\n\nimport {\n useCallback,\n useEffect,\n useId,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n type CSSProperties,\n type ReactNode,\n type RefObject,\n} from 'react'\nimport { createPortal } from 'react-dom'\nimport { ProviderLogo } from './provider-logo'\nimport type { CatalogModel } from '../runtime/model-catalog'\n\n// ── shared glyphs (no icon-library dependency) ────────────────────────────\n\nexport function ChevronDown({ className }: { className?: string }) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n )\n}\n\nfunction SearchGlyph({ className }: { className?: string }) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <circle cx=\"11\" cy=\"11\" r=\"8\" />\n <path d=\"m21 21-4.3-4.3\" />\n </svg>\n )\n}\n\nfunction SparkleGlyph({ className }: { className?: string }) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <path d=\"M12 3v3m0 12v3M3 12h3m12 0h3M5.6 5.6l2.1 2.1m8.6 8.6 2.1 2.1m0-12.8-2.1 2.1M7.7 16.3l-2.1 2.1\" />\n </svg>\n )\n}\n\n/** lucide `brain` (v1.27) inlined — `/web-react` ships no icon-library\n * dependency, so the thinking glyph follows the same pattern as the rest of\n * this set. */\nexport function BrainGlyph({ className }: { className?: string }) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <path d=\"M12 18V5\" />\n <path d=\"M15 13a4.17 4.17 0 0 1-3-4 4.17 4.17 0 0 1-3 4\" />\n <path d=\"M17.598 6.5A3 3 0 1 0 12 5a3 3 0 1 0-5.598 1.5\" />\n <path d=\"M17.997 5.125a4 4 0 0 1 2.526 5.77\" />\n <path d=\"M18 18a4 4 0 0 0 2-7.464\" />\n <path d=\"M19.967 17.483A4 4 0 1 1 12 18a4 4 0 1 1-7.967-.517\" />\n <path d=\"M6 18a4 4 0 0 1-2-7.464\" />\n <path d=\"M6.003 5.125a4 4 0 0 0-2.526 5.77\" />\n </svg>\n )\n}\n\n/** lucide `check` — the selected-row mark in the picker menus. */\nexport function CheckGlyph({ className }: { className?: string }) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <path d=\"M20 6 9 17l-5-5\" />\n </svg>\n )\n}\n\n/**\n * Keyboard + pointer model for a trigger-and-popover pair, dependency-free.\n * Outside-mousedown and Escape both close; Escape also returns focus to the\n * trigger so keyboard users aren't dropped at the top of the document. The\n * returned `triggerProps` carry the ARIA contract (`aria-haspopup`/\n * `aria-expanded`); spread them onto the trigger button.\n *\n * `panelRef` belongs to the popover panel and MUST be wired when the panel is\n * rendered through {@link PopoverSurface}: a portaled panel is not inside\n * `containerRef`, so a container-only outside test reads every click on the\n * menu's own rows as an outside click and closes before the row's handler runs.\n */\nexport function usePopover(open: boolean, setOpen: (open: boolean) => void) {\n const containerRef = useRef<HTMLDivElement>(null)\n const triggerRef = useRef<HTMLButtonElement>(null)\n const panelRef = useRef<HTMLDivElement>(null)\n\n useEffect(() => {\n if (!open) return\n function onMouseDown(e: MouseEvent) {\n const target = e.target as Node\n if (containerRef.current?.contains(target)) return\n const panel = panelRef.current\n if (panel?.contains(target)) return\n // A popover opened FROM this one portals as a SIBLING at body level, so\n // `contains` cannot see it and a click on its rows would read as outside.\n // Surfaces stamp their ancestor chain, so descendancy survives the portal.\n const ownPath = panel?.getAttribute(POPOVER_SURFACE_ATTR)\n const hitPath =\n target instanceof Element\n ? target.closest(`[${POPOVER_SURFACE_ATTR}]`)?.getAttribute(POPOVER_SURFACE_ATTR)\n : null\n if (ownPath && hitPath && (hitPath === ownPath || hitPath.startsWith(`${ownPath}${POPOVER_PATH_SEPARATOR}`))) return\n setOpen(false)\n }\n function onKeyDown(e: KeyboardEvent) {\n if (e.key === 'Escape') {\n setOpen(false)\n triggerRef.current?.focus()\n }\n }\n document.addEventListener('mousedown', onMouseDown)\n document.addEventListener('keydown', onKeyDown)\n return () => {\n document.removeEventListener('mousedown', onMouseDown)\n document.removeEventListener('keydown', onKeyDown)\n }\n }, [open, setOpen])\n\n return {\n containerRef,\n triggerRef,\n panelRef,\n triggerProps: {\n ref: triggerRef,\n 'aria-haspopup': true as const,\n 'aria-expanded': open,\n },\n }\n}\n\n// ── PopoverSurface ────────────────────────────────────────────────────────\n\n/** Distance between the trigger and the panel it opens. */\nconst POPOVER_GAP = 8\n/** Minimum distance the panel keeps from every viewport edge. */\nconst POPOVER_VIEWPORT_MARGIN = 16\n/** Floor for the computed max-height, so a cramped side still shows rows\n * rather than collapsing to a sliver the user cannot read. */\nconst POPOVER_MIN_HEIGHT = 120\n\n/**\n * Marks the portaled panel in the DOM. Products and audits (see\n * `playground/scripts/popover-hit-test.mjs`) select on this rather than on a\n * Tailwind class, which is presentation and free to change.\n *\n * Its VALUE is the surface's ancestor path (`outer/inner`), which is what\n * restores \"is this click inside my popover\" after the portal flattens two\n * nested panels into two siblings of `<body>`.\n */\nexport const POPOVER_SURFACE_ATTR = 'data-agent-app-popover'\nconst POPOVER_PATH_SEPARATOR = '/'\n\n/**\n * `PopoverSurface` is now mounted (closed) inside every server-rendered\n * composer, so its hooks run on the server once per composer per request.\n * React 18 logs \"useLayoutEffect does nothing on the server\" for that; React 19\n * does not (measured silent on react-dom 19.2.8). The peer floor is `react >=18`,\n * so bind the synchronous hook only where there is a DOM and the effect hook\n * elsewhere — placement never runs on the server either way.\n */\nconst useBrowserLayoutEffect = typeof document !== 'undefined' ? useLayoutEffect : useEffect\n\nexport interface PopoverSurfaceProps {\n open: boolean\n /** The trigger the panel anchors to — `usePopover`'s `triggerRef`. */\n triggerRef: RefObject<HTMLElement | null>\n /** `usePopover`'s `panelRef`; also what the outside-click test consults. */\n panelRef: RefObject<HTMLDivElement | null>\n /** Presentation classes. Placement and elevation are owned here — a caller\n * must not pass `absolute`/`fixed`/`top-*`/`bottom-*`/`z-*`. */\n className?: string\n role?: string\n id?: string\n /** Make the panel at least as wide as its trigger. A portaled panel has no\n * `w-full` to inherit — the trigger is no longer its offset parent — so a\n * menu that used to stretch to a full-width trigger declares it here. */\n matchTriggerWidth?: boolean\n children: ReactNode\n}\n\n/**\n * The floating panel every canonical picker opens.\n *\n * It renders through a PORTAL to `document.body` and anchors itself to the\n * trigger in viewport coordinates, because an in-place `absolute` panel's\n * visibility is decided by markup this package does not own. Measured in\n * production: the shipped chat composer docks these controls inside a\n * horizontally scrolling rail (`overflow-x-auto`), and a scroll container\n * clips every positioned descendant whose containing block sits inside it —\n * so a correct 420x457 menu with correct coordinates painted zero pixels and\n * could not be clicked. An ancestor `transform`/`filter`/`contain` would trap\n * it the same way through the stacking context instead of the clip. Leaving\n * the DOM subtree is the only placement a host cannot re-break.\n *\n * Placement prefers ABOVE the trigger (these controls dock at the bottom of a\n * composer), flips below when there is more room there, clamps horizontally\n * into the viewport, and caps its own height to the space on the chosen side.\n * The panel is `visibility: hidden` for the measure pass so it never paints at\n * the pre-placement origin.\n */\nexport function PopoverSurface({\n open,\n triggerRef,\n panelRef,\n className,\n role,\n id,\n matchTriggerWidth,\n children,\n}: PopoverSurfaceProps) {\n const surfaceId = useId()\n const [style, setStyle] = useState<CSSProperties>(() => ({\n position: 'fixed',\n top: 0,\n left: 0,\n visibility: 'hidden',\n }))\n\n const place = useCallback(() => {\n const trigger = triggerRef.current\n const panel = panelRef.current\n if (!trigger || !panel) return\n const anchor = trigger.getBoundingClientRect()\n const viewportWidth = window.innerWidth\n const viewportHeight = window.innerHeight\n\n // `scrollHeight` is the panel's CONTENT height, so it does not feed back\n // on the `maxHeight` this function just applied — reading `offsetHeight`\n // here would measure the previous pass's clamp and ratchet the panel\n // smaller on every scroll event.\n const contentHeight = panel.scrollHeight\n const panelWidth = panel.offsetWidth\n\n const roomAbove = anchor.top - POPOVER_GAP - POPOVER_VIEWPORT_MARGIN\n const roomBelow = viewportHeight - anchor.bottom - POPOVER_GAP - POPOVER_VIEWPORT_MARGIN\n const above = contentHeight <= roomAbove || roomAbove >= roomBelow\n const maxHeight = Math.max(POPOVER_MIN_HEIGHT, above ? roomAbove : roomBelow)\n const height = Math.min(contentHeight, maxHeight)\n\n const top = above ? Math.max(POPOVER_VIEWPORT_MARGIN, anchor.top - POPOVER_GAP - height) : anchor.bottom + POPOVER_GAP\n const rightBound = Math.max(POPOVER_VIEWPORT_MARGIN, viewportWidth - panelWidth - POPOVER_VIEWPORT_MARGIN)\n const left = Math.min(Math.max(POPOVER_VIEWPORT_MARGIN, anchor.left), rightBound)\n\n setStyle({\n position: 'fixed',\n top,\n left,\n maxHeight,\n visibility: 'visible',\n ...(matchTriggerWidth ? { minWidth: anchor.width } : {}),\n })\n }, [matchTriggerWidth, panelRef, triggerRef])\n\n // Layout effect: placement is resolved before the browser paints, so the\n // panel is never seen at the origin it mounts at.\n useBrowserLayoutEffect(() => {\n if (!open) {\n setStyle({ position: 'fixed', top: 0, left: 0, visibility: 'hidden' })\n return\n }\n place()\n }, [open, place])\n\n useEffect(() => {\n if (!open) return\n const onViewportChange = () => place()\n // `capture` so the rail's OWN scroll re-anchors the panel — a scroll inside\n // an ancestor does not bubble to window.\n window.addEventListener('scroll', onViewportChange, true)\n window.addEventListener('resize', onViewportChange)\n return () => {\n window.removeEventListener('scroll', onViewportChange, true)\n window.removeEventListener('resize', onViewportChange)\n }\n }, [open, place])\n\n if (!open || typeof document === 'undefined') return null\n\n const ownerPath = triggerRef.current?.closest?.(`[${POPOVER_SURFACE_ATTR}]`)?.getAttribute(POPOVER_SURFACE_ATTR)\n const path = ownerPath ? `${ownerPath}${POPOVER_PATH_SEPARATOR}${surfaceId}` : surfaceId\n\n return createPortal(\n <div\n ref={panelRef}\n id={id}\n role={role}\n style={style}\n {...{ [POPOVER_SURFACE_ATTR]: path }}\n className={`z-[1000] ${className ?? ''}`}\n >\n {children}\n </div>,\n document.body,\n )\n}\n\n/**\n * Focus treatment for a row inside a popover panel.\n *\n * The ring itself now comes from the `:focus-visible` floor in tokens.css, so\n * this no longer restates a width or a colour. What it still has to say is\n * WHERE the ring is drawn: a popover option is a full-width row inside a panel\n * that clips its own corners (`overflow-hidden rounded-xl`), and an outward\n * ring on the first or last row is clipped away by that panel. Pulling the\n * offset negative draws the same ring just inside the row instead.\n */\nexport const POPOVER_OPTION_FOCUS = 'focus-visible:[outline-offset:-2px]'\n\n/**\n * The one overlay elevation for floating surfaces — picker menus, popovers,\n * drawers, modals. Reads the theme's `--shadow-overlay` token, so every overlay\n * lifts with the same shadow and re-themes from one source. The floating\n * composer uses the quieter `shadow-raised` rung instead.\n *\n * Written as an arbitrary value rather than the preset's `shadow-overlay`\n * utility, because that utility only exists where the preset is part of the\n * Tailwind build. A host that gets its tokens through a precompiled bundle —\n * `@tangle-network/sandbox-ui` ships brand's tokens inlined, with no `@theme`\n * block surviving the compile — receives `--shadow-overlay` as a plain custom\n * property, from which no `shadow-overlay` utility can be generated, and these\n * surfaces render flat. The arbitrary form emits from the class alone and works\n * either way.\n */\nexport const OVERLAY_SHADOW = 'shadow-[var(--shadow-overlay)]'\n\n/**\n * Root geometry for a picker — the box that holds the trigger — in one place\n * because every picker in this family has to agree on it.\n *\n * Shrink-wrapping (`inline-flex`) is the default: these controls dock on a\n * composer row where an expanding pill shoves its neighbours around. A STACKED\n * panel wants the opposite — the compact `AgentSessionControls` gear popover\n * lays its controls out in a column, and a shrink-wrapped root there makes a\n * trigger's own `w-full` a no-op, since it fills a box the trigger itself\n * sized. That is what left Agent backend short of the panel edge and Thinking\n * narrower still.\n *\n * The panel is portaled ({@link PopoverSurface}), so widening the root widens\n * the TRIGGER only. A menu that should follow it declares `matchTriggerWidth`.\n */\nexport function pickerRootClass(fullWidth: boolean): string {\n return `relative ${fullWidth ? 'flex w-full' : 'inline-flex'}`\n}\n\n/**\n * Guard an async action against double-submit. `run` ignores re-entrant calls\n * while a promise is in flight and flips `pending` so the caller can disable\n * the control — the fix for double-charge / double-approve on a slow network.\n * Settles (success or throw) before clearing, and no-ops state updates after\n * unmount.\n */\nexport function usePending(): { pending: boolean; run: (action: () => void | Promise<void>) => void } {\n const [pending, setPending] = useState(false)\n const inFlight = useRef(false)\n const mounted = useRef(true)\n useEffect(() => {\n mounted.current = true\n return () => {\n mounted.current = false\n }\n }, [])\n const run = (action: () => void | Promise<void>) => {\n if (inFlight.current) return\n let result: void | Promise<void>\n try {\n result = action()\n } catch {\n return\n }\n if (!(result instanceof Promise)) return\n inFlight.current = true\n setPending(true)\n void result.finally(() => {\n inFlight.current = false\n if (mounted.current) setPending(false)\n })\n }\n return { pending, run }\n}\n\n// ── ModelPicker ───────────────────────────────────────────────────────────\n\nexport interface ModelPickerProps {\n value: string\n onChange: (id: string) => void\n /** Catalogue models — from `GET`ing the app's catalogue route (see\n * `runtime/model-catalog`), plus any product-specific entries appended. */\n models: CatalogModel[]\n loading?: boolean\n /** Render a provider logo/badge; default is a generic sparkle. */\n renderProviderBadge?: (provider: string) => ReactNode\n /** Section label for `featured` models. */\n recommendedLabel?: string\n /** Pin a labeled section to the TOP of the list (above Recommended) for the\n * models a product wants surfaced first — e.g. a tuner app's own fine-tuned\n * models (`{ label: 'Your Fine-Tuned Models', match: (m) => m.provider === 'tuner' }`).\n * Matching models are shown only in this section, not duplicated below. */\n priorityGroup?: {\n label: string\n match: (model: CatalogModel) => boolean\n }\n}\n\nfunction formatPrice(p?: string): string | undefined {\n if (!p) return undefined\n const n = Number(p)\n if (isNaN(n) || n === 0) return undefined\n const perM = n * 1_000_000\n return perM >= 1 ? `$${perM.toFixed(0)}/M` : `$${perM.toFixed(2)}/M`\n}\n\nfunction formatContext(len?: number): string | undefined {\n if (!len) return undefined\n if (len >= 1_000_000) return `${(len / 1_000_000).toFixed(1)}M ctx`\n if (len >= 1_000) return `${Math.round(len / 1_000)}K ctx`\n return `${len} ctx`\n}\n\nfunction SectionHeader({ children }: { children: ReactNode }) {\n return (\n <div className=\"px-3 pb-1 pt-3 text-xs font-semibold uppercase tracking-wide text-muted-foreground\">\n {children}\n </div>\n )\n}\n\nfunction ModelRow({\n model,\n selected,\n onSelect,\n renderProviderBadge,\n}: {\n model: CatalogModel\n selected: boolean\n onSelect: () => void\n renderProviderBadge?: (provider: string) => ReactNode\n}) {\n const price = formatPrice(model.pricing?.prompt)\n const ctx = formatContext(model.contextLength)\n return (\n <button\n type=\"button\"\n onClick={onSelect}\n className={`flex w-full items-center gap-2.5 rounded-md px-3 py-2.5 text-left text-sm transition ${POPOVER_OPTION_FOCUS} ${\n selected ? 'bg-primary/10 font-medium' : 'hover:bg-accent'\n }`}\n >\n {renderProviderBadge ? renderProviderBadge(model.provider) : <ProviderLogo provider={model.provider} size={16} />}\n <span className=\"truncate\">{model.name}</span>\n {!model.supportsTools && (\n <span className=\"shrink-0 rounded bg-secondary px-1.5 py-0.5 text-xs font-medium text-muted-foreground\">\n no tools\n </span>\n )}\n <span className=\"ml-auto flex shrink-0 items-center gap-2 text-xs text-muted-foreground\">\n {ctx && <span>{ctx}</span>}\n {price && <span>{price}</span>}\n </span>\n </button>\n )\n}\n\n/**\n * Searchable model picker pill + popover: a featured/recommended section\n * first, then per-provider groups in catalogue order (the server already\n * sorts providers by tier).\n *\n * This is the CANONICAL ecosystem model picker (see \"UI chrome ownership\n * (picker canon)\" in AGENTS.md). sandbox-ui's `dashboard/ModelPicker` is\n * legacy — deprecated, frozen, removed at sandbox-ui's next major; new code\n * belongs here.\n */\nexport function ModelPicker({ value, onChange, models, loading, renderProviderBadge, recommendedLabel = 'Recommended', priorityGroup }: ModelPickerProps) {\n const [open, setOpen] = useState(false)\n const [query, setQuery] = useState('')\n const { containerRef, triggerRef, panelRef, triggerProps } = usePopover(open, setOpen)\n const inputRef = useRef<HTMLInputElement>(null)\n const panelId = useId()\n\n useEffect(() => {\n if (open) inputRef.current?.focus()\n }, [open])\n\n const selected = models.find((m) => m.id === value)\n\n const filtered = useMemo(() => {\n const q = query.trim().toLowerCase()\n if (!q) return null\n return models.filter(\n (m) =>\n m.id.toLowerCase().includes(q) ||\n m.name.toLowerCase().includes(q) ||\n (m.description?.toLowerCase() ?? '').includes(q) ||\n m.provider.toLowerCase().includes(q),\n )\n }, [models, query])\n\n const sections = useMemo(() => {\n const isPriority = priorityGroup ? (m: CatalogModel) => priorityGroup.match(m) : () => false\n const priority = priorityGroup ? models.filter(isPriority) : []\n const recommended = models.filter((m) => m.featured && !isPriority(m))\n const byProvider: Array<{ provider: string; items: CatalogModel[] }> = []\n for (const m of models) {\n if (m.featured || isPriority(m)) continue\n const last = byProvider[byProvider.length - 1]\n if (last && last.provider === m.provider) last.items.push(m)\n else byProvider.push({ provider: m.provider, items: [m] })\n }\n return { priority, recommended, byProvider }\n }, [models, priorityGroup])\n\n const select = (id: string) => {\n onChange(id)\n setOpen(false)\n setQuery('')\n }\n\n return (\n <div ref={containerRef} className=\"relative inline-flex\">\n <button\n type=\"button\"\n {...triggerProps}\n aria-controls={open ? panelId : undefined}\n onClick={() => setOpen(!open)}\n className=\"inline-flex shrink-0 items-center gap-1.5 whitespace-nowrap rounded-full border border-border bg-card px-3 py-1.5 text-sm font-medium text-foreground transition hover:bg-accent\"\n >\n {selected ? (renderProviderBadge ? renderProviderBadge(selected.provider) : <ProviderLogo provider={selected.provider} size={16} />) : <SparkleGlyph className=\"h-3.5 w-3.5 text-muted-foreground\" />}\n <span className=\"max-w-[160px] truncate\">{selected?.name ?? value}</span>\n <ChevronDown className=\"h-3.5 w-3.5 text-muted-foreground\" />\n </button>\n\n <PopoverSurface\n open={open}\n id={panelId}\n triggerRef={triggerRef}\n panelRef={panelRef}\n className={`flex w-[420px] max-w-[calc(100vw-2rem)] flex-col overflow-hidden rounded-xl border border-card-edge bg-popover ${OVERLAY_SHADOW}`}\n >\n <div className=\"shrink-0 border-b border-border px-3 py-2\">\n <div className=\"flex items-center gap-2 rounded-lg border border-strong bg-background px-3 py-2\">\n <SearchGlyph className=\"h-3.5 w-3.5 text-muted-foreground\" />\n <input\n ref={inputRef}\n type=\"text\"\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n placeholder=\"Search models...\"\n className=\"flex-1 bg-transparent text-sm placeholder:text-muted-foreground\"\n />\n </div>\n </div>\n {/* `min-h-0` is what lets the list absorb the surface's computed\n max-height on a short viewport instead of overflowing the panel. */}\n <div className=\"max-h-[400px] min-h-0 overflow-y-auto p-1 pb-2\">\n {loading && <div className=\"px-3 py-4 text-center text-sm text-muted-foreground\">Loading models...</div>}\n {!loading && filtered && (\n <>\n {filtered.length === 0 && (\n <div className=\"px-3 py-4 text-center text-sm text-muted-foreground\">No models match your search</div>\n )}\n {filtered.map((m) => (\n <ModelRow key={m.id} model={m} selected={m.id === value} onSelect={() => select(m.id)} renderProviderBadge={renderProviderBadge} />\n ))}\n </>\n )}\n {!loading && !filtered && models.length === 0 && (\n <div className=\"px-3 py-4 text-center text-sm text-muted-foreground\">No models available</div>\n )}\n {!loading && !filtered && models.length > 0 && (\n <>\n {priorityGroup && sections.priority.length > 0 && (\n <>\n <SectionHeader>{priorityGroup.label}</SectionHeader>\n {sections.priority.map((m) => (\n <ModelRow key={m.id} model={m} selected={m.id === value} onSelect={() => select(m.id)} renderProviderBadge={renderProviderBadge} />\n ))}\n </>\n )}\n {sections.recommended.length > 0 && (\n <>\n <SectionHeader>{recommendedLabel}</SectionHeader>\n {sections.recommended.map((m) => (\n <ModelRow key={m.id} model={m} selected={m.id === value} onSelect={() => select(m.id)} renderProviderBadge={renderProviderBadge} />\n ))}\n </>\n )}\n {sections.byProvider.map((g) => (\n <div key={g.provider}>\n <SectionHeader>{g.provider}</SectionHeader>\n {g.items.map((m) => (\n <ModelRow key={m.id} model={m} selected={m.id === value} onSelect={() => select(m.id)} renderProviderBadge={renderProviderBadge} />\n ))}\n </div>\n ))}\n </>\n )}\n </div>\n </PopoverSurface>\n </div>\n )\n}\n\n// ── EffortPicker ──────────────────────────────────────────────────────────\n\n/** One reasoning-budget level: the engine `id` is unchanged (the value the\n * product sends to the loop); only the user-facing `label` is renamed to the\n * plainer \"how hard should it think\" vocabulary from docs/product-surfaces.md.\n * `low`→Quick, `medium`→Standard, `high`→Extended. The mapping is overridable\n * via `EffortPickerProps.levels`, so a product can relabel without losing the\n * ids the runtime expects. */\nexport interface EffortLevel {\n id: string\n label: string\n}\n\n/**\n * The engine ids this package NAMES, and the word each one reads as.\n *\n * Deliberately WIDER than {@link DEFAULT_EFFORT_LEVELS}: the default list is\n * what a picker OFFERS when a product declares nothing, while this is the\n * vocabulary — the labels a named id keeps wherever it appears. The two are\n * different questions, and folding them together is what made a real engine\n * rung read as its own id: `xhigh` and `ultracode` are rungs claude-code\n * applies above `high`, so a product declaring them got \"Xhigh\" while every\n * neighbouring rung had a plain English word. Naming them here is what stops\n * each product inventing its own — the drift `effortLevelsFromIds` exists to\n * prevent. They stay OUT of the offered default because most backends do not\n * apply them, and offering a rung the backend ignores is the defect\n * `effortLevels` was added to close.\n */\nconst KNOWN_EFFORT_LABELS = {\n off: 'Off',\n low: 'Quick',\n medium: 'Standard',\n high: 'Extended',\n xhigh: 'Extra',\n ultracode: 'Ultra',\n} as const\n\nexport const DEFAULT_EFFORT_LEVELS: readonly EffortLevel[] = [\n { id: 'off', label: KNOWN_EFFORT_LABELS.off },\n { id: 'low', label: KNOWN_EFFORT_LABELS.low },\n { id: 'medium', label: KNOWN_EFFORT_LABELS.medium },\n { id: 'high', label: KNOWN_EFFORT_LABELS.high },\n]\n\n/**\n * The user-facing label for an engine level id: the canonical vocabulary when\n * the id is one this package names, otherwise the id itself made readable\n * (`auto` -> \"Auto\", `ultra-code` -> \"Ultra code\"). Never invents a depth word,\n * so an id nobody declared a label for still reads as ITSELF and never as some\n * other level.\n */\nexport function effortLevelLabel(id: string): string {\n const known = (KNOWN_EFFORT_LABELS as Record<string, string | undefined>)[id]\n if (known) return known\n const words = id.replace(/[-_]+/g, ' ').trim()\n return words ? words.charAt(0).toUpperCase() + words.slice(1) : id\n}\n\n/**\n * Build a levels list from the engine ids a backend applies — the shape the\n * removed `ComposerAgentControls` took as `reasoning.available`, so a product\n * migrating that list has one call to make instead of a hand-written label map\n * per product (which is how \"Quick\" and \"Low\" drift apart across surfaces).\n */\nexport function effortLevelsFromIds(ids: readonly string[]): readonly EffortLevel[] {\n return ids.map((id) => ({ id, label: effortLevelLabel(id) }))\n}\n\n/**\n * The list {@link EffortPicker} RENDERS for `value` — the declared levels, plus\n * `value` itself when the declaration omits it.\n *\n * A picker cannot honestly resolve a selected value it was not given, and the\n * failure it used to take instead — fall back to the middle entry — renders the\n * session's real depth as a DIFFERENT level's name. That is exactly the defect\n * `levels` was added to prevent: the legacy adapter's `available` list excluded\n * the `auto` sentinel because the old picker injected it, so a product mapping\n * that list straight across ships a session running on `auto` labelled\n * \"Extended\". Admitting the value is not offering a new choice — it is\n * reporting the state the session is already in, and it disappears from the\n * list as soon as the user picks a declared level.\n *\n * A blank value is not admitted (there is no honest label for it); the picker\n * renders no selection instead.\n */\nexport function reconcileEffortLevels(\n value: string,\n levels: readonly EffortLevel[] = DEFAULT_EFFORT_LEVELS,\n): readonly EffortLevel[] {\n if (!value || levels.some((l) => l.id === value)) return levels\n return [{ id: value, label: effortLevelLabel(value) }, ...levels]\n}\n\n// ── effort strength meter ─────────────────────────────────────────────────\n\n/** Segments the meter draws — fixed geometry so the ladder stays tabular\n * across levels (and across the trigger and its menu rows). */\nexport const EFFORT_METER_SEGMENTS = 4\n\n/** Filled-segment opacity ladder: translucent on the left, heavy on the\n * right — the ramp carries strength even at a glance, the count carries it\n * exactly. Unfilled segments sit at a fixed ghost opacity. */\nconst EFFORT_METER_FILL_OPACITY = [0.25, 0.5, 0.75, 1] as const\nconst EFFORT_METER_GHOST_OPACITY = 0.15\n\n/** Level ids that mean \"no reasoning\" — the meter renders all-ghost. */\nconst OFF_LEVEL_IDS: ReadonlySet<string> = new Set(['off', 'none'])\n\n/**\n * Ids that name a POLICY rather than a depth: `auto` means \"let the harness and\n * model decide\", so it has no position on a strength ladder.\n *\n * Without this it lands wherever the declaration puts it and draws a filled\n * meter — measured on a product that offers `auto` first: every harness read\n * `Auto` at a FULL four bars, including `cli-base`, which has no agent to think\n * at all. A meter is a claim about how hard the run will think, and a sentinel\n * cannot make that claim.\n */\nconst UNPLACEABLE_LEVEL_IDS: ReadonlySet<string> = new Set(['auto'])\n\n/**\n * Filled-segment count for a level: 0 for off/none (or an id the levels list\n * does not carry); otherwise the level's position among the non-off choices\n * scaled onto the meter, so the ladder reads low < medium < high and the top\n * level fills the whole scale. The canonical four levels land 0 / 1 / 2 / 4.\n */\nexport function effortMeterFill(\n levelId: string,\n levels: readonly EffortLevel[] = DEFAULT_EFFORT_LEVELS,\n): number {\n if (OFF_LEVEL_IDS.has(levelId) || UNPLACEABLE_LEVEL_IDS.has(levelId)) return 0\n const active = levels.filter((l) => !OFF_LEVEL_IDS.has(l.id) && !UNPLACEABLE_LEVEL_IDS.has(l.id))\n const index = active.findIndex((l) => l.id === levelId)\n if (index < 0 || active.length === 0) return 0\n return Math.max(1, Math.floor(((index + 1) * EFFORT_METER_SEGMENTS) / active.length))\n}\n\n/**\n * The thinking-strength meter: four 12px bars, filled count = level, filled\n * opacity ramping 25→100% left to right (unfilled at a faint ghost). Purely\n * decorative — the level name is always rendered as text beside it, so the\n * meter is `aria-hidden` and adds no second accessible name.\n */\nexport function EffortMeter({ fill, className }: { fill: number; className?: string }) {\n return (\n <span aria-hidden className={`inline-flex items-center gap-[2px] ${className ?? ''}`}>\n {Array.from({ length: EFFORT_METER_SEGMENTS }, (_, i) => (\n <span\n key={i}\n className=\"h-3 w-[3px] rounded-full bg-current\"\n style={{ opacity: i < fill ? EFFORT_METER_FILL_OPACITY[i] : EFFORT_METER_GHOST_OPACITY }}\n />\n ))}\n </span>\n )\n}\n\nexport interface EffortPickerProps {\n value: string\n onChange: (id: string) => void\n /** Selectable levels (engine id + user-facing label). Defaults to the plain\n * \"Thinking\" vocabulary; override to relabel without changing the ids the\n * runtime receives.\n *\n * A list that omits the current `value` is not a rendering error the picker\n * papers over: `value` is reconciled INTO the rendered list under its own\n * name (see {@link reconcileEffortLevels}), because a control must report\n * the depth the session is running at and never some other list entry. */\n levels?: readonly EffortLevel[]\n /** Prefix shown before the active level on the pill — the \"what is this\"\n * context the bare value lacked. Default \"Thinking\". Pass '' to hide it. */\n label?: string\n /** Fill the container instead of shrink-wrapping — opt-in, default `false`;\n * see {@link pickerRootClass} for when and why. */\n fullWidth?: boolean\n}\n\n/** Thinking-budget selector pill, styled to match {@link ModelPicker}. Show\n * it only when the selected model `supportsReasoning`. \"Thinking\" is the\n * plain-English name for what was internally called \"effort\".\n *\n * The CANONICAL ecosystem effort picker — sandbox-ui's reasoning menu (inside\n * its `chat/AgentSessionControls`) is legacy and frozen. */\nexport function EffortPicker({ value, onChange, levels = DEFAULT_EFFORT_LEVELS, label = 'Thinking', fullWidth = false }: EffortPickerProps) {\n const [open, setOpen] = useState(false)\n const { containerRef, triggerRef, panelRef, triggerProps } = usePopover(open, setOpen)\n const panelId = useId()\n const rendered = reconcileEffortLevels(value, levels)\n // The strength ladder is computed over the DECLARED levels only, so admitting\n // the selected value cannot shift where the declared ones sit on the meter.\n // A level the declaration does not carry has no position on that ladder, so\n // it renders with NO meter — an all-ghost meter is what `off` looks like, and\n // \"we cannot place this\" is not \"no thinking\".\n const isDeclared = (id: string) => levels.some((l) => l.id === id) && !UNPLACEABLE_LEVEL_IDS.has(id)\n const selected = rendered.find((l) => l.id === value)\n\n return (\n <div ref={containerRef} className={pickerRootClass(fullWidth)}>\n <button\n type=\"button\"\n {...triggerProps}\n aria-controls={open ? panelId : undefined}\n onClick={() => setOpen(!open)}\n title={label ? `${label} — how hard the agent reasons before answering` : 'Reasoning effort'}\n className={`inline-flex min-h-[36px] shrink-0 items-center gap-1.5 whitespace-nowrap rounded-full border border-border bg-card px-3 py-1.5 text-sm font-medium text-foreground transition hover:bg-accent ${fullWidth ? 'w-full' : ''}`}\n >\n <BrainGlyph className=\"h-3.5 w-3.5 shrink-0 text-muted-foreground\" />\n {/* Full width gives the label the slack, so the meter and chevron park\n on the trailing edge and the glyph stays against the text. */}\n <span className={fullWidth ? 'flex-1 truncate text-left' : undefined}>\n {label ? <span className=\"text-muted-foreground\">{label}: </span> : null}\n {selected ? selected.label : '—'}\n </span>\n {selected && isDeclared(selected.id) && (\n <EffortMeter fill={effortMeterFill(selected.id, levels)} className=\"shrink-0 text-foreground\" />\n )}\n <ChevronDown className=\"h-3.5 w-3.5 shrink-0 text-muted-foreground\" />\n </button>\n <PopoverSurface\n open={open}\n id={panelId}\n role=\"menu\"\n triggerRef={triggerRef}\n panelRef={panelRef}\n // A portaled panel has no `w-full` to inherit, so a full-width trigger\n // hands its measured width to the panel instead.\n matchTriggerWidth={fullWidth}\n className={`w-44 overflow-y-auto rounded-xl border border-card-edge bg-popover p-1 ${OVERLAY_SHADOW}`}\n >\n {rendered.map((l) => (\n <button\n key={l.id}\n type=\"button\"\n role=\"menuitemradio\"\n aria-checked={l.id === value}\n onClick={() => {\n onChange(l.id)\n setOpen(false)\n }}\n className={`flex min-h-[40px] w-full items-center gap-2 rounded-md px-3 py-2 text-left text-sm transition ${POPOVER_OPTION_FOCUS} ${\n l.id === value ? 'bg-primary/10 font-medium' : 'hover:bg-accent'\n }`}\n >\n <BrainGlyph className=\"h-3.5 w-3.5 shrink-0 text-muted-foreground\" />\n <span className=\"truncate\">{l.label}</span>\n {isDeclared(l.id) && (\n <EffortMeter fill={effortMeterFill(l.id, levels)} className=\"ml-auto text-foreground\" />\n )}\n {l.id === value && (\n <CheckGlyph className={`${isDeclared(l.id) ? '' : 'ml-auto '}h-3.5 w-3.5 shrink-0 text-primary`} />\n )}\n </button>\n ))}\n </PopoverSurface>\n </div>\n )\n}\n"],"mappings":"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","names":["jsx","jsxs"]}
@@ -1,4 +1,3 @@
1
- import type { IntegrationConnection } from '@tangle-network/sandbox-ui/integrations';
2
1
  import type { Generation } from '../../studio';
3
2
  export declare const teaserA: Generation;
4
3
  export declare const storyboardGeneration: Generation;
@@ -10,7 +9,6 @@ export declare const queuedGeneration: Generation;
10
9
  export declare const runningGeneration: Generation;
11
10
  export declare const failedGeneration: Generation;
12
11
  export declare const storageFailedGeneration: Generation;
13
- export declare const publishPackageGeneration: Generation;
14
12
  /** The four-image batch a single "Images: 4" run produces. */
15
13
  export declare const teaserBatch: Generation[];
16
14
  /** Populated library: the newest batch first, every type and status represented. */
@@ -18,5 +16,3 @@ export declare const libraryGenerations: Generation[];
18
16
  export declare const libraryTotalCost = 1.87;
19
17
  /** Mirrors StudioWorkspace's default vaultHref (see generation-detail.test.tsx). */
20
18
  export declare const demoVaultHref: (filePath?: string | null) => string;
21
- /** Connected destinations for the publish-package composer (instagram + X). */
22
- export declare const demoConnections: IntegrationConnection[];
@@ -1,9 +1,4 @@
1
- /** Define the structure for a studio integration connection with status and provider identifiers */
2
- export interface StudioIntegrationConnection {
3
- status: string;
4
- providerId: string;
5
- connectorId?: string;
6
- }
1
+ import type { ModelOptionsMetadata } from './model-options';
7
2
  /** Define generation categories for media including image, video, speech, avatar, and transcription */
8
3
  export type GenerationType = 'image' | 'video' | 'speech' | 'avatar' | 'transcription';
9
4
  /** Define possible states representing the progress of a generation process */
@@ -21,7 +16,7 @@ export interface Generation {
21
16
  createdAt: Date | null;
22
17
  metadata: Record<string, unknown> | null;
23
18
  }
24
- /** Describe media model option properties including id, name, type, status, and optional provider and reason */
19
+ /** Describe a catalog media model and its optional wire-level option metadata. */
25
20
  export interface MediaModelOption {
26
21
  id: string;
27
22
  name: string;
@@ -29,6 +24,7 @@ export interface MediaModelOption {
29
24
  type: GenerationType;
30
25
  status: MediaModelStatus;
31
26
  reason?: string;
27
+ options?: ModelOptionsMetadata;
32
28
  }
33
29
  /** Represent media model catalog with default values, model options, and optional error message */
34
30
  export interface MediaModelCatalogResponse {
@@ -36,74 +32,20 @@ export interface MediaModelCatalogResponse {
36
32
  models: Record<GenerationType, MediaModelOption[]>;
37
33
  error?: string;
38
34
  }
39
- /** Define the structure for configuring package publishing details and evaluation criteria */
40
- export interface PublishPackage {
41
- caption: string;
42
- description: string;
43
- mentions: string[];
44
- destinations: string[];
45
- cadence: string;
46
- workflowDraft: boolean;
47
- evalContract: {
48
- artifactType: string;
49
- deterministicChecks: string[];
50
- };
51
- }
52
- /** Define a destination for publishing content with identifiers, label, provider IDs, and fields */
53
- export interface PublishDestination {
54
- id: string;
55
- label: string;
56
- providerIds: string[];
57
- fields: string;
58
- }
59
35
  /** Provide an array of supported generation types for media and content processing */
60
36
  export declare const GENERATION_TYPES: readonly GenerationType[];
61
37
  /** Resolve whether a string value matches a valid GenerationType */
62
38
  export declare function isGenerationType(value: string): value is GenerationType;
63
- /** List available social media platforms with their publishing fields and provider identifiers */
64
- export declare const DESTINATIONS: PublishDestination[];
65
- /** Provide an array of predefined cadence options for scheduling or approval processes */
66
- export declare const CADENCES: string[];
67
- /** Provide the valid image quality values for generation requests */
68
- export declare const IMAGE_QUALITIES: readonly ["low", "medium", "high", "auto"];
69
- /** Provide a placeholder hint listing the accepted image size values */
70
- export declare const IMAGE_SIZE_HINT = "1024x1024, 1536x1024, 1024x1536, or auto";
71
- /** Provide a validation pattern for the free-form image size field (WxH or auto) */
72
- export declare const IMAGE_SIZE_PATTERN: string;
73
- /** Provide the valid video duration values (seconds) for generation requests */
74
- export declare const VIDEO_DURATIONS: readonly ["4", "6", "8", "10", "12"];
75
- /** Provide the valid video resolution values for generation requests */
76
- export declare const VIDEO_RESOLUTIONS: readonly ["720p", "1080p"];
77
- /** Provide the valid video aspect ratio values for generation requests */
78
- export declare const VIDEO_ASPECT_RATIOS: readonly ["16:9", "9:16", "1:1"];
79
39
  /** Define the minimum number of images required for processing or validation */
80
40
  export declare const MIN_IMAGE_COUNT = 1;
81
41
  /** Define the maximum number of images allowed for upload or display */
82
- export declare const MAX_IMAGE_COUNT = 4;
42
+ export declare const MAX_IMAGE_COUNT = 8;
83
43
  /** Resolve a human-readable relative time string from a given date or return an empty string if null */
84
44
  export declare function relativeTime(date: Date | null): string;
85
45
  /** Resolve the output directory path based on the specified generation type */
86
46
  export declare function outputPathFor(type: GenerationType): string;
87
47
  /** Resolve the vault path string from a Generation object or return null if unavailable */
88
48
  export declare function generationVaultPath(generation: Generation): string | null;
89
- /** Build a PublishPackage object from caption, description, mentions, cadence, and destinations inputs */
90
- export declare function buildPublishPackage({ caption, postDescription, mentions, cadence, destinations, }: {
91
- caption: string;
92
- postDescription: string;
93
- mentions: string;
94
- cadence: string;
95
- destinations: string[];
96
- }): PublishPackage | null;
97
- /** Determine if a value conforms to the PublishPackage structure with optional metadata fields */
98
- export declare function isPublishPackage(value: unknown): value is {
99
- caption?: string;
100
- description?: string;
101
- mentions?: string[];
102
- destinations?: string[];
103
- cadence?: string;
104
- };
105
- /** Determine if a destination has any active connections in the given list of studio integration connections */
106
- export declare function isDestinationConnected(destination: PublishDestination, connections: StudioIntegrationConnection[]): boolean;
107
49
  /** Resolve selected models by applying defaults for missing or unavailable entries in the catalog */
108
50
  export declare function selectedModelsWithDefaults(current: Partial<Record<GenerationType, string>>, catalog: MediaModelCatalogResponse): Partial<Record<GenerationType, string>>;
109
51
  /** Resolve the preferred model ID for a given generation type from the media model catalog */
@@ -117,29 +59,29 @@ export interface GenerationRequestFields {
117
59
  type: GenerationType;
118
60
  model: string;
119
61
  prompt: string;
120
- negativePrompt: string;
121
- outputPath: string;
122
- publishPackage: PublishPackage | null;
123
62
  image: {
124
- size: string;
125
- quality: string;
63
+ size?: string;
64
+ quality?: string;
126
65
  count: number;
127
66
  };
128
67
  video: {
129
- duration: string;
130
- resolution: string;
131
- aspectRatio: string;
132
- referenceImageUrl: string;
68
+ duration?: string | number;
69
+ resolution?: string;
70
+ aspectRatio?: string;
71
+ referenceImageUrl?: string;
72
+ audio?: boolean;
73
+ mode?: string;
133
74
  };
134
75
  speech: {
135
- voice: string;
76
+ voice?: string;
77
+ speed?: number;
136
78
  };
137
- avatar: {
79
+ avatar?: {
138
80
  audioUrl: string;
139
81
  imageUrl: string;
140
82
  avatarId: string;
141
83
  };
142
- transcription: {
84
+ transcription?: {
143
85
  audioUrl: string;
144
86
  language: string;
145
87
  responseFormat: string;
@@ -2,11 +2,11 @@
2
2
  * Studio module: the generation model and the pure helpers a media-generation
3
3
  * surface needs — the `Generation` row shape and its lifecycle status/error
4
4
  * derivation, the optimistic + live-merge logic that backs polling, the media
5
- * model catalog types + selection helpers, the request-body builder, and the
6
- * publish-package model.
5
+ * model catalog types + selection helpers, and the request-body builder.
7
6
  *
8
7
  * Logic only — no React, no DOM, no design-system import. Safe in server
9
8
  * bundles. The React surface (composer, result canvas, library drawer, the
10
9
  * `StudioWorkspace` shell) lives in `@tangle-network/agent-app/studio-react`.
11
10
  */
12
11
  export * from './generation';
12
+ export * from './model-options';
@@ -1,71 +1,73 @@
1
1
  import {
2
- CADENCES,
3
- DESTINATIONS,
2
+ FALLBACK_VIDEO_MODEL_OPTIONS,
4
3
  GENERATION_TYPES,
5
- IMAGE_QUALITIES,
6
- IMAGE_SIZE_HINT,
7
- IMAGE_SIZE_PATTERN,
4
+ GPT_IMAGE_2_CUSTOM_SIZE,
5
+ IMAGE_TO_VIDEO_SIBLINGS,
8
6
  MAX_IMAGE_COUNT,
9
7
  MIN_IMAGE_COUNT,
10
- VIDEO_ASPECT_RATIOS,
11
- VIDEO_DURATIONS,
12
- VIDEO_RESOLUTIONS,
13
8
  buildGenerationRequestBody,
14
- buildPublishPackage,
9
+ curateComposerModels,
15
10
  failedOptimisticGeneration,
16
11
  generationError,
17
12
  generationMergeKey,
18
13
  generationStatus,
19
14
  generationVaultPath,
20
- isDestinationConnected,
15
+ imageToVideoSibling,
21
16
  isGenerationType,
22
17
  isLocalGeneration,
23
- isPublishPackage,
24
18
  latestBatchOf,
25
19
  mergeLiveGeneration,
26
20
  mergeLoaderAndLive,
27
21
  modelMessage,
28
22
  normalizeImageCount,
29
23
  optimisticGeneration,
24
+ optionChoices,
25
+ optionDefault,
30
26
  outputPathFor,
31
27
  preferredModelId,
28
+ reconcileOptionValues,
32
29
  relativeTime,
30
+ resolveComposerOptions,
33
31
  selectedModelsWithDefaults,
34
- userSafeGenerationMessage
35
- } from "../chunk-HSBJB46C.js";
32
+ supportsCustomImageSize,
33
+ textToVideoSibling,
34
+ userSafeGenerationMessage,
35
+ validateCustomImageSize
36
+ } from "../chunk-2RTYQU4W.js";
36
37
  export {
37
- CADENCES,
38
- DESTINATIONS,
38
+ FALLBACK_VIDEO_MODEL_OPTIONS,
39
39
  GENERATION_TYPES,
40
- IMAGE_QUALITIES,
41
- IMAGE_SIZE_HINT,
42
- IMAGE_SIZE_PATTERN,
40
+ GPT_IMAGE_2_CUSTOM_SIZE,
41
+ IMAGE_TO_VIDEO_SIBLINGS,
43
42
  MAX_IMAGE_COUNT,
44
43
  MIN_IMAGE_COUNT,
45
- VIDEO_ASPECT_RATIOS,
46
- VIDEO_DURATIONS,
47
- VIDEO_RESOLUTIONS,
48
44
  buildGenerationRequestBody,
49
- buildPublishPackage,
45
+ curateComposerModels,
50
46
  failedOptimisticGeneration,
51
47
  generationError,
52
48
  generationMergeKey,
53
49
  generationStatus,
54
50
  generationVaultPath,
55
- isDestinationConnected,
51
+ imageToVideoSibling,
56
52
  isGenerationType,
57
53
  isLocalGeneration,
58
- isPublishPackage,
59
54
  latestBatchOf,
60
55
  mergeLiveGeneration,
61
56
  mergeLoaderAndLive,
62
57
  modelMessage,
63
58
  normalizeImageCount,
64
59
  optimisticGeneration,
60
+ optionChoices,
61
+ optionDefault,
65
62
  outputPathFor,
66
63
  preferredModelId,
64
+ reconcileOptionValues,
67
65
  relativeTime,
66
+ resolveComposerOptions,
68
67
  selectedModelsWithDefaults,
69
- userSafeGenerationMessage
68
+ supportsCustomImageSize,
69
+ textToVideoSibling,
70
+ userSafeGenerationMessage,
71
+ validateCustomImageSize
70
72
  };
71
73
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,61 @@
1
+ import type { GenerationType, MediaModelOption } from './generation';
2
+ /** A wire-typed value accepted by a model option. */
3
+ export type ModelOptionValue = string | number | boolean;
4
+ /** Per-parameter option metadata, structurally identical to tangle-router's
5
+ * `ModelOptionMetadata` (lib/model-options.ts, shipped in router PR #429).
6
+ * `supported: false` means the model lacks or ignores the parameter.
7
+ * `values` is the exact wire-typed enum; `min` and `max` are inclusive.
8
+ * `default` applies when the caller omits the parameter. An absent entry or
9
+ * options object means unknown, so consumers must not invent a value. */
10
+ export interface ModelOptionMetadata {
11
+ supported?: boolean;
12
+ values?: readonly ModelOptionValue[];
13
+ min?: number;
14
+ max?: number;
15
+ default?: ModelOptionValue;
16
+ }
17
+ /** Per-parameter model option metadata keyed by the provider's wire field. */
18
+ export type ModelOptionsMetadata = Readonly<Record<string, ModelOptionMetadata>>;
19
+ /** Fallback video options matching tangle-router's wire-exact metadata. */
20
+ export declare const FALLBACK_VIDEO_MODEL_OPTIONS: Readonly<Record<string, ModelOptionsMetadata>>;
21
+ /** UI constraints for a custom gpt-image-2 size. */
22
+ export declare const GPT_IMAGE_2_CUSTOM_SIZE: {
23
+ readonly multipleOf: 16;
24
+ readonly maxLongEdge: 3840;
25
+ readonly maxRatio: 3;
26
+ };
27
+ /** Validate a custom gpt-image-2 size against its published UI constraints. */
28
+ export declare function validateCustomImageSize(width: number, height: number): {
29
+ ok: true;
30
+ } | {
31
+ ok: false;
32
+ reason: string;
33
+ };
34
+ /** Resolve live catalog options first, then exact or safe single-prefix fallbacks. */
35
+ export declare function resolveComposerOptions(input: {
36
+ type: 'image' | 'video' | 'speech';
37
+ modelId: string;
38
+ provider?: string;
39
+ catalogOptions?: ModelOptionsMetadata;
40
+ }): ModelOptionsMetadata | undefined;
41
+ /** Return whether a model supports the gpt-image-2 custom-size rule. */
42
+ export declare function supportsCustomImageSize(modelId: string): boolean;
43
+ /** Map verified text-to-video model ids to their image-to-video siblings. */
44
+ export declare const IMAGE_TO_VIDEO_SIBLINGS: Readonly<Record<string, string>>;
45
+ /** Resolve a verified image-to-video sibling for a text-to-video model. */
46
+ export declare function imageToVideoSibling(modelId: string): string | undefined;
47
+ /** Resolve the verified text-to-video sibling for an image-to-video model. */
48
+ export declare function textToVideoSibling(modelId: string): string | undefined;
49
+ /** Curate catalog models for the issue #449 composer lanes. */
50
+ export declare function curateComposerModels(type: GenerationType, models: MediaModelOption[]): MediaModelOption[];
51
+ /** Resolve an option default from its default, values, or lower bound. */
52
+ export declare function optionDefault(meta: ModelOptionMetadata): ModelOptionValue | undefined;
53
+ /** Return exact enum choices or an inclusive integer range. */
54
+ export declare function optionChoices(meta: ModelOptionMetadata): readonly ModelOptionValue[];
55
+ /** Reconcile selections against supported options and their wire-typed defaults.
56
+ * `allowCustomSize` keeps a legal off-enum `WxH` size selection (gpt-image-2's
57
+ * custom-size rule) — the caller decides via {@link supportsCustomImageSize},
58
+ * so the check holds even when the options came from the live catalog. */
59
+ export declare function reconcileOptionValues(options: ModelOptionsMetadata | undefined, current: Readonly<Record<string, ModelOptionValue>>, opts?: {
60
+ allowCustomSize?: boolean;
61
+ }): Record<string, ModelOptionValue>;
@@ -0,0 +1,140 @@
1
+ /**
2
+ * The studio composer's control row: the media-type segmented group, the
3
+ * horizontally scrolling option band, and the pills that dock in it.
4
+ *
5
+ * Every pill that opens a panel opens it through `usePopover` + `PopoverSurface`
6
+ * (`../web-react/controls`), never an in-place `absolute` panel: the band IS a
7
+ * scroll container (`overflow-x-auto`), and a scroll container clips every
8
+ * positioned descendant whose containing block sits inside it — the measured
9
+ * failure that left the chat composer's own menus painting zero pixels. The
10
+ * surface portals to `document.body` and re-anchors on capture-phase scroll, so
11
+ * scrolling the band moves an open menu with its pill. The one rule the surface
12
+ * cannot supply is when to give up: a menu whose pill has been scrolled out of
13
+ * the band no longer points at anything, so it closes when the pill's
14
+ * horizontal CENTRE leaves the band's box.
15
+ */
16
+ import { type ReactNode, type RefObject } from 'react';
17
+ import { type LucideIcon } from 'lucide-react';
18
+ import { type MediaModelOption, type ModelOptionValue } from '../studio';
19
+ /** One media lane's segment: the icon it shows collapsed, the word it shows
20
+ * when active. */
21
+ export interface MediaTypeSegment<T extends string> {
22
+ type: T;
23
+ label: string;
24
+ icon: LucideIcon;
25
+ }
26
+ /**
27
+ * The pinned-left media-type group: a track holding one filled pill (the active
28
+ * lane, icon + word) and icon-only siblings. `aria-pressed` carries the state
29
+ * and `aria-label` carries the word the collapsed items drop.
30
+ */
31
+ export declare function MediaTypeSegments<T extends string>({ value, segments, onChange, }: {
32
+ value: T;
33
+ segments: readonly MediaTypeSegment<T>[];
34
+ onChange: (type: T) => void;
35
+ }): import("react").JSX.Element;
36
+ /**
37
+ * The middle scroller. It hides its scrollbar and fades whichever edge has
38
+ * content behind it, at the DEPTH of what is actually hidden (capped at
39
+ * {@link MAX_FADE}) rather than a fixed gradient that claims more is off-screen
40
+ * than there is. A media-type switch resets the scroll to the start, because
41
+ * the control set itself changed; every other re-render leaves `scrollLeft`
42
+ * alone, so picking a value cannot jump the band back to the beginning.
43
+ */
44
+ export declare function ComposerBand({ bandRef, resetKey, children, }: {
45
+ bandRef: RefObject<HTMLDivElement | null>;
46
+ /** Changing this scrolls the band back to the start. */
47
+ resetKey: string;
48
+ children: ReactNode;
49
+ }): import("react").JSX.Element;
50
+ /** One renderable value: the wire value, untouched, and how it reads. */
51
+ export interface OptionChoice {
52
+ value: ModelOptionValue;
53
+ label: string;
54
+ }
55
+ /**
56
+ * A value pill and the menu it opens.
57
+ *
58
+ * The pill shows the VALUE and carries the parameter name in `title` and in its
59
+ * accessible name — a band of eight labelled pills does not fit a chat card,
60
+ * and "5s" alone tells a screen-reader user nothing about which parameter it
61
+ * belongs to.
62
+ */
63
+ export declare function OptionPill({ label, value, choices, onSelect, bandRef, custom, }: {
64
+ label: string;
65
+ value: ModelOptionValue | undefined;
66
+ choices: readonly OptionChoice[];
67
+ onSelect: (value: ModelOptionValue) => void;
68
+ bandRef: RefObject<HTMLDivElement | null>;
69
+ /** A trailing row that swaps the menu to a form (the gpt-image-2 custom
70
+ * size). Absent for every parameter whose values are an enum. */
71
+ custom?: {
72
+ label: string;
73
+ render: (args: {
74
+ close: () => void;
75
+ }) => ReactNode;
76
+ };
77
+ }): import("react").JSX.Element;
78
+ /**
79
+ * The custom-size form the Size menu swaps to.
80
+ *
81
+ * The reason a size is refused renders next to the fields that are wrong — a
82
+ * toast would be gone by the time the user looks back at the inputs — and Apply
83
+ * refuses rather than disables, so the reason is always reachable.
84
+ */
85
+ export declare function CustomSizeForm({ initial, onApply, onCancel, }: {
86
+ initial?: string;
87
+ onApply: (size: string) => void;
88
+ onCancel: () => void;
89
+ }): import("react").JSX.Element;
90
+ /**
91
+ * The audio switch. It is a toggle rather than a menu because the parameter has
92
+ * exactly two states, and a two-row menu to say "on" is a menu that costs a
93
+ * click for nothing.
94
+ */
95
+ export declare function AudioTogglePill({ on, onToggle }: {
96
+ on: boolean;
97
+ onToggle: (on: boolean) => void;
98
+ }): import("react").JSX.Element;
99
+ /**
100
+ * The reference-image pill: the only control that changes which MODEL runs, so
101
+ * the swap to the image-to-video sibling is the caller's (`onAttach`/`onRemove`)
102
+ * and the pill only reports what is attached.
103
+ *
104
+ * With no `pick` seam it opens a URL form instead of a file dialog — a URL is
105
+ * something a host with no upload endpoint can still supply, and refusing
106
+ * anything that is not `http(s)` keeps a pasted local path from reaching the
107
+ * provider as an unfetchable reference.
108
+ */
109
+ export declare function ReferencePill({ url, onAttach, onRemove, pick, bandRef, }: {
110
+ url: string | null;
111
+ onAttach: (url: string) => void;
112
+ onRemove: () => void;
113
+ pick?: () => Promise<string | null>;
114
+ bandRef: RefObject<HTMLDivElement | null>;
115
+ }): import("react").JSX.Element;
116
+ /**
117
+ * The model pill — first in the band on every lane, and the only pill that is
118
+ * always there. A model with no published option metadata renders this and
119
+ * nothing else, which is the honest reading of "we do not know what this model
120
+ * takes".
121
+ */
122
+ export declare function ModelPill({ models, value, displayName, provider, onSelect, bandRef, }: {
123
+ models: readonly MediaModelOption[];
124
+ value: string;
125
+ /** What the pill reads. Falls back to the id — including for a model the
126
+ * catalog does not list, such as an image-to-video sibling. */
127
+ displayName: string;
128
+ provider?: string;
129
+ onSelect: (id: string) => void;
130
+ bandRef: RefObject<HTMLDivElement | null>;
131
+ }): import("react").JSX.Element;
132
+ /**
133
+ * How a wire value reads, with the wire value itself untouched underneath.
134
+ *
135
+ * Keyed on the PARAMETER because the same scalar means different things across
136
+ * them: `5` is "5s" of video and `2` is "×2" images. Anything this table has no
137
+ * rule for reads as itself with a capital letter — never as an invented word,
138
+ * so `std` renders "Std" and not a "Standard" nobody published.
139
+ */
140
+ export declare function optionValueLabel(param: string, value: ModelOptionValue): string;