sygnal 5.4.0 → 6.1.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 (360) hide show
  1. package/CHANGELOG.md +716 -0
  2. package/README.md +24 -9
  3. package/dist/ai.cjs.js +115 -0
  4. package/dist/ai.cjs.js.map +1 -0
  5. package/dist/ai.esm.js +2 -0
  6. package/dist/ai.esm.js.map +1 -0
  7. package/dist/astro/client.cjs.js +16 -9
  8. package/dist/astro/client.cjs.js.map +1 -1
  9. package/dist/astro/client.mjs +16 -9
  10. package/dist/astro/client.mjs.map +1 -1
  11. package/dist/astro/index.cjs.js +958 -92
  12. package/dist/astro/index.cjs.js.map +1 -1
  13. package/dist/astro/index.mjs +958 -93
  14. package/dist/astro/index.mjs.map +1 -1
  15. package/dist/astro/server.cjs.js +3396 -165
  16. package/dist/astro/server.cjs.js.map +1 -1
  17. package/dist/astro/server.mjs +3396 -165
  18. package/dist/astro/server.mjs.map +1 -1
  19. package/dist/devtools.cjs.js +1840 -0
  20. package/dist/devtools.cjs.js.map +1 -0
  21. package/dist/devtools.esm.js +1827 -0
  22. package/dist/devtools.esm.js.map +1 -0
  23. package/dist/diagnostics.cjs.js +3190 -296
  24. package/dist/diagnostics.cjs.js.map +1 -1
  25. package/dist/diagnostics.esm.js +3190 -296
  26. package/dist/diagnostics.esm.js.map +1 -1
  27. package/dist/element.cjs.js +230 -0
  28. package/dist/element.cjs.js.map +1 -0
  29. package/dist/element.esm.js +228 -0
  30. package/dist/element.esm.js.map +1 -0
  31. package/dist/guide/accessibility.md +310 -0
  32. package/dist/guide/adapters.md +221 -0
  33. package/dist/guide/agent.md +588 -0
  34. package/dist/guide/ai-chat.md +693 -0
  35. package/dist/guide/ai-decisions.md +330 -0
  36. package/dist/guide/behaviors.md +312 -0
  37. package/dist/guide/browser-sources.md +203 -0
  38. package/dist/guide/drag-and-drop.md +342 -0
  39. package/dist/guide/element-commands.md +306 -0
  40. package/dist/guide/error-boundaries.md +169 -0
  41. package/dist/guide/forms-reference.md +409 -0
  42. package/dist/guide/forms.md +251 -0
  43. package/dist/guide/http.md +301 -0
  44. package/dist/guide/inputs.md +173 -0
  45. package/dist/guide/mcp-apps.md +272 -0
  46. package/dist/guide/persistence.md +245 -0
  47. package/dist/guide/recipes/ai-form-fill.md +163 -0
  48. package/dist/guide/recipes/ai-summarize.md +155 -0
  49. package/dist/guide/recipes/ai-support-inbox.md +190 -0
  50. package/dist/guide/recipes/carousel.md +155 -0
  51. package/dist/guide/recipes/charts.md +177 -0
  52. package/dist/guide/recipes/code-editor.md +135 -0
  53. package/dist/guide/recipes/data-grid.md +155 -0
  54. package/dist/guide/recipes/data-table.md +196 -0
  55. package/dist/guide/recipes/i18n.md +222 -0
  56. package/dist/guide/recipes/icons.md +115 -0
  57. package/dist/guide/recipes/overview.md +43 -0
  58. package/dist/guide/recipes/rich-text.md +154 -0
  59. package/dist/guide/resources.md +471 -0
  60. package/dist/guide/ssr.md +255 -0
  61. package/dist/guide/timers.md +178 -0
  62. package/dist/guide/ui/accordion.md +93 -0
  63. package/dist/guide/ui/combobox.md +144 -0
  64. package/dist/guide/ui/dialog.md +124 -0
  65. package/dist/guide/ui/disclosure.md +60 -0
  66. package/dist/guide/ui/menu.md +125 -0
  67. package/dist/guide/ui/overview.md +166 -0
  68. package/dist/guide/ui/popover.md +101 -0
  69. package/dist/guide/ui/select.md +114 -0
  70. package/dist/guide/ui/tabs.md +161 -0
  71. package/dist/guide/ui/toaster.md +152 -0
  72. package/dist/guide/ui/tooltip.md +103 -0
  73. package/dist/guide/undo.md +178 -0
  74. package/dist/guide/virtual-collections.md +183 -0
  75. package/dist/guide/web-components.md +179 -0
  76. package/dist/guide/webmcp.md +226 -0
  77. package/dist/guide/widgets.md +217 -0
  78. package/dist/index.cjs.js +19288 -7408
  79. package/dist/index.cjs.js.map +1 -1
  80. package/dist/index.d.ts +4466 -552
  81. package/dist/index.esm.js +19274 -7461
  82. package/dist/index.esm.js.map +1 -1
  83. package/dist/jsx-dev-runtime.cjs.js +26 -334
  84. package/dist/jsx-dev-runtime.cjs.js.map +1 -1
  85. package/dist/jsx-dev-runtime.esm.js +19 -327
  86. package/dist/jsx-dev-runtime.esm.js.map +1 -1
  87. package/dist/jsx-runtime.cjs.js +26 -334
  88. package/dist/jsx-runtime.cjs.js.map +1 -1
  89. package/dist/jsx-runtime.esm.js +19 -327
  90. package/dist/jsx-runtime.esm.js.map +1 -1
  91. package/dist/jsx.cjs.js +10 -314
  92. package/dist/jsx.cjs.js.map +1 -1
  93. package/dist/jsx.esm.js +8 -315
  94. package/dist/jsx.esm.js.map +1 -1
  95. package/dist/react.cjs.js +117 -0
  96. package/dist/react.cjs.js.map +1 -0
  97. package/dist/react.esm.js +115 -0
  98. package/dist/react.esm.js.map +1 -0
  99. package/dist/shims/globalthis.cjs +20 -0
  100. package/dist/sygnal.min.js +1 -1
  101. package/dist/sygnal.min.js.map +1 -1
  102. package/dist/ui-combobox.cjs.js +169 -0
  103. package/dist/ui-combobox.cjs.js.map +1 -0
  104. package/dist/ui-combobox.esm.js +148 -0
  105. package/dist/ui-combobox.esm.js.map +1 -0
  106. package/dist/ui-menu.cjs.js +87 -0
  107. package/dist/ui-menu.cjs.js.map +1 -0
  108. package/dist/ui-menu.esm.js +66 -0
  109. package/dist/ui-menu.esm.js.map +1 -0
  110. package/dist/ui-select.cjs.js +104 -0
  111. package/dist/ui-select.cjs.js.map +1 -0
  112. package/dist/ui-select.esm.js +83 -0
  113. package/dist/ui-select.esm.js.map +1 -0
  114. package/dist/ui.cjs.js +738 -0
  115. package/dist/ui.cjs.js.map +1 -0
  116. package/dist/ui.esm.js +727 -0
  117. package/dist/ui.esm.js.map +1 -0
  118. package/dist/vike/ClientOnly.cjs.js +4 -14
  119. package/dist/vike/ClientOnly.cjs.js.map +1 -1
  120. package/dist/vike/ClientOnly.mjs +4 -14
  121. package/dist/vike/ClientOnly.mjs.map +1 -1
  122. package/dist/vike/{+config.js → config/+config.js} +9 -1
  123. package/dist/vike/config/+config.js.map +1 -0
  124. package/dist/vike/config/package.json +6 -0
  125. package/dist/vike/onRenderClient.cjs.js +165 -95
  126. package/dist/vike/onRenderClient.cjs.js.map +1 -1
  127. package/dist/vike/onRenderClient.mjs +165 -95
  128. package/dist/vike/onRenderClient.mjs.map +1 -1
  129. package/dist/vike/onRenderHtml.cjs.js +59 -24
  130. package/dist/vike/onRenderHtml.cjs.js.map +1 -1
  131. package/dist/vike/onRenderHtml.mjs +60 -25
  132. package/dist/vike/onRenderHtml.mjs.map +1 -1
  133. package/dist/vite/plugin.cjs.js +919 -89
  134. package/dist/vite/plugin.cjs.js.map +1 -1
  135. package/dist/vite/plugin.mjs +919 -90
  136. package/dist/vite/plugin.mjs.map +1 -1
  137. package/dist/zag.cjs.js +408 -0
  138. package/dist/zag.cjs.js.map +1 -0
  139. package/dist/zag.esm.js +405 -0
  140. package/dist/zag.esm.js.map +1 -0
  141. package/llms.txt +172 -103
  142. package/package.json +112 -14
  143. package/src/ai.d.ts +1186 -0
  144. package/src/ai.ts +23 -0
  145. package/src/astro/client.ts +27 -16
  146. package/src/astro/index.d.ts +24 -0
  147. package/src/astro/index.ts +69 -3
  148. package/src/astro/server.ts +8 -2
  149. package/src/collection.ts +6 -93
  150. package/src/core/actions.ts +134 -0
  151. package/src/core/cell.ts +202 -0
  152. package/src/core/debug.ts +23 -0
  153. package/src/core/define.ts +242 -0
  154. package/src/core/hooks.ts +314 -0
  155. package/src/core/hosts/collection.ts +325 -0
  156. package/src/core/hosts/switchable.ts +146 -0
  157. package/src/core/instance.ts +592 -0
  158. package/src/core/markers/clientonly.ts +13 -0
  159. package/src/core/markers/lazy.ts +40 -0
  160. package/src/core/markers/portal.ts +96 -0
  161. package/src/core/markers/suspense.ts +41 -0
  162. package/src/core/markers/transition.ts +70 -0
  163. package/src/core/registry.ts +25 -0
  164. package/src/core/runtime.ts +573 -0
  165. package/src/core/statics.ts +104 -0
  166. package/src/core/teardown.ts +60 -0
  167. package/src/core/view.ts +37 -0
  168. package/src/cycle/dom/DocumentDOMSource.ts +13 -8
  169. package/src/cycle/dom/ElementFinder.ts +7 -9
  170. package/src/cycle/dom/EventDelegator.ts +174 -223
  171. package/src/cycle/dom/IsolateModule.ts +65 -35
  172. package/src/cycle/dom/MainDOMSource.ts +9 -2
  173. package/src/cycle/dom/PriorityQueue.ts +4 -2
  174. package/src/cycle/dom/SymbolTree.ts +18 -47
  175. package/src/cycle/dom/classNameModule.ts +9 -7
  176. package/src/cycle/dom/controlledInputModule.ts +23 -6
  177. package/src/cycle/dom/enrichEventStream.ts +25 -47
  178. package/src/cycle/dom/fragment.ts +7 -0
  179. package/src/cycle/dom/isolate.ts +30 -27
  180. package/src/cycle/dom/makeDOMDriver.ts +115 -56
  181. package/src/cycle/dom/mockDOMSource.ts +21 -1
  182. package/src/cycle/dom/modules.ts +16 -3
  183. package/src/cycle/dom/propsModule.ts +65 -0
  184. package/src/cycle/dom/selectModule.ts +22 -17
  185. package/src/cycle/dom/snabbdom.ts +1 -6
  186. package/src/cycle/dom/thunk.ts +3 -0
  187. package/src/cycle/dom/utils.ts +98 -0
  188. package/src/cycle/dom/viewTransition.ts +43 -0
  189. package/src/cycle/state/StateSource.ts +19 -3
  190. package/src/cycle/state/objIsEqual.ts +50 -0
  191. package/src/cycle/state/types.ts +0 -10
  192. package/src/defineComponent.ts +34 -0
  193. package/src/devtools.d.ts +170 -0
  194. package/src/devtools.ts +37 -0
  195. package/src/element.d.ts +59 -0
  196. package/src/element.ts +235 -0
  197. package/src/extra/ai/agent/index.ts +484 -0
  198. package/src/extra/ai/answers.ts +115 -0
  199. package/src/extra/ai/chat/behavior.ts +336 -0
  200. package/src/extra/ai/chat/driver.ts +271 -0
  201. package/src/extra/ai/chat/memoryTransport.ts +61 -0
  202. package/src/extra/ai/chat/output.ts +37 -0
  203. package/src/extra/ai/commandBar.ts +281 -0
  204. package/src/extra/ai/decide.ts +104 -0
  205. package/src/extra/ai/index.ts +42 -0
  206. package/src/extra/ai/link.ts +90 -0
  207. package/src/extra/ai/mcpApp.ts +314 -0
  208. package/src/extra/ai/messages.ts +51 -0
  209. package/src/extra/ai/schema/index.ts +166 -0
  210. package/src/extra/ai/schema/jsonSchema.ts +72 -0
  211. package/src/extra/ai/schema/strict.ts +152 -0
  212. package/src/extra/ai/transports/agui.ts +177 -0
  213. package/src/extra/ai/transports/anthropicMessages.ts +175 -0
  214. package/src/extra/ai/transports/chatCompletions.ts +105 -0
  215. package/src/extra/ai/transports/chromePrompt.ts +77 -0
  216. package/src/extra/ai/transports/encodeOpenResponses.ts +87 -0
  217. package/src/extra/ai/transports/fromAISDK.ts +126 -0
  218. package/src/extra/ai/transports/openResponses.ts +105 -0
  219. package/src/extra/ai/transports/shared.ts +125 -0
  220. package/src/extra/ai/transports/tools.ts +59 -0
  221. package/src/extra/ai/transports/uiMessageStream.ts +107 -0
  222. package/src/extra/ai/webmcp.ts +260 -0
  223. package/src/extra/backoff.ts +9 -0
  224. package/src/extra/behaviors.ts +169 -0
  225. package/src/extra/browserSignals.ts +23 -0
  226. package/src/extra/browserSources.ts +326 -0
  227. package/src/extra/command.ts +3 -3
  228. package/src/extra/controls.ts +47 -0
  229. package/src/extra/copyAsTest.ts +286 -0
  230. package/src/extra/devMcp.ts +386 -0
  231. package/src/extra/devtools.ts +101 -31
  232. package/src/extra/devtoolsActions.ts +379 -0
  233. package/src/extra/devtoolsHook.ts +9 -0
  234. package/src/extra/devtoolsNext.ts +83 -0
  235. package/src/extra/diagnostics/checks/actionLog.ts +108 -0
  236. package/src/extra/diagnostics/checks/behaviors.ts +49 -0
  237. package/src/extra/diagnostics/checks/browserSources.ts +96 -0
  238. package/src/extra/diagnostics/checks/chat.ts +71 -0
  239. package/src/extra/diagnostics/checks/collections.ts +1 -1
  240. package/src/extra/diagnostics/checks/controls.ts +148 -0
  241. package/src/extra/diagnostics/checks/dataset.ts +57 -0
  242. package/src/extra/diagnostics/checks/dom.ts +20 -11
  243. package/src/extra/diagnostics/checks/elementCommands.ts +175 -0
  244. package/src/extra/diagnostics/checks/events.ts +17 -2
  245. package/src/extra/diagnostics/checks/fetch.ts +135 -0
  246. package/src/extra/diagnostics/checks/forms.ts +171 -0
  247. package/src/extra/diagnostics/checks/index.ts +103 -4
  248. package/src/extra/diagnostics/checks/inspect.ts +140 -32
  249. package/src/extra/diagnostics/checks/next.ts +417 -0
  250. package/src/extra/diagnostics/checks/persist.ts +54 -0
  251. package/src/extra/diagnostics/checks/props.ts +10 -5
  252. package/src/extra/diagnostics/checks/public.d.ts +115 -7
  253. package/src/extra/diagnostics/checks/replies.ts +128 -0
  254. package/src/extra/diagnostics/checks/router.ts +100 -0
  255. package/src/extra/diagnostics/checks/rxjsHints.ts +1 -1
  256. package/src/extra/diagnostics/checks/shared.ts +86 -2
  257. package/src/extra/diagnostics/checks/shorthand.ts +115 -0
  258. package/src/extra/diagnostics/checks/sortable.ts +129 -0
  259. package/src/extra/diagnostics/checks/state.ts +100 -7
  260. package/src/extra/diagnostics/checks/statics.ts +48 -0
  261. package/src/extra/diagnostics/checks/strict.ts +10 -67
  262. package/src/extra/diagnostics/checks/timers.ts +80 -0
  263. package/src/extra/diagnostics/checks/viewTransitions.ts +47 -0
  264. package/src/extra/diagnostics/checks/virtual.ts +73 -0
  265. package/src/extra/diagnostics/checks/widgets.ts +109 -0
  266. package/src/extra/diagnostics/checks/wiring.ts +69 -10
  267. package/src/extra/diagnostics/codes.ts +335 -23
  268. package/src/extra/diagnostics/index.ts +34 -9
  269. package/src/extra/diagnostics/legacy.ts +17 -0
  270. package/src/extra/driverFactories.ts +83 -19
  271. package/src/extra/elementCommands.ts +53 -0
  272. package/src/extra/fetchDriver.ts +519 -0
  273. package/src/extra/focusWithin.ts +27 -0
  274. package/src/extra/form.ts +284 -0
  275. package/src/extra/formHelpers.ts +139 -0
  276. package/src/extra/formTool.ts +150 -0
  277. package/src/extra/head.ts +106 -0
  278. package/src/extra/hmr.ts +2 -3
  279. package/src/extra/owned.ts +24 -0
  280. package/src/extra/pager.ts +50 -0
  281. package/src/extra/persist.ts +133 -0
  282. package/src/extra/pwa.ts +1 -1
  283. package/src/extra/queryCache.ts +107 -0
  284. package/src/extra/reducers.ts +1 -1
  285. package/src/extra/reduxDevtools.ts +92 -0
  286. package/src/extra/replies.ts +54 -0
  287. package/src/extra/router.ts +323 -0
  288. package/src/extra/run.ts +90 -217
  289. package/src/extra/selection.ts +76 -0
  290. package/src/extra/socketDriver.ts +265 -0
  291. package/src/extra/sortable.ts +377 -0
  292. package/src/extra/ssr.ts +427 -160
  293. package/src/extra/standardSchema.ts +27 -0
  294. package/src/extra/testing.ts +2550 -175
  295. package/src/extra/timers.ts +95 -0
  296. package/src/extra/undo.ts +261 -0
  297. package/src/extra/viewTransitions.ts +38 -0
  298. package/src/extra/virtual.ts +513 -0
  299. package/src/extra/widget.ts +201 -0
  300. package/src/index.d.ts +2754 -127
  301. package/src/index.ts +45 -4
  302. package/src/jsx-runtime.ts +15 -1
  303. package/src/jsx.ts +2 -1
  304. package/src/lazy.ts +50 -7
  305. package/src/portal.ts +3 -1
  306. package/src/pragma/index.ts +256 -133
  307. package/src/react-peers.d.ts +5 -0
  308. package/src/react.d.ts +43 -0
  309. package/src/react.ts +110 -0
  310. package/src/shared.ts +57 -0
  311. package/src/slot.ts +1 -1
  312. package/src/suspense.ts +3 -1
  313. package/src/switchable.ts +6 -121
  314. package/src/transition.ts +3 -1
  315. package/src/ui/accordion.ts +64 -0
  316. package/src/ui/dialog.ts +129 -0
  317. package/src/ui/disclosure.ts +35 -0
  318. package/src/ui/popover.ts +45 -0
  319. package/src/ui/shared.ts +94 -0
  320. package/src/ui/tabs.ts +82 -0
  321. package/src/ui/toaster.ts +198 -0
  322. package/src/ui/tooltip.ts +70 -0
  323. package/src/ui/zag/combobox.ts +115 -0
  324. package/src/ui/zag/menu.ts +40 -0
  325. package/src/ui/zag/select.ts +54 -0
  326. package/src/ui/zag/shared.ts +43 -0
  327. package/src/ui-combobox.d.ts +20 -0
  328. package/src/ui-combobox.ts +6 -0
  329. package/src/ui-menu.d.ts +30 -0
  330. package/src/ui-menu.ts +6 -0
  331. package/src/ui-select.d.ts +17 -0
  332. package/src/ui-select.ts +6 -0
  333. package/src/ui-zag-types.d.ts +42 -0
  334. package/src/ui.d.ts +208 -0
  335. package/src/ui.ts +15 -0
  336. package/src/vike/+config.ts +9 -1
  337. package/src/vike/ClientOnly.ts +1 -1
  338. package/src/vike/onRenderClient.ts +144 -96
  339. package/src/vike/onRenderHtml.ts +69 -26
  340. package/src/vike/types.ts +7 -1
  341. package/src/vite/globalthis-shim.ts +18 -0
  342. package/src/vite/globalthis.ts +56 -0
  343. package/src/vite/mcp.ts +568 -0
  344. package/src/vite/plugin.d.ts +47 -3
  345. package/src/vite/plugin.ts +262 -25
  346. package/src/zag.d.ts +64 -0
  347. package/src/zag.ts +238 -0
  348. package/dist/vike/+config.cjs.js +0 -66
  349. package/dist/vike/+config.cjs.js.map +0 -1
  350. package/dist/vike/+config.js.map +0 -1
  351. package/src/component.ts +0 -2301
  352. package/src/cycle/isolate/index.ts +0 -196
  353. package/src/cycle/run/index.ts +0 -151
  354. package/src/cycle/run/internals.ts +0 -143
  355. package/src/cycle/state/Collection.ts +0 -184
  356. package/src/cycle/state/index.ts +0 -5
  357. package/src/cycle/state/pickCombine.ts +0 -201
  358. package/src/cycle/state/pickMerge.ts +0 -122
  359. package/src/cycle/state/withState.ts +0 -47
  360. package/src/pragma/fn.ts +0 -55
