@tangle-network/agent-app 0.46.2 → 0.46.4

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 (63) hide show
  1. package/README.md +1 -1
  2. package/dist/assistant/index.js +3 -1
  3. package/dist/assistant/index.js.map +1 -1
  4. package/dist/chunk-KKBTPZIE.js +805 -0
  5. package/dist/chunk-KKBTPZIE.js.map +1 -0
  6. package/dist/{chunk-2RTYQU4W.js → chunk-LEGZX5MQ.js} +150 -4
  7. package/dist/chunk-LEGZX5MQ.js.map +1 -0
  8. package/dist/chunk-RLOHIX5Y.js +36 -0
  9. package/dist/chunk-RLOHIX5Y.js.map +1 -0
  10. package/dist/{chunk-MBKNKAN2.js → chunk-YTEUZXX2.js} +584 -1252
  11. package/dist/chunk-YTEUZXX2.js.map +1 -0
  12. package/dist/mention-editor-GVLG3W7C.js +521 -0
  13. package/dist/mention-editor-GVLG3W7C.js.map +1 -0
  14. package/dist/stories/studio/StudioProviders.d.ts +10 -0
  15. package/dist/stories/studio/fixtures.d.ts +10 -2
  16. package/dist/studio/audio-preview.d.ts +12 -0
  17. package/dist/studio/generation.d.ts +22 -1
  18. package/dist/studio/index.d.ts +2 -0
  19. package/dist/studio/index.js +29 -1
  20. package/dist/studio/ports.d.ts +54 -0
  21. package/dist/studio-react/composer-option-controls.d.ts +19 -8
  22. package/dist/studio-react/download-generations.d.ts +4 -0
  23. package/dist/studio-react/index.d.ts +38 -22
  24. package/dist/studio-react/index.js +2198 -545
  25. package/dist/studio-react/index.js.map +1 -1
  26. package/dist/studio-react/media-tile.d.ts +21 -0
  27. package/dist/studio-react/media-viewer.d.ts +12 -0
  28. package/dist/studio-react/studio-confirm.d.ts +8 -0
  29. package/dist/studio-react/studio-generation-screen.d.ts +42 -0
  30. package/dist/studio-react/studio-history-screen.d.ts +28 -0
  31. package/dist/studio-react/studio-home-screen.d.ts +20 -0
  32. package/dist/studio-react/studio-playback.d.ts +31 -0
  33. package/dist/studio-react/studio-toasts.d.ts +21 -0
  34. package/dist/studio-react/studio.css +142 -72
  35. package/dist/studio-react/use-batch-navigation.d.ts +20 -0
  36. package/dist/studio-react/use-deferred-delete.d.ts +19 -0
  37. package/dist/studio-react/use-generation-history.d.ts +22 -0
  38. package/dist/studio-react/vault-path-popover.d.ts +12 -0
  39. package/dist/teams/drizzle/invitations-schema.d.ts +1 -1
  40. package/dist/teams/drizzle/schema.d.ts +1 -1
  41. package/dist/web-react/chat-composer.d.ts +24 -4
  42. package/dist/web-react/index.d.ts +1 -0
  43. package/dist/web-react/index.js +12 -6
  44. package/dist/web-react/mention-boundaries.d.ts +33 -0
  45. package/dist/web-react/mention-editor.d.ts +84 -0
  46. package/dist/web-react/mention-list.d.ts +35 -0
  47. package/dist/web-react/mention-pill.d.ts +10 -0
  48. package/dist/web-react/mention-serialize.d.ts +44 -0
  49. package/dist/web-react/use-file-mentions.d.ts +28 -14
  50. package/package.json +33 -7
  51. package/dist/chunk-2RTYQU4W.js.map +0 -1
  52. package/dist/chunk-MBKNKAN2.js.map +0 -1
  53. package/dist/studio-react/generation-card.d.ts +0 -9
  54. package/dist/studio-react/generation-detail-modal.d.ts +0 -11
  55. package/dist/studio-react/generation-detail.d.ts +0 -6
  56. package/dist/studio-react/generation-grid.d.ts +0 -15
  57. package/dist/studio-react/library-drawer.d.ts +0 -12
  58. package/dist/studio-react/library-panel.d.ts +0 -15
  59. package/dist/studio-react/result-canvas.d.ts +0 -6
  60. package/dist/studio-react/studio-header.d.ts +0 -5
  61. package/dist/studio-react/studio-sheet.d.ts +0 -12
  62. package/dist/studio-react/studio-workspace.d.ts +0 -21
  63. package/dist/studio-react/type-config.d.ts +0 -11
