@seed-ship/mcp-ui-solid 6.4.0 → 6.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/CHANGELOG.md +230 -0
  2. package/README.md +37 -0
  3. package/dist/adapters/connector.cjs +112 -0
  4. package/dist/adapters/connector.cjs.map +1 -0
  5. package/dist/adapters/connector.d.ts +71 -0
  6. package/dist/adapters/connector.d.ts.map +1 -0
  7. package/dist/adapters/connector.js +112 -0
  8. package/dist/adapters/connector.js.map +1 -0
  9. package/dist/adapters/index.d.ts +18 -0
  10. package/dist/adapters/index.d.ts.map +1 -0
  11. package/dist/adapters.cjs +6 -0
  12. package/dist/adapters.cjs.map +1 -0
  13. package/dist/adapters.d.cts +18 -0
  14. package/dist/adapters.d.ts +18 -0
  15. package/dist/adapters.js +6 -0
  16. package/dist/adapters.js.map +1 -0
  17. package/dist/components/ExpandableWrapper.cjs +24 -6
  18. package/dist/components/ExpandableWrapper.cjs.map +1 -1
  19. package/dist/components/ExpandableWrapper.d.ts.map +1 -1
  20. package/dist/components/ExpandableWrapper.js +24 -6
  21. package/dist/components/ExpandableWrapper.js.map +1 -1
  22. package/dist/components/FeedbackInline.cjs +6 -2
  23. package/dist/components/FeedbackInline.cjs.map +1 -1
  24. package/dist/components/FeedbackInline.d.ts +2 -2
  25. package/dist/components/FeedbackInline.d.ts.map +1 -1
  26. package/dist/components/FeedbackInline.js +7 -3
  27. package/dist/components/FeedbackInline.js.map +1 -1
  28. package/dist/components/PresentationFeedback.cjs +207 -0
  29. package/dist/components/PresentationFeedback.cjs.map +1 -0
  30. package/dist/components/PresentationFeedback.d.ts +113 -0
  31. package/dist/components/PresentationFeedback.d.ts.map +1 -0
  32. package/dist/components/PresentationFeedback.js +207 -0
  33. package/dist/components/PresentationFeedback.js.map +1 -0
  34. package/dist/components/StreamingUIRenderer.cjs +82 -195
  35. package/dist/components/StreamingUIRenderer.cjs.map +1 -1
  36. package/dist/components/StreamingUIRenderer.d.ts +25 -5
  37. package/dist/components/StreamingUIRenderer.d.ts.map +1 -1
  38. package/dist/components/StreamingUIRenderer.js +84 -197
  39. package/dist/components/StreamingUIRenderer.js.map +1 -1
  40. package/dist/components/UIResourceRenderer.cjs +40 -10
  41. package/dist/components/UIResourceRenderer.cjs.map +1 -1
  42. package/dist/components/UIResourceRenderer.d.ts +20 -0
  43. package/dist/components/UIResourceRenderer.d.ts.map +1 -1
  44. package/dist/components/UIResourceRenderer.js +42 -12
  45. package/dist/components/UIResourceRenderer.js.map +1 -1
  46. package/dist/components/index.d.ts +2 -0
  47. package/dist/components/index.d.ts.map +1 -1
  48. package/dist/components.cjs +3 -0
  49. package/dist/components.cjs.map +1 -1
  50. package/dist/components.d.cts +2 -0
  51. package/dist/components.d.ts +2 -0
  52. package/dist/components.js +3 -0
  53. package/dist/components.js.map +1 -1
  54. package/dist/context/MCPUIStringsContext.cjs +38 -0
  55. package/dist/context/MCPUIStringsContext.cjs.map +1 -0
  56. package/dist/context/MCPUIStringsContext.d.ts +95 -0
  57. package/dist/context/MCPUIStringsContext.d.ts.map +1 -0
  58. package/dist/context/MCPUIStringsContext.js +38 -0
  59. package/dist/context/MCPUIStringsContext.js.map +1 -0
  60. package/dist/index.cjs +12 -0
  61. package/dist/index.cjs.map +1 -1
  62. package/dist/index.d.cts +8 -0
  63. package/dist/index.d.ts +8 -0
  64. package/dist/index.d.ts.map +1 -1
  65. package/dist/index.js +12 -0
  66. package/dist/index.js.map +1 -1
  67. package/dist/mcp-ui-spec/dist/schemas.cjs +103 -0
  68. package/dist/mcp-ui-spec/dist/schemas.cjs.map +1 -1
  69. package/dist/mcp-ui-spec/dist/schemas.js +103 -0
  70. package/dist/mcp-ui-spec/dist/schemas.js.map +1 -1
  71. package/dist/utils/duplicate-mount-registry.cjs +27 -0
  72. package/dist/utils/duplicate-mount-registry.cjs.map +1 -0
  73. package/dist/utils/duplicate-mount-registry.d.ts +84 -0
  74. package/dist/utils/duplicate-mount-registry.d.ts.map +1 -0
  75. package/dist/utils/duplicate-mount-registry.js +27 -0
  76. package/dist/utils/duplicate-mount-registry.js.map +1 -0
  77. package/dist/utils/stable-key.cjs +41 -0
  78. package/dist/utils/stable-key.cjs.map +1 -0
  79. package/dist/utils/stable-key.d.ts +33 -0
  80. package/dist/utils/stable-key.d.ts.map +1 -0
  81. package/dist/utils/stable-key.js +41 -0
  82. package/dist/utils/stable-key.js.map +1 -0
  83. package/docs/briefs/ROADMAP-opendata-macro-mcpui.md +912 -0
  84. package/package.json +17 -5
  85. package/src/adapters/connector.test.ts +165 -0
  86. package/src/adapters/connector.ts +234 -0
  87. package/src/adapters/index.ts +24 -0
  88. package/src/components/ExpandableWrapper.test.tsx +5 -2
  89. package/src/components/ExpandableWrapper.tsx +8 -6
  90. package/src/components/FeedbackInline.test.tsx +6 -3
  91. package/src/components/FeedbackInline.tsx +8 -6
  92. package/src/components/PresentationFeedback.test.tsx +163 -0
  93. package/src/components/PresentationFeedback.tsx +326 -0
  94. package/src/components/StreamingUIRenderer.parity.test.tsx +158 -0
  95. package/src/components/StreamingUIRenderer.tsx +42 -166
  96. package/src/components/UIResourceRenderer.identity.test.tsx +161 -0
  97. package/src/components/UIResourceRenderer.tsx +63 -2
  98. package/src/components/index.ts +10 -0
  99. package/src/context/MCPUIStringsContext.test.tsx +116 -0
  100. package/src/context/MCPUIStringsContext.tsx +128 -0
  101. package/src/index.ts +35 -0
  102. package/src/utils/duplicate-mount-registry.test.ts +82 -0
  103. package/src/utils/duplicate-mount-registry.ts +113 -0
  104. package/src/utils/stable-key.test.ts +96 -0
  105. package/src/utils/stable-key.ts +91 -0
  106. package/tsconfig.tsbuildinfo +1 -1
  107. package/vite.config.ts +1 -0
