@tai42/studio-sdk 0.2.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 (422) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +15 -0
  3. package/README.md +318 -0
  4. package/SECURITY.md +51 -0
  5. package/dist/components/badge.d.ts +13 -0
  6. package/dist/components/badge.d.ts.map +1 -0
  7. package/dist/components/badge.js +14 -0
  8. package/dist/components/badge.js.map +1 -0
  9. package/dist/components/checkbox.d.ts +24 -0
  10. package/dist/components/checkbox.d.ts.map +1 -0
  11. package/dist/components/checkbox.js +31 -0
  12. package/dist/components/checkbox.js.map +1 -0
  13. package/dist/components/code-block.d.ts +7 -0
  14. package/dist/components/code-block.d.ts.map +1 -0
  15. package/dist/components/code-block.js +23 -0
  16. package/dist/components/code-block.js.map +1 -0
  17. package/dist/components/completion-input.d.ts +31 -0
  18. package/dist/components/completion-input.d.ts.map +1 -0
  19. package/dist/components/completion-input.js +61 -0
  20. package/dist/components/completion-input.js.map +1 -0
  21. package/dist/components/components.css +2212 -0
  22. package/dist/components/confirm-dialog.d.ts +40 -0
  23. package/dist/components/confirm-dialog.d.ts.map +1 -0
  24. package/dist/components/confirm-dialog.js +11 -0
  25. package/dist/components/confirm-dialog.js.map +1 -0
  26. package/dist/components/control-styles.d.ts +12 -0
  27. package/dist/components/control-styles.d.ts.map +1 -0
  28. package/dist/components/control-styles.js +14 -0
  29. package/dist/components/control-styles.js.map +1 -0
  30. package/dist/components/copy-field.d.ts +8 -0
  31. package/dist/components/copy-field.d.ts.map +1 -0
  32. package/dist/components/copy-field.js +106 -0
  33. package/dist/components/copy-field.js.map +1 -0
  34. package/dist/components/dialog.d.ts +21 -0
  35. package/dist/components/dialog.d.ts.map +1 -0
  36. package/dist/components/dialog.js +25 -0
  37. package/dist/components/dialog.js.map +1 -0
  38. package/dist/components/drawer.d.ts +22 -0
  39. package/dist/components/drawer.d.ts.map +1 -0
  40. package/dist/components/drawer.js +32 -0
  41. package/dist/components/drawer.js.map +1 -0
  42. package/dist/components/error-boundary.d.ts +33 -0
  43. package/dist/components/error-boundary.d.ts.map +1 -0
  44. package/dist/components/error-boundary.js +36 -0
  45. package/dist/components/error-boundary.js.map +1 -0
  46. package/dist/components/extension-combo-builder.d.ts +70 -0
  47. package/dist/components/extension-combo-builder.d.ts.map +1 -0
  48. package/dist/components/extension-combo-builder.js +154 -0
  49. package/dist/components/extension-combo-builder.js.map +1 -0
  50. package/dist/components/extension-grouping.d.ts +65 -0
  51. package/dist/components/extension-grouping.d.ts.map +1 -0
  52. package/dist/components/extension-grouping.js +78 -0
  53. package/dist/components/extension-grouping.js.map +1 -0
  54. package/dist/components/extension-picker.d.ts +35 -0
  55. package/dist/components/extension-picker.d.ts.map +1 -0
  56. package/dist/components/extension-picker.js +31 -0
  57. package/dist/components/extension-picker.js.map +1 -0
  58. package/dist/components/external-link.d.ts +19 -0
  59. package/dist/components/external-link.d.ts.map +1 -0
  60. package/dist/components/external-link.js +12 -0
  61. package/dist/components/external-link.js.map +1 -0
  62. package/dist/components/field.d.ts +64 -0
  63. package/dist/components/field.d.ts.map +1 -0
  64. package/dist/components/field.js +56 -0
  65. package/dist/components/field.js.map +1 -0
  66. package/dist/components/fleet-report.d.ts +39 -0
  67. package/dist/components/fleet-report.d.ts.map +1 -0
  68. package/dist/components/fleet-report.js +50 -0
  69. package/dist/components/fleet-report.js.map +1 -0
  70. package/dist/components/fonts.css +22 -0
  71. package/dist/components/icons.d.ts +142 -0
  72. package/dist/components/icons.d.ts.map +1 -0
  73. package/dist/components/icons.js +139 -0
  74. package/dist/components/icons.js.map +1 -0
  75. package/dist/components/inputs.d.ts +52 -0
  76. package/dist/components/inputs.d.ts.map +1 -0
  77. package/dist/components/inputs.js +35 -0
  78. package/dist/components/inputs.js.map +1 -0
  79. package/dist/components/json-diff.d.ts +40 -0
  80. package/dist/components/json-diff.d.ts.map +1 -0
  81. package/dist/components/json-diff.js +119 -0
  82. package/dist/components/json-diff.js.map +1 -0
  83. package/dist/components/json-tree.d.ts +8 -0
  84. package/dist/components/json-tree.d.ts.map +1 -0
  85. package/dist/components/json-tree.js +70 -0
  86. package/dist/components/json-tree.js.map +1 -0
  87. package/dist/components/modal-focus.d.ts +15 -0
  88. package/dist/components/modal-focus.d.ts.map +1 -0
  89. package/dist/components/modal-focus.js +136 -0
  90. package/dist/components/modal-focus.js.map +1 -0
  91. package/dist/components/page-header.d.ts +56 -0
  92. package/dist/components/page-header.d.ts.map +1 -0
  93. package/dist/components/page-header.js +24 -0
  94. package/dist/components/page-header.js.map +1 -0
  95. package/dist/components/primitives.d.ts +186 -0
  96. package/dist/components/primitives.d.ts.map +1 -0
  97. package/dist/components/primitives.js +204 -0
  98. package/dist/components/primitives.js.map +1 -0
  99. package/dist/components/progress-bar.d.ts +29 -0
  100. package/dist/components/progress-bar.d.ts.map +1 -0
  101. package/dist/components/progress-bar.js +48 -0
  102. package/dist/components/progress-bar.js.map +1 -0
  103. package/dist/components/radio-group.d.ts +37 -0
  104. package/dist/components/radio-group.d.ts.map +1 -0
  105. package/dist/components/radio-group.js +56 -0
  106. package/dist/components/radio-group.js.map +1 -0
  107. package/dist/components/reveal-input.d.ts +13 -0
  108. package/dist/components/reveal-input.d.ts.map +1 -0
  109. package/dist/components/reveal-input.js +31 -0
  110. package/dist/components/reveal-input.js.map +1 -0
  111. package/dist/components/scroll-region.d.ts +85 -0
  112. package/dist/components/scroll-region.d.ts.map +1 -0
  113. package/dist/components/scroll-region.js +574 -0
  114. package/dist/components/scroll-region.js.map +1 -0
  115. package/dist/components/select.d.ts +53 -0
  116. package/dist/components/select.d.ts.map +1 -0
  117. package/dist/components/select.js +33 -0
  118. package/dist/components/select.js.map +1 -0
  119. package/dist/components/table.d.ts +59 -0
  120. package/dist/components/table.d.ts.map +1 -0
  121. package/dist/components/table.js +20 -0
  122. package/dist/components/table.js.map +1 -0
  123. package/dist/components/tabs.d.ts +15 -0
  124. package/dist/components/tabs.d.ts.map +1 -0
  125. package/dist/components/tabs.js +13 -0
  126. package/dist/components/tabs.js.map +1 -0
  127. package/dist/components/tags.d.ts +46 -0
  128. package/dist/components/tags.d.ts.map +1 -0
  129. package/dist/components/tags.js +68 -0
  130. package/dist/components/tags.js.map +1 -0
  131. package/dist/components/tokens.css +392 -0
  132. package/dist/components/tokens.d.ts +15 -0
  133. package/dist/components/tokens.d.ts.map +1 -0
  134. package/dist/components/tokens.js +101 -0
  135. package/dist/components/tokens.js.map +1 -0
  136. package/dist/components/tool-picker.d.ts +24 -0
  137. package/dist/components/tool-picker.d.ts.map +1 -0
  138. package/dist/components/tool-picker.js +97 -0
  139. package/dist/components/tool-picker.js.map +1 -0
  140. package/dist/components/tooltip.d.ts +17 -0
  141. package/dist/components/tooltip.d.ts.map +1 -0
  142. package/dist/components/tooltip.js +18 -0
  143. package/dist/components/tooltip.js.map +1 -0
  144. package/dist/components/version-history-panel.d.ts +71 -0
  145. package/dist/components/version-history-panel.d.ts.map +1 -0
  146. package/dist/components/version-history-panel.js +148 -0
  147. package/dist/components/version-history-panel.js.map +1 -0
  148. package/dist/download.d.ts +7 -0
  149. package/dist/download.d.ts.map +1 -0
  150. package/dist/download.js +18 -0
  151. package/dist/download.js.map +1 -0
  152. package/dist/element-slot.d.ts +4 -0
  153. package/dist/element-slot.d.ts.map +1 -0
  154. package/dist/element-slot.js +20 -0
  155. package/dist/element-slot.js.map +1 -0
  156. package/dist/elicitation/ElicitationForm.d.ts +34 -0
  157. package/dist/elicitation/ElicitationForm.d.ts.map +1 -0
  158. package/dist/elicitation/ElicitationForm.js +24 -0
  159. package/dist/elicitation/ElicitationForm.js.map +1 -0
  160. package/dist/errors.d.ts +9 -0
  161. package/dist/errors.d.ts.map +1 -0
  162. package/dist/errors.js +15 -0
  163. package/dist/errors.js.map +1 -0
  164. package/dist/extension-combos.d.ts +13 -0
  165. package/dist/extension-combos.d.ts.map +1 -0
  166. package/dist/extension-combos.js +9 -0
  167. package/dist/extension-combos.js.map +1 -0
  168. package/dist/hooks/useApi.d.ts +1222 -0
  169. package/dist/hooks/useApi.d.ts.map +1 -0
  170. package/dist/hooks/useApi.js +22 -0
  171. package/dist/hooks/useApi.js.map +1 -0
  172. package/dist/hooks/useAuth.d.ts +13 -0
  173. package/dist/hooks/useAuth.d.ts.map +1 -0
  174. package/dist/hooks/useAuth.js +53 -0
  175. package/dist/hooks/useAuth.js.map +1 -0
  176. package/dist/hooks/useBreakpoint.d.ts +10 -0
  177. package/dist/hooks/useBreakpoint.d.ts.map +1 -0
  178. package/dist/hooks/useBreakpoint.js +66 -0
  179. package/dist/hooks/useBreakpoint.js.map +1 -0
  180. package/dist/hooks/useCapabilities.d.ts +88 -0
  181. package/dist/hooks/useCapabilities.d.ts.map +1 -0
  182. package/dist/hooks/useCapabilities.js +203 -0
  183. package/dist/hooks/useCapabilities.js.map +1 -0
  184. package/dist/hooks/useSse.d.ts +13 -0
  185. package/dist/hooks/useSse.d.ts.map +1 -0
  186. package/dist/hooks/useSse.js +298 -0
  187. package/dist/hooks/useSse.js.map +1 -0
  188. package/dist/hooks/useTheme.d.ts +20 -0
  189. package/dist/hooks/useTheme.d.ts.map +1 -0
  190. package/dist/hooks/useTheme.js +97 -0
  191. package/dist/hooks/useTheme.js.map +1 -0
  192. package/dist/hooks/useUnauthorized.d.ts +3 -0
  193. package/dist/hooks/useUnauthorized.d.ts.map +1 -0
  194. package/dist/hooks/useUnauthorized.js +18 -0
  195. package/dist/hooks/useUnauthorized.js.map +1 -0
  196. package/dist/host.d.ts +11 -0
  197. package/dist/host.d.ts.map +1 -0
  198. package/dist/host.js +10 -0
  199. package/dist/host.js.map +1 -0
  200. package/dist/index.d.ts +118 -0
  201. package/dist/index.d.ts.map +1 -0
  202. package/dist/index.js +91 -0
  203. package/dist/index.js.map +1 -0
  204. package/dist/navigation/context.d.ts +25 -0
  205. package/dist/navigation/context.d.ts.map +1 -0
  206. package/dist/navigation/context.js +45 -0
  207. package/dist/navigation/context.js.map +1 -0
  208. package/dist/navigation/index.d.ts +4 -0
  209. package/dist/navigation/index.d.ts.map +1 -0
  210. package/dist/navigation/index.js +2 -0
  211. package/dist/navigation/index.js.map +1 -0
  212. package/dist/navigation/types.d.ts +97 -0
  213. package/dist/navigation/types.d.ts.map +1 -0
  214. package/dist/navigation/types.js +16 -0
  215. package/dist/navigation/types.js.map +1 -0
  216. package/dist/plugin/host-state.d.ts +45 -0
  217. package/dist/plugin/host-state.d.ts.map +1 -0
  218. package/dist/plugin/host-state.js +62 -0
  219. package/dist/plugin/host-state.js.map +1 -0
  220. package/dist/plugin/registry.d.ts +35 -0
  221. package/dist/plugin/registry.d.ts.map +1 -0
  222. package/dist/plugin/registry.js +129 -0
  223. package/dist/plugin/registry.js.map +1 -0
  224. package/dist/plugin/types.d.ts +165 -0
  225. package/dist/plugin/types.d.ts.map +1 -0
  226. package/dist/plugin/types.js +2 -0
  227. package/dist/plugin/types.js.map +1 -0
  228. package/dist/plugin/version.d.ts +34 -0
  229. package/dist/plugin/version.d.ts.map +1 -0
  230. package/dist/plugin/version.js +37 -0
  231. package/dist/plugin/version.js.map +1 -0
  232. package/dist/query-keys.d.ts +31 -0
  233. package/dist/query-keys.d.ts.map +1 -0
  234. package/dist/query-keys.js +31 -0
  235. package/dist/query-keys.js.map +1 -0
  236. package/dist/schema-editor/SchemaEditor.d.ts +43 -0
  237. package/dist/schema-editor/SchemaEditor.d.ts.map +1 -0
  238. package/dist/schema-editor/SchemaEditor.js +106 -0
  239. package/dist/schema-editor/SchemaEditor.js.map +1 -0
  240. package/dist/schema-editor/index.d.ts +10 -0
  241. package/dist/schema-editor/index.d.ts.map +1 -0
  242. package/dist/schema-editor/index.js +8 -0
  243. package/dist/schema-editor/index.js.map +1 -0
  244. package/dist/schema-editor/lint.d.ts +37 -0
  245. package/dist/schema-editor/lint.d.ts.map +1 -0
  246. package/dist/schema-editor/lint.js +49 -0
  247. package/dist/schema-editor/lint.js.map +1 -0
  248. package/dist/schema-form/SchemaForm.d.ts +70 -0
  249. package/dist/schema-form/SchemaForm.d.ts.map +1 -0
  250. package/dist/schema-form/SchemaForm.js +13 -0
  251. package/dist/schema-form/SchemaForm.js.map +1 -0
  252. package/dist/schema-form/array-field.d.ts +20 -0
  253. package/dist/schema-form/array-field.d.ts.map +1 -0
  254. package/dist/schema-form/array-field.js +39 -0
  255. package/dist/schema-form/array-field.js.map +1 -0
  256. package/dist/schema-form/classify.d.ts +87 -0
  257. package/dist/schema-form/classify.d.ts.map +1 -0
  258. package/dist/schema-form/classify.js +199 -0
  259. package/dist/schema-form/classify.js.map +1 -0
  260. package/dist/schema-form/context.d.ts +4 -0
  261. package/dist/schema-form/context.d.ts.map +1 -0
  262. package/dist/schema-form/context.js +14 -0
  263. package/dist/schema-form/context.js.map +1 -0
  264. package/dist/schema-form/default-value.d.ts +8 -0
  265. package/dist/schema-form/default-value.d.ts.map +1 -0
  266. package/dist/schema-form/default-value.js +64 -0
  267. package/dist/schema-form/default-value.js.map +1 -0
  268. package/dist/schema-form/enum-field.d.ts +19 -0
  269. package/dist/schema-form/enum-field.d.ts.map +1 -0
  270. package/dist/schema-form/enum-field.js +26 -0
  271. package/dist/schema-form/enum-field.js.map +1 -0
  272. package/dist/schema-form/field-group.d.ts +9 -0
  273. package/dist/schema-form/field-group.d.ts.map +1 -0
  274. package/dist/schema-form/field-group.js +32 -0
  275. package/dist/schema-form/field-group.js.map +1 -0
  276. package/dist/schema-form/field-node.d.ts +24 -0
  277. package/dist/schema-form/field-node.d.ts.map +1 -0
  278. package/dist/schema-form/field-node.js +51 -0
  279. package/dist/schema-form/field-node.js.map +1 -0
  280. package/dist/schema-form/index.d.ts +13 -0
  281. package/dist/schema-form/index.d.ts.map +1 -0
  282. package/dist/schema-form/index.js +11 -0
  283. package/dist/schema-form/index.js.map +1 -0
  284. package/dist/schema-form/media-field.d.ts +24 -0
  285. package/dist/schema-form/media-field.d.ts.map +1 -0
  286. package/dist/schema-form/media-field.js +169 -0
  287. package/dist/schema-form/media-field.js.map +1 -0
  288. package/dist/schema-form/media.d.ts +40 -0
  289. package/dist/schema-form/media.d.ts.map +1 -0
  290. package/dist/schema-form/media.js +65 -0
  291. package/dist/schema-form/media.js.map +1 -0
  292. package/dist/schema-form/object-fields.d.ts +20 -0
  293. package/dist/schema-form/object-fields.d.ts.map +1 -0
  294. package/dist/schema-form/object-fields.js +24 -0
  295. package/dist/schema-form/object-fields.js.map +1 -0
  296. package/dist/schema-form/resolve.d.ts +41 -0
  297. package/dist/schema-form/resolve.d.ts.map +1 -0
  298. package/dist/schema-form/resolve.js +102 -0
  299. package/dist/schema-form/resolve.js.map +1 -0
  300. package/dist/schema-form/string-field.d.ts +20 -0
  301. package/dist/schema-form/string-field.d.ts.map +1 -0
  302. package/dist/schema-form/string-field.js +68 -0
  303. package/dist/schema-form/string-field.js.map +1 -0
  304. package/dist/schema-form/styles.d.ts +18 -0
  305. package/dist/schema-form/styles.d.ts.map +1 -0
  306. package/dist/schema-form/styles.js +18 -0
  307. package/dist/schema-form/styles.js.map +1 -0
  308. package/dist/schema-form/types.d.ts +67 -0
  309. package/dist/schema-form/types.d.ts.map +1 -0
  310. package/dist/schema-form/types.js +10 -0
  311. package/dist/schema-form/types.js.map +1 -0
  312. package/dist/schema-form/union-field.d.ts +24 -0
  313. package/dist/schema-form/union-field.d.ts.map +1 -0
  314. package/dist/schema-form/union-field.js +55 -0
  315. package/dist/schema-form/union-field.js.map +1 -0
  316. package/dist/schema-form/union.d.ts +4 -0
  317. package/dist/schema-form/union.d.ts.map +1 -0
  318. package/dist/schema-form/union.js +68 -0
  319. package/dist/schema-form/union.js.map +1 -0
  320. package/dist/schema-form/unsupported-notice.d.ts +18 -0
  321. package/dist/schema-form/unsupported-notice.d.ts.map +1 -0
  322. package/dist/schema-form/unsupported-notice.js +8 -0
  323. package/dist/schema-form/unsupported-notice.js.map +1 -0
  324. package/dist/schema-form/validate.d.ts +18 -0
  325. package/dist/schema-form/validate.d.ts.map +1 -0
  326. package/dist/schema-form/validate.js +162 -0
  327. package/dist/schema-form/validate.js.map +1 -0
  328. package/dist/structured-output/StructuredOutput.d.ts +27 -0
  329. package/dist/structured-output/StructuredOutput.d.ts.map +1 -0
  330. package/dist/structured-output/StructuredOutput.js +41 -0
  331. package/dist/structured-output/StructuredOutput.js.map +1 -0
  332. package/dist/testing.d.ts +80 -0
  333. package/dist/testing.d.ts.map +1 -0
  334. package/dist/testing.js +207 -0
  335. package/dist/testing.js.map +1 -0
  336. package/package.json +101 -0
  337. package/src/components/badge.tsx +31 -0
  338. package/src/components/checkbox.tsx +93 -0
  339. package/src/components/code-block.tsx +37 -0
  340. package/src/components/completion-input.tsx +137 -0
  341. package/src/components/components.css +2212 -0
  342. package/src/components/confirm-dialog.tsx +77 -0
  343. package/src/components/control-styles.ts +14 -0
  344. package/src/components/copy-field.tsx +178 -0
  345. package/src/components/dialog.tsx +76 -0
  346. package/src/components/drawer.tsx +83 -0
  347. package/src/components/error-boundary.tsx +56 -0
  348. package/src/components/extension-combo-builder.tsx +305 -0
  349. package/src/components/extension-grouping.ts +116 -0
  350. package/src/components/extension-picker.tsx +97 -0
  351. package/src/components/external-link.tsx +40 -0
  352. package/src/components/field.tsx +169 -0
  353. package/src/components/fleet-report.tsx +136 -0
  354. package/src/components/fonts.css +22 -0
  355. package/src/components/icons.tsx +504 -0
  356. package/src/components/inputs.tsx +125 -0
  357. package/src/components/json-diff.tsx +192 -0
  358. package/src/components/json-tree.tsx +129 -0
  359. package/src/components/modal-focus.ts +150 -0
  360. package/src/components/page-header.tsx +107 -0
  361. package/src/components/primitives.tsx +489 -0
  362. package/src/components/progress-bar.tsx +100 -0
  363. package/src/components/radio-group.tsx +160 -0
  364. package/src/components/reveal-input.tsx +87 -0
  365. package/src/components/scroll-region.tsx +703 -0
  366. package/src/components/select.tsx +149 -0
  367. package/src/components/table.tsx +94 -0
  368. package/src/components/tabs.tsx +51 -0
  369. package/src/components/tags.tsx +137 -0
  370. package/src/components/tokens.css +392 -0
  371. package/src/components/tokens.ts +102 -0
  372. package/src/components/tool-picker.tsx +175 -0
  373. package/src/components/tooltip.tsx +46 -0
  374. package/src/components/version-history-panel.tsx +407 -0
  375. package/src/css.d.ts +2 -0
  376. package/src/download.ts +17 -0
  377. package/src/element-slot.ts +22 -0
  378. package/src/elicitation/ElicitationForm.tsx +87 -0
  379. package/src/errors.ts +12 -0
  380. package/src/extension-combos.ts +18 -0
  381. package/src/hooks/useApi.ts +27 -0
  382. package/src/hooks/useAuth.ts +67 -0
  383. package/src/hooks/useBreakpoint.ts +78 -0
  384. package/src/hooks/useCapabilities.ts +242 -0
  385. package/src/hooks/useSse.ts +314 -0
  386. package/src/hooks/useTheme.ts +133 -0
  387. package/src/hooks/useUnauthorized.ts +20 -0
  388. package/src/host.ts +15 -0
  389. package/src/index.ts +267 -0
  390. package/src/navigation/context.tsx +90 -0
  391. package/src/navigation/index.ts +9 -0
  392. package/src/navigation/types.ts +90 -0
  393. package/src/plugin/host-state.ts +93 -0
  394. package/src/plugin/registry.ts +174 -0
  395. package/src/plugin/types.ts +178 -0
  396. package/src/plugin/version.ts +44 -0
  397. package/src/query-keys.ts +34 -0
  398. package/src/schema-editor/SchemaEditor.tsx +186 -0
  399. package/src/schema-editor/index.ts +9 -0
  400. package/src/schema-editor/lint.ts +70 -0
  401. package/src/schema-form/SchemaForm.tsx +107 -0
  402. package/src/schema-form/array-field.tsx +110 -0
  403. package/src/schema-form/classify.ts +302 -0
  404. package/src/schema-form/context.ts +17 -0
  405. package/src/schema-form/default-value.ts +65 -0
  406. package/src/schema-form/enum-field.tsx +65 -0
  407. package/src/schema-form/field-group.tsx +71 -0
  408. package/src/schema-form/field-node.tsx +161 -0
  409. package/src/schema-form/index.ts +12 -0
  410. package/src/schema-form/media-field.tsx +281 -0
  411. package/src/schema-form/media.ts +69 -0
  412. package/src/schema-form/object-fields.tsx +74 -0
  413. package/src/schema-form/resolve.ts +114 -0
  414. package/src/schema-form/string-field.tsx +161 -0
  415. package/src/schema-form/styles.ts +20 -0
  416. package/src/schema-form/types.ts +71 -0
  417. package/src/schema-form/union-field.tsx +122 -0
  418. package/src/schema-form/union.ts +78 -0
  419. package/src/schema-form/unsupported-notice.tsx +39 -0
  420. package/src/schema-form/validate.ts +183 -0
  421. package/src/structured-output/StructuredOutput.tsx +81 -0
  422. package/src/testing.ts +235 -0