package/src/ai.d.ts ADDED
@@ -0,0 +1,1186 @@
1
+ // Types for 'sygnal/ai' (PLAN-6). The implementation lives in src/extra/ai/ (D253).
2
+ import type { FetchRequest, Behavior, BehaviorTarget, FieldErrors } from 'sygnal'
3
+ import type { Stream } from 'xstream'
4
+
5
+ /** A part of a chat message (AI SDK UIMessage-shaped, D252) */
6
+ export type MessagePart =
7
+ | { type: 'text'; text: string }
8
+ | { type: 'reasoning'; text: string; /** provider data to send back with it (an Anthropic thinking signature) */ providerMetadata?: Record<string, Record<string, unknown>> }
9
+ | { type: `tool-${string}`; toolCallId: string; state: string; input?: unknown; output?: unknown; errorText?: string; approval?: { id: string; approved?: boolean; reason?: string; [key: string]: unknown }; providerExecuted?: boolean }
10
+ | { type: 'file'; mediaType: string; url: string; filename?: string }
11
+ | { type: `source-${string}`; [key: string]: unknown }
12
+ | { type: `data-${string}`; id?: string; data: unknown }
13
+
14
+ /** A chat message: parts, or a plain `content` string for messages written by hand */
15
+ export type Message =
16
+ | { id?: string; role: 'system' | 'user' | 'assistant'; parts: MessagePart[] }
17
+ | { id?: string; role: 'system' | 'user' | 'assistant'; content: string }
18
+
19
+ /** The text of a message: its text parts joined (or its `content`) */
20
+ export function messageText(message: Message | null | undefined): string
21
+
22
+ /**
23
+ * The message with its open tool calls answered (the raw chat driver's tool loop): each
24
+ * `tool-<name>` part in `input-available` gets its result, `state: 'output-available'` with
25
+ * `output`, or `'output-error'` with `errorText` for an `Error` (returned or thrown). `results`: a
26
+ * function of the call, or an object keyed by the call's id, else by the tool's name; `undefined`
27
+ * leaves a part open. The `chat` behavior does this itself.
28
+ */
29
+ export function withToolResults<M extends Message>(message: M, results: ((call: ChatCall) => unknown) | Record<string, unknown>): M
30
+
31
+ // ---- M-1: decisions -------------------------------------------------------------------------
32
+
33
+ /** A question's instructions: text, or an object / array holding the question and the data it names */
34
+ export type DecisionInstructions = string | Record<string, unknown> | readonly unknown[]
35
+
36
+ /** `choice(...)`: pick one option. `K`: the option names */
37
+ export interface ChoiceQuestion<K extends string = string> {
38
+ type: 'choice'
39
+ instructions: DecisionInstructions
40
+ /** Option name → description (or null) */
41
+ criteria: { [P in K]: string | null }
42
+ }
43
+
44
+ /** `noul(...)`: the probability (0 to 1) that the answer is yes */
45
+ export interface NoulQuestion {
46
+ type: 'noul'
47
+ instructions: DecisionInstructions
48
+ /** What counts as true and as false (recommended: answers are better calibrated with them) */
49
+ criteria?: { true: string; false: string }
50
+ }
51
+
52
+ /** `score(...)`: an ordered scale. `L`: the level descriptions, lowest first */
53
+ export interface ScoreQuestion<L extends string = string> {
54
+ type: 'score'
55
+ instructions: DecisionInstructions
56
+ /** Level descriptions, lowest first (the answer's `score` is the probability-weighted index) */
57
+ criteria: readonly L[]
58
+ }
59
+
60
+ export type Question = ChoiceQuestion<any> | NoulQuestion | ScoreQuestion<any>
61
+ /** Questions by name; the answers come back under the same names */
62
+ export type Questions = Record<string, Question>
63
+
64
+ /** The answer to a `choice` question */
65
+ export interface ChoiceAnswer<K extends string = string> {
66
+ type: 'choice'
67
+ /** The most likely option */
68
+ choice: K
69
+ /** Every option's probability (they sum to 1) */
70
+ probabilities: Record<K, number>
71
+ /** 0 to 1: how strongly the model favours one option (not a guarantee of correctness) */
72
+ confidence: number
73
+ }
74
+
75
+ /** The answer to a `noul` question */
76
+ export interface NoulAnswer {
77
+ type: 'noul'
78
+ /** The probability of yes, 0 to 1 */
79
+ noul: number
80
+ }
81
+
82
+ /** The answer to a `score` question */
83
+ export interface ScoreAnswer<L extends string = string> {
84
+ type: 'score'
85
+ /** The probability-weighted level index (0 = the first level); can fall between levels */
86
+ score: number
87
+ /** Level index ('0', '1', …) → its description */
88
+ legend: Record<string, L>
89
+ /** Level index ('0', '1', …) → its probability */
90
+ probabilities: Record<string, number>
91
+ confidence: number
92
+ }
93
+
94
+ /** `decide.openai()` only: OpenAI Decisions refused this question */
95
+ export interface RefusalAnswer {
96
+ type: 'refusal'
97
+ [key: string]: unknown
98
+ }
99
+
100
+ /** The answer type of a question */
101
+ export type Answer<Q> =
102
+ Q extends ChoiceQuestion<infer K> ? ChoiceAnswer<K>
103
+ : Q extends ScoreQuestion<infer L> ? ScoreAnswer<L>
104
+ : Q extends NoulQuestion ? NoulAnswer
105
+ : never
106
+
107
+ /** The answers to a set of questions, by name */
108
+ export type Answers<Q extends Questions> = { [N in keyof Q]: Answer<Q[N]> }
109
+
110
+ /**
111
+ * A decision reply (the `ok` action's data, a resource's `data`): `Decision<typeof questions>`.
112
+ * `decide.openai()` replies are `Decision<Q, RefusalAnswer>` (an answer can be a refusal)
113
+ */
114
+ export interface Decision<Q extends Questions = Questions, R = never> {
115
+ model?: string
116
+ answers: { [N in keyof Q]: Answer<Q[N]> | R }
117
+ usage?: { input_tokens: number; output_tokens: number }
118
+ [key: string]: unknown
119
+ }
120
+
121
+ type FetchRequestObject = Extract<FetchRequest, { url: string }>
122
+
123
+ /** What `decide()` / `decide.openai()` take: the decision, plus any `makeFetchDriver` request keys */
124
+ export type DecideOptions<Q extends Questions> = Omit<FetchRequestObject, 'url' | 'json' | 'body'> & {
125
+ /** Default '/api/decide' (the app's own route, which holds the API key) */
126
+ url?: string
127
+ /** The decision model, e.g. 'jev-latest' (TypeSafe), 'nimble' (Ollama), 'gpt-6-luna' (OpenAI) */
128
+ model: string
129
+ /** What the questions are about: text, or an object / array (JSON) */
130
+ state: unknown
131
+ questions: Q
132
+ /** Base64 PNG / JPEG / WebP files shared by every question (models that read images only) */
133
+ images?: readonly string[]
134
+ /** As a resource: keep the previous `data` on a new request */
135
+ keepPrevious?: boolean
136
+ /** As a resource: refetch every this many ms */
137
+ refetchEvery?: number
138
+ /** As a resource: stay live in a hidden Switchable page */
139
+ background?: boolean
140
+ }
141
+
142
+ /** A decision as a `makeFetchDriver` request (POST JSON); its reply is a `Decision<Q, R>` */
143
+ export type DecideRequest<Q extends Questions = Questions, R = never> = Omit<DecideOptions<Q>, 'model' | 'state' | 'questions' | 'images' | 'url'> & {
144
+ url: string
145
+ method: 'POST'
146
+ json: Record<string, unknown>
147
+ /** Type only: the reply's shape (`Decision<Q, R>`); never set */
148
+ readonly __decision?: Decision<Q, R>
149
+ }
150
+
151
+ /**
152
+ * PLAN-6 M-1: a decision request for `makeFetchDriver`, in the dictionary form TypeSafe
153
+ * (`/v1/systemone`), Ollama ≥ 0.35 (`/v1/systemone`), OpenRouter and the AI Gateway take:
154
+ * `{ url, method: 'POST', json: { model, state, questions, images? }, ...requestKeys }`. Use it
155
+ * as a reply-action request (`{ HTTP: () => decide({ ..., ok: 'TRIAGED' }) }`) or as a
156
+ * `resources` entry; the reply is `{ model, answers: { name: answer }, usage }` (`Decision<Q>`).
157
+ * Any other key (`ok`, `error`, `key`, `latest`, `headers`, ...) passes through to the request.
158
+ */
159
+ export const decide: {
160
+ <Q extends Questions>(options: DecideOptions<Q>): DecideRequest<Q>
161
+ /**
162
+ * The same questions as an OpenAI Decisions request (`{ model, input, questions: [...] }`,
163
+ * `predicate` / `choice` / `score`); its `parse` maps the reply's answer array back to the
164
+ * dictionary form, so reply actions and resources get a `Decision<Q, RefusalAnswer>`. A
165
+ * `noul` question's criteria are appended to the predicate's instructions. Point `url` at
166
+ * your server route that adds the key (OpenAI: `POST /v1/decisions`)
167
+ */
168
+ openai<Q extends Questions>(options: DecideOptions<Q>): DecideRequest<Q, RefusalAnswer>
169
+ }
170
+
171
+ /** A `choice` question: the most likely of the options (`{ name: description | null }` or names) */
172
+ export function choice<K extends string>(instructions: DecisionInstructions, criteria: { [P in K]: string | null }): ChoiceQuestion<K>
173
+ export function choice<const K extends string>(instructions: DecisionInstructions, criteria: readonly K[]): ChoiceQuestion<K>
174
+
175
+ /** A `noul` question: the probability of yes; `criteria` describe what counts as true / false */
176
+ export function noul(instructions: DecisionInstructions, criteria?: { true: string; false: string }): NoulQuestion
177
+
178
+ /** A `score` question: an ordered scale of 2 or more level descriptions, lowest first */
179
+ export function score<const L extends string>(instructions: DecisionInstructions, levels: readonly [L, L, ...L[]]): ScoreQuestion<L>
180
+ // ---- M-2: decision reply fixtures (3-M) ----------------------------------------------------------
181
+
182
+ /**
183
+ * What `answers()` takes for one question:
184
+ * - choice: an option name, `{ choice?, confidence? }`, or `{ probabilities }` (the most likely wins);
185
+ * - noul: `true` (0.95), `false` (0.05) or the probability of yes;
186
+ * - score: a level index (fractional: between two levels), a level description, or `{ score, confidence? }`
187
+ */
188
+ export type AnswerPick<Q> =
189
+ Q extends ChoiceQuestion<infer K> ? K | { choice?: K; confidence?: number } | { probabilities: Partial<Record<K, number>>; choice?: K; confidence?: number }
190
+ : Q extends ScoreQuestion<infer L> ? number | L | { score: number | L; confidence?: number }
191
+ : Q extends NoulQuestion ? boolean | number | { noul: number }
192
+ : never
193
+
194
+ /** The picks by question name (each optional) */
195
+ export type AnswerPicks<Q extends Questions> = { [N in keyof Q]?: AnswerPick<Q[N]> }
196
+
197
+ /**
198
+ * PLAN-6 M-2: a decision reply for tests, typed from the questions: what a decision model would
199
+ * send for `decide({ questions })`, with what the test doesn't pick filled in (a choice: the first
200
+ * option at confidence 0.9, the rest sharing the remaining probability; a noul: false; a score: 0).
201
+ * `confidence` is computed as TypeSafe / Ollama `nimble` report it (1 minus the normalized
202
+ * entropy), unless given. Unknown question names, options or levels throw.
203
+ *
204
+ * await t.respond('HTTP', answers(questions, { topic: { choice: 'billing', confidence: 0.35 } }), 'TRIAGED')
205
+ */
206
+ export const answers: {
207
+ <Q extends Questions>(questions: Q, picks?: AnswerPicks<Q>, options?: { model?: string; usage?: { input_tokens: number; output_tokens: number } }): Decision<Q> & { model: string }
208
+ /** the same reply in OpenAI Decisions' array form, for a `decide.openai()` request (its `parse` maps it back) */
209
+ openai<Q extends Questions>(questions: Q, picks?: AnswerPicks<Q>, options?: { model?: string }): { model: string; answers: Array<Record<string, unknown> & { type: 'predicate' | 'choice' | 'score'; name: string }> }
210
+ }
211
+ // ------------------------------------------------------------------------------------------------
212
+ // A-1: schemas (the input contract) and the agent layer
213
+
214
+ /** A plain JSON Schema object (for `jsonSchema()`) */
215
+ export type JsonSchemaObject = {
216
+ readonly type?: string | readonly string[]
217
+ readonly properties?: Record<string, unknown>
218
+ readonly '~standard'?: never
219
+ readonly [keyword: string]: unknown
220
+ }
221
+
222
+ /**
223
+ * A schema for an agent action's `input` (or a chat request's `output`): a Standard Schema
224
+ * whose validated output is assignable to D, that also implements Standard JSON Schema (Zod
225
+ * 4.2+, ArkType 2.1.28+, Valibot through `toStandardJsonSchema()`), or `jsonSchema()`. Without a
226
+ * JSON Schema form the tool isn't offered (SYG240). Structural: no dependency on
227
+ * `@standard-schema/spec`.
228
+ */
229
+ export type AgentSchema<D = unknown> = {
230
+ readonly '~standard': {
231
+ readonly version: 1
232
+ readonly vendor: string
233
+ readonly validate: (value: unknown, options?: any) => unknown
234
+ readonly types?: { readonly input: unknown; readonly output: D } | undefined
235
+ readonly jsonSchema?: { readonly input: (options: { target: string; libraryOptions?: Record<string, unknown> }) => Record<string, unknown> }
236
+ }
237
+ }
238
+
239
+ /**
240
+ * Plain JSON Schema as an `input` / `output` (D262): the schema goes to the model as is
241
+ * (normalized), and a subset validator checks the value (type, enum, const, required,
242
+ * properties, additionalProperties, items, prefixItems, anyOf / oneOf, minimum / maximum,
243
+ * minLength / maxLength, minItems / maxItems, pattern, local `$ref`s; format, allOf, not,
244
+ * if / then / else, multipleOf and uniqueItems are not checked). `{ validate }` keeps a library's
245
+ * own validation: `jsonSchema(z.toJSONSchema(s), { validate: s })` for Zod Mini. T is asserted,
246
+ * not inferred.
247
+ */
248
+ export function jsonSchema<T = unknown>(schema: JsonSchemaObject, options?: { validate?: { readonly '~standard': { readonly validate: (value: unknown) => unknown } } }): AgentSchema<T>
249
+
250
+ /** A schema converted for a model: the tool schema, or why there is none */
251
+ export interface ConvertedSchema {
252
+ /** the normalized, input-side JSON Schema (draft 2020-12); always an object root */
253
+ schema?: Record<string, any>
254
+ /** a non-object root was wrapped as `{ value }` (the unwrap also takes bare arguments) */
255
+ wrapped?: boolean
256
+ /** a lossy conversion (SYG243): what the model doesn't see (it is still validated) */
257
+ lossy?: string
258
+ /** no JSON Schema form (SYG240) */
259
+ error?: string
260
+ }
261
+
262
+ /**
263
+ * The JSON Schema a model gets for a schema: the input side, normalized for every provider
264
+ * (`$schema` dropped, `oneOf` -> `anyOf`, Zod's safe-integer bounds and a tuple's `items: false`
265
+ * dropped, a root `"$ref": "#"` moved into `$defs`), a non-object root wrapped as `{ value }`.
266
+ * Cached by schema object.
267
+ */
268
+ export function toJsonSchema(schema: unknown): ConvertedSchema
269
+
270
+ /** The validated output type of a schema */
271
+ export type SchemaOutput<S> = S extends { readonly '~standard': { readonly types?: infer T } } ? NonNullable<T> extends { readonly output: infer O } ? O : unknown : unknown
272
+
273
+ /** One validation failure */
274
+ export interface AgentIssue { message: string; path?: PropertyKey[] }
275
+
276
+ /**
277
+ * A model's arguments for a schema -> the validated value: the `{ value }` unwrap (bare
278
+ * arguments accepted), `repair` (numeric and boolean strings where the schema says number /
279
+ * integer / boolean; `repair: false` turns it off, D264), then the schema's validation.
280
+ */
281
+ export function parseInput<S extends AgentSchema<any>>(schema: S, args: unknown, options?: { repair?: boolean }): Promise<
282
+ | { value: SchemaOutput<S>; issues?: undefined; error?: undefined }
283
+ | { value?: undefined; issues: AgentIssue[]; error?: undefined }
284
+ | { value?: undefined; issues?: undefined; error: string }
285
+ >
286
+
287
+ type AgentNoData<D> = 0 extends 1 & D ? false : [D] extends [undefined | void | null] ? true : [D] extends [Event] ? true : false
288
+ type AgentIsAny<D> = 0 extends 1 & D ? true : false
289
+
290
+ /**
291
+ * One entry of `agent.actions`, typed by the model's data type D for that action: no data
292
+ * (undefined, void, null, or the DOM event a click delivers): no `input`; an untyped component:
293
+ * `input` optional; otherwise `input` is required and its schema's output must be assignable to D.
294
+ */
295
+ export type AgentAction<STATE, D> = {
296
+ /** What the action does, for the model (WebMCP: at most 500 characters) */
297
+ description: string
298
+ /** Ask the user before running it (the chat behavior's `pending`, WebMCP's confirm) */
299
+ consequential?: boolean
300
+ /** Offered only while this is true (cached by state identity) */
301
+ when?: (state: STATE) => boolean
302
+ /** A call that changes nothing is a success (`{ ok: true, unchanged: true }`), not an error (D256) */
303
+ idempotent?: boolean
304
+ } & (AgentNoData<D> extends true ? { input?: never } : AgentIsAny<D> extends true ? { input?: AgentSchema<any> } : { input: AgentSchema<D> })
305
+
306
+ /**
307
+ * The `agent` static: what agents may do with a component and see of it. Only the listed actions
308
+ * are exposed, as tools `<name>_<action>` (snake case) plus `<name>_read`; a Collection item
309
+ * component's actions are one tool per action with an `id` parameter (`item` when the input has
310
+ * its own `id`).
311
+ */
312
+ export type AgentDeclaration<STATE = any, ACTIONS = any> = {
313
+ /** Tool name prefix: `todos` -> todos_add, todos_read (letters, digits, _) */
314
+ name: string
315
+ description?: string
316
+ /** What the model sees of this component: in its context after every flush, and in call results */
317
+ read?: (state: STATE) => unknown
318
+ /**
319
+ * A Collection item's label for the model (D258). User text unless `untrusted: false` (G-644):
320
+ * then the key parameter lists ids only and the labels go with the data (the chat behavior's
321
+ * app state as `<name>_labels`, the command bar's decision `state.labels`)
322
+ */
323
+ label?: (state: STATE) => string
324
+ /**
325
+ * `read` (and `label`) return user-entered strings (WebMCP's untrustedContentHint; the chat behavior names
326
+ * the declaration as untrusted in its app-state block); `false`: only the app's own text. Unset,
327
+ * it is inferred (WebMCP: SYG244): any string is user text except values under keys named `id`,
328
+ * `status`, `type` or `kind`, and values that are one of this declaration's input enum values (G-623)
329
+ */
330
+ untrusted?: boolean
331
+ actions: keyof ACTIONS extends never
332
+ ? { [action: string]: AgentAction<STATE, any> }
333
+ : { [K in keyof ACTIONS]?: AgentAction<STATE, ACTIONS[K]> }
334
+ }
335
+
336
+ /** One tool, as an agent sees it */
337
+ export interface AgentTool {
338
+ name: string
339
+ description: string
340
+ /** the input JSON Schema (an object root) */
341
+ inputSchema: Record<string, any>
342
+ annotations: { readOnlyHint?: true; consequentialHint?: true; untrustedContentHint?: true }
343
+ /** a declared tool that isn't offered (SYG240); only in `list({ all: true })` and `t.tools()` */
344
+ error?: string
345
+ }
346
+
347
+ /** What a consequential call asks the user about */
348
+ export interface AgentConfirmInfo {
349
+ tool: string
350
+ component: string
351
+ action: string
352
+ description: string
353
+ /** the validated input */
354
+ input: unknown
355
+ /** a Collection item's key and label */
356
+ key?: unknown
357
+ label?: string
358
+ /** The chat behavior's `pending` for a server tool's approval request (AI SDK `needsApproval`): its approval id (`component` is 'server') */
359
+ approvalId?: string
360
+ }
361
+ export type AgentConfirm = boolean | ((info: AgentConfirmInfo) => boolean | Promise<boolean>)
362
+
363
+ /**
364
+ * A tool call's result, for the model:
365
+ * - `{ ok: true, state }`: done; `state` is the nearest `read` projection (the item's, or its
366
+ * list's); `removed: true` when an item removed itself; `unchanged: true` for an idempotent no-op
367
+ * - `{ ok: false, error, state?, issues?, keys? }`: invalid input (`issues`), an unknown or hidden
368
+ * key (`keys`), not available (`when`), declined, refused (`abort(reason)`), threw, or changed
369
+ * nothing ("already so, or the input matched nothing")
370
+ */
371
+ export type AgentResult =
372
+ | { ok: true; state?: unknown; removed?: true; unchanged?: true }
373
+ | { ok: false; error: string; state?: unknown; issues?: AgentIssue[]; keys?: unknown[] }
374
+
375
+ export interface AgentToolsOptions {
376
+ /** consequential calls: `true` runs them, a function asks (default: declined) */
377
+ confirm?: AgentConfirm
378
+ /** run calls one at a time, in call order (default true, D260) */
379
+ serial?: boolean
380
+ /** coerce numeric / boolean strings where the schema says so (default true, D264) */
381
+ repair?: boolean
382
+ /** only this instance (an InstanceView or its id) and its descendants */
383
+ from?: unknown
384
+ /** only these components' declarations */
385
+ components?: Function[]
386
+ }
387
+
388
+ /** The live tool set `agentTools()` returns */
389
+ export interface AgentToolSet {
390
+ /** the tools offered now; `{ all: true }` adds declared tools that can't be offered, with `error` */
391
+ list(options?: { all?: boolean }): AgentTool[]
392
+ /** run a tool as an agent (cause 'agent'); resolves after the action's flush */
393
+ call(name: string, args?: unknown, options?: { confirm?: AgentConfirm }): Promise<AgentResult>
394
+ /** the `read` projections by declaration name (item declarations: arrays with `id`) */
395
+ context(): Record<string, unknown>
396
+ /** called with the tools and the context after each flush that changed either; returns unsubscribe */
397
+ subscribe(listener: (change: { tools: AgentTool[]; context: Record<string, unknown> }) => void): () => void
398
+ /** detach from the app */
399
+ stop(): void
400
+ }
401
+
402
+ /**
403
+ * The agent tools of an app's shown components (their `agent` statics): the building block of
404
+ * the chat behavior, WebMCP and `t.tools()`. Pass the `run()` result (followed through HMR) or a
405
+ * runtime (`app.__runtime`). Every call runs as an action with cause 'agent', through the same
406
+ * queue, reducers and flush as a click.
407
+ */
408
+ export function agentTools(app: unknown, options?: AgentToolsOptions): AgentToolSet
409
+ // ---- L-1: the chat driver (1-L) -------------------------------------------------------------
410
+
411
+ /** A Standard Schema (https://standardschema.dev), as `output` takes it; Standard JSON Schema lets transports send it */
412
+ export type ChatOutputSchema<OUT = unknown> = {
413
+ readonly '~standard': {
414
+ validate: (value: unknown) => unknown;
415
+ readonly types?: { readonly input: unknown; readonly output: OUT };
416
+ [key: string]: any;
417
+ };
418
+ }
419
+
420
+ /** The validated type of an `output` schema */
421
+ export type ChatOutputOf<S> = S extends { readonly '~standard': { readonly types?: infer T } }
422
+ ? NonNullable<T> extends { readonly output: infer O } ? O : unknown
423
+ : unknown
424
+
425
+ /** A tool offered to the model (the chat behavior builds these from `agent` declarations) */
426
+ export type ChatTool = {
427
+ description?: string;
428
+ /** The input-side JSON Schema of the tool's arguments (an object schema) */
429
+ inputSchema?: Record<string, unknown>;
430
+ }
431
+
432
+ /**
433
+ * A request to a makeChatDriver() sink. The driver passes it to the transport as is, so a
434
+ * transport's own options go here too. The reply keys name the sender's actions:
435
+ *
436
+ * SEND: { LLM: (state) => ({ messages: state.messages, key: 'reply', delta: 'DELTA', ok: 'DONE', error: 'FAILED' }) },
437
+ * DELTA: (state, { text }) => ({ ...state, draft: text }), // ChatDelta
438
+ * DONE: (state, { message }) => ({ ...state, messages: [...state.messages, message], draft: '' }), // ChatOk
439
+ * FAILED: (state, { error }) => ({ ...state, error: error.message }), // ChatError
440
+ * STOP: { LLM: () => ({ abort: 'reply' }) },
441
+ */
442
+ export type ChatRequest<OUT = unknown> = {
443
+ /** The conversation so far */
444
+ messages: Message[];
445
+ /** System instructions */
446
+ instructions?: string;
447
+ /** Tools the model may call, by name */
448
+ tools?: Record<string, ChatTool>;
449
+ /** A model id, overriding the transport's */
450
+ model?: string;
451
+ /**
452
+ * Structured output: a Standard Schema with Standard JSON Schema (Zod 4.2+, ArkType, Valibot via
453
+ * toStandardJsonSchema()). The transport sends its input-side JSON Schema (`outputJsonSchema`);
454
+ * `ok` gets the validated `value`, a failure is the `error` reply with `issues`
455
+ */
456
+ output?: ChatOutputSchema<OUT>;
457
+ /** The request's key for `latest` and `abort` (default: `ok`, else `error`) */
458
+ key?: string;
459
+ /** A new request aborts the sender's request under the same key (default true) */
460
+ latest?: boolean;
461
+ /** Action for streamed text and reasoning (ChatDelta), coalesced per `coalesce` */
462
+ delta?: string;
463
+ /** Action for the finished reply (ChatOk) */
464
+ ok?: string;
465
+ /** Action for a failure (ChatError). Without one a failure is logged (SYG678) */
466
+ error?: string;
467
+ /** Action for each completed tool call (ChatToolCall) */
468
+ tool?: string;
469
+ /**
470
+ * The reply continues the last message, an assistant message (after tool results or an approval,
471
+ * as the AI SDK does): `message` starts with its id and parts plus a `step-start` part; `text`,
472
+ * `toolCalls` and `delta` are the new step's (G-628)
473
+ */
474
+ continue?: boolean;
475
+ /**
476
+ * How often `delta` fires: 'frame' (default: at most once per animation frame and per 15 ms,
477
+ * about once a second in a hidden tab), 'none' (once per streamed event) or a number of ms
478
+ */
479
+ coalesce?: 'frame' | 'none' | number;
480
+ /** Not allowed: a `then` key makes the request a thenable (SYG610, not sent). Use `ok` */
481
+ then?: never;
482
+ /** Not allowed (SYG610, not sent). Use `error` */
483
+ catch?: never;
484
+ /** Transport options */
485
+ [option: string]: unknown;
486
+ }
487
+
488
+ /** Stops the sender's requests under a key (`{ abort: 'reply' }`, `{ abort: true, key: 'reply' }`), or all of them (`{ abort: true }`) */
489
+ export type ChatAbort = { abort: string | true; key?: string }
490
+
491
+ /** `delta` action data: the reply so far */
492
+ export type ChatDelta = {
493
+ key: string;
494
+ /** All the text so far */
495
+ text: string;
496
+ /** All the reasoning so far */
497
+ reasoning: string;
498
+ /** The text that is new since the previous delta ('' when only reasoning grew) */
499
+ delta: string;
500
+ /** The assistant message so far */
501
+ message: Message;
502
+ }
503
+
504
+ /** A completed tool call */
505
+ export type ChatCall = { id: string; name: string; input: any }
506
+
507
+ /** `tool` action data */
508
+ export type ChatToolCall = { key: string; call: ChatCall }
509
+
510
+ /** `ok` action data: the finished reply */
511
+ export type ChatOk<VALUE = unknown> = {
512
+ key: string;
513
+ /** The assistant message (append it to the conversation) */
514
+ message: Message;
515
+ /** Its text */
516
+ text: string;
517
+ /** The validated structured output (requests with `output`) */
518
+ value?: VALUE;
519
+ toolCalls: ChatCall[];
520
+ /** The transport's finish reason ('stop', 'length', 'tool-calls', 'content-filter', …); default 'stop', or 'tool-calls' after tool calls */
521
+ finishReason: string;
522
+ usage?: unknown;
523
+ }
524
+
525
+ /** `error` action data */
526
+ export type ChatError = {
527
+ key: string;
528
+ error: Error & { status?: number };
529
+ request: ChatRequest;
530
+ /** Structured output that failed validation (or wasn't JSON) */
531
+ issues?: ReadonlyArray<{ message: string; path?: ReadonlyArray<any> }>;
532
+ }
533
+
534
+ /**
535
+ * What a transport yields, in order. Unknown types are ignored; a known type with the wrong shape
536
+ * is skipped (SYG673 in dev). Text and reasoning grow the message's trailing part of that type; a
537
+ * tool call adds a `tool-<name>` part (`state: 'input-available'`), which a later tool-result /
538
+ * tool-error with its id completes; `start` sets the message id.
539
+ */
540
+ export type ChatEvent =
541
+ | { type: 'start'; id?: string }
542
+ | { type: 'text'; delta: string }
543
+ /** `providerMetadata` closes the reasoning part with it (L-2: an Anthropic thinking signature, to send back) */
544
+ | { type: 'reasoning'; delta: string; providerMetadata?: Record<string, Record<string, unknown>> }
545
+ | { type: 'tool-call'; id?: string; name: string; input?: unknown; /** the server runs it (L-2 uiMessageStream): a message part only, no `tool` reply */ executed?: boolean; providerExecuted?: boolean }
546
+ | { type: 'tool-result'; id: string; output: unknown }
547
+ | { type: 'tool-error'; id: string; error: string }
548
+ /** the server asks the user to approve the call (AI SDK `needsApproval`): the part goes to `approval-requested` */
549
+ | { type: 'tool-approval'; id: string; approval: { id: string; requestReason?: string; [key: string]: unknown } }
550
+ /** the server reports the call denied: the part goes to `output-denied` */
551
+ | { type: 'tool-denied'; id: string }
552
+ | { type: 'data'; name: string; data: unknown; id?: string }
553
+ | { type: `data-${string}`; data: unknown; id?: string }
554
+ | { type: 'file'; mediaType: string; url: string; filename?: string }
555
+ | { type: `source-${string}`; [key: string]: unknown }
556
+ | { type: 'finish'; reason?: string; usage?: unknown }
557
+
558
+ /**
559
+ * Speaks one wire protocol to the chat driver. `stream` is called once per request, synchronously
560
+ * when the request is sent; stop reading (and abort the fetch) when `signal` aborts. A throw, or a
561
+ * rejected iteration, is the request's failure.
562
+ */
563
+ export interface ChatTransport {
564
+ stream(request: ChatRequest, signal: AbortSignal): AsyncIterable<ChatEvent>;
565
+ }
566
+
567
+ export type ChatDriverOptions = {
568
+ transport: ChatTransport;
569
+ /** The default `coalesce` of requests (default 'frame') */
570
+ coalesce?: 'frame' | 'none' | number;
571
+ }
572
+
573
+ /** The chat source: reply actions only (no select()) */
574
+ export type ChatSource = { readonly __sygnalReplies: true; dispose(): void; [key: string]: any }
575
+
576
+ /**
577
+ * makeChatDriver({ transport }): an LLM chat driver with reply actions, like makeFetchDriver.
578
+ * Requests are ChatRequest / ChatAbort sink values; replies reach the sending component only.
579
+ * `latest` per (sender, key); a removed component's and the app's streams are aborted, and an
580
+ * aborted stream delivers nothing.
581
+ */
582
+ export function makeChatDriver(options: ChatDriverOptions): (sink$: Stream<ChatRequest | ChatAbort>) => ChatSource
583
+
584
+ /**
585
+ * The JSON Schema a transport sends for a request's `output`: the input side of its Standard
586
+ * JSON Schema, a non-object root wrapped as `{ value }` (`wrapped: true`; the driver unwraps the
587
+ * reply). Undefined when the schema has no JSON Schema form.
588
+ */
589
+ export function outputJsonSchema(output: ChatOutputSchema | undefined): { schema: Record<string, unknown>; wrapped: boolean } | undefined
590
+
591
+ // ---- A-2: WebMCP (2-W; experimental, D241: tracks the draft, outside semver) --------------------
592
+
593
+ export interface ExposeWebMcpOptions {
594
+ /** origins the tools are exposed to (passed to `registerTool` where the browser takes it) */
595
+ exposedTo?: string[]
596
+ /** consequential calls: `true` runs them, `false` declines, a function asks (default: a native modal `<dialog>`) */
597
+ confirm?: AgentConfirm
598
+ /** prepended to every tool name (`shop_` -> shop_todos_add) */
599
+ prefix?: string
600
+ /** the WebMCP context (default `document.modelContext ?? navigator.modelContext`) */
601
+ modelContext?: unknown
602
+ }
603
+ /** Stops exposing the tools (unregisters them); `available` is false where there is no WebMCP */
604
+ export type WebMcpHandle = (() => void) & { readonly available: boolean }
605
+
606
+ /**
607
+ * Experimental: offer an app's agent tools (its components' `agent` statics, as `agentTools()`
608
+ * sees them) to the browser's agent through WebMCP (`document.modelContext`). Each tool is
609
+ * registered with `registerTool` and re-registered when it changes (a `when`, the live Collection
610
+ * keys, the state summary in its description); results are always objects within Chrome's
611
+ * budgets. A no-op where WebMCP is missing.
612
+ */
613
+ export function experimentalExposeWebMcp(app: unknown, options?: ExposeWebMcpOptions): WebMcpHandle
614
+
615
+ // ---- A-3: form(…, { tool: formTool() }) (3-A3; experimental, D241; pay per use, D291) --------
616
+
617
+ /**
618
+ * **Experimental** (D241): a `form`'s declarative WebMCP tool, as `formTool()` takes it.
619
+ * Chrome's budgets: name ≤ 30 characters of `[A-Za-z0-9_.-]`, description ≤ 500.
620
+ */
621
+ export interface FormTool {
622
+ /** The tool name (the `<form>`'s `toolname`) */
623
+ name: string
624
+ /** What the form does, for the agent (`tooldescription`) */
625
+ description: string
626
+ /**
627
+ * Submit when the agent calls the tool (`toolautosubmit`). Default false (D270): the call fills
628
+ * the fields and stays pending until the user submits the form; then the agent gets the answer
629
+ */
630
+ autosubmit?: boolean
631
+ }
632
+ /** What `formTool()` returns: the spec plus the hook the form behavior calls (pass it as `tool`) */
633
+ export type FormToolHandle = Readonly<FormTool> & { readonly $: (behavior: unknown, result: unknown) => void }
634
+ /**
635
+ * **Experimental** (D241): what an agent's call of a `form` tool resolves with: the schema's
636
+ * output (what the host's submit action got; a submit sent as a request answers after
637
+ * `form.DONE`), the field errors by name (schema, check and server errors; `''` is form-level), or
638
+ * an `error` when a submit was already running or the form went away first.
639
+ */
640
+ export type FormToolResult =
641
+ | { ok: true; values: any }
642
+ | { ok: false; errors: FieldErrors }
643
+ | { ok: false; error: string }
644
+ /**
645
+ * **Experimental** (PLAN-6 A-3, D241): the `tool` option of `form()`, which offers the form to
646
+ * the browser's agent as a declarative WebMCP tool:
647
+ *
648
+ * Signup.uses = { form: form(schema, { values, submit: 'SIGN_UP', tool: formTool({ name: 'sign_up', description: 'Create an account' }) }) }
649
+ *
650
+ * The tool code is in this function, so an app using `form` without it doesn't carry it (D291).
651
+ */
652
+ export function formTool(tool: FormTool): FormToolHandle
653
+ // ---- L-3: the chat behavior (2-C) -----------------------------------------------------------
654
+
655
+ /** 'ready' | 'submitted' (sent, nothing received yet) | 'streaming' (receiving, or running tools) | 'error' (the AI SDK's names) */
656
+ export type ChatStatus = 'ready' | 'submitted' | 'streaming' | 'error'
657
+
658
+ /** The chat behavior's slice: `state.assistant` for `uses = { assistant: chat(...) }` */
659
+ export interface ChatState {
660
+ /** The conversation: the user's messages and the model's replies, with their `tool-<name>` parts */
661
+ messages: Message[]
662
+ /** The prompt field's value */
663
+ prompt: string
664
+ /** The text of the reply being streamed ('' otherwise) */
665
+ draft: string
666
+ /** The reasoning of the reply being streamed ('' otherwise; the finished reply keeps it as `reasoning` parts) */
667
+ draftReasoning: string
668
+ status: ChatStatus
669
+ /** A consequential tool call waiting for APPROVE / DENY (or a server tool's approval request: `approvalId`), else null */
670
+ pending: AgentConfirmInfo | null
671
+ /** The last failure's message, else null */
672
+ error: string | null
673
+ }
674
+
675
+ /** `DONE` action data: the turn is over */
676
+ export interface ChatDone {
677
+ /** The last reply */
678
+ message: Message
679
+ text: string
680
+ finishReason: string
681
+ usage?: unknown
682
+ /** Requests sent in the turn (at most `maxSteps`) */
683
+ steps: number
684
+ }
685
+
686
+ /** The chat behavior's actions, as `'<key>.<ACTION>'` on the host */
687
+ export interface ChatActions {
688
+ /** Send the prompt (the form's submit), or the text given as data */
689
+ SEND: string | { text: string } | Event | undefined
690
+ /** Abort the reply being streamed (its text and reasoning so far are kept) and decline a waiting call; calls that already ran keep their results */
691
+ STOP: any
692
+ /** Send the last user message again, dropping the replies after it */
693
+ REGENERATE: any
694
+ /** Run the pending consequential call (a server approval: answered approved, and the conversation sent again) */
695
+ APPROVE: any
696
+ /** Decline the pending consequential call (the model is told the user declined; a server approval: answered not approved) */
697
+ DENY: any
698
+ /** The turn is over (a host entry `'assistant.DONE'` runs after the behavior's) */
699
+ DONE: ChatDone
700
+ /** Internal: the prompt field's input */
701
+ PROMPT: string
702
+ }
703
+
704
+ export interface ChatOptions {
705
+ /** The chat driver's sink (default 'LLM') */
706
+ sink?: string
707
+ /** The form element whose submit sends the prompt */
708
+ form?: BehaviorTarget
709
+ /** The prompt field (its input events set `prompt`) */
710
+ prompt?: BehaviorTarget
711
+ /** Its clicks STOP */
712
+ stop?: BehaviorTarget
713
+ /** Its clicks APPROVE the pending call */
714
+ approve?: BehaviorTarget
715
+ /** Its clicks DENY the pending call */
716
+ deny?: BehaviorTarget
717
+ /** Its clicks REGENERATE */
718
+ regenerate?: BehaviorTarget
719
+ /**
720
+ * System instructions. The tools' `read` projections are not appended: they go as app state, a
721
+ * user-role message of their own right before the last user message on every request (id
722
+ * 'sygnal-app-state', framed as data, not instructions, with the declarations holding
723
+ * user-entered text named; G-631)
724
+ */
725
+ instructions?: string
726
+ /** A model id for the requests (overrides the transport's) */
727
+ model?: string
728
+ /**
729
+ * Whose `agent` declarations become tools: by default the host's and its shown descendants'
730
+ * (D249); `false`: none; `[Comp, …]`: only these components' (in the host's subtree)
731
+ */
732
+ agent?: false | Function[]
733
+ /** The most requests per turn (default 8): the tool calls of a reply past it are not run */
734
+ maxSteps?: number
735
+ /** Extra request keys for the transport */
736
+ transportOptions?: Record<string, unknown>
737
+ }
738
+
739
+ /**
740
+ * PLAN-6 L-3: an in-app assistant as a behavior. `uses = { assistant: chat({ form: '.ask',
741
+ * prompt: '.prompt', stop: '.stop', approve: '.approve', deny: '.deny', instructions }) }` gives
742
+ * `state.assistant` (ChatState) and the actions 'assistant.SEND', 'assistant.STOP',
743
+ * 'assistant.REGENERATE', 'assistant.APPROVE', 'assistant.DENY' and 'assistant.DONE'. It sends
744
+ * requests to the chat driver (`sink`), runs the model's tool calls on the host's `agent` tools
745
+ * (and its descendants') through the agent layer (validation, no-op detection, cause 'agent'),
746
+ * asks before consequential ones (`pending`), and loops until a reply without tool calls or
747
+ * `maxSteps`. Its selectors are the host's own: render the markup in the host's view.
748
+ */
749
+ export function chat(options?: ChatOptions): Behavior<ChatState, ChatActions, {}, ChatOptions>
750
+ // ---- L-2: transports, wave 1 (2-T) ---------------------------------------------------------------
751
+
752
+ /** `ok.usage` from the L-2 transports (AI SDK names) */
753
+ export type ChatUsage = { inputTokens?: number; outputTokens?: number; totalTokens?: number; reasoningTokens?: number }
754
+
755
+ /** Options every HTTP transport takes */
756
+ export interface HttpTransportOptions {
757
+ /** Request headers, or a function of the request (e.g. a fresh session token) */
758
+ headers?: Record<string, string> | ((request: ChatRequest) => Record<string, string> | Promise<Record<string, string>>);
759
+ /** The fetch to use (default: globalThis.fetch): a proxy, a test or demo server, SSR */
760
+ fetch?: (url: string, init: RequestInit) => Promise<Response>;
761
+ /** Extra JSON body fields (a request's own `body` is merged over them) */
762
+ body?: Record<string, unknown>;
763
+ /**
764
+ * In a browser, a request with an auth header (Authorization, x-api-key, ...) to a host that is
765
+ * neither local nor the page's origin is refused (SYG670): the key would ship to every visitor.
766
+ * `true` allows it, for a key the user typed in themselves
767
+ */
768
+ dangerouslyAllowBrowser?: boolean;
769
+ }
770
+
771
+ export interface OpenResponsesOptions extends HttpTransportOptions {
772
+ /** The API root (default '/v1'); POSTs to `${baseURL}/responses`. Ollama: 'http://localhost:11434/v1' */
773
+ baseURL?: string;
774
+ /** The model (a request's `model` overrides it) */
775
+ model?: string;
776
+ /**
777
+ * OpenAI strict schemas for tools and `output` (default off, D266, D285): pass `strictSchemas`
778
+ * (imported from sygnal/ai, so apps that don't use strict mode don't carry it). Each schema is
779
+ * rewritten to the strict subset (all keys required, optional ones nullable, no extra keys), a
780
+ * schema with no strict form is sent non-strict (SYG675), the nulls are dropped again before
781
+ * validation. `true` is a dev error (SYG672): import strictSchemas
782
+ */
783
+ strict?: StrictSchemas | false;
784
+ }
785
+
786
+ export interface ChatCompletionsOptions extends HttpTransportOptions {
787
+ /** The API root (default '/v1'); POSTs to `${baseURL}/chat/completions` */
788
+ baseURL?: string;
789
+ model?: string;
790
+ /** As openResponses' `strict`: `strictSchemas` */
791
+ strict?: StrictSchemas | false;
792
+ }
793
+
794
+ export interface UIMessageStreamOptions extends HttpTransportOptions {}
795
+
796
+ export type ChromePromptStatus = 'available' | 'downloadable' | 'downloading' | 'unavailable'
797
+
798
+ export interface ChromePromptOptions {
799
+ temperature?: number;
800
+ topK?: number;
801
+ expectedInputs?: unknown[];
802
+ expectedOutputs?: unknown[];
803
+ /** Sees the model download's progress (`downloadprogress` events) */
804
+ monitor?: (monitor: EventTarget) => void;
805
+ /** The Prompt API object (default: globalThis.LanguageModel; tests pass a stub) */
806
+ LanguageModel?: unknown;
807
+ }
808
+
809
+ export interface ChromePromptTransport extends ChatTransport {
810
+ /**
811
+ * `LanguageModel.availability()`, or 'unavailable' without the API. A component reads it
812
+ * through a driver (G-647): `MODEL: driverFromAsync(transport.status)` in `run()`, and
813
+ * `MODEL: { ok: 'MODEL_STATUS' }` from the model (the reply action gets the status string)
814
+ */
815
+ status(): Promise<ChromePromptStatus>;
816
+ }
817
+
818
+ /**
819
+ * openResponses({ baseURL, model }): a transport for Open Responses / OpenAI Responses SSE
820
+ * (Ollama, vLLM, OpenRouter, OpenAI). Unknown events are ignored, as the spec requires.
821
+ *
822
+ * run(App, { LLM: makeChatDriver({ transport: openResponses({ baseURL: 'http://localhost:11434/v1', model: 'llama3.2' }) }) })
823
+ */
824
+ export function openResponses(options?: OpenResponsesOptions): ChatTransport
825
+
826
+ /** chatCompletions({ baseURL, model }): a transport for Chat Completions SSE (older local servers) */
827
+ export function chatCompletions(options?: ChatCompletionsOptions): ChatTransport
828
+
829
+ /**
830
+ * uiMessageStream(url): a transport for an AI SDK 7 server route (`toUIMessageStreamResponse()`,
831
+ * UI message stream v1). Messages go as UIMessages; server-run tools, approvals and `data-*`
832
+ * parts come back as message parts. The production default: the provider key stays on the server.
833
+ */
834
+ export function uiMessageStream(url: string, options?: UIMessageStreamOptions): ChatTransport
835
+
836
+ /**
837
+ * chromePrompt(): a transport for Chrome's on-device Prompt API (`LanguageModel`); `status()` says
838
+ * whether it can run. No tools (the Prompt API has none); `instructions` and every `system`
839
+ * message are joined into the session's one system prompt (G-630)
840
+ */
841
+ export function chromePrompt(options?: ChromePromptOptions): ChromePromptTransport
842
+
843
+ /** One Open Responses SSE event (`event: type`, `data: JSON`) */
844
+ export interface OpenResponsesEvent { type: string; sequence_number: number; [key: string]: unknown }
845
+
846
+ /**
847
+ * The Open Responses events of a scripted reply (D273), for demo and test servers:
848
+ * chunks as `t.stream` takes them (strings, `{ reasoning }`, `{ toolCall }`, `{ finish }`) or ChatEvents.
849
+ *
850
+ * 'POST /v1/responses': () => ({ sse: encodeOpenResponses(['Hello ', 'there']) })
851
+ */
852
+ export function encodeOpenResponses(
853
+ chunks: ReadonlyArray<string | { reasoning: string } | { toolCall: { id?: string; name: string; input?: unknown } } | { finish: string | { reason?: string; usage?: unknown } } | ChatEvent>,
854
+ options?: { id?: string; model?: string },
855
+ ): OpenResponsesEvent[]
856
+ // ---- M-3: the command bar (3-M) -----------------------------------------------------------------
857
+
858
+ /** 'ready' | 'deciding' (the decision request is out) | 'running' (the action runs or waits for APPROVE / DENY) | 'error' */
859
+ export type CommandBarStatus = 'ready' | 'deciding' | 'running' | 'error'
860
+
861
+ /** Why a command wasn't run (`unsure`, or an escalated `result`) */
862
+ export interface CommandBarUnsure {
863
+ command: string
864
+ /**
865
+ * 'confidence': the action's (or the target's) confidence is below `below`; 'no-action': the
866
+ * model picked none of the actions; 'target': an item action with no clear item; 'input': the
867
+ * action needs an argument the bar can't fill (a chat model can)
868
+ */
869
+ reason: 'confidence' | 'no-action' | 'target' | 'input'
870
+ /** The tool the model leaned to, and its description */
871
+ tool: string | null
872
+ description: string | null
873
+ /** The Collection item key it leaned to, and its `agent.label` */
874
+ target: unknown
875
+ label: string | null
876
+ confidence: number
877
+ }
878
+
879
+ /** The last outcome: a call's result, or the hand-over to the chat behavior */
880
+ export type CommandBarResult =
881
+ | ({ command: string; tool: string; input: unknown } & AgentResult)
882
+ | (CommandBarUnsure & { escalated: string })
883
+
884
+ /** The command bar's slice: `state.cmd` for `uses = { cmd: commandBar(...) }` */
885
+ export interface CommandBarState {
886
+ /** The input's value (cleared when a command ran) */
887
+ text: string
888
+ status: CommandBarStatus
889
+ /** The command being handled, else null */
890
+ command: string | null
891
+ /** A consequential action waiting for APPROVE / DENY, else null */
892
+ pending: AgentConfirmInfo | null
893
+ /** Why the last command wasn't run (without `escalate`), else null */
894
+ unsure: CommandBarUnsure | null
895
+ result: CommandBarResult | null
896
+ /** The decision request's failure, else null */
897
+ error: string | null
898
+ }
899
+
900
+ /** The command bar's actions, as `'<key>.<ACTION>'` on the host */
901
+ export interface CommandBarActions {
902
+ /** Run the input's command (Enter, or the form's submit, or a click of `run`), or the command given as data */
903
+ RUN: string | Event | undefined
904
+ /** Run the pending consequential action */
905
+ APPROVE: any
906
+ /** Decline it */
907
+ DENY: any
908
+ /** A command ran (a host entry `'cmd.DONE'` runs after the behavior's) */
909
+ DONE: { command: string; tool: string; input: unknown } & AgentResult
910
+ /** Internal: the input's value */
911
+ INPUT: string
912
+ }
913
+
914
+ export interface CommandBarOptions {
915
+ /** The command field: its input events set `text`; Enter runs it (unless `form` is given) */
916
+ input: BehaviorTarget
917
+ /** A form whose submit runs the command (instead of Enter in the field) */
918
+ form?: BehaviorTarget
919
+ /** Its clicks run the field's command too (a Go button, D288) */
920
+ run?: BehaviorTarget
921
+ /**
922
+ * The decision request: `decide()` options without `state` / `questions` (`{ url, model }`), or
923
+ * a function building the request (`(q) => decide.openai({ ...q, url, model })`)
924
+ */
925
+ decide: Omit<DecideOptions<Questions>, 'state' | 'questions'> | ((q: { state: unknown; questions: Questions }) => DecideRequest<any, any> | Record<string, unknown>)
926
+ /** Below this confidence (the action's, and the target's for an item action) nothing runs (default 0.6) */
927
+ below?: number
928
+ /** The `uses` key of a `chat` behavior on the same host: an unsure command goes to its SEND */
929
+ escalate?: string
930
+ /** Whose `agent` actions are offered: by default the host's and its shown descendants' (D249); `[Comp, …]`: only these */
931
+ agent?: Function[]
932
+ /** The fetch driver's sink (default 'HTTP') */
933
+ sink?: string
934
+ /** Its clicks APPROVE the pending action */
935
+ approve?: BehaviorTarget
936
+ /** Its clicks DENY it */
937
+ deny?: BehaviorTarget
938
+ /**
939
+ * The free text argument of a picked action whose input is one string (ADD's text). Default: a
940
+ * heuristic, a quoted part of the command or else the command without its lead-in (fillers, an
941
+ * "add" verb and what follows it, a destination: "remind me to call mom" → "call mom", "put milk
942
+ * on my list" → "milk"; a question gives none, G-634); undefined escalates (or `unsure`, reason 'input')
943
+ */
944
+ freeText?: (command: string, tool: string) => string | undefined
945
+ }
946
+
947
+ /**
948
+ * PLAN-6 M-3: a command bar on a decision model. `uses = { cmd: commandBar({ input: '.command',
949
+ * decide: { url: '/api/decide', model: 'jev-latest' }, below: 0.6, escalate: 'assistant' }) }`:
950
+ * one decision request per command picks the action (a `choice` over the `agent` actions'
951
+ * descriptions; an enum input gives one option per value) and the target (a `choice` over the
952
+ * live Collection item keys; user-entered `agent.label`s go in the decision's `state.labels`, not
953
+ * the criteria, G-644), then runs it through the agent layer
954
+ * (validation, no-op detection, cause 'agent', `pending` for a consequential action). Below
955
+ * `below` confidence it hands the command to the chat behavior named by `escalate`, or sets
956
+ * `unsure`. Its selectors are the host's own: render the field in the host's view.
957
+ */
958
+ export function commandBar(options: CommandBarOptions): Behavior<CommandBarState, CommandBarActions, {}, CommandBarOptions>
959
+ // ---- L-2: transports, wave 2, and the strict layer (3-W2) ----------------------------------------
960
+
961
+ /** The provider dialect of a strict schema */
962
+ export type StrictDialect = 'openai' | 'anthropic'
963
+
964
+ /**
965
+ * The strict schema layer (D285): pass it as a transport's `strict` option. Rewrites a portable
966
+ * tool / output schema into the provider's strict subset; `errors` when it has none
967
+ */
968
+ export type StrictSchemas = (schema: Record<string, unknown>, dialect?: StrictDialect) => { schema: Record<string, unknown>; errors: string[]; optional: number; unions: number }
969
+
970
+ /**
971
+ * Strict mode for openResponses, chatCompletions and anthropicMessages, as an import so apps that
972
+ * don't use it don't carry it:
973
+ *
974
+ * openResponses({ baseURL, model, strict: strictSchemas })
975
+ */
976
+ export const strictSchemas: StrictSchemas
977
+
978
+ export interface AnthropicMessagesOptions extends HttpTransportOptions {
979
+ /** The API root (default '/v1'); POSTs to `${baseURL}/messages`. Ollama: 'http://localhost:11434/v1' */
980
+ baseURL?: string;
981
+ /** The model (a request's `model` overrides it) */
982
+ model?: string;
983
+ /** `max_tokens` (default 16000; a request's `maxTokens` overrides it) */
984
+ maxTokens?: number;
985
+ /**
986
+ * Anthropic strict tool use and structured output (default off): `strictSchemas`. Schemas are
987
+ * rewritten to Anthropic's subset; one without a strict form, or over Anthropic's per-request
988
+ * limits (20 strict tools, 24 optional / 16 union-typed parameters), is sent non-strict (SYG675)
989
+ */
990
+ strict?: StrictSchemas | false;
991
+ /** Anthropic server tools sent with the request's tools, e.g. `{ type: 'web_search_20260209', name: 'web_search' }`; their calls come back as executed tool parts */
992
+ serverTools?: Array<Record<string, unknown>>;
993
+ }
994
+
995
+ /**
996
+ * anthropicMessages({ baseURL, model }): a transport for Anthropic Messages SSE (Claude through
997
+ * your server's proxy, or Ollama's /v1/messages). Thinking comes back as reasoning parts with
998
+ * their signature (sent back on the next request), server tools as executed tool parts.
999
+ * A hosted endpoint from the browser needs `dangerouslyAllowBrowser` (SYG670): proxy it instead.
1000
+ *
1001
+ * anthropicMessages({ baseURL: '/api/anthropic/v1', model: 'claude-opus-5-5' })
1002
+ */
1003
+ export function anthropicMessages(options?: AnthropicMessagesOptions): ChatTransport
1004
+
1005
+ export interface AguiOptions extends HttpTransportOptions {
1006
+ /** The AG-UI thread id (a request's `chatId` overrides it; default: one per transport) */
1007
+ threadId?: string;
1008
+ /** The initial agent state (a request's `state` overrides it; later runs send the latest state the agent sent) */
1009
+ state?: unknown;
1010
+ /** AG-UI context entries sent with every run */
1011
+ context?: Array<{ description: string; value: string }>;
1012
+ }
1013
+
1014
+ /**
1015
+ * agui(url): a transport for an AG-UI agent endpoint (TanStack AI, CopilotKit, LangGraph,
1016
+ * Mastra servers). Tool calls the agent answers come back as executed tool parts; STATE_SNAPSHOT
1017
+ * / STATE_DELTA become a `data-agui-state` part (id 'state') holding the agent's current state,
1018
+ * MESSAGES_SNAPSHOT a `data-agui-messages` part, ACTIVITY_SNAPSHOT a `data-agui-activity` part.
1019
+ */
1020
+ export function agui(url: string, options?: AguiOptions): ChatTransport
1021
+
1022
+ export interface FromAISDKOptions {
1023
+ /** The AI SDK's `streamText` (import { streamText } from 'ai'): sygnal never imports `ai` */
1024
+ streamText: (options: any) => any;
1025
+ /** An AI SDK LanguageModel, used when a request names no `model` */
1026
+ model: unknown;
1027
+ /**
1028
+ * Resolves a request's `model` string (G-637): a function (a provider such as `anthropic`, or
1029
+ * `registry.languageModel`) or a map of ids to LanguageModels. A request `model` string with no
1030
+ * `models`, or one it doesn't resolve, fails the request (never the AI SDK's global provider)
1031
+ */
1032
+ models?: ((id: string) => unknown) | Record<string, unknown>;
1033
+ /** The AI SDK's `Output`, for requests with `output` */
1034
+ Output?: { object: (options: { schema: any }) => unknown };
1035
+ /** Other streamText settings (temperature, providerOptions, maxRetries, ...) */
1036
+ [setting: string]: unknown;
1037
+ }
1038
+
1039
+ /**
1040
+ * fromAISDK({ streamText, model }): the AI SDK 7 in process as a transport, for SSR, servers
1041
+ * and tests. The provider key is wherever this runs: in a browser use uiMessageStream() instead.
1042
+ *
1043
+ * import { streamText, Output } from 'ai'
1044
+ * makeChatDriver({ transport: fromAISDK({ streamText, Output, model: anthropic('claude-opus-5-5') }) })
1045
+ * // requests may pick another model by id: { model: 'claude-haiku-5-5', ... }
1046
+ * fromAISDK({ streamText, model: anthropic('claude-opus-5-5'), models: anthropic })
1047
+ */
1048
+ export function fromAISDK(options: FromAISDKOptions): ChatTransport
1049
+
1050
+ // ---------------------------------------------------------------------------------------------
1051
+ // PLAN-6 X-1 (3-X): MCP Apps, the view side (SEP-1865, protocol 2026-01-26)
1052
+ // ---------------------------------------------------------------------------------------------
1053
+
1054
+ export type McpAppDisplayMode = 'inline' | 'fullscreen' | 'pip'
1055
+ /** an MCP content block (text, image, audio, resource, resource_link) */
1056
+ export type McpContentBlock = { type: string; [key: string]: any }
1057
+ /** an MCP CallToolResult: what a server tool returned */
1058
+ export interface McpToolResult {
1059
+ content: McpContentBlock[]
1060
+ structuredContent?: Record<string, any>
1061
+ isError?: boolean
1062
+ _meta?: Record<string, any>
1063
+ [key: string]: any
1064
+ }
1065
+ /** the host context: theme, display mode, container size, locale, ... (merged across changes) */
1066
+ export interface McpHostContext {
1067
+ theme?: 'light' | 'dark'
1068
+ displayMode?: McpAppDisplayMode
1069
+ availableDisplayModes?: McpAppDisplayMode[]
1070
+ locale?: string
1071
+ timeZone?: string
1072
+ platform?: 'web' | 'desktop' | 'mobile'
1073
+ styles?: { variables?: Record<string, string | undefined>; css?: { fonts?: string } }
1074
+ containerDimensions?: Record<string, number | undefined>
1075
+ safeAreaInsets?: { top: number; right: number; bottom: number; left: number }
1076
+ toolInfo?: { id?: string | number; tool: { name: string; [key: string]: any } }
1077
+ [key: string]: any
1078
+ }
1079
+ /** what an `error` reply action of the MCP driver gets */
1080
+ export interface McpAppError {
1081
+ error: string
1082
+ /** the JSON-RPC error code, when the host answered with an error */
1083
+ code?: number
1084
+ /** the result, when it came back with isError: true */
1085
+ result?: McpToolResult
1086
+ request: McpAppRequest
1087
+ }
1088
+ type McpReplies = { ok?: string; error?: string }
1089
+ /** a value for an MCP driver sink (reply actions `ok` / `error` go to the sending component) */
1090
+ export type McpAppRequest =
1091
+ | ({ callTool: string; args?: Record<string, unknown> } & McpReplies)
1092
+ | ({ updateModelContext: string | McpContentBlock[] | Record<string, unknown> } & McpReplies)
1093
+ | ({ message: string | McpContentBlock[] } & McpReplies)
1094
+ | ({ openLink: string } & McpReplies)
1095
+ | ({ displayMode: McpAppDisplayMode } & McpReplies)
1096
+ /** G-641: `ui/download-file` (the host asks the person first); a bare resource contents object is embedded */
1097
+ | ({ downloadFile: McpDownloadItem | McpDownloadItem[] } & McpReplies)
1098
+ /** `resources/read` on the MCP server through the host; ok gets `{ contents }` */
1099
+ | ({ readResource: string | { uri: string; [key: string]: unknown } } & McpReplies)
1100
+ /** `resources/list` through the host; ok gets `{ resources, nextCursor? }` */
1101
+ | ({ listResources: true | { cursor?: string } } & McpReplies)
1102
+ /** `sampling/createMessage`: a completion from the host's model (check `hostCapabilities.sampling`); maxTokens defaults to 1024 */
1103
+ | ({ createMessage: string | McpSamplingRequest } & McpReplies)
1104
+ /** `notifications/message`: a log line for the host (not shown to the model); ok runs once sent */
1105
+ | ({ log: unknown; level?: McpLogLevel; logger?: string } & McpReplies)
1106
+ /** `ui/notifications/request-teardown`: ask the host to remove the view (it may send `teardown`); ok runs once sent */
1107
+ | ({ requestTeardown: true } & McpReplies)
1108
+
1109
+ /** resource contents for `downloadFile`: `{ uri, mimeType?, text }` or `{ uri, mimeType?, blob }` (base64) */
1110
+ export type McpResourceContents = { uri: string; mimeType?: string; text: string; [key: string]: any } | { uri: string; mimeType?: string; blob: string; [key: string]: any }
1111
+ /** an EmbeddedResource / ResourceLink content block, or bare resource contents (embedded for you) */
1112
+ export type McpDownloadItem = McpContentBlock | McpResourceContents
1113
+ export type McpLogLevel = 'debug' | 'info' | 'notice' | 'warning' | 'error' | 'critical' | 'alert' | 'emergency'
1114
+ /** MCP CreateMessageRequest params; `messages` may be one user text */
1115
+ export interface McpSamplingRequest {
1116
+ messages: string | Array<{ role: 'user' | 'assistant'; content: McpContentBlock | McpContentBlock[] }>
1117
+ maxTokens?: number
1118
+ systemPrompt?: string
1119
+ temperature?: number
1120
+ stopSequences?: string[]
1121
+ modelPreferences?: Record<string, any>
1122
+ includeContext?: 'none' | 'thisServer' | 'allServers'
1123
+ metadata?: Record<string, any>
1124
+ tools?: any[]
1125
+ toolChoice?: any
1126
+ [key: string]: any
1127
+ }
1128
+ /** the handshake's answer: `MCP.select('host')` */
1129
+ export interface McpHostInfo {
1130
+ hostInfo?: { name: string; version: string; [key: string]: any }
1131
+ /** openLinks, downloadFile, serverTools, serverResources, logging, sampling, updateModelContext, message, ... */
1132
+ hostCapabilities: Record<string, any>
1133
+ protocolVersion?: string
1134
+ }
1135
+
1136
+ export interface McpAppSource {
1137
+ readonly __sygnalReplies: true
1138
+ /** the arguments the model called the tool with (replayed to a stream that starts later) */
1139
+ select(type: 'tool-input' | 'tool-input-partial'): Stream<Record<string, any>>
1140
+ /** the server tool's result (replayed to a stream that starts later) */
1141
+ select(type: 'tool-result'): Stream<McpToolResult>
1142
+ select(type: 'tool-cancelled'): Stream<{ reason?: string }>
1143
+ /** the whole host context: first the handshake's, then after each change (replayed) */
1144
+ select(type: 'host-context-changed'): Stream<McpHostContext>
1145
+ /** the host is about to remove the view (answered after the actions it caused have run) */
1146
+ select(type: 'teardown'): Stream<{}>
1147
+ /** the handshake's hostInfo, hostCapabilities and protocolVersion (replayed) */
1148
+ select(type: 'host'): Stream<McpHostInfo>
1149
+ dispose(): void
1150
+ }
1151
+
1152
+ export interface McpAppDriverOptions {
1153
+ /** the handshake's `appInfo` (default `{ name: document.title || 'sygnal-app', version: '0.0.0' }`) */
1154
+ appInfo?: { name: string; version: string; [key: string]: any }
1155
+ /** display modes the view supports */
1156
+ availableDisplayModes?: McpAppDisplayMode[]
1157
+ /** report the document's size to the host (`ui/notifications/size-changed`, default true) */
1158
+ autoResize?: boolean
1159
+ /**
1160
+ * `agentTools`: offer the app's `agent` tools (A-1) to the host as the view's own tools
1161
+ * (`appCapabilities.tools`, `tools/list`, `tools/call`, `notifications/tools/list_changed`).
1162
+ * Passed in so an app without it doesn't bundle the agent layer. Call makeMcpAppDriver()
1163
+ * before run(), as in `run(App, { MCP: makeMcpAppDriver({ tools: agentTools }) })`.
1164
+ */
1165
+ tools?: typeof agentTools
1166
+ /** with `tools`: consequential calls run when it resolves true (default: declined) */
1167
+ confirm?: AgentConfirm
1168
+ /** the host window (default `window.parent`) */
1169
+ host?: { postMessage(message: any, targetOrigin: string): void }
1170
+ /** the window to listen on (default `window`) */
1171
+ window?: any
1172
+ /** the protocol version offered (default '2026-01-26') */
1173
+ protocolVersion?: string
1174
+ }
1175
+
1176
+ /**
1177
+ * makeMcpAppDriver(options?): the MCP Apps bridge as a driver, for a Sygnal app shown by an MCP
1178
+ * host (Claude, ChatGPT, VS Code, ...) in a sandboxed iframe. It does the `ui/initialize`
1179
+ * handshake on start. Source: `MCP.select('tool-input' | 'tool-input-partial' | 'tool-result' |
1180
+ * 'tool-cancelled' | 'host-context-changed' | 'teardown' | 'host')`. Sinks: `{ callTool, args, ok, error }`,
1181
+ * `{ updateModelContext }`, `{ message }`, `{ openLink }`, `{ displayMode }`, `{ downloadFile }`,
1182
+ * `{ readResource }`, `{ listResources }`, `{ createMessage }`, `{ log }`, `{ requestTeardown }`.
1183
+ *
1184
+ * run(WeatherCard, { MCP: makeMcpAppDriver() })
1185
+ */
1186
+ export function makeMcpAppDriver(options?: McpAppDriverOptions): (sink$: Stream<McpAppRequest>) => McpAppSource