synthos 0.10.1 → 0.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (311) hide show
  1. package/README.md +5 -5
  2. package/default-pages/elevenlabs_effects_studio/chat-history.json +1 -0
  3. package/default-pages/elevenlabs_effects_studio/page.html +1345 -1363
  4. package/default-pages/elevenlabs_effects_studio/page.json +13 -11
  5. package/default-pages/elevenlabs_voice_studio/chat-history.json +1 -0
  6. package/default-pages/elevenlabs_voice_studio/page.html +782 -801
  7. package/default-pages/elevenlabs_voice_studio/page.json +13 -11
  8. package/default-pages/json_tools/chat-history.json +1 -0
  9. package/default-pages/json_tools/page.html +70 -90
  10. package/default-pages/json_tools/page.json +12 -10
  11. package/default-pages/my_notes/chat-history.json +1 -0
  12. package/default-pages/my_notes/page.html +115 -131
  13. package/default-pages/my_notes/page.json +14 -12
  14. package/default-pages/neon_asteroids/chat-history.json +1 -0
  15. package/default-pages/neon_asteroids/page.html +1777 -1803
  16. package/default-pages/neon_asteroids/page.json +14 -12
  17. package/default-pages/oregon_trail/chat-history.json +1 -0
  18. package/default-pages/oregon_trail/page.html +290 -307
  19. package/default-pages/oregon_trail/page.json +14 -12
  20. package/default-pages/solar_explorer/chat-history.json +1 -0
  21. package/default-pages/solar_explorer/page.html +1929 -1951
  22. package/default-pages/solar_explorer/page.json +14 -12
  23. package/default-pages/solar_tutorial/chat-history.json +1 -0
  24. package/default-pages/solar_tutorial/page.html +464 -478
  25. package/default-pages/solar_tutorial/page.json +12 -10
  26. package/default-pages/us_map/chat-history.json +1 -0
  27. package/default-pages/us_map/page.html +170 -193
  28. package/default-pages/us_map/page.json +14 -12
  29. package/default-pages/us_map/page.light.png +0 -0
  30. package/default-pages/us_map_1850/chat-history.json +1 -0
  31. package/default-pages/us_map_1850/page.html +302 -326
  32. package/default-pages/us_map_1850/page.json +14 -12
  33. package/default-pages/western_cities_1850/chat-history.json +1 -0
  34. package/default-pages/western_cities_1850/page.html +503 -527
  35. package/default-pages/western_cities_1850/page.json +14 -12
  36. package/default-themes/aurora-dawn.v3.css +15 -14
  37. package/default-themes/aurora-dusk.v3.css +26 -26
  38. package/default-themes/cosmos-dawn.v3.css +15 -14
  39. package/default-themes/cosmos-dusk.v3.css +26 -26
  40. package/default-themes/elemental-dawn.v3.css +200 -0
  41. package/default-themes/nebula-dawn.v3.css +15 -14
  42. package/default-themes/nebula-dusk.v3.css +24 -24
  43. package/default-themes/solar-flare-dawn.v3.css +15 -14
  44. package/default-themes/solar-flare-dusk.v3.css +26 -26
  45. package/dist/builders/anthropic.d.ts +26 -2
  46. package/dist/builders/anthropic.d.ts.map +1 -1
  47. package/dist/builders/anthropic.js +132 -31
  48. package/dist/builders/anthropic.js.map +1 -1
  49. package/dist/builders/claudecode.d.ts +13 -0
  50. package/dist/builders/claudecode.d.ts.map +1 -0
  51. package/dist/builders/claudecode.js +253 -0
  52. package/dist/builders/claudecode.js.map +1 -0
  53. package/dist/builders/index.d.ts +2 -1
  54. package/dist/builders/index.d.ts.map +1 -1
  55. package/dist/builders/index.js +8 -1
  56. package/dist/builders/index.js.map +1 -1
  57. package/dist/builders/openai.js +2 -1
  58. package/dist/builders/openai.js.map +1 -1
  59. package/dist/builders/types.d.ts +31 -7
  60. package/dist/builders/types.d.ts.map +1 -1
  61. package/dist/builders/types.js +60 -28
  62. package/dist/builders/types.js.map +1 -1
  63. package/dist/connectors/types.d.ts +8 -0
  64. package/dist/connectors/types.d.ts.map +1 -1
  65. package/dist/init.d.ts.map +1 -1
  66. package/dist/init.js +13 -6
  67. package/dist/init.js.map +1 -1
  68. package/dist/migrations.d.ts.map +1 -1
  69. package/dist/migrations.js +161 -14
  70. package/dist/migrations.js.map +1 -1
  71. package/dist/models/anthropic.d.ts +1 -0
  72. package/dist/models/anthropic.d.ts.map +1 -1
  73. package/dist/models/anthropic.js +129 -29
  74. package/dist/models/anthropic.js.map +1 -1
  75. package/dist/models/chainOfThought.d.ts.map +1 -1
  76. package/dist/models/chainOfThought.js +32 -19
  77. package/dist/models/chainOfThought.js.map +1 -1
  78. package/dist/models/index.d.ts +2 -2
  79. package/dist/models/index.d.ts.map +1 -1
  80. package/dist/models/index.js +2 -1
  81. package/dist/models/index.js.map +1 -1
  82. package/dist/models/providers.d.ts +1 -0
  83. package/dist/models/providers.d.ts.map +1 -1
  84. package/dist/models/providers.js +12 -4
  85. package/dist/models/providers.js.map +1 -1
  86. package/dist/models/types.d.ts +15 -1
  87. package/dist/models/types.d.ts.map +1 -1
  88. package/dist/models/types.js.map +1 -1
  89. package/dist/pages.d.ts +57 -8
  90. package/dist/pages.d.ts.map +1 -1
  91. package/dist/pages.js +258 -45
  92. package/dist/pages.js.map +1 -1
  93. package/dist/service/createCompletePrompt.d.ts.map +1 -1
  94. package/dist/service/createCompletePrompt.js +5 -0
  95. package/dist/service/createCompletePrompt.js.map +1 -1
  96. package/dist/service/mediaCache.d.ts +36 -0
  97. package/dist/service/mediaCache.d.ts.map +1 -0
  98. package/dist/service/mediaCache.js +182 -0
  99. package/dist/service/mediaCache.js.map +1 -0
  100. package/dist/service/pageValidator.d.ts +25 -0
  101. package/dist/service/pageValidator.d.ts.map +1 -0
  102. package/dist/service/pageValidator.js +315 -0
  103. package/dist/service/pageValidator.js.map +1 -0
  104. package/dist/service/server.d.ts.map +1 -1
  105. package/dist/service/server.js +4 -0
  106. package/dist/service/server.js.map +1 -1
  107. package/dist/service/sharedTableSchema.d.ts +73 -0
  108. package/dist/service/sharedTableSchema.d.ts.map +1 -0
  109. package/dist/service/sharedTableSchema.js +206 -0
  110. package/dist/service/sharedTableSchema.js.map +1 -0
  111. package/dist/service/transformPage.d.ts +49 -11
  112. package/dist/service/transformPage.d.ts.map +1 -1
  113. package/dist/service/transformPage.js +354 -241
  114. package/dist/service/transformPage.js.map +1 -1
  115. package/dist/service/useApiRoutes.d.ts.map +1 -1
  116. package/dist/service/useApiRoutes.js +285 -34
  117. package/dist/service/useApiRoutes.js.map +1 -1
  118. package/dist/service/useConnectorRoutes.d.ts.map +1 -1
  119. package/dist/service/useConnectorRoutes.js +170 -32
  120. package/dist/service/useConnectorRoutes.js.map +1 -1
  121. package/dist/service/useDataRoutes.d.ts.map +1 -1
  122. package/dist/service/useDataRoutes.js +59 -2
  123. package/dist/service/useDataRoutes.js.map +1 -1
  124. package/dist/service/useExtractRoutes.d.ts +4 -0
  125. package/dist/service/useExtractRoutes.d.ts.map +1 -0
  126. package/dist/service/useExtractRoutes.js +304 -0
  127. package/dist/service/useExtractRoutes.js.map +1 -0
  128. package/dist/service/usePageRoutes.d.ts +17 -0
  129. package/dist/service/usePageRoutes.d.ts.map +1 -1
  130. package/dist/service/usePageRoutes.js +1388 -483
  131. package/dist/service/usePageRoutes.js.map +1 -1
  132. package/dist/service/useSharedDataRoutes.d.ts.map +1 -1
  133. package/dist/service/useSharedDataRoutes.js +54 -2
  134. package/dist/service/useSharedDataRoutes.js.map +1 -1
  135. package/dist/settings.d.ts +27 -0
  136. package/dist/settings.d.ts.map +1 -1
  137. package/dist/settings.js +40 -1
  138. package/dist/settings.js.map +1 -1
  139. package/dist/themes.d.ts +0 -5
  140. package/dist/themes.d.ts.map +1 -1
  141. package/dist/themes.js +3 -95
  142. package/dist/themes.js.map +1 -1
  143. package/migration-rules/v2-to-v3.md +277 -119
  144. package/package.json +5 -1
  145. package/{default-pages/application → required-pages/_shell}/page.html +56 -42
  146. package/required-pages/_shell/page.json +14 -0
  147. package/required-pages/_starters/page.html +534 -0
  148. package/required-pages/_starters/page.json +12 -0
  149. package/required-pages/builder/page.html +353 -43
  150. package/required-pages/builder/page.json +12 -10
  151. package/required-pages/pages/page.html +697 -924
  152. package/required-pages/pages/page.json +12 -10
  153. package/required-pages/settings/page.html +1888 -1753
  154. package/required-pages/settings/page.json +12 -10
  155. package/required-pages/synthos_apis/page.html +834 -845
  156. package/required-pages/synthos_apis/page.json +12 -10
  157. package/required-pages/synthos_scripts/page.html +74 -88
  158. package/required-pages/synthos_scripts/page.json +12 -10
  159. package/scripts/append-instructions.py +90 -0
  160. package/scripts/audit-instructions.py +76 -0
  161. package/scripts/cleanup-shell-markup.mjs +112 -0
  162. package/service-connectors/buffer/connector.json +46 -0
  163. package/service-connectors/canva/connector.json +67 -0
  164. package/service-connectors/elevenlabs/connector.json +1 -1
  165. package/src/builders/anthropic.ts +150 -25
  166. package/src/builders/claudecode.ts +310 -0
  167. package/src/builders/index.ts +7 -1
  168. package/src/builders/openai.ts +2 -1
  169. package/src/builders/types.ts +93 -32
  170. package/src/connectors/types.ts +8 -0
  171. package/src/init.ts +13 -7
  172. package/src/migrations.ts +187 -16
  173. package/src/models/anthropic.ts +140 -30
  174. package/src/models/chainOfThought.ts +33 -18
  175. package/src/models/index.ts +2 -2
  176. package/src/models/providers.ts +10 -1
  177. package/src/models/types.ts +21 -1
  178. package/src/pages.ts +271 -35
  179. package/src/service/createCompletePrompt.ts +6 -0
  180. package/src/service/mediaCache.ts +206 -0
  181. package/src/service/pageValidator.ts +337 -0
  182. package/src/service/server.ts +4 -0
  183. package/src/service/sharedTableSchema.ts +236 -0
  184. package/src/service/transformPage.ts +370 -260
  185. package/src/service/useApiRoutes.ts +283 -32
  186. package/src/service/useConnectorRoutes.ts +189 -34
  187. package/src/service/useDataRoutes.ts +198 -116
  188. package/src/service/useExtractRoutes.ts +331 -0
  189. package/src/service/usePageRoutes.ts +1414 -394
  190. package/src/service/useSharedDataRoutes.ts +184 -109
  191. package/src/settings.ts +65 -0
  192. package/src/themes.ts +78 -180
  193. package/starters/blank_starter/chat-history.json +1 -0
  194. package/starters/blank_starter/page.dark.png +0 -0
  195. package/starters/blank_starter/page.html +47 -0
  196. package/starters/blank_starter/page.json +13 -0
  197. package/starters/blank_starter/page.light.png +0 -0
  198. package/starters/calculator_starter/chat-history.json +1 -0
  199. package/starters/calculator_starter/page.dark.png +0 -0
  200. package/starters/calculator_starter/page.html +232 -0
  201. package/starters/calculator_starter/page.json +13 -0
  202. package/starters/calculator_starter/page.light.png +0 -0
  203. package/starters/calendar_starter/chat-history.json +1 -0
  204. package/starters/calendar_starter/page.dark.png +0 -0
  205. package/starters/calendar_starter/page.html +495 -0
  206. package/starters/calendar_starter/page.json +13 -0
  207. package/starters/calendar_starter/page.light.png +0 -0
  208. package/starters/chat_starter/chat-history.json +1 -0
  209. package/starters/chat_starter/page.dark.png +0 -0
  210. package/starters/chat_starter/page.html +351 -0
  211. package/starters/chat_starter/page.json +13 -0
  212. package/starters/chat_starter/page.light.png +0 -0
  213. package/starters/checklist_starter/chat-history.json +1 -0
  214. package/starters/checklist_starter/page.dark.png +0 -0
  215. package/starters/checklist_starter/page.html +437 -0
  216. package/starters/checklist_starter/page.json +13 -0
  217. package/starters/checklist_starter/page.light.png +0 -0
  218. package/starters/dashboard_starter/chat-history.json +1 -0
  219. package/starters/dashboard_starter/page.dark.png +0 -0
  220. package/starters/dashboard_starter/page.html +195 -0
  221. package/starters/dashboard_starter/page.json +13 -0
  222. package/starters/dashboard_starter/page.light.png +0 -0
  223. package/starters/form_starter/chat-history.json +1 -0
  224. package/starters/form_starter/page.dark.png +0 -0
  225. package/starters/form_starter/page.html +313 -0
  226. package/starters/form_starter/page.json +13 -0
  227. package/starters/form_starter/page.light.png +0 -0
  228. package/starters/gallery_starter/chat-history.json +1 -0
  229. package/starters/gallery_starter/page.dark.png +0 -0
  230. package/starters/gallery_starter/page.html +418 -0
  231. package/starters/gallery_starter/page.json +13 -0
  232. package/starters/gallery_starter/page.light.png +0 -0
  233. package/starters/generator_starter/chat-history.json +1 -0
  234. package/starters/generator_starter/page.dark.png +0 -0
  235. package/starters/generator_starter/page.html +261 -0
  236. package/starters/generator_starter/page.json +13 -0
  237. package/starters/generator_starter/page.light.png +0 -0
  238. package/starters/index.html +538 -0
  239. package/starters/kanban_starter/chat-history.json +1 -0
  240. package/starters/kanban_starter/page.dark.png +0 -0
  241. package/starters/kanban_starter/page.html +432 -0
  242. package/starters/kanban_starter/page.json +13 -0
  243. package/starters/kanban_starter/page.light.png +0 -0
  244. package/starters/presentation_builder/chat-history.json +1 -0
  245. package/starters/presentation_builder/page.dark.png +0 -0
  246. package/starters/presentation_builder/page.html +970 -0
  247. package/starters/presentation_builder/page.json +15 -0
  248. package/starters/presentation_builder/page.light.png +0 -0
  249. package/starters/presentation_builder/presentation_voice/voice_config.json +9 -0
  250. package/starters/pulse_starter/chat-history.json +1 -0
  251. package/starters/pulse_starter/page.dark.png +0 -0
  252. package/starters/pulse_starter/page.html +698 -0
  253. package/starters/pulse_starter/page.json +13 -0
  254. package/starters/pulse_starter/page.light.png +0 -0
  255. package/starters/quiz_starter/chat-history.json +1 -0
  256. package/starters/quiz_starter/page.dark.png +0 -0
  257. package/starters/quiz_starter/page.html +292 -0
  258. package/starters/quiz_starter/page.json +13 -0
  259. package/starters/quiz_starter/page.light.png +0 -0
  260. package/starters/reference_starter/chat-history.json +1 -0
  261. package/starters/reference_starter/page.dark.png +0 -0
  262. package/starters/reference_starter/page.html +250 -0
  263. package/starters/reference_starter/page.json +13 -0
  264. package/starters/reference_starter/page.light.png +0 -0
  265. package/starters/retro_game_starter/chat-history.json +1 -0
  266. package/starters/retro_game_starter/page.dark.png +0 -0
  267. package/{default-pages → starters}/retro_game_starter/page.html +1281 -1308
  268. package/starters/retro_game_starter/page.json +15 -0
  269. package/starters/retro_game_starter/page.light.png +0 -0
  270. package/starters/roster_starter/chat-history.json +1 -0
  271. package/starters/roster_starter/page.dark.png +0 -0
  272. package/starters/roster_starter/page.html +600 -0
  273. package/starters/roster_starter/page.json +13 -0
  274. package/starters/roster_starter/page.light.png +0 -0
  275. package/starters/server.js +182 -0
  276. package/starters/start.cmd +1 -0
  277. package/starters/timeline_starter/chat-history.json +1 -0
  278. package/starters/timeline_starter/page.dark.png +0 -0
  279. package/starters/timeline_starter/page.html +446 -0
  280. package/starters/timeline_starter/page.json +13 -0
  281. package/starters/timeline_starter/page.light.png +0 -0
  282. package/starters/tutorial_starter/chat-history.json +1 -0
  283. package/starters/tutorial_starter/page.dark.png +0 -0
  284. package/starters/tutorial_starter/page.html +283 -0
  285. package/starters/tutorial_starter/page.json +13 -0
  286. package/starters/tutorial_starter/page.light.png +0 -0
  287. package/static-files/agent.v3.js +122 -0
  288. package/static-files/connector.v3.js +48 -0
  289. package/static-files/extract.v3.js +188 -0
  290. package/static-files/helpers.v3.js +50 -6
  291. package/static-files/page-bridge.js +114 -0
  292. package/static-files/page.v3.js +1292 -1290
  293. package/static-files/script.v3.js +32 -0
  294. package/static-files/server.v3.js +89 -0
  295. package/static-files/shell-bridge.v3.js +174 -0
  296. package/static-files/shell-modals.v3.js +521 -0
  297. package/static-files/{shell.css → shell.v3.css} +271 -22
  298. package/static-files/shell.v3.js +1865 -0
  299. package/static-files/storage.v3.js +176 -0
  300. package/tests/anthropic.spec.ts +42 -7
  301. package/tests/builders.spec.ts +70 -2
  302. package/tests/pageValidator.spec.ts +548 -0
  303. package/tests/profiles.spec.ts +122 -0
  304. package/tests/sharedTableSchema.spec.ts +242 -0
  305. package/tests/transformPage.spec.ts +62 -81
  306. package/default-pages/application/page.json +0 -10
  307. package/default-pages/retro_game_starter/page.json +0 -12
  308. package/default-pages/sidebar_page/page.html +0 -51
  309. package/default-pages/sidebar_page/page.json +0 -10
  310. package/default-pages/two-panel_page/page.html +0 -68
  311. package/default-pages/two-panel_page/page.json +0 -10