@@ -0,0 +1,33 @@
1
+ /**
2
+ * The one boundary vocabulary for reading `@<path>` mention tokens out of
3
+ * plain text. Two inverse readings share it — the transcript segmenter
4
+ * (`chat-mentions.ts`) and editor-pill restore (`mention-serialize.ts`) — and
5
+ * they must agree, or a token one accepts the other mangles. Kept OUT of the
6
+ * `/web-react` barrel on purpose: these are internals of the grammar, not
7
+ * public API.
8
+ */
9
+ /** A character that could plausibly continue the SAME path/filename past a
10
+ * matched token. Without this lookahead a mention of `@a/b.md` would match
11
+ * inside the unrelated `@a/b.md.bak` and split it mid-filename.
12
+ *
13
+ * Unicode-aware because the wire validator (`validateSandboxMentionPath`)
14
+ * deliberately ALLOWS non-ASCII paths — in-box filenames are arbitrary. An
15
+ * ASCII-only class here would accept input the readers then mangle.
16
+ * `\p{M}` keeps DECOMPOSED filenames whole: without it, a combining accent
17
+ * (`a` + U+0301) reads as a boundary and splits the name after its base
18
+ * letter. */
19
+ export declare const PATH_CONTINUATION_CHAR: RegExp;
20
+ /** A character that, immediately BEFORE an `@`, means the `@` is part of a
21
+ * longer token (an email local part, a handle) rather than a mention start.
22
+ * Unicode-aware for the same reasons, combining marks included. */
23
+ export declare const WORD_CHAR: RegExp;
24
+ /** The full character (code point) ENDING just before `index`, or undefined
25
+ * at the start. Indexing `text[index - 1]` hands a lone UTF-16 surrogate to
26
+ * the Unicode-aware classes above, which then misread every astral letter —
27
+ * boundary checks must see whole characters. `index` must be a code-point
28
+ * boundary (both callers derive it from `@` positions and id lengths, which
29
+ * are). */
30
+ export declare function charBefore(text: string, index: number): string | undefined;
31
+ /** The full character (code point) starting at `index`, or undefined past the
32
+ * end — same surrogate rationale and precondition as {@link charBefore}. */
33
+ export declare function charAt(text: string, index: number): string | undefined;
@@ -0,0 +1,84 @@
1
+ /**
2
+ * The mention-capable rich input behind `ChatComposer`'s `mention` prop.
3
+ *
4
+ * The five `@tiptap/*` packages are OPTIONAL peers, and every value access to
5
+ * them goes through `loadTiptapModules()`'s dynamic `import()` — never a
6
+ * static named import. A bundler resolves a missing optional peer to a stub
7
+ * module with a default export only, so a static `import { mergeAttributes }`
8
+ * fails the CONSUMER'S build even when nothing ever renders the mention path
9
+ * (measured against Vite 7's `__vite-optional-peer-dep` stub). The dynamic
10
+ * form builds clean and defers the failure to the first actual editor load,
11
+ * where it throws with the missing packages named — the same doctrine as
12
+ * `sequences-react`'s transcription peer. Type-only imports are erased and
13
+ * stay allowed.
14
+ */
15
+ import type { MentionOptions } from '@tiptap/extension-mention';
16
+ import type * as TiptapCore from '@tiptap/core';
17
+ import type * as TiptapExtensionMention from '@tiptap/extension-mention';
18
+ import type * as TiptapReact from '@tiptap/react';
19
+ import type * as TiptapStarterKit from '@tiptap/starter-kit';
20
+ import type * as TiptapSuggestion from '@tiptap/suggestion';
21
+ import { type ComponentType } from 'react';
22
+ import type { ComposerMentionProp } from './use-file-mentions';
23
+ /** The loaded namespaces of the five optional peers, as one bag. */
24
+ export interface TiptapModules {
25
+ core: typeof TiptapCore;
26
+ extensionMention: typeof TiptapExtensionMention;
27
+ react: typeof TiptapReact;
28
+ starterKit: typeof TiptapStarterKit;
29
+ suggestion: typeof TiptapSuggestion;
30
+ }
31
+ /** Resolve the optional peers, or throw naming every one of them. */
32
+ export declare function loadTiptapModules(): Promise<TiptapModules>;
33
+ export interface MentionEditorProps {
34
+ value: string;
35
+ onChange: (value: string) => void;
36
+ /** Fired when Enter (no Shift, popover closed, not composing) should send. */
37
+ onSubmit: () => void;
38
+ placeholder: string;
39
+ disabled?: boolean;
40
+ autoFocus?: boolean;
41
+ /** Pixel floor for the input box. The composer computes it from `minRows`
42
+ * against its own line metrics, so the two input modes cannot drift. */
43
+ minHeight: number;
44
+ /** Pixel height the input grows to before it scrolls. */
45
+ maxHeight: number;
46
+ mention: ComposerMentionProp;
47
+ /** Registers a focus callback the composer wires to Cmd/Ctrl+L; called with
48
+ * `null` on unmount so the composer never focuses a destroyed editor. */
49
+ registerFocus?: (focus: (() => void) | null) => void;
50
+ /**
51
+ * Clipboard files pulled off a paste. Returns true when consumed so the
52
+ * editor suppresses its default text paste — the same funnel the textarea
53
+ * path routes through to `onAttach`.
54
+ */
55
+ onPasteFiles?: (files: FileList) => boolean;
56
+ }
57
+ /** Only text, hard breaks, and atomic mention pills — no marks, no formatting. */
58
+ export declare function buildComposerStarterKit(tiptap: TiptapModules): TiptapCore.Extension<TiptapStarterKit.StarterKitOptions, any>;
59
+ /**
60
+ * The mention node: an inline atom (`inline: true, selectable: false,
61
+ * atom: true` from `@tiptap/extension-mention`) carrying `{ id, label, kind }`,
62
+ * rendered as a themed pill showing the label with the full id in `title`. Atom
63
+ * means the cursor can't enter it and a single backspace removes the whole
64
+ * thing. The `suggestion` config is supplied by the caller so the popover's
65
+ * fetch/keyboard wiring stays in the component.
66
+ */
67
+ export declare function buildMentionExtension(tiptap: TiptapModules, trigger: string, suggestion: MentionOptions['suggestion']): TiptapCore.Node<MentionOptions<any, TiptapExtensionMention.MentionNodeAttrs>, any>;
68
+ /** `React.lazy` payload: resolve the peers, then build the component. */
69
+ export declare function loadMentionEditor(): Promise<{
70
+ default: ComponentType<MentionEditorProps>;
71
+ }>;
72
+ /**
73
+ * Builds the mention-capable rich input against loaded tiptap modules. Loaded
74
+ * lazily by `ChatComposer` so textarea-only consumers never pull TipTap into
75
+ * their initial bundle. It preserves every textarea behavior — controlled
76
+ * plain-text `value`, Enter to send / Shift+Enter for a newline, placeholder,
77
+ * disabled, autofocus, file-paste routing — and adds `@`-mention pills backed
78
+ * by an async provider.
79
+ *
80
+ * The suggestion list renders through `PopoverSurface` anchored to the input
81
+ * box, like the composer's slash menu: an in-place panel is a panel the host
82
+ * clips away (see the popover canon in AGENTS.md).
83
+ */
84
+ export declare function createMentionEditor(tiptap: TiptapModules): ComponentType<MentionEditorProps>;
@@ -0,0 +1,35 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { MentionItem } from './use-file-mentions';
3
+ /** Imperative surface the editor's suggestion keymap drives. */
4
+ export interface MentionListHandle {
5
+ /** Returns true when the key was consumed — the editor must not also act. */
6
+ onKeyDown: (event: KeyboardEvent) => boolean;
7
+ }
8
+ export interface MentionListProps {
9
+ items: MentionItem[];
10
+ loading: boolean;
11
+ error: boolean;
12
+ /** Shown when the fetch resolved to zero items. Default "No matches". */
13
+ emptyText?: string;
14
+ renderItem?: (item: MentionItem) => ReactNode;
15
+ onSelect: (item: MentionItem) => void;
16
+ /** Root `role="listbox"` element id; option rows derive stable ids from it
17
+ * (`<id>-opt-<index>`) so the editor can point `aria-activedescendant` at
18
+ * the highlighted row. */
19
+ id?: string;
20
+ /** Fired whenever the highlight moves (keys, hover, or a result-set
21
+ * re-home) — what keeps the editor's `aria-activedescendant` current. */
22
+ onActiveChange?: (index: number) => void;
23
+ /** Extra classes merged onto the panel's root element, applied last so
24
+ * they win over the component's own. */
25
+ className?: string;
26
+ }
27
+ /**
28
+ * The mention suggestion list: a flat, keyboard-driven menu with loading,
29
+ * empty, and error states. Selection is owned here so ↑/↓ and Enter/Tab
30
+ * resolve against the highlighted row; every key it handles is reported
31
+ * consumed so the composer's Enter-to-send never fires while open. The panel
32
+ * carries its own surface classes but no positioning — the editor places it
33
+ * through `PopoverSurface`, per the popover canon.
34
+ */
35
+ export declare const MentionList: import("react").ForwardRefExoticComponent<MentionListProps & import("react").RefAttributes<MentionListHandle>>;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Visual contract for a rendered mention pill. The TipTap extension in
3
+ * `mention-editor.tsx` styles its inline atom node with this, and any other
4
+ * surface that renders a resolved mention (e.g. a sent-message transcript)
5
+ * uses the same constant so the two stay one visual contract instead of
6
+ * silently drifting apart. Kept in its own module (no TipTap import) so
7
+ * importing it never pulls the lazily-loaded editor chunk into a consumer's
8
+ * bundle.
9
+ */
10
+ export declare const MENTION_PILL_CLASS = "rounded-md bg-primary/10 px-1 py-0.5 font-medium text-primary";
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Round-trip between the mention editor's document and the composer's plain
3
+ * `value` string. A mention node serializes to `@<id>`; parsing restores a
4
+ * pill only for ids the session already knows (fetched, inserted, or
5
+ * restored), so unknown `@…` runs stay literal text.
6
+ *
7
+ * The document shape is kept structural (no TipTap import) so this round-trip
8
+ * is unit-testable on its own and never pulls the editor chunk into a bundle.
9
+ */
10
+ import type { MentionItem } from './use-file-mentions';
11
+ /**
12
+ * The subset of a TipTap/ProseMirror JSON document the mention editor
13
+ * produces: a `doc` of paragraphs, each holding text, hard breaks, and atomic
14
+ * mention nodes.
15
+ */
16
+ export interface MentionDocNode {
17
+ type: string;
18
+ text?: string;
19
+ attrs?: {
20
+ id?: string | null;
21
+ label?: string | null;
22
+ kind?: string | null;
23
+ };
24
+ content?: MentionDocNode[];
25
+ }
26
+ /**
27
+ * Plain-text serialization of the editor document. Paragraphs join with a
28
+ * newline, hard breaks become a newline, and a mention node becomes `@<id>` —
29
+ * the stable text form the controlled `value` carries.
30
+ */
31
+ export declare function serializeMentionDoc(doc: MentionDocNode): string;
32
+ /** Every mention node in the document, in order, as `MentionItem`s. */
33
+ export declare function collectMentions(doc: MentionDocNode): MentionItem[];
34
+ /**
35
+ * Parse a controlled `value` string back into an editor document. `@<id>`
36
+ * runs that match a currently-known mention restore as atomic mention nodes;
37
+ * every other `@…` stays literal text. Each line becomes a paragraph — a
38
+ * deliberate asymmetry with `serializeMentionDoc`, which also flattens an
39
+ * in-paragraph `hardBreak` to `\n`: the string round-trips byte-identically,
40
+ * but a restored Shift+Enter comes back as a paragraph break. The two render
41
+ * identically under the composer's styling (no paragraph margins), so the
42
+ * plain-text `value` contract stays the source of truth.
43
+ */
44
+ export declare function parseMentionValue(value: string, known: Map<string, MentionItem>): MentionDocNode;
@@ -1,7 +1,7 @@
1
1
  /**
2
- * `useFileMentions` — the glue a host passes straight into `AgentComposer`'s
3
- * `mention` prop (`@tangle-network/sandbox-ui#184`) to wire up `@`-file
4
- * mentions against `createSandboxFileIndexRoute` (`/chat-routes`).
2
+ * `useFileMentions` — the glue a host passes straight into `ChatComposer`'s
3
+ * `mention` prop to wire up `@`-file mentions against
4
+ * `createSandboxFileIndexRoute` (`/chat-routes`).
5
5
  *
6
6
  * Fetches the index once per session from `indexUrl`, refreshes it in the
7
7
  * background whenever the popover opens (a `fetchItems` call) if the cached
@@ -10,31 +10,45 @@
10
10
  * `refresh()` lets a caller force a re-fetch immediately instead of waiting
11
11
  * on `refreshAfterMs` — e.g. right after the agent creates a file mid-session.
12
12
  *
13
- * `MentionItem`/the `mention` prop shape mirror the FROZEN contract from
14
- * sandbox-ui#184 structurally (no import: `/web-react` stays dependency-free
15
- * beyond React, and `@tangle-network/sandbox-ui` is an optional peer).
13
+ * `MentionItem` and `ComposerMentionProp` are the mention contract this
14
+ * package defines and `mention-editor.tsx` renders. The shape stays
15
+ * structurally identical to sandbox-ui#184's `AgentComposerProps['mention']`,
16
+ * so the same value drives sandbox-ui's `AgentComposer` during migration
17
+ * (tangle-network/agent-dev-container#5934) without an import either way.
16
18
  */