package/CHANGELOG.md CHANGED
@@ -5,6 +5,236 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [6.6.0] - 2026-05-21
9
+
10
+ Sprint OpenData / macros — cf. `docs/briefs/ROADMAP-opendata-macro-mcpui.md`
11
+ (decisions D1-D10 + R1-R4). Ships steps 1-5 of the execution order :
12
+ `StreamingUIRenderer` parity, `MCPUIStringsProvider`, the
13
+ `ConnectorDynamicResultV1` contract consumption, `PresentationFeedback`,
14
+ and the opt-in connector adapters. The macro adapters are deferred to a
15
+ later phase (they need a macro-run contract). Pairs with
16
+ `@seed-ship/mcp-ui-spec@5.1.0`.
17
+
18
+ ### Added — opt-in connector adapters (`@seed-ship/mcp-ui-solid/adapters`) (D5 / D6)
19
+
20
+ New dedicated subpath export — **never** imported by the core renderer
21
+ path, so consumers that don't emit connector results pay nothing for it:
22
+
23
+ ```ts
24
+ import { connectorResultToUILayout } from '@seed-ship/mcp-ui-solid/adapters'
25
+
26
+ const layout = connectorResultToUILayout(connectorResult)
27
+ ```
28
+
29
+ - `connectorResultToUILayout(result)` — assembles a `ConnectorDynamicResultV1`
30
+ (`primary` + `supplemental[]` + `actions`) into one `UILayout`. **Pure**
31
+ (D5) — deterministic, no side effects, safe to re-run after feedback.
32
+ - `connectorActionsToActionGroup(actions)` — wraps connector actions into
33
+ an `action-group` `UIComponent`.
34
+ - **Unknown `schemaVersion` never throws** (R2) : a usable-but-unversioned
35
+ envelope still renders, prefixed with a visible warning notice ; a truly
36
+ unreadable payload becomes an explicit degraded `UILayout` (a `text`
37
+ notice with id `connector-degraded`). Never a silent disappearance.
38
+
39
+ **Scope note** : this ships the *connector* adapters. The *macro* adapters
40
+ (`macroRunToScratchpadState`, `macroInterrogationToChatPromptConfig`) are
41
+ deferred — they need a macro-run contract that the roadmap leaves to
42
+ Phase 2.
43
+
44
+ ### Added — `PresentationFeedback` component (R3 / D9 / Phase 4)
45
+
46
+ A new opt-in feedback widget — **distinct** from `FeedbackInline` :
47
+
48
+ - `FeedbackInline` — was the *answer* good? (response quality)
49
+ - `PresentationFeedback` — was the answer *shown well*? (layout / readability)
50
+
51
+ They are separate components, separate exports, separate payloads — the
52
+ two axes never collapse (cf. R3). `PresentationFeedback` collects a
53
+ `verdict` (`readable` / `not_readable`) and, when not readable, problem
54
+ tags + an optional preferred layout + a free-text comment, then emits a
55
+ `ConnectorRenderFeedback` payload via `onSubmit`.
56
+
57
+ - Stateless : the host persists the feedback and owns any re-render
58
+ (cf. D1 — adapter pure + host state). The component never mutates the
59
+ rendered result.
60
+ - Best-effort : a rejected `onSubmit` promise is swallowed, the UI flips.
61
+ - Localizable : all labels ship in English, overridable via the `labels`
62
+ prop (`DEFAULT_PRESENTATION_FEEDBACK_LABELS` exported).
63
+ - `ConnectorRenderFeedback` / `ConnectorRenderProblem` /
64
+ `ConnectorPreferredLayout` types are re-exported from the package root
65
+ for convenience (defined in `@seed-ship/mcp-ui-spec`).
66
+
67
+ ### Added — `MCPUIStringsProvider` : i18n for the library's chrome (D2 / R4)
68
+
69
+ MCP-UI hardcoded a handful of its own UI strings (the expand-button
70
+ tooltip, the feedback acknowledgements…) — and they were an inconsistent
71
+ FR/EN mix. New opt-in context to localize them :
72
+
73
+ ```tsx
74
+ import { MCPUIStringsProvider } from '@seed-ship/mcp-ui-solid'
75
+
76
+ <MCPUIStringsProvider strings={{ expand: 'Agrandir', feedbackUseful: 'Utile' }}>
77
+ <App />
78
+ </MCPUIStringsProvider>
79
+ ```
80
+
81
+ - `MCPUIStrings` — flat string map of the library's own "chrome" strings.
82
+ **Content** (table headers, chart titles, action labels) is NOT covered:
83
+ it comes from the payload, already localized by the producer.
84
+ - `DEFAULT_MCPUI_STRINGS` — English defaults. A published library ships no
85
+ hardcoded non-English chrome.
86
+ - `<MCPUIStringsProvider strings={...}>` — partial override; unset keys fall
87
+ back to the EN defaults.
88
+ - `useMCPUIStrings()` — reads the active strings; returns the EN defaults
89
+ when no provider is mounted (every renderer works standalone).
90
+
91
+ Wired consumers : `FeedbackInline` (button tooltips + acks),
92
+ `ExpandableWrapper` (expand / copy / close chrome), `StreamingUIRenderer`
93
+ (retry button). Component props that already carry a label
94
+ (`FeedbackInline.positiveAck`, `ExpandableWrapper.copyLabel`) keep priority
95
+ over the provider.
96
+
97
+ **Behavior change** : `FeedbackInline`'s ack defaults were French
98
+ (`'Merci !'`, `"Noté, on s'améliore"`) — they are now English
99
+ (`'Thanks!'`, `"Noted — we'll improve"`). Consumers relying on the FR
100
+ defaults pass a `<MCPUIStringsProvider>` with FR strings, or the
101
+ `positiveAck` / `negativeAck` props. `ExpandableWrapper`'s default heading
102
+ casing was also unified to `'Expanded view'` (it was inconsistently
103
+ `'Expanded View'` for the heading vs `'Expanded view'` for the aria-label).
104
+
105
+ ### Changed — `StreamingUIRenderer` renders with full fidelity (Gap 1 / D3)
106
+
107
+ `StreamingUIRenderer` previously rendered each streamed component through an
108
+ inline simplified renderer (`StreamingComponentRenderer`) that only showed a
109
+ type label, the title, and — for metrics — the value. A streamed `table` /
110
+ `chart` / `map` did NOT render the real component.
111
+
112
+ Each streamed `UIComponent` is now delegated to the real
113
+ `<UIResourceRenderer>`. Streaming and static paths use the literal same
114
+ renderer, so they cannot drift. Validation, telemetry, the error boundary
115
+ and `errorMode` all come from `<UIResourceRenderer>` — the duplicated copies
116
+ in the streaming path are deleted.
117
+
118
+ - New `toolbarVariant` prop on `<StreamingUIRenderer>`, forwarded to streamed
119
+ components (parity with the static `<UIResourceRenderer toolbarVariant>`).
120
+ - Delegation is a one-way value import — `UIResourceRenderer` never imports
121
+ `StreamingUIRenderer`, no cycle.
122
+ - Progress bar, skeletons, arrival animation and metadata display unchanged.
123
+ - The streamed component's grid `position` is normalized to full-width
124
+ before delegation: `StreamingUIRenderer` owns the 12-column layout,
125
+ `<UIResourceRenderer>` only renders the component.
126
+
127
+ ### Changed — `size-limit` budgets
128
+
129
+ The "Streaming renderer" `size-limit` budget was raised from 30 KB to 1 MB.
130
+ Post-D3 that entry's reachable graph equals the full renderer set, and
131
+ `size-limit` measures the pre-built `dist/` including lazy `import()` chunks
132
+ (leaflet, `@antv/g6`, chart.js) that are fetched on demand, not at import.
133
+ The figure is a worst-case total, not eager load cost — the budget is
134
+ generous headroom, since `size-limit` here is a regression guardrail and
135
+ does not gate CI. The "Hooks only" and "Full bundle" entries are unchanged —
136
+ their pre-existing overages predate this sprint.
137
+
138
+ ## [6.5.0] - 2026-05-05
139
+
140
+ Closes Demande 1 + Demande 2 of `deposium_solid`'s
141
+ `BRIEF-MCPUI-2026-05-10.md` (rescue-duplicate root-cause investigation).
142
+
143
+ ### Added — `getUiResourceStableKey(content)` helper
144
+
145
+ New public function exported from the package root :
146
+
147
+ ```ts
148
+ import { getUiResourceStableKey } from '@seed-ship/mcp-ui-solid'
149
+
150
+ getUiResourceStableKey({ id: 'dashboard-q3', components: [...] })
151
+ // → 'dashboard-q3' (passthrough)
152
+
153
+ getUiResourceStableKey({ type: 'chart', params: { ... } })
154
+ // → 'a4f3b91' (FNV-1a 32-bit, 7 chars base36)
155
+ ```
156
+
157
+ If `content.id` is a non-empty string, the helper returns it verbatim.
158
+ Otherwise it derives a deterministic hash from the content with `id` and
159
+ `metadata.generatedAt` stripped — stable across renders for the same
160
+ logical payload, sync, no peer dependency.
161
+
162
+ This is the canonical implementation of the spec's "bare payload"
163
+ fallback policy (cf. `mcp-ui-spec` README → §Runtime Payload Identity).
164
+ Host apps that pre-process payloads before passing them to a renderer
165
+ (e.g. wrapping a bare chart config into a layout) should reuse this
166
+ helper instead of generating `Date.now()` or counter-based ids — those
167
+ break `<For>` reconciliation and double-mount detection.
168
+
169
+ ### Added — opt-in duplicate-mount observability
170
+
171
+ `<UIResourceRenderer>` now exposes two ways for consumers to detect
172
+ when the same content key is mounted concurrently more than once :
173
+
174
+ 1. **Per-instance callback** :
175
+
176
+ ```tsx
177
+ <UIResourceRenderer
178
+ content={layout}
179
+ onMountDuplicate={({ key, count, firstMountedAt }) => {
180
+ console.warn('[app] duplicate mount', { key, count })
181
+ }}
182
+ />
183
+ ```
184
+
185
+ 2. **Module-level reporter** (app-wide telemetry) :
186
+
187
+ ```ts
188
+ import { setDuplicateMountReporter } from '@seed-ship/mcp-ui-solid'
189
+
190
+ setDuplicateMountReporter(({ key, count }) => {
191
+ telemetry.warn('mcp-ui.duplicate-mount', { key, count })
192
+ })
193
+ ```
194
+
195
+ The new `debugDuplicateMounts` prop forces a `console.warn` from a
196
+ single instance even when the global `setDebugMode()` flag is off —
197
+ useful when you want to diagnose one suspect surface without flipping
198
+ the global switch.
199
+
200
+ **The renderer never deduplicates visually on its own.** Hiding a 2nd
201
+ mount would mask parent-framework bugs and could remove legitimate
202
+ co-mounts (drawer + main panel showing the same card). Consumers who
203
+ want dedup implement it on top of the reported events.
204
+
205
+ ### Added — `data-mcp-ui-{layout|component}-id` DOM attributes
206
+
207
+ Every `<UIResourceRenderer>` wrapper now carries a stable identity
208
+ attribute :
209
+
210
+ - The outer wrapper carries `data-mcp-ui-layout-id` when `content` is
211
+ a `UILayout` (composite), or `data-mcp-ui-component-id` when `content`
212
+ is a single `UIComponent`.
213
+ - Each per-component wrapper inside a layout carries
214
+ `data-mcp-ui-component-id`.
215
+
216
+ This enables CSS targeting, debug overlay tooling, and DOM-based
217
+ double-mount detection without a wrapper :
218
+
219
+ ```js
220
+ document.querySelectorAll('[data-mcp-ui-layout-id="dashboard-q3"]').length
221
+ // → 2 (whoops — somewhere in the parent framework, this is mounted twice)
222
+ ```
223
+
224
+ ### Internal — no `Date.now()` in identity-bearing code paths
225
+
226
+ Audit confirmed : every `Date.now()` call inside `mcp-ui-solid` is for
227
+ telemetry timestamps (`ts: Date.now()`), cache TTLs, or download-filename
228
+ fallbacks — none feed into a rendered DOM `id` or a key passed to
229
+ `<For>`. The new `getUiResourceStableKey` helper preserves this
230
+ invariant by hashing content rather than reading the clock.
231
+
232
+ ### Spec companion — `mcp-ui-spec@5.0.6`
233
+
234
+ Documentation-only patch bump : the spec README now formalizes the
235
+ runtime-payload identity contract (§Runtime Payload Identity) — `id`
236
+ obligation, fallback policy, and pointer to `getUiResourceStableKey`.
237
+
8
238
  ## [6.4.0] - 2026-05-03