package/src/migrations.ts CHANGED
@@ -119,31 +119,202 @@ async function migrateV1toV2(html: string, completePrompt: completePrompt): Prom
119
119
  return migrated;
120
120
  }
121
121
 
122
+ /**
123
+ * v2-compat CSS block — injected into the <head> of every page migrated from v2
124
+ * to v3. Maps the legacy v2 CSS tokens (`--bg-*`, `--accent-*`, `--text-*`,
125
+ * `--border-color`) onto the active v3 theme's semantic tokens (`--bodyBackground`,
126
+ * `--themePrimary`, etc.), so a migrated page inherits whatever v3 theme is
127
+ * active instead of being frozen on the old nebula-dusk palette. Layout-only
128
+ * constants (`--header-*`) stay as literal values.
129
+ *
130
+ * Dropped from the previous compat block: baked `body`, `.viewer-panel`,
131
+ * `.viewer-panel::before`, `@keyframes nebula-pulse`, and `::-webkit-scrollbar*`
132
+ * rules. Those contained hardcoded purple/blue rgba values that looked wrong
133
+ * under non-nebula v3 themes (aurora, cosmos, solar-flare, high-contrast).
134
+ * The shell injects the iframe viewport height chain separately, and
135
+ * FluentLM's base CSS covers global resets.
136
+ *
137
+ * Keep in sync with `migration-rules/v2-to-v3.md` §3.
138
+ */
139
+ const V2_COMPAT_STYLE = `<style id="v2-compat">
140
+ /* --- v2 → v3 token aliases ---
141
+ Each legacy v2 var resolves to a v3 SEMANTIC token (role-based) so the page
142
+ re-themes automatically between light and dark variants. Do NOT use palette
143
+ tokens (--neutralDark, --neutralPrimaryAlt, etc.) here — those encode a
144
+ FIXED color value and stay dark under light themes, producing hard-coded
145
+ black backgrounds on v2 pages.
146
+
147
+ Component-surface tokens use --defaultStateBackground / --defaultHoverBackground
148
+ (the v3 tokens v3 pages use for card/panel surfaces) instead of the
149
+ body*Background tokens. Every alias has a hex fallback so the compat block
150
+ still produces sensible light defaults even if the theme CSS fails to load
151
+ or a token isn't defined (otherwise the chain resolves to 'initial' and
152
+ backgrounds go transparent). Header geometry stays literal because it's
153
+ layout, not color. */
154
+ :root {
155
+ --bg-primary: var(--bodyBackground, #ffffff);
156
+ --bg-secondary: var(--defaultStateBackground, #faf9f8);
157
+ --bg-tertiary: var(--bodyBackgroundHovered, #f3f2f1);
158
+
159
+ --accent-primary: var(--themePrimary, #0078d4);
160
+ --accent-secondary: var(--themeDarkAlt, #106ebe);
161
+ --accent-tertiary: var(--themeSecondary, #2b88d8);
162
+ --accent-glow: rgba(0,0,0,0.3);
163
+
164
+ --text-primary: var(--bodyText, #333333);
165
+ --text-secondary: var(--bodySubtext, #605e5c);
166
+
167
+ --border-color: var(--bodyDivider, #edebe9);
168
+
169
+ --header-min-height: 58px;
170
+ --header-padding-vertical: 14px;
171
+ --header-padding-horizontal: 20px;
172
+ --header-line-height: 1.25;
173
+ }
174
+ </style>`;
175
+
176
+ /**
177
+ * v2 nebula-dusk palette → v3 token map. Each entry's `rgb` triple is the
178
+ * hardcoded RGB that appeared in the v2 theme (and therefore in v2 page CSS
179
+ * that was hand-matched to it). The `token` is the v3 semantic/palette var
180
+ * that best fits the same role — so under light themes (nebula-dawn,
181
+ * aurora-dawn, solar-flare-dawn, high-contrast-light) the color re-themes
182
+ * instead of standing out as a dark purple-blue blob.
183
+ *
184
+ * Keep in sync with `migration-rules/v2-to-v3.md` §3 "Legacy color mapping".
185
+ *
186
+ * `rgba(0,0,0,*)` is intentionally NOT remapped — theme-neutral shadows read
187
+ * fine under both light and dark themes.
188
+ */
189
+ type LegacyColorRule = { rgb: [number, number, number]; token: string; hex: string };
190
+ const LEGACY_COLOR_RULES: LegacyColorRule[] = [
191
+ // Brand / accent family (v2 purple-blue ramp)
192
+ { rgb: [102, 126, 234], token: 'var(--themePrimary)', hex: '#667eea' },
193
+ { rgb: [118, 75, 162], token: 'var(--themeDarkAlt)', hex: '#764ba2' },
194
+ { rgb: [138, 43, 226], token: 'var(--themeDarker)', hex: '#8a2be2' },
195
+ { rgb: [ 75, 0, 130], token: 'var(--themeDarker)', hex: '#4b0082' },
196
+ { rgb: [240, 147, 251], token: 'var(--themeSecondary)', hex: '#f093fb' },
197
+ { rgb: [183, 148, 246], token: 'var(--themeTertiary)', hex: '#b794f6' },
198
+ // Dark background family (v2 bg-primary/secondary/tertiary) — mapped to
199
+ // SEMANTIC tokens that flip between light and dark themes. Palette tokens
200
+ // would freeze these as dark under light themes (black blobs in dawn mode).
201
+ { rgb: [ 26, 26, 46], token: 'var(--bodyBackground)', hex: '#1a1a2e' },
202
+ { rgb: [ 22, 33, 62], token: 'var(--bodyStandoutBackground)', hex: '#16213e' },
203
+ { rgb: [ 15, 15, 35], token: 'var(--bodyBackgroundHovered)', hex: '#0f0f23' },
204
+ // Status colors (Bootstrap danger/success/warning used pervasively in v2 pages)
205
+ { rgb: [220, 53, 69], token: 'var(--red)', hex: '#dc3545' },
206
+ { rgb: [ 40, 167, 69], token: 'var(--green)', hex: '#28a745' },
207
+ { rgb: [255, 193, 7], token: 'var(--yellow)', hex: '#ffc107' },
208
+ ];
209
+
210
+ /**
211
+ * Replace hardcoded v2-era RGB/hex colors in the full HTML (style blocks,
212
+ * inline `style=""` attrs, and JS template literals) with theme-aware v3
213
+ * tokens. Alpha is preserved via `color-mix(in srgb, <token> X%, transparent)`;
214
+ * solid occurrences collapse to `var(--token)` directly. `rgba(0,0,0,*)` is
215
+ * left as-is by design — theme-neutral shadows.
216
+ *
217
+ * The patterns match specific numeric triples and 6-digit hex codes only, so
218
+ * false positives in non-color contexts (IDs, content text, URLs) are
219
+ * negligible. Hex matches are guarded by `(?<![0-9a-fA-F])` / `(?![0-9a-fA-F])`
220
+ * lookarounds to avoid eating neighboring hex digits (e.g. an 8-digit
221
+ * hex-with-alpha like `#667eeaff`).
222
+ */
223
+ function remapLegacyColors(html: string): string {
224
+ let out = html;
225
+ for (const rule of LEGACY_COLOR_RULES) {
226
+ const [r, g, b] = rule.rgb;
227
+
228
+ // rgba(R,G,B,A) — preserve alpha via color-mix
229
+ const rgbaPattern = new RegExp(
230
+ `rgba\\(\\s*${r}\\s*,\\s*${g}\\s*,\\s*${b}\\s*,\\s*([\\d.]+)\\s*\\)`,
231
+ 'gi'
232
+ );
233
+ out = out.replace(rgbaPattern, (_match, alpha) => {
234
+ const a = parseFloat(alpha);
235
+ if (!isFinite(a) || a >= 1) return rule.token;
236
+ const pct = Math.round(a * 1000) / 10; // 1 decimal
237
+ return `color-mix(in srgb, ${rule.token} ${pct}%, transparent)`;
238
+ });
239
+
240
+ // rgb(R,G,B) or rgba(R,G,B) without alpha — solid color
241
+ const rgbPattern = new RegExp(
242
+ `rgba?\\(\\s*${r}\\s*,\\s*${g}\\s*,\\s*${b}\\s*\\)`,
243
+ 'gi'
244
+ );
245
+ out = out.replace(rgbPattern, rule.token);
246
+
247
+ // Bare 6-digit hex (case-insensitive, guarded to not split 8-digit hex)
248
+ const hexEscaped = rule.hex.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
249
+ const hexPattern = new RegExp(`(?<![0-9a-fA-F])${hexEscaped}(?![0-9a-fA-F])`, 'gi');
250
+ out = out.replace(hexPattern, rule.token);
251
+ }
252
+ return out;
253
+ }
254
+
255
+ /**
256
+ * Shell-owned DOM + scripts that shipped inline on every v2 page but belong
257
+ * to the v3 iframe shell. At v3 serve time the shell strips these anyway
258
+ * (see `stripShellMarkup$` in `src/service/usePageRoutes.ts`), but baking
259
+ * the removal into the stored page keeps the on-disk HTML clean and stops
260
+ * validators from flagging orphan references to shell-only IDs
261
+ * (`#defaultGreeting`, `#firstRunGreeting`) once the shell DOM that owned
262
+ * them is gone.
263
+ *
264
+ * Keep in sync with `migration-rules/v2-to-v3.md` §2.
265
+ */
266
+ const V2_SHELL_SELECTORS = [
267
+ '.chat-panel', // chat header + messages + link-group + chatForm + greetings
268
+ '.chat-toggle', // collapsed-chat toggle button
269
+ '#loadingOverlay', // shell provides this via blueprint overlay
270
+ 'script#idle-animation', // top-level hideIdleAnimation / showIdleAnimation (also stubbed at runtime)
271
+ 'script#first-run-check', // references #defaultGreeting / #firstRunGreeting in the stripped chat-panel
272
+ 'script#page-helpers', // shell re-injects with cache-busted URL
273
+ 'script#page-script', // shell re-injects with cache-busted URL
274
+ 'script#synthos-error-capture', // shell injects when needed
275
+ ];
276
+
122
277
  /**
123
278
  * v2 -> v3: Cheerio-based migration (no LLM).
124
- * - Removes .link-group div
125
- * - Converts chat <input> to <textarea>
126
- * - Removes .chat-submit button (v3 creates it dynamically)
279
+ * - Strips v2 shell chrome (.chat-panel, .chat-toggle, #loadingOverlay) and
280
+ * the inline scripts that only make sense alongside it (idle-animation,
281
+ * first-run-check, legacy page-helpers/page-script).
282
+ * - Injects <style id="v2-compat"> at the top of <head> (aliases legacy v2
283
+ * CSS custom properties to active v3 theme tokens).
284
+ * - Remaps hardcoded v2-palette RGB/hex colors to theme-aware v3 tokens so
285
+ * migrated pages re-theme under light/dark variants instead of freezing
286
+ * on the old nebula-dusk palette.
127
287
  */