17
19
  import type { ReactNode } from 'react';
18
20
  import type { FileMention } from '../chat-routes/wire';
19
- /** Mirrors sandbox-ui#184's `MentionItem` — the atomic pill's payload. For a
20
- * file mention, `id` is the workspace-relative path (the pill's stable
21
- * identity and the `@<id>` serialization sandbox-ui uses to round-trip
22
- * `value`), `label` is the display name, and `detail` carries the full path
23
- * for the popover row's secondary line. */
21
+ /** The atomic pill's payload. For a file mention, `id` is the
22
+ * workspace-relative path (the pill's stable identity and the `@<id>`
23
+ * serialization the editor uses to round-trip `value`), `label` is the
24
+ * display name, and `detail` carries the full path for the popover row's
25
+ * secondary line. */
24
26
  export interface MentionItem {
25
27
  id: string;
26
28
  label: string;
27
29
  detail?: string;
28
30
  kind?: string;
29
31
  }
30
- /** Mirrors sandbox-ui#184's `AgentComposerProps['mention']` shape — plug the
31
- * hook's `mention` return value straight into that prop. */
32
+ /** `ChatComposer`'s `mention` prop shape — plug the hook's `mention` return
33
+ * value straight into it. Structurally identical to sandbox-ui#184's
34
+ * `AgentComposerProps['mention']`, so it also drives `AgentComposer`. */
32
35
  export interface ComposerMentionProp {
36
+ /** The character that opens the popover. Default "@". Read once when the
37
+ * editor mounts — a runtime change does not re-key the editor. */
33
38
  trigger?: string;
39
+ /** Async provider called with the query typed after the trigger. */
34
40
  fetchItems(query: string): Promise<MentionItem[]>;
41
+ /** Fired with the mentions currently in the document whenever they change. */
35
42
  onMentionsChange?(mentions: MentionItem[]): void;
43
+ /** Custom row renderer for a popover item. */
36
44
  renderItem?(item: MentionItem): ReactNode;
45
+ /** Shown when a fetch resolves to zero items. Default "No matches". */
37
46
  emptyText?: string;
47
+ /** Extra classes merged onto the suggestion panel's root element
48
+ * (`role="listbox"`), applied last so they win over the component's own —
49
+ * the supported way to retheme the popover instead of targeting it by its
50
+ * ARIA attributes. */
51
+ popoverClassName?: string;
38
52
  }