@@ -0,0 +1,46 @@
1
+ /**
2
+ * `Tooltip` — a design-system wrapper over Radix Tooltip (content gets role
3
+ * `tooltip`, shown on hover/focus, wired to the trigger by Radix). Each instance
4
+ * carries its own Provider so a single tooltip works without the caller mounting
5
+ * one, and `delayDuration` tunes that Provider's open delay.
6
+ *
7
+ * The bubble is the `tai-tooltip` surface; the arrow is the one paint the class
8
+ * layer cannot reach (Radix renders it as an inline SVG), so it takes the same
9
+ * raised-surface token the bubble's background resolves from.
10
+ */
11
+ import * as RadixTooltip from '@radix-ui/react-tooltip';
12
+ import type { ReactElement, ReactNode } from 'react';
13
+
14
+ import { assertSlotElement } from '../element-slot';
15
+
16
+ export interface TooltipProps {
17
+ readonly content: ReactNode;
18
+ /**
19
+ * The element the bubble describes and hangs off.
20
+ *
21
+ * It is a single ELEMENT, not any node: Radix clones its props onto it, so a
22
+ * string throws. The type admits a FRAGMENT — `<></>` is a `ReactElement` —
23
+ * which would render children carrying none of the hover/focus wiring nor the
24
+ * `aria-describedby` back to the bubble, so {@link assertSlotElement} rejects
25
+ * that one at runtime.
26
+ */
27
+ readonly children: ReactElement;
28
+ readonly delayDuration?: number;
29
+ }
30
+
31
+ export function Tooltip({ content, children, delayDuration = 200 }: TooltipProps) {
32
+ assertSlotElement(children, 'Tooltip `children`');
33
+ return (
34
+ <RadixTooltip.Provider delayDuration={delayDuration}>
35
+ <RadixTooltip.Root>
36
+ <RadixTooltip.Trigger asChild>{children}</RadixTooltip.Trigger>
37
+ <RadixTooltip.Portal>
38
+ <RadixTooltip.Content sideOffset={4} className="tai-tooltip">
39
+ {content}
40
+ <RadixTooltip.Arrow style={{ fill: 'var(--tai-color-surface-raised)' }} />
41
+ </RadixTooltip.Content>
42
+ </RadixTooltip.Portal>
43
+ </RadixTooltip.Root>
44
+ </RadixTooltip.Provider>
45
+ );
46
+ }
@@ -0,0 +1,407 @@
1
+ /**
2
+ * `VersionHistoryPanel` — a GENERIC, kind-agnostic viewer for a versioned
3
+ * document's history. It knows nothing about presets, AC policies, or authored
4
+ * agents: it takes a plain versions list (`{version, body, is_current,
5
+ * created_at}`, optionally `tags`) plus a `onRollback` callback, renders the list
6
+ * (a "Current" status mark driven by `is_current`), a `JsonTree` view of the selected
7
+ * version's opaque body, a per-row Rollback action behind a confirm dialog, and a
8
+ * per-row Compare action that opens a structural `JsonDiff` of two versions' bodies.
9
+ * Each consumer (e.g. presets, AC policies) wires it to its own routes.
10
+ *
11
+ * Two optional affordances, inert unless supplied:
12
+ * - a Tags column appears when any entry carries `tags` OR `onEditTags` is given
13
+ * (so tag-less consumers see no empty column);
14
+ * - when `onEditTags` is provided, a per-row "Edit tags" action opens a small
15
+ * dialog seeded from the row's tags; SAVE calls the callback with the NEW list
16
+ * and the panel awaits it with INTERNAL pending/error state, rendering a
17
+ * rejection's message verbatim.
18
+ *
19
+ * SAFETY: `body` and tags are arbitrary server JSON, rendered through `JsonTree` /
20
+ * `Badge` (React escapes every value) — never an HTML sink.
21
+ *
22
+ * Geometry and ink come from the design-system classes. Two rules shape the table:
23
+ * a row's state is a MARK plus a label (`tai-status` + `CheckCircleIcon` /
24
+ * `PendingIcon`), never color alone, and the version number and timestamp are the
25
+ * machine voice (`tai-table-id` / `tai-mono`). The table itself sits in a
26
+ * `ScrollRegion` so a narrow viewport scrolls it instead of the page.
27
+ */
28
+ import { useEffect, useRef, useState, type ReactNode } from 'react';
29
+
30
+ import { errorMessage } from '../errors';
31
+ import { Dialog } from './dialog';
32
+ import { Field } from './field';
33
+ import { CheckCircleIcon, PendingIcon } from './icons';
34
+ import { JsonDiff } from './json-diff';
35
+ import { JsonTree } from './json-tree';
36
+ import { Button, Card, ErrorState, Spinner } from './primitives';
37
+ import { ScrollRegion } from './scroll-region';
38
+ import { Table, TBody, TD, TH, THead, TR } from './table';
39
+ import { TagChips, TagsInput } from './tags';
40
+
41
+ /** One version row — the kind-agnostic shape every consumer projects into. */
42
+ export interface VersionHistoryEntry {
43
+ readonly version: number;
44
+ readonly body: unknown;
45
+ readonly is_current: boolean;
46
+ readonly created_at: string;
47
+ /** The version's generic label list, surfaced as a Tags column when present. */
48
+ readonly tags?: readonly string[];
49
+ }
50
+
51
+ export interface VersionHistoryPanelProps {
52
+ readonly versions: readonly VersionHistoryEntry[];
53
+ /** Roll the active pointer to `version`. The consumer owns the mutation. */
54
+ readonly onRollback: (version: number) => void;
55
+ /** Fired when a version is selected for viewing (optional). */
56
+ readonly onView?: (version: number) => void;
57
+ /** The consumer's rollback mutation is in flight. */
58
+ readonly rollbackPending?: boolean;
59
+ /** A loud rollback failure message to surface in the confirm dialog. */
60
+ readonly rollbackError?: string;
61
+ /**
62
+ * A read-only history view: the version list and each version's body stay
63
+ * visible (history is a read surface), but EVERY mutating action is hidden —
64
+ * the per-row Rollback and, where `onEditTags` is supplied, Edit tags.
65
+ * Defaults to interactive.
66
+ */
67
+ readonly readOnly?: boolean;
68
+ /**
69
+ * Extra consequence copy appended to the rollback confirm dialog's description
70
+ * (announced to assistive tech via the dialog's `aria-describedby`). Consumers
71
+ * whose rollback has an immediate live effect (e.g. re-pointing access-control
72
+ * enforcement) pass a sentence stating so; omitted when there is nothing extra.
73
+ */
74
+ readonly rollbackConfirmDescription?: string;
75
+ /**
76
+ * Replace one version's tag annotation. When provided, a per-row "Edit tags"
77
+ * action opens a dialog seeded from the row's tags and SAVE calls this with the
78
+ * NEW list; the panel awaits the returned promise with internal pending/error
79
+ * state. Omitted ⇒ no edit affordance (tags stay read-only).
80
+ */
81
+ readonly onEditTags?: (version: number, tags: string[]) => Promise<void>;
82
+ }
83
+
84
+ /** The initially-selected version: the current one, else the first listed. */
85
+ function initialSelection(versions: readonly VersionHistoryEntry[]): number | null {
86
+ const current = versions.find((entry) => entry.is_current);
87
+ return current?.version ?? versions[0]?.version ?? null;
88
+ }
89
+
90
+ /**
91
+ * The per-version tag editor dialog. Seeds a draft from the row's tags and calls
92
+ * `onSave` on submit, awaiting it with LOCAL pending/error state: a rejection's
93
+ * message renders verbatim and the dialog stays open; success closes it.
94
+ */
95
+ function EditTagsDialog({
96
+ version,
97
+ initialTags,
98
+ onSave,
99
+ onClose,
100
+ }: {
101
+ readonly version: number;
102
+ readonly initialTags: readonly string[];
103
+ readonly onSave: (version: number, tags: string[]) => Promise<void>;
104
+ readonly onClose: () => void;
105
+ }): ReactNode {
106
+ const [tags, setTags] = useState<string[]>([...initialTags]);
107
+ const [pending, setPending] = useState(false);
108
+ const [error, setError] = useState<string | undefined>(undefined);
109
+
110
+ const submit = (): void => {
111
+ setPending(true);
112
+ setError(undefined);
113
+ onSave(version, tags).then(
114
+ () => {
115
+ onClose();
116
+ },
117
+ (reason: unknown) => {
118
+ setError(errorMessage(reason));
119
+ setPending(false);
120
+ },
121
+ );
122
+ };
123
+
124
+ return (
125
+ <Dialog
126
+ title={`Edit tags — version ${String(version)}`}
127
+ description="Version tags are labels on an immutable body; editing them rebinds nothing."
128
+ open
129
+ onOpenChange={(next) => {
130
+ if (!next && !pending) onClose();
131
+ }}
132
+ >
133
+ <div className="tai-stack">
134
+ {/* The editor is named by its Field, like every other site: its draft input
135
+ claims the Field's control id, so the visible label IS the name. */}
136
+ <Field label="Tags" description="Labels on this version's immutable body.">
137
+ <TagsInput value={tags} onChange={setTags} disabled={pending} />
138
+ </Field>
139
+ {error !== undefined ? <ErrorState message={error} /> : null}
140
+ </div>
141
+ <div className="tai-dialog-actions">
142
+ <Button
143
+ onClick={() => {
144
+ onClose();
145
+ }}
146
+ disabled={pending}
147
+ >
148
+ Cancel
149
+ </Button>
150
+ <Button variant="primary" onClick={submit} disabled={pending}>
151
+ {pending ? <Spinner label="Saving tags" /> : null}
152
+ Save tags
153
+ </Button>
154
+ </div>
155
+ </Dialog>
156
+ );
157
+ }
158
+
159
+ export function VersionHistoryPanel({
160
+ versions,
161
+ onRollback,
162
+ onView,
163
+ rollbackPending,
164
+ rollbackError,
165
+ readOnly = false,
166
+ rollbackConfirmDescription,
167
+ onEditTags,
168
+ }: VersionHistoryPanelProps): ReactNode {
169
+ const [selected, setSelected] = useState<number | null>(() => initialSelection(versions));
170
+ const [confirming, setConfirming] = useState<number | null>(null);
171
+ // The armed Compare source (first click), and the open compare pair (second click).
172
+ const [compareFrom, setCompareFrom] = useState<number | null>(null);
173
+ const [comparePair, setComparePair] = useState<{ from: number; to: number } | null>(null);
174
+ const [editingTags, setEditingTags] = useState<number | null>(null);
175
+
176
+ // Close the confirm dialog on a SUCCESSFUL rollback: the consumer's mutation
177
+ // drives `rollbackPending` true→false, and success is that transition WITHOUT a
178
+ // `rollbackError`. We compare the prior `rollbackPending` to the current one via a
179
+ // ref so this fires exactly once on the true→false edge — never on mount (the ref
180
+ // seeds from the initial value, so no edge exists) and never on a failure (a set
181
+ // `rollbackError` keeps the dialog open showing the error).
182
+ const wasRollbackPending = useRef<boolean | undefined>(rollbackPending);
183
+ useEffect(() => {
184
+ const settledFromPending = wasRollbackPending.current === true && rollbackPending !== true;
185
+ wasRollbackPending.current = rollbackPending;
186
+ if (settledFromPending && rollbackError === undefined) {
187
+ setConfirming(null);
188
+ }
189
+ }, [rollbackPending, rollbackError]);
190
+
191
+ const selectedEntry = versions.find((entry) => entry.version === selected);
192
+ const showTags =
193
+ onEditTags !== undefined || versions.some((entry) => (entry.tags ?? []).length > 0);
194
+ const canCompare = versions.length >= 2;
195
+
196
+ const view = (version: number): void => {
197
+ setSelected(version);
198
+ onView?.(version);
199
+ };
200
+
201
+ const compare = (version: number): void => {
202
+ if (compareFrom === null) {
203
+ setCompareFrom(version);
204
+ } else if (compareFrom === version) {
205
+ setCompareFrom(null);
206
+ } else {
207
+ setComparePair({ from: compareFrom, to: version });
208
+ }
209
+ };
210
+
211
+ const closeCompare = (): void => {
212
+ setComparePair(null);
213
+ setCompareFrom(null);
214
+ };
215
+
216
+ const editingEntry = versions.find((entry) => entry.version === editingTags);
217
+ const fromBody = versions.find((entry) => entry.version === comparePair?.from)?.body;
218
+ const toBody = versions.find((entry) => entry.version === comparePair?.to)?.body;
219
+
220
+ return (
221
+ <div className="tai-stack">
222
+ <Card>
223
+ {/* The action column makes this table wider than a phone viewport; it scrolls
224
+ inside its own region rather than pushing the page sideways. */}
225
+ <ScrollRegion label="Version history">
226
+ <Table>
227
+ <THead>
228
+ <TR>
229
+ <TH numeric>Version</TH>
230
+ <TH>Created</TH>
231
+ <TH>Status</TH>
232
+ {showTags ? <TH>Tags</TH> : null}
233
+ <TH>Actions</TH>
234
+ </TR>
235
+ </THead>
236
+ <TBody>
237
+ {versions.map((entry) => (
238
+ <TR key={entry.version} data-testid={`version-row-${String(entry.version)}`}>
239
+ <TD className="tai-table-id" numeric>
240
+ {entry.version}
241
+ </TD>
242
+ <TD className="tai-mono">{entry.created_at}</TD>
243
+ <TD>
244
+ {entry.is_current ? (
245
+ <span className="tai-status tai-status-ok">
246
+ <CheckCircleIcon />
247
+ Current
248
+ </span>
249
+ ) : (
250
+ <span className="tai-status tai-status-pending">
251
+ <PendingIcon />
252
+ Historical
253
+ </span>
254
+ )}
255
+ </TD>
256
+ {showTags ? (
257
+ <TD>
258
+ {(entry.tags ?? []).length > 0 ? (
259
+ <TagChips tags={entry.tags ?? []} />
260
+ ) : (
261
+ <span className="tai-muted">—</span>
262
+ )}
263
+ </TD>
264
+ ) : null}
265
+ <TD>
266
+ <div className="tai-row">
267
+ <Button
268
+ aria-label={`View version ${String(entry.version)}`}
269
+ onClick={() => {
270
+ view(entry.version);
271
+ }}
272
+ >
273
+ View
274
+ </Button>
275
+ {canCompare ? (
276
+ <Button
277
+ // The name starts with the word the button is SHOWING,
278
+ // which is what WCAG 2.5.3 (Label in Name) asks: a constant
279
+ // "Compare …" would leave a button reading "Comparing…"
280
+ // named "Compare", and a voice-control user naming a control
281
+ // they cannot see. The button stays ENABLED while armed —
282
+ // unlike an Edit action, a second click on the armed row is
283
+ // the disarm, so there is nothing to take away.
284
+ aria-label={`${compareFrom === entry.version ? 'Comparing' : 'Compare'} version ${String(entry.version)}`}
285
+ onClick={() => {
286
+ compare(entry.version);
287
+ }}
288
+ >
289
+ {compareFrom === entry.version ? 'Comparing…' : 'Compare'}
290
+ </Button>
291
+ ) : null}
292
+ {onEditTags !== undefined && !readOnly ? (
293
+ <Button
294
+ aria-label={`Edit tags for version ${String(entry.version)}`}
295
+ onClick={() => {
296
+ setEditingTags(entry.version);
297
+ }}
298
+ >
299
+ Edit tags
300
+ </Button>
301
+ ) : null}
302
+ {readOnly ? null : (
303
+ <Button
304
+ variant="secondary"
305
+ aria-label={`Roll back to version ${String(entry.version)}`}
306
+ disabled={entry.is_current}
307
+ onClick={() => {
308
+ setConfirming(entry.version);
309
+ }}
310
+ >
311
+ Roll back
312
+ </Button>
313
+ )}
314
+ </div>
315
+ </TD>
316
+ </TR>
317
+ ))}
318
+ </TBody>
319
+ </Table>
320
+ </ScrollRegion>
321
+ </Card>
322
+
323
+ {compareFrom !== null && comparePair === null ? (
324
+ <p role="status" className="tai-muted">
325
+ Comparing from version {compareFrom} — pick another version, or Compare again to cancel.
326
+ </p>
327
+ ) : null}
328
+
329
+ {selectedEntry !== undefined ? (
330
+ <section className="tai-stack tai-stack-2">
331
+ <h3 className="tai-section-title">Version {selectedEntry.version} body</h3>
332
+ <Card>
333
+ <JsonTree
334
+ data={selectedEntry.body}
335
+ label={`Version ${String(selectedEntry.version)} body`}
336
+ />
337
+ </Card>
338
+ </section>
339
+ ) : null}
340
+
341
+ {comparePair !== null ? (
342
+ <Dialog
343
+ title={`v${String(comparePair.from)} compared with v${String(comparePair.to)}`}
344
+ description="A structural diff of the two version bodies. Arrays compare whole."
345
+ open
346
+ onOpenChange={(next) => {
347
+ if (!next) closeCompare();
348
+ }}
349
+ >
350
+ {/* A deep diff row can run wider than the dialog; it scrolls in place. */}
351
+ <ScrollRegion label="Version diff">
352
+ <JsonDiff before={fromBody} after={toBody} />
353
+ </ScrollRegion>
354
+ <div className="tai-dialog-actions">
355
+ <Button onClick={closeCompare}>Close</Button>
356
+ </div>
357
+ </Dialog>
358
+ ) : null}
359
+
360
+ {editingEntry !== undefined && onEditTags !== undefined ? (
361
+ <EditTagsDialog
362
+ version={editingEntry.version}
363
+ initialTags={editingEntry.tags ?? []}
364
+ onSave={onEditTags}
365
+ onClose={() => {
366
+ setEditingTags(null);
367
+ }}
368
+ />
369
+ ) : null}
370
+
371
+ {confirming !== null ? (
372
+ <Dialog
373
+ title="Roll back version"
374
+ description={
375
+ `Make version ${String(confirming)} the active version? The version history is preserved.` +
376
+ (rollbackConfirmDescription !== undefined ? ` ${rollbackConfirmDescription}` : '')
377
+ }
378
+ open
379
+ onOpenChange={(next) => {
380
+ if (!next) setConfirming(null);
381
+ }}
382
+ >
383
+ {rollbackError !== undefined ? <ErrorState message={rollbackError} /> : null}
384
+ <div className="tai-dialog-actions">
385
+ <Button
386
+ onClick={() => {
387
+ setConfirming(null);
388
+ }}
389
+ >
390
+ Cancel
391
+ </Button>
392
+ <Button
393
+ variant="primary"
394
+ disabled={rollbackPending}
395
+ onClick={() => {
396
+ onRollback(confirming);
397
+ }}
398
+ >
399
+ {rollbackPending ? <Spinner label="Rolling back" /> : null}
400
+ Roll back
401
+ </Button>
402
+ </div>
403
+ </Dialog>
404
+ ) : null}
405
+ </div>
406
+ );
407
+ }
package/src/css.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ /** Side-effect CSS imports (the precompiled DS token stylesheet). */
2
+ declare module '*.css';
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Stream a `Blob` (an export- or content-route response) to the user as a file
3
+ * download via a transient object URL. The object URL is revoked on the next tick —
4
+ * revoking it synchronously can cancel the download before the browser commits it.
5
+ */
6
+ export function downloadBlob(blob: Blob, filename: string): void {
7
+ const url = URL.createObjectURL(blob);
8
+ const anchor = document.createElement('a');
9
+ anchor.href = url;
10
+ anchor.download = filename;
11
+ document.body.appendChild(anchor);
12
+ anchor.click();
13
+ anchor.remove();
14
+ setTimeout(() => {
15
+ URL.revokeObjectURL(url);
16
+ }, 0);
17
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * The runtime half of the `asChild` slot contract.
3
+ *
4
+ * Radix clones its own props onto the node an `asChild` slot is given, so the
5
+ * slot takes exactly one ELEMENT. `ReactElement` states half of that: it rejects
6
+ * a string, which would throw inside Radix anyway. It does NOT reject a
7
+ * FRAGMENT — `<></>` is a `ReactElement` whose `type` is the fragment symbol —
8
+ * and a fragment is the worse of the two, because React drops every prop cloned
9
+ * onto it: the opener renders, carries no `aria-haspopup`/`aria-expanded`/
10
+ * `aria-controls` and no handler, and fails nothing.
11
+ */
12
+ import { Fragment } from 'react';
13
+ import type { ReactElement } from 'react';
14
+
15
+ /** Raises when an `asChild` slot is handed a fragment. `slot` names the prop. */
16
+ export function assertSlotElement(element: ReactElement, slot: string): void {
17
+ if (element.type !== Fragment) return;
18
+ throw new Error(
19
+ `${slot} takes a single element, not a fragment: React drops the props the ` +
20
+ 'slot clones onto it, so the wiring and the handler would be lost.',
21
+ );
22
+ }
@@ -0,0 +1,87 @@
1
+ /**
2
+ * `ElicitationForm` — the Studio-as-elicit-client widget. It renders a
3
+ * FastMCP elicitation request (its `message` + `requestedSchema`) as a
4
+ * `SchemaForm`, validates the answer against the schema, and hands the typed
5
+ * answer back on submit. The schema is carried through intact, so the caller
6
+ * gets exactly the shape it asked for.
7
+ *
8
+ * Accept-or-submit only: this widget's job is to collect and return a valid
9
+ * answer. A decline/cancel is not modeled here (the platform bridge is
10
+ * accept-or-raise); the optional `onCancel` is a UI affordance, not an MCP
11
+ * decline round-trip. Reusable by any feature that must answer an elicit.
12
+ */
13
+ import type { ReactNode } from 'react';
14
+ import { useState } from 'react';
15
+
16
+ import { Button } from '../components/primitives';
17
+ import { defaultValueForSchema } from '../schema-form/default-value';
18
+ import { SchemaForm } from '../schema-form/SchemaForm';
19
+ import type { JsonSchema, SchemaFormErrors } from '../schema-form/types';
20
+ import { validateAgainstSchema } from '../schema-form/validate';
21
+
22
+ export interface ElicitationFormProps {
23
+ /** The elicit request's human-readable message. */
24
+ readonly message: string;
25
+ /** The elicit `requestedSchema` — the answer's form schema. */
26
+ readonly schema: JsonSchema;
27
+ /** Called with the validated answer when the human submits. */
28
+ readonly onSubmit: (answer: unknown) => void;
29
+ /** Optional cancel affordance (UI only; not an MCP decline). */
30
+ readonly onCancel?: () => void;
31
+ /** Disables submission while an answer is in flight. */
32
+ readonly busy?: boolean;
33
+ readonly submitLabel?: string;
34
+ }
35
+
36
+ /**
37
+ * Render the elicit message + a schema-driven answer form. On submit the answer
38
+ * is validated against the schema; on success `onSubmit` receives the typed
39
+ * value, otherwise the per-field errors surface and nothing is emitted.
40
+ */
41
+ export function ElicitationForm({
42
+ message,
43
+ schema,
44
+ onSubmit,
45
+ onCancel,
46
+ busy = false,
47
+ submitLabel = 'Submit',
48
+ }: ElicitationFormProps): ReactNode {
49
+ const [value, setValue] = useState<unknown>(() => defaultValueForSchema(schema));
50
+ const [errors, setErrors] = useState<SchemaFormErrors>({});
51
+
52
+ const submit = () => {
53
+ const found = validateAgainstSchema(schema, value);
54
+ setErrors(found);
55
+ if (Object.keys(found).length === 0) {
56
+ onSubmit(value);
57
+ }
58
+ };
59
+
60
+ return (
61
+ <div className="tai-stack" data-testid="elicitation-form">
62
+ {/* The message is text the Studio did not author — the prose measure keeps
63
+ a long elicit prompt to a readable line length. It applies to a
64
+ PARAGRAPH INSIDE `.tai-prose`, so the class goes on the wrapper. */}
65
+ <div className="tai-prose">
66
+ <p>{message}</p>
67
+ </div>
68
+ <SchemaForm
69
+ schema={schema}
70
+ value={value}
71
+ onChange={setValue}
72
+ errors={errors}
73
+ idPrefix="elicit-form"
74
+ />
75
+ <div className="tai-row">
76
+ <Button variant="primary" onClick={submit} disabled={busy}>
77
+ {submitLabel}
78
+ </Button>
79
+ {onCancel && (
80
+ <Button variant="secondary" onClick={onCancel} disabled={busy}>
81
+ Cancel
82
+ </Button>
83
+ )}
84
+ </div>
85
+ </div>
86
+ );
87
+ }
package/src/errors.ts ADDED
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Normalize an unknown thrown/rejected value (a TanStack Query `error`, a
3
+ * rejected mutation, a zod/`ApiError`) into a human-readable string for the loud
4
+ * `ErrorState` / `Field` surfaces. This only turns a caught value into
5
+ * displayable text — it never swallows the failure; the caller decides where to
6
+ * show it, and the message is always shown, never blank.
7
+ */
8
+ export function errorMessage(error: unknown): string {
9
+ if (error instanceof Error) return error.message;
10
+ if (typeof error === 'string') return error;
11
+ return 'An unexpected error occurred.';
12
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Helpers for a preset/tool's list of extension combos, whose elements may carry
3
+ * author `config` (`{ name, config }`) alongside bare-name elements. The editors
4
+ * hold the full config-bearing elements as their value, so config rides through an
5
+ * edit directly; these helpers project a combo to its bare extension names where a
6
+ * name-only view is needed (a picker's selection, a branch-tool name, a badge row).
7
+ */
8
+ import type { PresetExtensionElement } from '@tai42/api-client';
9
+
10
+ /** The bare extension name of one combo element (drops any `config`). */
11
+ export function extensionElementName(element: PresetExtensionElement): string {
12
+ return typeof element === 'string' ? element : element.name;
13
+ }
14
+
15
+ /** Project a combo's elements to their bare extension names (drops any `config`). */
16
+ export function comboElementNames(combo: readonly PresetExtensionElement[]): string[] {
17
+ return combo.map(extensionElementName);
18
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * `useApi` — the typed client, provided through an SDK-owned React context.
3
+ *
4
+ * SDK boundary invariant: this context lives INSIDE the shared module set
5
+ * (the SDK is a shared singleton across the plugin boundary), so a plugin calling
6
+ * `useApi()` reaches the SAME client the shell built. It must NEVER wrap a
7
+ * TanStack Query/Router context (those are deliberately NOT shared) — it stays a
8
+ * plain typed client. The `ApiClient` type is imported type-only, so there is no
9
+ * runtime dependency on @tai42/api-client (the SDK imports nothing internal at
10
+ * runtime).
11
+ */
12
+ import { createContext, useContext } from 'react';
13
+ import type { ApiClient } from '@tai42/api-client';
14
+
15
+ const ApiContext = createContext<ApiClient | null>(null);
16
+
17
+ export const ApiProvider = ApiContext.Provider;
18
+
19
+ export function useApi(): ApiClient {
20
+ const client = useContext(ApiContext);
21
+ if (client === null) {
22
+ throw new Error('useApi must be used within an <ApiProvider> (the shell provides it)');
23
+ }
24
+ return client;
25
+ }
26
+
27
+ export type { ApiClient };