128
288
  async function migrateV2toV3(html: string, _completePrompt: completePrompt): Promise<string> {
129
- const $ = cheerio.load(html, { decodeEntities: false });
289
+ // Run postProcessV2 first for its shared-CSS cleanup. Must happen BEFORE
290
+ // the v2-compat injection — postProcessV2 strips :root, *, body, html,
291
+ // .viewer-panel, @keyframes nebula-pulse, and ::-webkit-scrollbar* rules
292
+ // from every <style> block, and the v2-compat block intentionally
293
+ // contains exactly those rules. postProcessV2 also RESTORES missing
294
+ // chat-panel/thoughts/loadingOverlay which we then strip below — accept
295
+ // the transient insertion; the shared-CSS strip is the load-bearing step.
296
+ const out = postProcessV2(html);
130
297
 
131
- // 1. Remove .link-group div and its children
132
- $('.link-group').remove();
298
+ const $ = cheerio.load(out, { decodeEntities: false });
133
299
 
134
- // 2. Replace <input type="text" id="chatInput" ...> with <textarea>
135
- const chatInput = $('input#chatInput, input.chat-input');
136
- if (chatInput.length > 0) {
137
- chatInput.replaceWith(
138
- '<textarea class="chat-input" id="chatInput" name="message" rows="2" placeholder="Type a message..."></textarea>'
139
- );
300
+ // Strip v2 shell chrome + scripts. This supersedes the older
301
+ // link-group/chat-input/chat-submit fixups all three lived inside
302
+ // .chat-panel, so removing the panel wholesale obsoletes them.
303
+ for (const sel of V2_SHELL_SELECTORS) {
304
+ $(sel).remove();
140
305
  }
141
306
 
142
- // 3. Remove .chat-submit button inside #chatForm (or anywhere)
143
- $('button.chat-submit').remove();
307
+ // Inject v2-compat CSS as the first child of <head>. Idempotent — skip
308
+ // if an existing v2-compat block is already present.
309
+ if ($('style#v2-compat').length === 0) {
310
+ $('head').prepend(V2_COMPAT_STYLE + '\n');
311
+ }
144
312
 
145
- // Run through postProcessV2 to ensure structural integrity
146
- return postProcessV2($.html());
313
+ // Remap hardcoded v2-era RGB/hex colors to theme-aware v3 tokens. Runs
314
+ // LAST so the compat block (which uses intentional theme-neutral
315
+ // rgba(0,0,0,0.3) for --accent-glow) is in the output but untouched by
316
+ // the remap (0,0,0 is not in the rule table).
317
+ return remapLegacyColors($.html());
147
318
  }
148
319
 
149
320
  /**
@@ -1,5 +1,5 @@
1
1
  import Anthropic from '@anthropic-ai/sdk';
2
- import { AgentCompletion, completePrompt, PromptCompletionArgs, RequestError, isMultimodalContent } from './types';
2
+ import { AgentCompletion, completePrompt, PromptCompletionArgs, RequestError, isMultimodalContent, ToolHandler } from './types';
3
3
 
4
4
  export interface AnthropicArgs {
5
5
  apiKey: string;
@@ -18,6 +18,7 @@ export function buildAnthropicRequest(args: PromptCompletionArgs, defaultTemp: n
18
18
  system: string | Anthropic.TextBlockParam[] | undefined;
19
19
  temperature: number;
20
20
  outputConfig?: Anthropic.OutputConfig;
21
+ tools?: Anthropic.Tool[];
21
22
  } {
22
23
  const reqTemp = args.temperature ?? defaultTemp;
23
24
 
@@ -45,14 +46,11 @@ export function buildAnthropicRequest(args: PromptCompletionArgs, defaultTemp: n
45
46
  userContent = promptContent;
46
47
  }
47
48
 
48
- // Structured output via output_config is incompatible with prefilling
49
- const useJsonPrefill = !args.outputSchema && (args.jsonMode || args.jsonSchema);
50
- if (useJsonPrefill) {
51
- messages.push({ role: 'user', content: userContent });
52
- messages.push({ role: 'assistant', content: '{' });
53
- } else {
54
- messages.push({ role: 'user', content: userContent });
55
- }
49
+ // Claude 4.6+ rejects assistant-message prefill ("This model does not support
50
+ // assistant message prefill. The conversation must end with a user message.").
51
+ // The chainOfThought parser already extracts JSON via indexOf('{')/lastIndexOf('}'),
52
+ // and the system prompt instructs the model to emit JSON, so no prefill is needed.
53
+ messages.push({ role: 'user', content: userContent });
56
54
 
57
55
  let system = args.system?.content;
58
56
  if (args.jsonSchema) {
@@ -70,7 +68,74 @@ export function buildAnthropicRequest(args: PromptCompletionArgs, defaultTemp: n
70
68
  ? { format: { type: 'json_schema', schema: args.outputSchema } }
71
69
  : undefined;
72
70
 
73
- return { messages, system: finalSystem, temperature: reqTemp, outputConfig };
71
+ // Tool definitions
72
+ const tools: Anthropic.Tool[] | undefined = args.tools && args.tools.length > 0
73
+ ? args.tools.map(t => ({
74
+ name: t.name,
75
+ description: t.description,
76
+ input_schema: t.input_schema as Anthropic.Tool.InputSchema,
77
+ }))
78
+ : undefined;
79
+
80
+ return { messages, system: finalSystem, temperature: reqTemp, outputConfig, tools };
81
+ }
82
+
83
+ interface PendingToolUse {
84
+ id: string;
85
+ name: string;
86
+ partialJson: string;
87
+ }
88
+
89
+ /**
90
+ * Stream a single Anthropic request, accumulating text deltas and any tool_use
91
+ * blocks the model emits. Returns both the text buffer and the tool_use blocks
92
+ * (if any) so the caller can decide whether to loop on tool results.
93
+ */
94
+ async function streamOnce(
95
+ client: Anthropic,
96
+ params: Anthropic.MessageCreateParamsStreaming
97
+ ): Promise<{ text: string; toolUses: PendingToolUse[]; stopReason: Anthropic.StopReason | null }> {
98
+ const stream = await client.messages.create(params);
99
+
100
+ // Track content blocks by stream index. Tool_use blocks get their partial_json
101
+ // routed to their own buffer; text + structured-output input_json_deltas go to text.
102
+ const textByIndex = new Map<number, string>();
103
+ const toolByIndex = new Map<number, PendingToolUse>();
104
+ let stopReason: Anthropic.StopReason | null = null;
105
+
106
+ for await (const event of stream) {
107
+ if (event.type === 'content_block_start') {
108
+ const block = event.content_block;
109
+ if (block.type === 'tool_use') {
110
+ toolByIndex.set(event.index, { id: block.id, name: block.name, partialJson: '' });
111
+ } else if (block.type === 'text') {
112
+ textByIndex.set(event.index, '');
113
+ }
114
+ } else if (event.type === 'content_block_delta') {
115
+ if (event.delta.type === 'text_delta') {
116
+ textByIndex.set(event.index, (textByIndex.get(event.index) ?? '') + event.delta.text);
117
+ } else if (event.delta.type === 'input_json_delta') {
118
+ const tool = toolByIndex.get(event.index);
119
+ if (tool) {
120
+ tool.partialJson += event.delta.partial_json;
121
+ } else {
122
+ // Structured output streams JSON as input_json_delta into a text block —
123
+ // accumulate into the text buffer so parseBuilderResponse sees the JSON.
124
+ textByIndex.set(event.index, (textByIndex.get(event.index) ?? '') + event.delta.partial_json);
125
+ }
126
+ }
127
+ } else if (event.type === 'message_delta') {
128
+ if (event.delta.stop_reason) stopReason = event.delta.stop_reason;
129
+ }
130
+ }
131
+
132
+ const text = Array.from(textByIndex.entries())
133
+ .sort(([a], [b]) => a - b)
134
+ .map(([, v]) => v)
135
+ .join('');
136
+
137
+ const toolUses = Array.from(toolByIndex.values());
138
+ return { text, toolUses, stopReason };
74
139
  }
75
140
 
76
141
  export function anthropic(args: AnthropicArgs): completePrompt {
@@ -79,33 +144,78 @@ export function anthropic(args: AnthropicArgs): completePrompt {
79
144
  const client = new Anthropic({ apiKey, baseURL, maxRetries });
80
145
 
81
146
  return async (completionArgs: PromptCompletionArgs): Promise<AgentCompletion<string>> => {
82
- const { messages, system: systemContent, temperature: reqTemp, outputConfig } = buildAnthropicRequest(completionArgs, temperature);
147
+ const { messages, system: systemContent, temperature: reqTemp, outputConfig, tools } =
148
+ buildAnthropicRequest(completionArgs, temperature);
83
149
 
84
- const useJsonPrefill = !completionArgs.outputSchema && (completionArgs.jsonMode || completionArgs.jsonSchema);
150
+ const hasTools = !!(tools && tools.length > 0);
151
+ const maxIterations = completionArgs.maxToolIterations ?? 3;
152
+ const toolHandlers = completionArgs.toolHandlers ?? {};
85
153
 
86
154
  try {
87
- const stream = await client.messages.create({
88
- model,
89
- max_tokens: 32768,
90
- temperature: reqTemp,
91
- system: systemContent,
92
- messages: messages as Anthropic.MessageParam[],
93
- stream: true,
94
- ...(outputConfig && { output_config: outputConfig }),
95
- });
96
-
97
- let text = '';
98
- for await (const event of stream) {
99
- if (event.type === 'content_block_delta' && event.delta.type === 'text_delta') {
100
- text += event.delta.text;
155
+ // Mutable copy of messages — extended each tool-use iteration.
156
+ const turnMessages: Anthropic.MessageParam[] = messages as Anthropic.MessageParam[];
157
+ let lastText = '';
158
+
159
+ for (let iteration = 0; iteration < maxIterations; iteration++) {
160
+ const params: Anthropic.MessageCreateParamsStreaming = {
161
+ model,
162
+ max_tokens: 32768,
163
+ temperature: reqTemp,
164
+ system: systemContent,
165
+ messages: turnMessages,
166
+ stream: true,
167
+ ...(outputConfig && { output_config: outputConfig }),
168
+ ...(hasTools && { tools }),
169
+ };
170
+
171
+ const { text, toolUses, stopReason } = await streamOnce(client, params);
172
+ lastText = text;
173
+
174
+ // Terminal turn: no tool_use blocks OR model stopped for a non-tool reason.
175
+ if (!hasTools || toolUses.length === 0 || stopReason !== 'tool_use') {
176
+ return { completed: true, value: lastText };
101
177
  }
102
- }
103
178
 
104
- if (useJsonPrefill) {
105
- text = '{' + text;
179
+ // Tool-use turn: notify, execute handlers in parallel, append results, loop.
180
+ if (completionArgs.onToolCall) {
181
+ completionArgs.onToolCall(toolUses.map(t => t.name));
182
+ }
183
+
184
+ const toolResults: Anthropic.ToolResultBlockParam[] = await Promise.all(
185
+ toolUses.map(async (t): Promise<Anthropic.ToolResultBlockParam> => {
186
+ const handler: ToolHandler | undefined = toolHandlers[t.name];
187
+ if (!handler) {
188
+ return { type: 'tool_result', tool_use_id: t.id, content: `Error: no handler registered for tool "${t.name}"`, is_error: true };
189
+ }
190
+ let input: Record<string, unknown> = {};
191
+ try {
192
+ if (t.partialJson.trim()) input = JSON.parse(t.partialJson);
193
+ } catch (err) {
194
+ return { type: 'tool_result', tool_use_id: t.id, content: `Error parsing tool input: ${(err as Error).message}`, is_error: true };
195
+ }
196
+ try {
197
+ const output = await handler(input);
198
+ return { type: 'tool_result', tool_use_id: t.id, content: output };
199
+ } catch (err) {
200
+ return { type: 'tool_result', tool_use_id: t.id, content: `Error: ${(err as Error).message}`, is_error: true };
201
+ }
202
+ })
203
+ );
204
+
205
+ // Append the assistant's tool_use turn and our tool_result reply.
206
+ const assistantBlocks: Anthropic.ContentBlockParam[] = [];
207
+ if (text.trim()) assistantBlocks.push({ type: 'text', text });
208
+ for (const t of toolUses) {
209
+ let parsedInput: unknown = {};
210
+ try { if (t.partialJson.trim()) parsedInput = JSON.parse(t.partialJson); } catch { /* empty input */ }
211
+ assistantBlocks.push({ type: 'tool_use', id: t.id, name: t.name, input: parsedInput });
212
+ }
213
+ turnMessages.push({ role: 'assistant', content: assistantBlocks });
214
+ turnMessages.push({ role: 'user', content: toolResults });
106
215
  }
107
216
 
108
- return { completed: true, value: text };
217
+ // Iteration budget exhausted.
218
+ return { completed: false, error: new Error(`Tool-use loop exceeded ${maxIterations} iterations`) };
109
219
  } catch (err: unknown) {
110
220
  let error: Error;
111
221
  if (err instanceof Anthropic.APIError && (err as any).status !== undefined) {
@@ -27,22 +27,8 @@ export async function chainOfThought(args: ChainOfThoughtArgs): Promise<AgentCom
27
27
  return { completed: false, error: result.error };
28
28
  }
29
29
 
30
- try {
31
- let parsed: any;
32
- if (typeof result.value === 'object') {
33
- parsed = result.value;
34
- } else {
35
- let text = result.value as string;
36
- // Strip markdown code fences if present
37
- text = text.replace(/^```(?:json)?\s*/i, '').replace(/\s*```\s*$/, '');
38
- // Extract the first JSON object from the text
39
- const start = text.indexOf('{');
40
- const end = text.lastIndexOf('}');
41
- if (start !== -1 && end > start) {
42
- text = text.substring(start, end + 1);
43
- }
44
- parsed = JSON.parse(text);
45
- }
30
+ if (typeof result.value === 'object') {
31
+ const parsed = result.value as any;
46
32
  return {
47
33
  completed: true,
48
34
  value: {
@@ -50,7 +36,36 @@ export async function chainOfThought(args: ChainOfThoughtArgs): Promise<AgentCom
50
36
  answer: parsed.answer ?? '',
51
37
  },
52
38
  };
53
- } catch {
54
- return { completed: false, error: new Error('Failed to parse chain-of-thought response') };
55
39
  }
40
+
41
+ const rawText = result.value as string;
42
+ let text = rawText.replace(/^```(?:json)?\s*/i, '').replace(/\s*```\s*$/, '');
43
+ const start = text.indexOf('{');
44
+ const end = text.lastIndexOf('}');
45
+ if (start !== -1 && end > start) {
46
+ const candidate = text.substring(start, end + 1);
47
+ try {
48
+ const parsed = JSON.parse(candidate);
49
+ return {
50
+ completed: true,
51
+ value: {
52
+ explanation: parsed.explanation ?? '',
53
+ answer: parsed.answer ?? '',
54
+ },
55
+ };
56
+ } catch (err) {
57
+ console.error('chainOfThought: JSON.parse failed:', (err as Error).message);
58
+ console.error('chainOfThought: raw response was:', rawText);
59
+ }
60
+ } else {
61
+ console.error('chainOfThought: no JSON object found in response. Raw text:', rawText);
62
+ }
63
+
64
+ // Graceful fallback: model returned prose without parseable JSON.
65
+ // Treat the full text as the answer so the user still sees something useful.
66
+ const fallback = rawText.trim();
67
+ if (fallback) {
68
+ return { completed: true, value: { explanation: '', answer: fallback } };
69
+ }
70
+ return { completed: false, error: new Error('Empty response from model') };
56
71
  }
@@ -1,5 +1,5 @@
1
- export { ProviderName, ProviderConfig, ModelEntry, Provider, SystemMessage, UserMessage, Message, AgentCompletion, completePrompt, PromptCompletionArgs, AgentArgs, RequestError, TextBlock, ImageBlock, ContentBlock, MessageContent, isMultimodalContent } from './types';
2
- export { AnthropicProvider, OpenAIProvider, PROVIDERS, getProvider, detectProvider } from './providers';
1
+ export { ProviderName, ProviderConfig, ModelEntry, Provider, SystemMessage, UserMessage, Message, AgentCompletion, completePrompt, PromptCompletionArgs, AgentArgs, RequestError, TextBlock, ImageBlock, ContentBlock, MessageContent, isMultimodalContent, ToolDefinition, ToolHandler } from './types';
2
+ export { AnthropicProvider, OpenAIProvider, ClaudeCodeProvider, PROVIDERS, getProvider, detectProvider } from './providers';
3
3
  export { anthropic, AnthropicArgs, buildAnthropicRequest } from './anthropic';
4
4
  export { openai, OpenaiArgs, buildOpenAIRequest } from './openai';
5
5
  export { fireworksai, FireworksAIArgs, resolveFireworksModel, buildFireworksRequest } from './fireworksai';
@@ -27,7 +27,16 @@ export const FireworksAIProvider: Provider = {
27
27
  }
28
28
  };
29
29
 
30
- export const PROVIDERS: Provider[] = [AnthropicProvider, OpenAIProvider, FireworksAIProvider];
30
+ export const ClaudeCodeProvider: Provider = {
31
+ name: 'ClaudeCode',
32
+ builderModels: ['claude-opus-4-6', 'claude-sonnet-4-6'],
33
+ chatModels: ['claude-sonnet-4-6', 'claude-haiku-4-5'],
34
+ detectModel(_model: string): boolean {
35
+ return false; // Never auto-detect — must be explicitly selected
36
+ }
37
+ };
38
+
39
+ export const PROVIDERS: Provider[] = [AnthropicProvider, OpenAIProvider, FireworksAIProvider, ClaudeCodeProvider];
31
40
 
32
41
  export function getProvider(name: ProviderName): Provider {
33
42
  const provider = PROVIDERS.find(p => p.name === name);
@@ -2,7 +2,7 @@
2
2
  // Provider types
3
3
  // ---------------------------------------------------------------------------
4
4
 
5
- export type ProviderName = 'Anthropic' | 'OpenAI' | 'FireworksAI';
5
+ export type ProviderName = 'Anthropic' | 'OpenAI' | 'FireworksAI' | 'ClaudeCode';
6
6
 
7
7
  export interface ProviderConfig {
8
8
  apiKey: string;
@@ -69,6 +69,18 @@ export interface UserMessage {
69
69
  name?: string;
70
70
  }
71
71
 
72
+ // ---------------------------------------------------------------------------
73
+ // Tool use
74
+ // ---------------------------------------------------------------------------
75
+
76
+ export interface ToolDefinition {
77
+ name: string;
78
+ description: string;
79
+ input_schema: Record<string, unknown>;
80
+ }
81
+
82
+ export type ToolHandler = (input: Record<string, unknown>) => Promise<string>;
83
+
72
84
  // ---------------------------------------------------------------------------
73
85
  // Completions
74
86
  // ---------------------------------------------------------------------------
@@ -91,6 +103,14 @@ export interface PromptCompletionArgs {
91
103
  cacheSystem?: boolean;
92
104
  /** JSON schema for structured output via constrained decoding (Anthropic output_config). */
93
105
  outputSchema?: Record<string, unknown>;
106
+ /** Tools the model may invoke before producing its final answer. */
107
+ tools?: ToolDefinition[];
108
+ /** Executors for each tool name. Required when `tools` is set. */
109
+ toolHandlers?: Record<string, ToolHandler>;
110
+ /** Max tool-use rounds before the loop aborts. Default: 3. */
111
+ maxToolIterations?: number;
112
+ /** Fired once per tool-use iteration with the names of tools about to execute. */
113
+ onToolCall?: (names: string[]) => void;
94
114
  }
95
115
 
96
116
  export type completePrompt<TValue = string> = (args: PromptCompletionArgs) => Promise<AgentCompletion<TValue>>;