39
53
  /**
40
54
  * Ranks `files` against `query` (case-insensitive), capped to `limit`:
@@ -77,7 +91,7 @@ export interface UseFileMentionsOptions {
77
91
  }
78
92
  /** Provide properties and methods to manage and refresh file mentions in a composer interface */
79
93
  export interface UseFileMentionsResult {
80
- /** Spread straight into `AgentComposer`'s `mention` prop. */
94
+ /** Spread straight into `ChatComposer`'s `mention` prop. */
81
95
  mention: ComposerMentionProp;
82
96
  /** The files currently referenced by mentions in the composer's value —
83
97
  * the send-body list (map through `fileMentionsToParts`). */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/agent-app",
3
- "version": "0.46.2",
3
+ "version": "0.46.4",
4
4
  "packageManager": "pnpm@11.17.0",
5
5
  "description": "Build agent applications with typed chat, tools, sandboxes, integrations, billing, and evaluation.",
6
6
  "keywords": [
@@ -502,7 +502,7 @@
502
502
  "prepare": "pnpm run build",
503
503
  "test": "vitest run",
504
504
  "test:generated": "node .github/scripts/test-generated-projects.mjs",
505
- "test:gates": "vitest run src/sandbox/index.test.ts src/profile/index.test.ts tests/chat-stream.test.ts tests/browser-safe-subpaths.test.ts tests/test-quality.test.ts tests/knip-entries-fresh.test.ts tests/web-react/popover-escapes-host.test.tsx tests/web-react/popover-canon-source.test.ts",
505
+ "test:gates": "vitest run src/sandbox/index.test.ts src/profile/index.test.ts tests/chat-stream.test.ts tests/browser-safe-subpaths.test.ts tests/test-quality.test.ts tests/knip-entries-fresh.test.ts tests/web-react/popover-escapes-host.test.tsx tests/web-react/popover-canon-source.test.ts tests/studio/layer-order.test.ts",
506
506
  "test:watch": "vitest",
507
507
  "typecheck": "tsc --noEmit",
508
508
  "docs:gen": "agent-docs",
@@ -515,7 +515,6 @@
515
515
  "@cloudflare/workers-types": "5.20260722.1",
516
516
  "@firecrawl/pdf-inspector-wasm": "^0.1.3",
517
517
  "@hocuspocus/provider": "^4.4.0",
518
- "@radix-ui/react-dialog": "^1.1.15",
519
518
  "@storybook/react": "^10.5.5",
520
519
  "@storybook/react-vite": "^10.5.5",
521
520
  "@tangle-network/agent-docs": "0.2.1",
@@ -531,6 +530,13 @@
531
530
  "@tangle-network/ui": "^11.5.0",
532
531
  "@testing-library/dom": "^10.4.1",
533
532
  "@testing-library/react": "^16.3.2",
533
+ "@testing-library/user-event": "^14.6.1",
534
+ "@tiptap/core": "^3.28.0",
535
+ "@tiptap/extension-mention": "^3.28.0",
536
+ "@tiptap/pm": "^3.28.0",
537
+ "@tiptap/react": "^3.28.0",
538
+ "@tiptap/starter-kit": "^3.28.0",
539
+ "@tiptap/suggestion": "^3.28.0",
534
540
  "@types/better-sqlite3": "^7.6.13",
535
541
  "@types/node": "^22.20.1",
536
542
  "@types/react": "^19.0.0",
@@ -570,7 +576,6 @@
570
576
  "peerDependencies": {
571
577
  "@firecrawl/pdf-inspector-wasm": ">=0.1.3",
572
578
  "@huggingface/transformers": ">=3",
573
- "@radix-ui/react-dialog": ">=1.1",
574
579
  "@tangle-network/agent-eval": ">=0.149.0 <0.150.0",
575
580
  "@tangle-network/agent-integrations": ">=0.52.0",
576
581
  "@tangle-network/agent-interface": "^1.0.0",
@@ -581,6 +586,12 @@
581
586
  "@tangle-network/sandbox": ">=0.29.0 <0.30.0",
582
587
  "@tangle-network/sandbox-ui": ">=0.105.0",
583
588
  "@tangle-network/ui": ">=11.5.0",
589
+ "@tiptap/core": ">=3.28.0 <4.0.0",
590
+ "@tiptap/extension-mention": ">=3.28.0 <4.0.0",
591
+ "@tiptap/pm": ">=3.28.0 <4.0.0",
592
+ "@tiptap/react": ">=3.28.0 <4.0.0",
593
+ "@tiptap/starter-kit": ">=3.28.0 <4.0.0",
594
+ "@tiptap/suggestion": ">=3.28.0 <4.0.0",
584
595
  "@xyflow/react": ">=12.0.0",
585
596
  "better-auth": ">=1.6.16",
586
597
  "drizzle-orm": ">=0.36",
@@ -636,6 +647,24 @@
636
647
  "@tangle-network/brand": {
637
648
  "optional": true
638
649
  },
650
+ "@tiptap/core": {
651
+ "optional": true
652
+ },
653
+ "@tiptap/extension-mention": {
654
+ "optional": true
655
+ },
656
+ "@tiptap/pm": {
657
+ "optional": true
658
+ },
659
+ "@tiptap/react": {
660
+ "optional": true
661
+ },
662
+ "@tiptap/starter-kit": {
663
+ "optional": true
664
+ },
665
+ "@tiptap/suggestion": {
666
+ "optional": true
667
+ },
639
668
  "@xyflow/react": {
640
669
  "optional": true
641
670
  },
@@ -645,9 +674,6 @@
645
674
  "lucide-react": {
646
675
  "optional": true
647
676
  },
648
- "@radix-ui/react-dialog": {
649
- "optional": true
650
- },
651
677
  "resend": {
652
678
  "optional": true
653
679
  },
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/studio/generation.ts","../src/studio/model-options.ts"],"sourcesContent":["import type { ModelOptionsMetadata } from './model-options'\n\n/** Define generation categories for media including image, video, speech, avatar, and transcription */\nexport type GenerationType = 'image' | 'video' | 'speech' | 'avatar' | 'transcription'\n\n/** Define possible states representing the progress of a generation process */\nexport type GenerationStatus = 'pending' | 'running' | 'succeeded' | 'failed'\n\n/** Define possible status values for a media model's availability and accessibility */\nexport type MediaModelStatus = 'available' | 'limited' | 'unavailable'\n\n/** Define the structure for a generation entity including its metadata and creation details */\nexport interface Generation {\n id: string\n type: string\n prompt: string\n result: string | null\n model: string | null\n cost: number | null\n createdAt: Date | null\n metadata: Record<string, unknown> | null\n}\n\n/** Describe a catalog media model and its optional wire-level option metadata. */\nexport interface MediaModelOption {\n id: string\n name: string\n provider?: string\n type: GenerationType\n status: MediaModelStatus\n reason?: string\n options?: ModelOptionsMetadata\n}\n\n/** Represent media model catalog with default values, model options, and optional error message */\nexport interface MediaModelCatalogResponse {\n defaults: Record<GenerationType, string>\n models: Record<GenerationType, MediaModelOption[]>\n error?: string\n}\n\n// Order drives the library type filter tabs. The composer offers its own\n// subset (`COMPOSER_TYPES` in studio-react) while avatar/transcription are\n// disabled (#451).\n/** Provide an array of supported generation types for media and content processing */\nexport const GENERATION_TYPES: readonly GenerationType[] = ['image', 'video', 'avatar', 'speech', 'transcription']\n\n/** Resolve whether a string value matches a valid GenerationType */\nexport function isGenerationType(value: string): value is GenerationType {\n return (GENERATION_TYPES as readonly string[]).includes(value)\n}\n\n/** Define the minimum number of images required for processing or validation */\nexport const MIN_IMAGE_COUNT = 1\n/** Define the maximum number of images allowed for upload or display */\nexport const MAX_IMAGE_COUNT = 8\n\n/** Resolve a human-readable relative time string from a given date or return an empty string if null */\nexport function relativeTime(date: Date | null): string {\n if (!date) return ''\n const now = Date.now()\n const diff = now - new Date(date).getTime()\n const minutes = Math.floor(diff / 60000)\n if (minutes < 1) return 'just now'\n if (minutes < 60) return `${minutes}m ago`\n const hours = Math.floor(minutes / 60)\n if (hours < 24) return `${hours}h ago`\n const days = Math.floor(hours / 24)\n if (days < 7) return `${days}d ago`\n return new Date(date).toLocaleDateString('en-US', { month: 'short', day: 'numeric' })\n}\n\n/** Resolve the output directory path based on the specified generation type */\nexport function outputPathFor(type: GenerationType): string {\n if (type === 'image') return 'generated/images'\n if (type === 'video') return 'generated/videos'\n if (type === 'avatar') return 'generated/avatars'\n if (type === 'speech') return 'generated/audio'\n return 'generated/transcripts'\n}\n\n/** Resolve the vault path string from a Generation object or return null if unavailable */\nexport function generationVaultPath(generation: Generation): string | null {\n const value = generation.metadata?.vaultPath\n return typeof value === 'string' && value.trim() ? value.trim() : null\n}\n\n/** Resolve selected models by applying defaults for missing or unavailable entries in the catalog */\nexport function selectedModelsWithDefaults(\n current: Partial<Record<GenerationType, string>>,\n catalog: MediaModelCatalogResponse,\n): Partial<Record<GenerationType, string>> {\n const next = { ...current }\n for (const key of GENERATION_TYPES) {\n const models = catalog.models[key] ?? []\n const currentOption = models.find((model) => model.id === next[key])\n // Reset when: no selection, selection not in catalog, or selection is unavailable.\n // This ensures the Generate button is never stuck disabled when routeable\n // models exist but the stored default isn't one of them.\n if (!next[key] || !currentOption || currentOption.status === 'unavailable') {\n next[key] = preferredModelId(key, catalog) ?? ''\n }\n }\n return next\n}\n\n/** Resolve the preferred model ID for a given generation type from the media model catalog */\nexport function preferredModelId(type: GenerationType, catalog: MediaModelCatalogResponse | null): string | undefined {\n if (!catalog) return undefined\n const models = catalog.models[type] ?? []\n const preferred = catalog.defaults[type]\n return models.find((model) => model.id === preferred)?.id\n ?? models.find((model) => model.status !== 'unavailable')?.id\n ?? models[0]?.id\n}\n\n/** Resolve the appropriate status message for a media model based on loading state and availability */\nexport function modelMessage(model: MediaModelOption | undefined, loading: boolean, count: number): string | null {\n if (loading) return 'Loading media models...'\n if (count === 0) return 'No models are available for this media type.'\n if (!model) return 'Select a model.'\n if (model.status === 'unavailable') return model.reason ?? 'This model is not configured.'\n if (model.status === 'limited') return model.reason ? `Limited: ${model.reason}` : 'Limited availability.'\n return null\n}\n\n/** Define fields required to configure and request various types of media generation */\nexport interface GenerationRequestFields {\n workspaceId: string\n clientRequestId: string\n type: GenerationType\n model: string\n prompt: string\n // Every per-lane parameter except the image COUNT is optional, because the\n // composer only sends what the selected model publishes: a model whose\n // metadata omits `size` (or marks it `supported: false`) must send no `size`\n // at all, and a model that publishes nothing — `ltx-video` — sends only the\n // prompt. `count` stays required: it is the number of optimistic cards the\n // caller already drew, not a model parameter.\n image: { size?: string; quality?: string; count: number }\n video: {\n duration?: string | number\n resolution?: string\n aspectRatio?: string\n referenceImageUrl?: string\n audio?: boolean\n mode?: string\n }\n speech: { voice?: string; speed?: number }\n // Optional while the composer lanes are disabled (#451); the server capability stays.\n avatar?: { audioUrl: string; imageUrl: string; avatarId: string }\n transcription?: { audioUrl: string; language: string; responseFormat: string; temperature: string }\n}\n\n// image.count must already be normalized — it is also the optimistic-card count on the caller side\n/** Build the request body object for a generation operation from provided fields */\nexport function buildGenerationRequestBody(fields: GenerationRequestFields): Record<string, unknown> {\n const body: Record<string, unknown> = {\n workspaceId: fields.workspaceId,\n clientRequestId: fields.clientRequestId,\n type: fields.type,\n model: fields.model,\n prompt: fields.prompt.trim(),\n }\n if (fields.type === 'image') {\n if (fields.image.size) body.size = fields.image.size\n if (fields.image.quality) body.quality = fields.image.quality\n body.n = fields.image.count\n }\n if (fields.type === 'video') {\n if (fields.video.duration !== undefined) body.duration = fields.video.duration\n if (fields.video.resolution) body.resolution = fields.video.resolution\n if (fields.video.aspectRatio) body.aspectRatio = fields.video.aspectRatio\n if (fields.video.referenceImageUrl) body.referenceImageUrl = fields.video.referenceImageUrl\n if (fields.video.audio !== undefined) body.audio = fields.video.audio\n if (fields.video.mode) body.mode = fields.video.mode\n }\n if (fields.type === 'speech') {\n if (fields.speech.voice) body.voice = fields.speech.voice\n if (fields.speech.speed !== undefined) body.speed = fields.speech.speed\n }\n if (fields.type === 'avatar' && fields.avatar) Object.assign(body, {\n audioUrl: fields.avatar.audioUrl.trim(),\n imageUrl: fields.avatar.imageUrl.trim() || undefined,\n avatarId: fields.avatar.avatarId.trim() || undefined,\n })\n if (fields.type === 'transcription' && fields.transcription) {\n const temperature = Number(fields.transcription.temperature)\n Object.assign(body, {\n audioUrl: fields.transcription.audioUrl.trim(),\n language: fields.transcription.language.trim() || undefined,\n responseFormat: fields.transcription.responseFormat,\n // omit (let the API default) rather than serialize NaN → null on bad input\n temperature: Number.isFinite(temperature) ? temperature : undefined,\n })\n }\n return body\n}\n\n/** Resolve the current status of a generation based on its metadata and result fields */\nexport function generationStatus(generation: Generation): GenerationStatus {\n const metadata = generation.metadata ?? {}\n const status = typeof metadata.generationStatus === 'string' ? metadata.generationStatus : ''\n if (status === 'pending' || status === 'running' || status === 'failed' || status === 'succeeded') return status\n return generation.result ? 'succeeded' : 'pending'\n}\n\n/** Resolve and return the first user-safe error message from generation metadata or null if none exist */\nexport function generationError(generation: Generation): string | null {\n const metadata = generation.metadata ?? {}\n if (typeof metadata.providerError === 'string' && metadata.providerError.trim()) {\n return userSafeGenerationMessage(metadata.providerError)\n }\n if (typeof metadata.storageError === 'string' && metadata.storageError.trim()) {\n return metadata.storageError\n }\n return null\n}\n\nfunction generationClientRequestId(generation: Generation): string | null {\n const metadata = generation.metadata ?? {}\n return typeof metadata.clientRequestId === 'string' && metadata.clientRequestId.trim()\n ? metadata.clientRequestId\n : null\n}\n\nfunction generationBatchSlotKey(generation: Generation): string | null {\n const metadata = generation.metadata ?? {}\n const batchId = typeof metadata.batchId === 'string' && metadata.batchId.trim() ? metadata.batchId : null\n return batchId && typeof metadata.outputIndex === 'number'\n ? `${batchId}:${metadata.outputIndex}`\n : null\n}\n\n/** Resolve a unique merge key from a generation using batch slot or client request ID */\nexport function generationMergeKey(generation: Generation): string | null {\n return generationBatchSlotKey(generation) ?? generationClientRequestId(generation)\n}\n\n/** Merge a new generation into the current list by replacing or prepending it based on matching keys */\nexport function mergeLiveGeneration(current: Generation[], generation: Generation): Generation[] {\n const mergeKey = generationMergeKey(generation)\n const existingIndex = current.findIndex((item) => (\n item.id === generation.id\n || (mergeKey && generationMergeKey(item) === mergeKey)\n ))\n if (existingIndex === -1) return [generation, ...current]\n\n const next = [...current]\n next[existingIndex] = generation\n return next\n}\n\n// Overlay in-flight `live` generations on the loader's rows: each live row leads\n// (prefer the matching loader row by merge key / id so it carries the freshest\n// server state), then the remaining loader rows that no live row already\n// represents — deduped by BOTH id and merge key so a server row and its\n// optimistic twin never both appear. Returns `loader` unchanged when nothing is\n// live. Drives the canvas, library, and polling off one list.\n/** Merge two Generation arrays prioritizing live entries and matching by merge keys or IDs */\nexport function mergeLoaderAndLive(loader: Generation[], live: Generation[]): Generation[] {\n if (live.length === 0) return loader\n const leading = live.map((generation) => {\n const mergeKey = generationMergeKey(generation)\n return mergeKey\n ? loader.find((gen) => generationMergeKey(gen) === mergeKey) ?? generation\n : loader.find((gen) => gen.id === generation.id) ?? generation\n })\n const leadingIds = new Set(leading.map((gen) => gen.id))\n const leadingMergeKeys = new Set(leading\n .map((gen) => generationMergeKey(gen))\n .filter((id): id is string => Boolean(id)))\n return [\n ...leading,\n ...loader.filter((gen) => (\n !leadingIds.has(gen.id)\n && !leadingMergeKeys.has(generationMergeKey(gen) ?? '')\n )),\n ]\n}\n\n/** Determine if a generation ID indicates a local generation */\nexport function isLocalGeneration(generation: Generation): boolean {\n return generation.id.startsWith('local-')\n}\n\nfunction generationOutputIndex(generation: Generation): number {\n const value = generation.metadata?.outputIndex\n return typeof value === 'number' ? value : 0\n}\n\n// The most-recent run: all generations sharing the leading item's clientRequestId\n// (a multi-image batch), ordered by output slot. Falls back to the single leading\n// item when no request id is present. Drives the result canvas.\n/** Resolve and return the latest batch of generations grouped and sorted by client request ID and output index */\nexport function latestBatchOf(generations: Generation[]): Generation[] {\n const first = generations[0]\n if (!first) return []\n const key = generationClientRequestId(first)\n const batch = key\n ? generations.filter((generation) => generationClientRequestId(generation) === key)\n : [first]\n return [...batch].sort((a, b) => generationOutputIndex(a) - generationOutputIndex(b))\n}\n\n/** Resolve a user-safe generation message by filtering sensitive or error-related content */\nexport function userSafeGenerationMessage(message?: string): string {\n if (!message) return 'Generation failed'\n if (/Tangle API key is invalid or expired/i.test(message)) return message\n if (/(api[_ -]?key|secret|token|credential|env|configured|configuration)/i.test(message)) {\n return 'Generation failed'\n }\n return message\n}\n\n/** Generate content optimistically based on input parameters and optional model and output details */\nexport function optimisticGeneration({\n type,\n prompt,\n model,\n clientRequestId,\n outputIndex,\n outputCount,\n}: {\n type: GenerationType\n prompt: string\n model?: string\n clientRequestId: string\n outputIndex?: number\n outputCount?: number\n}): Generation {\n const batchId = outputIndex == null ? undefined : clientRequestId\n return {\n id: outputIndex == null ? `local-${clientRequestId}` : `local-${clientRequestId}-${outputIndex}`,\n type,\n prompt,\n result: null,\n model: model ?? null,\n cost: null,\n createdAt: new Date(),\n metadata: {\n generationStatus: 'pending',\n provider: type,\n clientRequestId,\n batchId,\n outputIndex,\n outputCount,\n },\n }\n}\n\n/** Mark a generation as failed with updated status and error information */\nexport function failedOptimisticGeneration(generation: Generation): Generation {\n return {\n ...generation,\n metadata: {\n ...(generation.metadata ?? {}),\n generationStatus: 'failed',\n providerError: 'Generation failed',\n },\n }\n}\n\n/** Normalize a value to a finite integer within the allowed image count range */\nexport function normalizeImageCount(value: unknown): number {\n const numeric = typeof value === 'number' ? value : Number(value)\n if (!Number.isFinite(numeric)) return MIN_IMAGE_COUNT\n return Math.min(Math.max(Math.trunc(numeric), MIN_IMAGE_COUNT), MAX_IMAGE_COUNT)\n}\n","import type { GenerationType, MediaModelOption } from './generation'\n\n/** A wire-typed value accepted by a model option. */\nexport type ModelOptionValue = string | number | boolean\n\n/** Per-parameter option metadata, structurally identical to tangle-router's\n * `ModelOptionMetadata` (lib/model-options.ts, shipped in router PR #429).\n * `supported: false` means the model lacks or ignores the parameter.\n * `values` is the exact wire-typed enum; `min` and `max` are inclusive.\n * `default` applies when the caller omits the parameter. An absent entry or\n * options object means unknown, so consumers must not invent a value. */\nexport interface ModelOptionMetadata {\n supported?: boolean\n values?: readonly ModelOptionValue[]\n min?: number\n max?: number\n default?: ModelOptionValue\n}\n\n/** Per-parameter model option metadata keyed by the provider's wire field. */\nexport type ModelOptionsMetadata = Readonly<Record<string, ModelOptionMetadata>>\n\nconst SEEDANCE_2_0: ModelOptionsMetadata = {\n duration: {\n values: ['auto', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15'],\n default: 'auto',\n },\n resolution: { values: ['480p', '720p', '1080p', '4k'], default: '720p' },\n aspect_ratio: { values: ['auto', '21:9', '16:9', '4:3', '1:1', '3:4', '9:16'], default: 'auto' },\n audio: { default: true },\n}\n\n// These values mirror tangle-router VIDEO_MODEL_OPTIONS from PR #429,\n// observedAt 2026-08-19. Catalog-provided live options always win.\n/** Fallback video options matching tangle-router's wire-exact metadata. */\nexport const FALLBACK_VIDEO_MODEL_OPTIONS: Readonly<Record<string, ModelOptionsMetadata>> = {\n 'runway/gen4.5': {\n duration: { min: 2, max: 10, default: 5 },\n aspect_ratio: { values: ['16:9', '9:16'], default: '16:9' },\n resolution: { supported: false },\n audio: { supported: false },\n },\n 'runway/gen4_turbo': {\n duration: { min: 2, max: 10, default: 5 },\n aspect_ratio: { values: ['16:9', '9:16', '4:3', '3:4', '1:1', '21:9'], default: '16:9' },\n resolution: { supported: false },\n audio: { supported: false },\n },\n 'kling/kling-v1-6': {\n duration: { values: [5, 10], default: 5 },\n aspect_ratio: { values: ['16:9', '9:16', '1:1'], default: '16:9' },\n resolution: { supported: false },\n audio: { supported: false },\n mode: { values: ['std', 'pro'], default: 'std' },\n },\n 'kling/kling-v2-master': {\n duration: { values: [5, 10], default: 5 },\n aspect_ratio: { values: ['16:9', '9:16', '1:1'], default: '16:9' },\n resolution: { supported: false },\n audio: { supported: false },\n mode: { supported: false },\n },\n 'bytedance/seedance-2.0/text-to-video': SEEDANCE_2_0,\n 'bytedance/seedance-2.0/image-to-video': SEEDANCE_2_0,\n 'fal-ai/kling-video/v3/pro/text-to-video': {\n duration: { values: ['3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15'], default: '5' },\n resolution: { supported: false },\n aspect_ratio: { values: ['16:9', '9:16', '1:1'], default: '16:9' },\n audio: { default: true },\n },\n 'fal-ai/veo3.1': {\n duration: { values: ['4s', '6s', '8s'], default: '8s' },\n resolution: { values: ['720p', '1080p', '4k'], default: '720p' },\n aspect_ratio: { values: ['16:9', '9:16'], default: '16:9' },\n audio: { default: true },\n },\n 'xai/grok-imagine-video/text-to-video': {\n duration: { min: 1, max: 15, default: 6 },\n resolution: { values: ['480p', '720p'], default: '720p' },\n aspect_ratio: { values: ['16:9', '4:3', '3:2', '1:1', '2:3', '3:4', '9:16'], default: '16:9' },\n audio: { supported: false },\n },\n}\n\n// Source: provider research recorded in router #420 and agent-app #449 on\n// 2026-08-18. Live catalog options remain authoritative when present.\nconst IMAGE_MODEL_OPTIONS: Readonly<Record<string, ModelOptionsMetadata>> = {\n 'gpt-image-2': {\n size: { values: ['auto', '1024x1024', '1536x1024', '1024x1536'], default: 'auto' },\n quality: { values: ['low', 'medium', 'high', 'auto'], default: 'auto' },\n n: { values: [1, 2, 4, 8], default: 1 },\n },\n}\n\nconst OPENAI_TTS_VOICES = ['alloy', 'ash', 'coral', 'echo', 'fable', 'onyx', 'nova', 'sage', 'shimmer'] as const\nconst OPENAI_GPT4O_MINI_TTS_VOICES = [...OPENAI_TTS_VOICES, 'ballad', 'cedar', 'marin', 'verse'] as const\n// These aliases are the router's GEMINI_VOICE_MAP keys. The router translates\n// them to Kore, Puck, Charon, Algenib, Aoede, and Leda respectively.\nconst GOOGLE_TTS_VOICE_ALIASES = ['alloy', 'echo', 'fable', 'onyx', 'nova', 'shimmer'] as const\n\nconst OPENAI_AUDIO_MODEL_OPTIONS: Readonly<Record<string, ModelOptionsMetadata>> = {\n 'tts-1': {\n voice: { values: OPENAI_TTS_VOICES, default: 'alloy' },\n speed: { min: 0.25, max: 4, default: 1 },\n },\n 'tts-1-hd': {\n voice: { values: OPENAI_TTS_VOICES, default: 'alloy' },\n speed: { min: 0.25, max: 4, default: 1 },\n },\n 'gpt-4o-mini-tts': {\n voice: { values: OPENAI_GPT4O_MINI_TTS_VOICES, default: 'alloy' },\n speed: { min: 0.25, max: 4, default: 1 },\n },\n}\n\nconst GOOGLE_AUDIO_MODEL_OPTIONS: ModelOptionsMetadata = {\n voice: { values: GOOGLE_TTS_VOICE_ALIASES, default: 'alloy' },\n speed: { supported: false },\n}\n\n// Mistral's preset voices are enumerable only through its authenticated\n// /v1/audio/voices API; no publicly verifiable list existed at research time.\n// Unknown means show nothing invented, so Voxtral uses the router's provider\n// default (gb_jane_neutral) until router #420 publishes live catalog options.\n\n/** UI constraints for a custom gpt-image-2 size. */\nexport const GPT_IMAGE_2_CUSTOM_SIZE = { multipleOf: 16, maxLongEdge: 3840, maxRatio: 3 } as const\n\n/** Validate a custom gpt-image-2 size against its published UI constraints. */\nexport function validateCustomImageSize(width: number, height: number): { ok: true } | { ok: false; reason: string } {\n if (!Number.isInteger(width) || !Number.isInteger(height) || width <= 0 || height <= 0) {\n return { ok: false, reason: 'Width and height must be positive integers.' }\n }\n if (width % GPT_IMAGE_2_CUSTOM_SIZE.multipleOf !== 0 || height % GPT_IMAGE_2_CUSTOM_SIZE.multipleOf !== 0) {\n return { ok: false, reason: 'Each side must be a multiple of 16.' }\n }\n if (Math.max(width, height) > GPT_IMAGE_2_CUSTOM_SIZE.maxLongEdge) {\n return { ok: false, reason: 'The long edge must be 3840 pixels or less.' }\n }\n if (Math.max(width / height, height / width) > GPT_IMAGE_2_CUSTOM_SIZE.maxRatio) {\n return { ok: false, reason: 'The aspect ratio must be between 1:3 and 3:1.' }\n }\n return { ok: true }\n}\n\nconst KNOWN_PROVIDER_ALIASES = new Set([\n 'openai',\n 'google',\n 'gemini',\n 'fal',\n 'fal-ai',\n 'runway',\n 'kling',\n 'bytedance',\n 'xai',\n])\n\nfunction bareSingleSlashId(modelId: string): string | undefined {\n const segments = modelId.split('/')\n if (segments.length !== 2 || !KNOWN_PROVIDER_ALIASES.has(segments[0] ?? '')) return undefined\n return segments[1]\n}\n\nfunction audioOptions(modelId: string, provider?: string): ModelOptionsMetadata | undefined {\n const bareId = bareSingleSlashId(modelId) ?? modelId\n const exact = OPENAI_AUDIO_MODEL_OPTIONS[modelId] ?? OPENAI_AUDIO_MODEL_OPTIONS[bareId]\n if (exact) return exact\n\n const normalizedProvider = provider?.toLowerCase()\n if (\n (bareId.toLowerCase().startsWith('gemini') && bareId.toLowerCase().includes('tts'))\n || normalizedProvider === 'google'\n || normalizedProvider === 'gemini'\n ) return GOOGLE_AUDIO_MODEL_OPTIONS\n\n return undefined\n}\n\n/** Resolve live catalog options first, then exact or safe single-prefix fallbacks. */\nexport function resolveComposerOptions(input: {\n type: 'image' | 'video' | 'speech'\n modelId: string\n provider?: string\n catalogOptions?: ModelOptionsMetadata\n}): ModelOptionsMetadata | undefined {\n if (input.catalogOptions) return input.catalogOptions\n if (input.type === 'speech') return audioOptions(input.modelId, input.provider)\n\n const table = input.type === 'image' ? IMAGE_MODEL_OPTIONS : FALLBACK_VIDEO_MODEL_OPTIONS\n const exact = table[input.modelId]\n if (exact) return exact\n\n // Only a known provider prefix on an id with exactly one slash is stripped;\n // multi-slash fal ids must remain whole.\n const bareId = bareSingleSlashId(input.modelId)\n return bareId ? table[bareId] : undefined\n}\n\n/** Return whether a model supports the gpt-image-2 custom-size rule. */\nexport function supportsCustomImageSize(modelId: string): boolean {\n return modelId === 'gpt-image-2' || bareSingleSlashId(modelId) === 'gpt-image-2'\n}\n\n/** Map verified text-to-video model ids to their image-to-video siblings. */\nexport const IMAGE_TO_VIDEO_SIBLINGS: Readonly<Record<string, string>> = {\n 'bytedance/seedance-2.0/text-to-video': 'bytedance/seedance-2.0/image-to-video',\n}\n\n/** Resolve a verified image-to-video sibling for a text-to-video model. */\nexport function imageToVideoSibling(modelId: string): string | undefined {\n return IMAGE_TO_VIDEO_SIBLINGS[modelId]\n}\n\n/** Resolve the verified text-to-video sibling for an image-to-video model. */\nexport function textToVideoSibling(modelId: string): string | undefined {\n return Object.entries(IMAGE_TO_VIDEO_SIBLINGS).find(([, sibling]) => sibling === modelId)?.[0]\n}\n\n/** Curate catalog models for the issue #449 composer lanes. */\nexport function curateComposerModels(\n type: GenerationType,\n models: MediaModelOption[],\n): MediaModelOption[] {\n if (type === 'image') return models.filter((model) => supportsCustomImageSize(model.id))\n if (type === 'video') {\n const imageToVideoIds = new Set(Object.values(IMAGE_TO_VIDEO_SIBLINGS))\n return models.filter((model) => !model.id.toLowerCase().includes('sora') && !imageToVideoIds.has(model.id))\n }\n return models\n}\n\n/** Resolve an option default from its default, values, or lower bound. */\nexport function optionDefault(meta: ModelOptionMetadata): ModelOptionValue | undefined {\n return meta.default ?? meta.values?.[0] ?? meta.min\n}\n\n/** Return exact enum choices or an inclusive integer range. */\nexport function optionChoices(meta: ModelOptionMetadata): readonly ModelOptionValue[] {\n if (meta.values) return meta.values\n if (meta.min == null || meta.max == null) return []\n const values: number[] = []\n for (let value = Math.ceil(meta.min); value <= Math.floor(meta.max); value += 1) values.push(value)\n return values\n}\n\nfunction isCustomSize(value: ModelOptionValue): boolean {\n if (typeof value !== 'string') return false\n const match = /^(\\d+)x(\\d+)$/.exec(value)\n if (!match) return false\n return validateCustomImageSize(Number(match[1]), Number(match[2])).ok\n}\n\nfunction isLegalOptionValue(meta: ModelOptionMetadata, value: ModelOptionValue): boolean {\n if (meta.values) return meta.values.includes(value)\n if (typeof value === 'number' && meta.min != null && meta.max != null) {\n return value >= meta.min && value <= meta.max\n }\n return meta.min == null && meta.max == null\n}\n\n/** Reconcile selections against supported options and their wire-typed defaults.\n * `allowCustomSize` keeps a legal off-enum `WxH` size selection (gpt-image-2's\n * custom-size rule) — the caller decides via {@link supportsCustomImageSize},\n * so the check holds even when the options came from the live catalog. */\nexport function reconcileOptionValues(\n options: ModelOptionsMetadata | undefined,\n current: Readonly<Record<string, ModelOptionValue>>,\n opts?: { allowCustomSize?: boolean },\n): Record<string, ModelOptionValue> {\n if (!options) return {}\n const reconciled: Record<string, ModelOptionValue> = {}\n for (const [key, meta] of Object.entries(options)) {\n if (meta.supported === false) continue\n const selected = current[key]\n const customSizeIsLegal = key === 'size'\n && selected !== undefined\n && opts?.allowCustomSize === true\n && isCustomSize(selected)\n const selectionIsLegal = selected !== undefined\n && (isLegalOptionValue(meta, selected) || customSizeIsLegal)\n const next = selectionIsLegal ? selected : optionDefault(meta)\n if (next !== undefined) reconciled[key] = next\n }\n return reconciled\n}\n"],"mappings":"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","names":[]}