9
239
 
10
240
  Closes axe 3 of `deposium_solid`'s
package/README.md CHANGED
@@ -5,6 +5,43 @@ SolidJS components + chat toolkit for MCP-generated UI. Part of the [MCP UI ecos
5
5
  [![npm version](https://img.shields.io/npm/v/@seed-ship/mcp-ui-solid.svg)](https://www.npmjs.com/package/@seed-ship/mcp-ui-solid)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
7
 
8
+ ## What's New in v6.6.0
9
+
10
+ Sprint OpenData / macros — `docs/briefs/ROADMAP-opendata-macro-mcpui.md`.
11
+
12
+ - **`StreamingUIRenderer` renders with full fidelity** — each streamed
13
+ component is now delegated to the real `<UIResourceRenderer>`. A streamed
14
+ `table` / `chart` / `map` renders exactly like a static one (no more
15
+ simplified type-label placeholder). New `toolbarVariant` prop forwarded
16
+ to streamed components.
17
+ - **`<MCPUIStringsProvider>`** — opt-in i18n for the library's own *chrome*
18
+ strings (expand tooltip, feedback acks…). English defaults; override
19
+ partially. Payload *content* is untouched — it stays the producer's job.
20
+
21
+ ```tsx
22
+ import { MCPUIStringsProvider } from '@seed-ship/mcp-ui-solid'
23
+ <MCPUIStringsProvider strings={{ expand: 'Agrandir' }}><App /></MCPUIStringsProvider>
24
+ ```
25
+ - **`<PresentationFeedback>`** — a feedback widget for how a result was
26
+ *presented* (layout / readability), distinct from `FeedbackInline`
27
+ (response quality). Emits a `ConnectorRenderFeedback` payload; stateless
28
+ (the host persists + re-renders).
29
+ - **`@seed-ship/mcp-ui-solid/adapters`** — new opt-in subpath.
30
+ `connectorResultToUILayout()` assembles a `ConnectorDynamicResultV1` into
31
+ a `UILayout`; `connectorActionsToActionGroup()` wraps connector actions.
32
+ Pure functions; an unknown `schemaVersion` degrades gracefully, never
33
+ throws.
34
+
35
+ ```ts
36
+ import { connectorResultToUILayout } from '@seed-ship/mcp-ui-solid/adapters'
37
+ const layout = connectorResultToUILayout(connectorResult)
38
+ ```
39
+
40
+ > **Note** — `FeedbackInline`'s acknowledgement defaults changed from
41
+ > French to English (`'Thanks!'`, `"Noted — we'll improve"`). Wrap your app
42
+ > in `<MCPUIStringsProvider>` with French strings, or pass `positiveAck` /
43
+ > `negativeAck`, to restore French.
44
+
8
45
  ## What's New in v5.2.0 (`mcp-ui-solid` only)
9
46
 
10
47
  - **`createChatPromptController()`** primitive — closes the v5.1.0 boilerplate. Owns resolver closure + `AbortSignal` wiring + re-entrance. Consumers write `bus.commands.handle('showChatPrompt', ctrl.handle)` + `<Show when={ctrl.activePrompt()}>{cfg => <ChatPrompt ... />}</Show>`. `PromptReplacedError` exported for `instanceof` checks.
@@ -0,0 +1,112 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const schemas = require("../mcp-ui-spec/dist/schemas.cjs");
4
+ const types = require("../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/types.cjs");
5
+ function connectorActionsToActionGroup(actions, options = {}) {
6
+ return {
7
+ id: options.id ?? "connector-actions",
8
+ type: "action-group",
9
+ position: options.position ?? { colStart: 1, colSpan: 12 },
10
+ params: {
11
+ actions,
12
+ ...options.title ? { title: options.title } : {},
13
+ layout: options.layout ?? "horizontal",
14
+ gap: options.gap ?? "md"
15
+ }
16
+ };
17
+ }
18
+ const LenientResultSchema = types.object({
19
+ schemaVersion: types.string(),
20
+ connectorId: types.string().min(1),
21
+ toolName: types.string().min(1),
22
+ query: types.string(),
23
+ queryHash: types.string().optional(),
24
+ intent: types.string().optional(),
25
+ primary: types.record(types.unknown()),
26
+ supplemental: types.array(types.record(types.unknown())).optional(),
27
+ actions: types.array(types.record(types.unknown())).optional()
28
+ });
29
+ const DEFAULT_GRID = { columns: 12, gap: "1rem" };
30
+ function withPosition(component) {
31
+ if (component && component.position) return component;
32
+ return { ...component, position: { colStart: 1, colSpan: 12 } };
33
+ }
34
+ function isLayoutShape(value) {
35
+ return Array.isArray(value.components);
36
+ }
37
+ function degradedTextComponent(id, message) {
38
+ return {
39
+ id,
40
+ type: "text",
41
+ position: { colStart: 1, colSpan: 12 },
42
+ params: { markdown: true, content: message }
43
+ };
44
+ }
45
+ function connectorResultToUILayout(result, options = {}) {
46
+ const strict = schemas.ConnectorDynamicResultV1Schema.safeParse(result);
47
+ if (!strict.success) {
48
+ const lenient = LenientResultSchema.safeParse(result);
49
+ if (!lenient.success) {
50
+ const version = result && typeof result === "object" ? result.schemaVersion : void 0;
51
+ return {
52
+ id: "connector-degraded",
53
+ components: [
54
+ degradedTextComponent(
55
+ "connector-degraded-notice",
56
+ `### Résultat non rendu
57
+
58
+ Le résultat du connecteur n'a pas pu être interprété${typeof version === "string" ? ` (schéma : \`${version}\`)` : ""}. Cet état explicite remplace une disparition silencieuse du rendu.`
59
+ )
60
+ ],
61
+ grid: { ...DEFAULT_GRID }
62
+ };
63
+ }
64
+ const r2 = lenient.data;
65
+ const components2 = [
66
+ degradedTextComponent(
67
+ "connector-version-warning",
68
+ `> ⚠ Schéma connecteur non reconnu (\`${r2.schemaVersion}\`, attendu \`${schemas.CONNECTOR_DYNAMIC_RESULT_V1}\`). Le rendu ci-dessous est en mode dégradé.`
69
+ ),
70
+ ...collectComponents(r2)
71
+ ];
72
+ return {
73
+ id: options.id ?? `connector-degraded-${r2.connectorId}`,
74
+ components: components2.map(withPosition),
75
+ grid: { ...DEFAULT_GRID }
76
+ };
77
+ }
78
+ const r = strict.data;
79
+ const components = collectComponents(r, options.actionsTitle).map(withPosition);
80
+ return {
81
+ id: options.id ?? layoutId(r.connectorId, r.queryHash ?? r.toolName),
82
+ components,
83
+ grid: { ...DEFAULT_GRID }
84
+ };
85
+ }
86
+ function collectComponents(r, actionsTitle) {
87
+ const components = [];
88
+ if (isLayoutShape(r.primary)) {
89
+ const inner = r.primary.components ?? [];
90
+ components.push(...inner);
91
+ } else {
92
+ components.push(r.primary);
93
+ }
94
+ if (r.supplemental) {
95
+ components.push(...r.supplemental);
96
+ }
97
+ if (Array.isArray(r.actions) && r.actions.length > 0) {
98
+ components.push(
99
+ connectorActionsToActionGroup(r.actions, {
100
+ id: "connector-actions",
101
+ title: actionsTitle
102
+ })
103
+ );
104
+ }
105
+ return components;
106
+ }
107
+ function layoutId(connectorId, suffix) {
108
+ return `connector-${connectorId}-${suffix}`;
109
+ }
110
+ exports.connectorActionsToActionGroup = connectorActionsToActionGroup;
111
+ exports.connectorResultToUILayout = connectorResultToUILayout;
112
+ //# sourceMappingURL=connector.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connector.cjs","sources":["../../src/adapters/connector.ts"],"sourcesContent":["/**\n * Connector adapters — `ConnectorDynamicResultV1` → MCP-UI render structures.\n *\n * @since v6.6.0 (D5 / D6 of ROADMAP-opendata-macro-mcpui)\n *\n * ## Opt-in, pure\n *\n * These adapters are published under the dedicated subpath\n * `@seed-ship/mcp-ui-solid/adapters` — they are NEVER imported by the core\n * renderer path, so a consumer that does not emit connector results pays\n * nothing for them.\n *\n * Every adapter here is a **pure function** (D5) : same input → same\n * output, no `fetch`, no `localStorage`, no global state, no clock, no\n * randomness. This is what lets a host re-run an adapter deterministically\n * after presentation feedback (D1) and replay fixtures in tests.\n *\n * ## Unknown schema version — never throw (R2)\n *\n * `connectorResultToUILayout()` never throws on the runtime render path.\n * A payload it cannot read becomes an explicit degraded `UILayout` (a\n * visible notice), never a silent disappearance and never an exception.\n */\n\nimport type { UIComponent, UILayout, GridPosition } from '../types'\nimport {\n ConnectorDynamicResultV1Schema,\n CONNECTOR_DYNAMIC_RESULT_V1,\n type ConnectorAction,\n} from '@seed-ship/mcp-ui-spec'\nimport { z } from 'zod'\n\n// ─────────────────────────────────────────────────────────────\n// connectorActionsToActionGroup\n// ─────────────────────────────────────────────────────────────\n\nexport interface ConnectorActionsToActionGroupOptions {\n /** Component id. Default `'connector-actions'`. */\n id?: string\n /** Optional heading shown above the buttons. */\n title?: string\n /** Button row layout. Default `'horizontal'`. */\n layout?: 'horizontal' | 'vertical' | 'space-between' | 'end' | 'center'\n /** Gap between buttons. Default `'md'`. */\n gap?: 'none' | 'sm' | 'md' | 'lg'\n /** Grid position. Default full-width. */\n position?: GridPosition\n}\n\n/**\n * Wraps a connector's `actions` into an `action-group` `UIComponent`.\n *\n * `ConnectorAction` is the exact `action-group` action shape, so this is a\n * thin, pure envelope — no transformation of the actions themselves.\n */\nexport function connectorActionsToActionGroup(\n actions: ConnectorAction[],\n options: ConnectorActionsToActionGroupOptions = {}\n): UIComponent {\n return {\n id: options.id ?? 'connector-actions',\n type: 'action-group',\n position: options.position ?? { colStart: 1, colSpan: 12 },\n params: {\n actions,\n ...(options.title ? { title: options.title } : {}),\n layout: options.layout ?? 'horizontal',\n gap: options.gap ?? 'md',\n },\n } as UIComponent\n}\n\n// ─────────────────────────────────────────────────────────────\n// connectorResultToUILayout\n// ─────────────────────────────────────────────────────────────\n\nexport interface ConnectorResultToUILayoutOptions {\n /** Layout id. Default derived from `connectorId` + `queryHash` / `toolName`. */\n id?: string\n /** Heading for the actions `action-group`. */\n actionsTitle?: string\n}\n\n/**\n * Lenient mirror of `ConnectorDynamicResultV1Schema` — `schemaVersion`\n * relaxed to any string. Used to tell apart \"unknown version but otherwise\n * a usable envelope\" (→ render with a warning, R2) from \"truly unreadable\"\n * (→ explicit error state).\n */\nconst LenientResultSchema = z.object({\n schemaVersion: z.string(),\n connectorId: z.string().min(1),\n toolName: z.string().min(1),\n query: z.string(),\n queryHash: z.string().optional(),\n intent: z.string().optional(),\n primary: z.record(z.unknown()),\n supplemental: z.array(z.record(z.unknown())).optional(),\n actions: z.array(z.record(z.unknown())).optional(),\n})\n\nconst DEFAULT_GRID = { columns: 12, gap: '1rem' } as const\n\nfunction withPosition(component: UIComponent): UIComponent {\n if (component && component.position) return component\n return { ...component, position: { colStart: 1, colSpan: 12 } }\n}\n\n/** A component is a UILayout when it carries a `components` array. */\nfunction isLayoutShape(value: Record<string, unknown>): boolean {\n return Array.isArray((value as { components?: unknown }).components)\n}\n\nfunction degradedTextComponent(id: string, message: string): UIComponent {\n return {\n id,\n type: 'text',\n position: { colStart: 1, colSpan: 12 },\n params: { markdown: true, content: message },\n } as UIComponent\n}\n\n/**\n * Assembles a `ConnectorDynamicResultV1` into a single `UILayout` :\n * `primary` + `supplemental[]` + (`actions` → an `action-group`).\n *\n * - When `primary` is itself a layout, its components are spread in.\n * - Raw data is never sacrificed — every supplied component is kept.\n * - Pure : no side effects, deterministic.\n *\n * ### Degraded behavior (R2)\n *\n * - Valid v1 payload → normal assembled layout.\n * - Unknown `schemaVersion` but an otherwise-usable envelope → the\n * components are still rendered, prefixed with a visible warning notice.\n * - Unreadable payload → an explicit error `UILayout` (a single `text`\n * notice). Never throws, never returns an empty layout silently.\n *\n * A degraded layout always has an `id` starting with `connector-degraded`,\n * so a host can detect it (e.g. for telemetry) without inspecting content.\n */\nexport function connectorResultToUILayout(\n result: unknown,\n options: ConnectorResultToUILayoutOptions = {}\n): UILayout {\n const strict = ConnectorDynamicResultV1Schema.safeParse(result)\n\n // ── Tier 3 : unreadable ───────────────────────────────────\n if (!strict.success) {\n const lenient = LenientResultSchema.safeParse(result)\n if (!lenient.success) {\n const version =\n result && typeof result === 'object'\n ? (result as { schemaVersion?: unknown }).schemaVersion\n : undefined\n return {\n id: 'connector-degraded',\n components: [\n degradedTextComponent(\n 'connector-degraded-notice',\n `### Résultat non rendu\\n\\nLe résultat du connecteur n'a pas pu être interprété${\n typeof version === 'string' ? ` (schéma : \\`${version}\\`)` : ''\n }. Cet état explicite remplace une disparition silencieuse du rendu.`\n ),\n ],\n grid: { ...DEFAULT_GRID },\n }\n }\n // ── Tier 2 : usable envelope, unknown version ───────────\n const r = lenient.data\n const components: UIComponent[] = [\n degradedTextComponent(\n 'connector-version-warning',\n `> ⚠ Schéma connecteur non reconnu (\\`${r.schemaVersion}\\`, attendu \\`${CONNECTOR_DYNAMIC_RESULT_V1}\\`). Le rendu ci-dessous est en mode dégradé.`\n ),\n ...collectComponents(r),\n ]\n return {\n id: options.id ?? `connector-degraded-${r.connectorId}`,\n components: components.map(withPosition),\n grid: { ...DEFAULT_GRID },\n }\n }\n\n // ── Tier 1 : valid v1 ─────────────────────────────────────\n const r = strict.data\n const components = collectComponents(r, options.actionsTitle).map(withPosition)\n return {\n id: options.id ?? layoutId(r.connectorId, r.queryHash ?? r.toolName),\n components,\n grid: { ...DEFAULT_GRID },\n }\n}\n\n/**\n * Flattens `primary` + `supplemental` + `actions` into a component list.\n * Shared by the valid and degraded-but-usable paths.\n */\nfunction collectComponents(\n r: {\n primary: Record<string, unknown>\n supplemental?: Record<string, unknown>[]\n actions?: unknown[]\n },\n actionsTitle?: string\n): UIComponent[] {\n const components: UIComponent[] = []\n\n if (isLayoutShape(r.primary)) {\n const inner = (r.primary as { components?: UIComponent[] }).components ?? []\n components.push(...inner)\n } else {\n components.push(r.primary as unknown as UIComponent)\n }\n\n if (r.supplemental) {\n components.push(...(r.supplemental as unknown as UIComponent[]))\n }\n\n if (Array.isArray(r.actions) && r.actions.length > 0) {\n components.push(\n connectorActionsToActionGroup(r.actions as ConnectorAction[], {\n id: 'connector-actions',\n title: actionsTitle,\n })\n )\n }\n\n return components\n}\n\nfunction layoutId(connectorId: string, suffix: string): string {\n return `connector-${connectorId}-${suffix}`\n}\n"],"names":["z.object","z.string","z.record","z.unknown","z.array","ConnectorDynamicResultV1Schema","r","components","CONNECTOR_DYNAMIC_RESULT_V1"],"mappings":"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"}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Connector adapters — `ConnectorDynamicResultV1` → MCP-UI render structures.
3
+ *
4
+ * @since v6.6.0 (D5 / D6 of ROADMAP-opendata-macro-mcpui)
5
+ *
6
+ * ## Opt-in, pure
7
+ *
8
+ * These adapters are published under the dedicated subpath
9
+ * `@seed-ship/mcp-ui-solid/adapters` — they are NEVER imported by the core
10
+ * renderer path, so a consumer that does not emit connector results pays
11
+ * nothing for them.
12
+ *
13
+ * Every adapter here is a **pure function** (D5) : same input → same
14
+ * output, no `fetch`, no `localStorage`, no global state, no clock, no
15
+ * randomness. This is what lets a host re-run an adapter deterministically
16
+ * after presentation feedback (D1) and replay fixtures in tests.
17
+ *
18
+ * ## Unknown schema version — never throw (R2)
19
+ *
20
+ * `connectorResultToUILayout()` never throws on the runtime render path.
21
+ * A payload it cannot read becomes an explicit degraded `UILayout` (a
22
+ * visible notice), never a silent disappearance and never an exception.
23
+ */
24
+ import type { UIComponent, UILayout, GridPosition } from '../types';
25
+ import { type ConnectorAction } from '@seed-ship/mcp-ui-spec';
26
+ export interface ConnectorActionsToActionGroupOptions {
27
+ /** Component id. Default `'connector-actions'`. */
28
+ id?: string;
29
+ /** Optional heading shown above the buttons. */
30
+ title?: string;
31
+ /** Button row layout. Default `'horizontal'`. */
32
+ layout?: 'horizontal' | 'vertical' | 'space-between' | 'end' | 'center';
33
+ /** Gap between buttons. Default `'md'`. */
34
+ gap?: 'none' | 'sm' | 'md' | 'lg';
35
+ /** Grid position. Default full-width. */
36
+ position?: GridPosition;
37
+ }
38
+ /**
39
+ * Wraps a connector's `actions` into an `action-group` `UIComponent`.
40
+ *
41
+ * `ConnectorAction` is the exact `action-group` action shape, so this is a
42
+ * thin, pure envelope — no transformation of the actions themselves.
43
+ */
44
+ export declare function connectorActionsToActionGroup(actions: ConnectorAction[], options?: ConnectorActionsToActionGroupOptions): UIComponent;
45
+ export interface ConnectorResultToUILayoutOptions {
46
+ /** Layout id. Default derived from `connectorId` + `queryHash` / `toolName`. */
47
+ id?: string;
48
+ /** Heading for the actions `action-group`. */
49
+ actionsTitle?: string;
50
+ }
51
+ /**
52
+ * Assembles a `ConnectorDynamicResultV1` into a single `UILayout` :
53
+ * `primary` + `supplemental[]` + (`actions` → an `action-group`).
54
+ *
55
+ * - When `primary` is itself a layout, its components are spread in.
56
+ * - Raw data is never sacrificed — every supplied component is kept.
57
+ * - Pure : no side effects, deterministic.
58
+ *
59
+ * ### Degraded behavior (R2)
60
+ *
61
+ * - Valid v1 payload → normal assembled layout.
62
+ * - Unknown `schemaVersion` but an otherwise-usable envelope → the
63
+ * components are still rendered, prefixed with a visible warning notice.
64
+ * - Unreadable payload → an explicit error `UILayout` (a single `text`
65
+ * notice). Never throws, never returns an empty layout silently.
66
+ *
67
+ * A degraded layout always has an `id` starting with `connector-degraded`,
68
+ * so a host can detect it (e.g. for telemetry) without inspecting content.
69
+ */
70
+ export declare function connectorResultToUILayout(result: unknown, options?: ConnectorResultToUILayoutOptions): UILayout;
71
+ //# sourceMappingURL=connector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connector.d.ts","sourceRoot":"","sources":["../../src/adapters/connector.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AACnE,OAAO,EAGL,KAAK,eAAe,EACrB,MAAM,wBAAwB,CAAA;AAO/B,MAAM,WAAW,oCAAoC;IACnD,mDAAmD;IACnD,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,gDAAgD;IAChD,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,iDAAiD;IACjD,MAAM,CAAC,EAAE,YAAY,GAAG,UAAU,GAAG,eAAe,GAAG,KAAK,GAAG,QAAQ,CAAA;IACvE,2CAA2C;IAC3C,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;IACjC,yCAAyC;IACzC,QAAQ,CAAC,EAAE,YAAY,CAAA;CACxB;AAED;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,eAAe,EAAE,EAC1B,OAAO,GAAE,oCAAyC,GACjD,WAAW,CAYb;AAMD,MAAM,WAAW,gCAAgC;IAC/C,gFAAgF;IAChF,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,8CAA8C;IAC9C,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAyCD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,OAAO,EACf,OAAO,GAAE,gCAAqC,GAC7C,QAAQ,CAgDV"}
@@ -0,0 +1,112 @@
1
+ import { ConnectorDynamicResultV1Schema, CONNECTOR_DYNAMIC_RESULT_V1 } from "../mcp-ui-spec/dist/schemas.js";
2
+ import { object as objectType, array as arrayType, record as recordType, string as stringType, unknown as unknownType } from "../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/types.js";
3
+ function connectorActionsToActionGroup(actions, options = {}) {
4
+ return {
5
+ id: options.id ?? "connector-actions",
6
+ type: "action-group",
7
+ position: options.position ?? { colStart: 1, colSpan: 12 },
8
+ params: {
9
+ actions,
10
+ ...options.title ? { title: options.title } : {},
11
+ layout: options.layout ?? "horizontal",
12
+ gap: options.gap ?? "md"
13
+ }
14
+ };
15
+ }
16
+ const LenientResultSchema = objectType({
17
+ schemaVersion: stringType(),
18
+ connectorId: stringType().min(1),
19
+ toolName: stringType().min(1),
20
+ query: stringType(),
21
+ queryHash: stringType().optional(),
22
+ intent: stringType().optional(),
23
+ primary: recordType(unknownType()),
24
+ supplemental: arrayType(recordType(unknownType())).optional(),
25
+ actions: arrayType(recordType(unknownType())).optional()
26
+ });
27
+ const DEFAULT_GRID = { columns: 12, gap: "1rem" };
28
+ function withPosition(component) {
29
+ if (component && component.position) return component;
30
+ return { ...component, position: { colStart: 1, colSpan: 12 } };
31
+ }
32
+ function isLayoutShape(value) {
33
+ return Array.isArray(value.components);
34
+ }
35
+ function degradedTextComponent(id, message) {
36
+ return {
37
+ id,
38
+ type: "text",
39
+ position: { colStart: 1, colSpan: 12 },
40
+ params: { markdown: true, content: message }
41
+ };
42
+ }
43
+ function connectorResultToUILayout(result, options = {}) {
44
+ const strict = ConnectorDynamicResultV1Schema.safeParse(result);
45
+ if (!strict.success) {
46
+ const lenient = LenientResultSchema.safeParse(result);
47
+ if (!lenient.success) {
48
+ const version = result && typeof result === "object" ? result.schemaVersion : void 0;
49
+ return {
50
+ id: "connector-degraded",
51
+ components: [
52
+ degradedTextComponent(
53
+ "connector-degraded-notice",
54
+ `### Résultat non rendu
55
+
56
+ Le résultat du connecteur n'a pas pu être interprété${typeof version === "string" ? ` (schéma : \`${version}\`)` : ""}. Cet état explicite remplace une disparition silencieuse du rendu.`
57
+ )
58
+ ],
59
+ grid: { ...DEFAULT_GRID }
60
+ };
61
+ }
62
+ const r2 = lenient.data;
63
+ const components2 = [
64
+ degradedTextComponent(
65
+ "connector-version-warning",
66
+ `> ⚠ Schéma connecteur non reconnu (\`${r2.schemaVersion}\`, attendu \`${CONNECTOR_DYNAMIC_RESULT_V1}\`). Le rendu ci-dessous est en mode dégradé.`
67
+ ),
68
+ ...collectComponents(r2)
69
+ ];
70
+ return {
71
+ id: options.id ?? `connector-degraded-${r2.connectorId}`,
72
+ components: components2.map(withPosition),
73
+ grid: { ...DEFAULT_GRID }
74
+ };
75
+ }
76
+ const r = strict.data;
77
+ const components = collectComponents(r, options.actionsTitle).map(withPosition);
78
+ return {
79
+ id: options.id ?? layoutId(r.connectorId, r.queryHash ?? r.toolName),
80
+ components,
81
+ grid: { ...DEFAULT_GRID }
82
+ };
83
+ }
84
+ function collectComponents(r, actionsTitle) {
85
+ const components = [];
86
+ if (isLayoutShape(r.primary)) {
87
+ const inner = r.primary.components ?? [];
88
+ components.push(...inner);
89
+ } else {
90
+ components.push(r.primary);
91
+ }
92
+ if (r.supplemental) {
93
+ components.push(...r.supplemental);
94
+ }
95
+ if (Array.isArray(r.actions) && r.actions.length > 0) {
96
+ components.push(
97
+ connectorActionsToActionGroup(r.actions, {
98
+ id: "connector-actions",
99
+ title: actionsTitle
100
+ })
101
+ );
102
+ }
103
+ return components;
104
+ }
105
+ function layoutId(connectorId, suffix) {
106
+ return `connector-${connectorId}-${suffix}`;
107
+ }
108
+ export {
109
+ connectorActionsToActionGroup,
110
+ connectorResultToUILayout
111
+ };
112
+ //# sourceMappingURL=connector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connector.js","sources":["../../src/adapters/connector.ts"],"sourcesContent":["/**\n * Connector adapters — `ConnectorDynamicResultV1` → MCP-UI render structures.\n *\n * @since v6.6.0 (D5 / D6 of ROADMAP-opendata-macro-mcpui)\n *\n * ## Opt-in, pure\n *\n * These adapters are published under the dedicated subpath\n * `@seed-ship/mcp-ui-solid/adapters` — they are NEVER imported by the core\n * renderer path, so a consumer that does not emit connector results pays\n * nothing for them.\n *\n * Every adapter here is a **pure function** (D5) : same input → same\n * output, no `fetch`, no `localStorage`, no global state, no clock, no\n * randomness. This is what lets a host re-run an adapter deterministically\n * after presentation feedback (D1) and replay fixtures in tests.\n *\n * ## Unknown schema version — never throw (R2)\n *\n * `connectorResultToUILayout()` never throws on the runtime render path.\n * A payload it cannot read becomes an explicit degraded `UILayout` (a\n * visible notice), never a silent disappearance and never an exception.\n */\n\nimport type { UIComponent, UILayout, GridPosition } from '../types'\nimport {\n ConnectorDynamicResultV1Schema,\n CONNECTOR_DYNAMIC_RESULT_V1,\n type ConnectorAction,\n} from '@seed-ship/mcp-ui-spec'\nimport { z } from 'zod'\n\n// ─────────────────────────────────────────────────────────────\n// connectorActionsToActionGroup\n// ─────────────────────────────────────────────────────────────\n\nexport interface ConnectorActionsToActionGroupOptions {\n /** Component id. Default `'connector-actions'`. */\n id?: string\n /** Optional heading shown above the buttons. */\n title?: string\n /** Button row layout. Default `'horizontal'`. */\n layout?: 'horizontal' | 'vertical' | 'space-between' | 'end' | 'center'\n /** Gap between buttons. Default `'md'`. */\n gap?: 'none' | 'sm' | 'md' | 'lg'\n /** Grid position. Default full-width. */\n position?: GridPosition\n}\n\n/**\n * Wraps a connector's `actions` into an `action-group` `UIComponent`.\n *\n * `ConnectorAction` is the exact `action-group` action shape, so this is a\n * thin, pure envelope — no transformation of the actions themselves.\n */\nexport function connectorActionsToActionGroup(\n actions: ConnectorAction[],\n options: ConnectorActionsToActionGroupOptions = {}\n): UIComponent {\n return {\n id: options.id ?? 'connector-actions',\n type: 'action-group',\n position: options.position ?? { colStart: 1, colSpan: 12 },\n params: {\n actions,\n ...(options.title ? { title: options.title } : {}),\n layout: options.layout ?? 'horizontal',\n gap: options.gap ?? 'md',\n },\n } as UIComponent\n}\n\n// ─────────────────────────────────────────────────────────────\n// connectorResultToUILayout\n// ─────────────────────────────────────────────────────────────\n\nexport interface ConnectorResultToUILayoutOptions {\n /** Layout id. Default derived from `connectorId` + `queryHash` / `toolName`. */\n id?: string\n /** Heading for the actions `action-group`. */\n actionsTitle?: string\n}\n\n/**\n * Lenient mirror of `ConnectorDynamicResultV1Schema` — `schemaVersion`\n * relaxed to any string. Used to tell apart \"unknown version but otherwise\n * a usable envelope\" (→ render with a warning, R2) from \"truly unreadable\"\n * (→ explicit error state).\n */\nconst LenientResultSchema = z.object({\n schemaVersion: z.string(),\n connectorId: z.string().min(1),\n toolName: z.string().min(1),\n query: z.string(),\n queryHash: z.string().optional(),\n intent: z.string().optional(),\n primary: z.record(z.unknown()),\n supplemental: z.array(z.record(z.unknown())).optional(),\n actions: z.array(z.record(z.unknown())).optional(),\n})\n\nconst DEFAULT_GRID = { columns: 12, gap: '1rem' } as const\n\nfunction withPosition(component: UIComponent): UIComponent {\n if (component && component.position) return component\n return { ...component, position: { colStart: 1, colSpan: 12 } }\n}\n\n/** A component is a UILayout when it carries a `components` array. */\nfunction isLayoutShape(value: Record<string, unknown>): boolean {\n return Array.isArray((value as { components?: unknown }).components)\n}\n\nfunction degradedTextComponent(id: string, message: string): UIComponent {\n return {\n id,\n type: 'text',\n position: { colStart: 1, colSpan: 12 },\n params: { markdown: true, content: message },\n } as UIComponent\n}\n\n/**\n * Assembles a `ConnectorDynamicResultV1` into a single `UILayout` :\n * `primary` + `supplemental[]` + (`actions` → an `action-group`).\n *\n * - When `primary` is itself a layout, its components are spread in.\n * - Raw data is never sacrificed — every supplied component is kept.\n * - Pure : no side effects, deterministic.\n *\n * ### Degraded behavior (R2)\n *\n * - Valid v1 payload → normal assembled layout.\n * - Unknown `schemaVersion` but an otherwise-usable envelope → the\n * components are still rendered, prefixed with a visible warning notice.\n * - Unreadable payload → an explicit error `UILayout` (a single `text`\n * notice). Never throws, never returns an empty layout silently.\n *\n * A degraded layout always has an `id` starting with `connector-degraded`,\n * so a host can detect it (e.g. for telemetry) without inspecting content.\n */\nexport function connectorResultToUILayout(\n result: unknown,\n options: ConnectorResultToUILayoutOptions = {}\n): UILayout {\n const strict = ConnectorDynamicResultV1Schema.safeParse(result)\n\n // ── Tier 3 : unreadable ───────────────────────────────────\n if (!strict.success) {\n const lenient = LenientResultSchema.safeParse(result)\n if (!lenient.success) {\n const version =\n result && typeof result === 'object'\n ? (result as { schemaVersion?: unknown }).schemaVersion\n : undefined\n return {\n id: 'connector-degraded',\n components: [\n degradedTextComponent(\n 'connector-degraded-notice',\n `### Résultat non rendu\\n\\nLe résultat du connecteur n'a pas pu être interprété${\n typeof version === 'string' ? ` (schéma : \\`${version}\\`)` : ''\n }. Cet état explicite remplace une disparition silencieuse du rendu.`\n ),\n ],\n grid: { ...DEFAULT_GRID },\n }\n }\n // ── Tier 2 : usable envelope, unknown version ───────────\n const r = lenient.data\n const components: UIComponent[] = [\n degradedTextComponent(\n 'connector-version-warning',\n `> ⚠ Schéma connecteur non reconnu (\\`${r.schemaVersion}\\`, attendu \\`${CONNECTOR_DYNAMIC_RESULT_V1}\\`). Le rendu ci-dessous est en mode dégradé.`\n ),\n ...collectComponents(r),\n ]\n return {\n id: options.id ?? `connector-degraded-${r.connectorId}`,\n components: components.map(withPosition),\n grid: { ...DEFAULT_GRID },\n }\n }\n\n // ── Tier 1 : valid v1 ─────────────────────────────────────\n const r = strict.data\n const components = collectComponents(r, options.actionsTitle).map(withPosition)\n return {\n id: options.id ?? layoutId(r.connectorId, r.queryHash ?? r.toolName),\n components,\n grid: { ...DEFAULT_GRID },\n }\n}\n\n/**\n * Flattens `primary` + `supplemental` + `actions` into a component list.\n * Shared by the valid and degraded-but-usable paths.\n */\nfunction collectComponents(\n r: {\n primary: Record<string, unknown>\n supplemental?: Record<string, unknown>[]\n actions?: unknown[]\n },\n actionsTitle?: string\n): UIComponent[] {\n const components: UIComponent[] = []\n\n if (isLayoutShape(r.primary)) {\n const inner = (r.primary as { components?: UIComponent[] }).components ?? []\n components.push(...inner)\n } else {\n components.push(r.primary as unknown as UIComponent)\n }\n\n if (r.supplemental) {\n components.push(...(r.supplemental as unknown as UIComponent[]))\n }\n\n if (Array.isArray(r.actions) && r.actions.length > 0) {\n components.push(\n connectorActionsToActionGroup(r.actions as ConnectorAction[], {\n id: 'connector-actions',\n title: actionsTitle,\n })\n )\n }\n\n return components\n}\n\nfunction layoutId(connectorId: string, suffix: string): string {\n return `connector-${connectorId}-${suffix}`\n}\n"],"names":["z.object","z.string","z.record","z.unknown","z.array","r","components"],"mappings":";;AAuDO,SAAS,8BACd,SACA,UAAgD,IACnC;AACb,SAAO;AAAA,IACL,IAAI,QAAQ,MAAM;AAAA,IAClB,MAAM;AAAA,IACN,UAAU,QAAQ,YAAY,EAAE,UAAU,GAAG,SAAS,GAAA;AAAA,IACtD,QAAQ;AAAA,MACN;AAAA,MACA,GAAI,QAAQ,QAAQ,EAAE,OAAO,QAAQ,MAAA,IAAU,CAAA;AAAA,MAC/C,QAAQ,QAAQ,UAAU;AAAA,MAC1B,KAAK,QAAQ,OAAO;AAAA,IAAA;AAAA,EACtB;AAEJ;AAmBA,MAAM,sBAAsBA,WAAS;AAAA,EACnC,eAAeC,WAAE;AAAA,EACjB,aAAaA,WAAE,EAAS,IAAI,CAAC;AAAA,EAC7B,UAAUA,WAAE,EAAS,IAAI,CAAC;AAAA,EAC1B,OAAOA,WAAE;AAAA,EACT,WAAWA,WAAE,EAAS,SAAA;AAAA,EACtB,QAAQA,WAAE,EAAS,SAAA;AAAA,EACnB,SAASC,WAASC,aAAW;AAAA,EAC7B,cAAcC,UAAQF,WAASC,YAAE,CAAS,CAAC,EAAE,SAAA;AAAA,EAC7C,SAASC,UAAQF,WAASC,YAAE,CAAS,CAAC,EAAE,SAAA;AAC1C,CAAC;AAED,MAAM,eAAe,EAAE,SAAS,IAAI,KAAK,OAAA;AAEzC,SAAS,aAAa,WAAqC;AACzD,MAAI,aAAa,UAAU,SAAU,QAAO;AAC5C,SAAO,EAAE,GAAG,WAAW,UAAU,EAAE,UAAU,GAAG,SAAS,KAAG;AAC9D;AAGA,SAAS,cAAc,OAAyC;AAC9D,SAAO,MAAM,QAAS,MAAmC,UAAU;AACrE;AAEA,SAAS,sBAAsB,IAAY,SAA8B;AACvE,SAAO;AAAA,IACL;AAAA,IACA,MAAM;AAAA,IACN,UAAU,EAAE,UAAU,GAAG,SAAS,GAAA;AAAA,IAClC,QAAQ,EAAE,UAAU,MAAM,SAAS,QAAA;AAAA,EAAQ;AAE/C;AAqBO,SAAS,0BACd,QACA,UAA4C,IAClC;AACV,QAAM,SAAS,+BAA+B,UAAU,MAAM;AAG9D,MAAI,CAAC,OAAO,SAAS;AACnB,UAAM,UAAU,oBAAoB,UAAU,MAAM;AACpD,QAAI,CAAC,QAAQ,SAAS;AACpB,YAAM,UACJ,UAAU,OAAO,WAAW,WACvB,OAAuC,gBACxC;AACN,aAAO;AAAA,QACL,IAAI;AAAA,QACJ,YAAY;AAAA,UACV;AAAA,YACE;AAAA,YACA;AAAA;AAAA,sDACE,OAAO,YAAY,WAAW,gBAAgB,OAAO,QAAQ,EAC/D;AAAA,UAAA;AAAA,QACF;AAAA,QAEF,MAAM,EAAE,GAAG,aAAA;AAAA,MAAa;AAAA,IAE5B;AAEA,UAAME,KAAI,QAAQ;AAClB,UAAMC,cAA4B;AAAA,MAChC;AAAA,QACE;AAAA,QACA,wCAAwCD,GAAE,aAAa,iBAAiB,2BAA2B;AAAA,MAAA;AAAA,MAErG,GAAG,kBAAkBA,EAAC;AAAA,IAAA;AAExB,WAAO;AAAA,MACL,IAAI,QAAQ,MAAM,sBAAsBA,GAAE,WAAW;AAAA,MACrD,YAAYC,YAAW,IAAI,YAAY;AAAA,MACvC,MAAM,EAAE,GAAG,aAAA;AAAA,IAAa;AAAA,EAE5B;AAGA,QAAM,IAAI,OAAO;AACjB,QAAM,aAAa,kBAAkB,GAAG,QAAQ,YAAY,EAAE,IAAI,YAAY;AAC9E,SAAO;AAAA,IACL,IAAI,QAAQ,MAAM,SAAS,EAAE,aAAa,EAAE,aAAa,EAAE,QAAQ;AAAA,IACnE;AAAA,IACA,MAAM,EAAE,GAAG,aAAA;AAAA,EAAa;AAE5B;AAMA,SAAS,kBACP,GAKA,cACe;AACf,QAAM,aAA4B,CAAA;AAElC,MAAI,cAAc,EAAE,OAAO,GAAG;AAC5B,UAAM,QAAS,EAAE,QAA2C,cAAc,CAAA;AAC1E,eAAW,KAAK,GAAG,KAAK;AAAA,EAC1B,OAAO;AACL,eAAW,KAAK,EAAE,OAAiC;AAAA,EACrD;AAEA,MAAI,EAAE,cAAc;AAClB,eAAW,KAAK,GAAI,EAAE,YAAyC;AAAA,EACjE;AAEA,MAAI,MAAM,QAAQ,EAAE,OAAO,KAAK,EAAE,QAAQ,SAAS,GAAG;AACpD,eAAW;AAAA,MACT,8BAA8B,EAAE,SAA8B;AAAA,QAC5D,IAAI;AAAA,QACJ,OAAO;AAAA,MAAA,CACR;AAAA,IAAA;AAAA,EAEL;AAEA,SAAO;AACT;AAEA,SAAS,SAAS,aAAqB,QAAwB;AAC7D,SAAO,aAAa,WAAW,IAAI,MAAM;AAC3C;"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * @seed-ship/mcp-ui-solid/adapters
3
+ *
4
+ * Opt-in, pure adapters that turn connector / macro contracts into MCP-UI
5
+ * render structures. Published as a dedicated subpath so the core renderer
6
+ * path never depends on them.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * import { connectorResultToUILayout } from '@seed-ship/mcp-ui-solid/adapters'
11
+ *
12
+ * const layout = connectorResultToUILayout(connectorResult)
13
+ * // → <UIResourceRenderer content={layout} />
14
+ * ```
15
+ */
16
+ export { connectorResultToUILayout, connectorActionsToActionGroup, } from './connector';
17
+ export type { ConnectorResultToUILayoutOptions, ConnectorActionsToActionGroupOptions, } from './connector';
18
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/adapters/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EACL,yBAAyB,EACzB,6BAA6B,GAC9B,MAAM,aAAa,CAAA;AACpB,YAAY,EACV,gCAAgC,EAChC,oCAAoC,GACrC,MAAM,aAAa,CAAA"}