synthos 0.7.1 → 0.8.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 (263) hide show
  1. package/README.md +215 -65
  2. package/default-pages/application.json +1 -0
  3. package/default-pages/json_tools.json +1 -1
  4. package/default-pages/oregon_trail.html +321 -0
  5. package/default-pages/oregon_trail.json +12 -0
  6. package/default-pages/sidebar_page.json +1 -0
  7. package/default-pages/solar_explorer.html +10 -18
  8. package/default-pages/solar_explorer.json +2 -2
  9. package/default-pages/two-panel_page.json +1 -0
  10. package/default-pages/us_map.html +192 -0
  11. package/default-pages/us_map.json +12 -0
  12. package/default-pages/us_map_1850.html +325 -0
  13. package/default-pages/us_map_1850.json +12 -0
  14. package/default-pages/western_cities_1850.html +526 -0
  15. package/default-pages/western_cities_1850.json +12 -0
  16. package/default-themes/{nebula-dawn.css → nebula-dawn.v2.css} +24 -0
  17. package/default-themes/{nebula-dusk.css → nebula-dusk.v2.css} +24 -0
  18. package/dist/agents/a2a/a2aProvider.d.ts +3 -0
  19. package/dist/agents/a2a/a2aProvider.d.ts.map +1 -0
  20. package/dist/agents/a2a/a2aProvider.js +126 -0
  21. package/dist/agents/a2a/a2aProvider.js.map +1 -0
  22. package/dist/agents/discovery.d.ts +30 -0
  23. package/dist/agents/discovery.d.ts.map +1 -0
  24. package/dist/agents/discovery.js +52 -0
  25. package/dist/agents/discovery.js.map +1 -0
  26. package/dist/agents/index.d.ts +7 -0
  27. package/dist/agents/index.d.ts.map +1 -0
  28. package/dist/agents/index.js +19 -0
  29. package/dist/agents/index.js.map +1 -0
  30. package/dist/agents/openclaw/gatewayManager.d.ts +113 -0
  31. package/dist/agents/openclaw/gatewayManager.d.ts.map +1 -0
  32. package/dist/agents/openclaw/gatewayManager.js +470 -0
  33. package/dist/agents/openclaw/gatewayManager.js.map +1 -0
  34. package/dist/agents/openclaw/openclawProvider.d.ts +3 -0
  35. package/dist/agents/openclaw/openclawProvider.d.ts.map +1 -0
  36. package/dist/agents/openclaw/openclawProvider.js +239 -0
  37. package/dist/agents/openclaw/openclawProvider.js.map +1 -0
  38. package/dist/agents/openclaw/sshTunnelManager.d.ts +23 -0
  39. package/dist/agents/openclaw/sshTunnelManager.d.ts.map +1 -0
  40. package/dist/agents/openclaw/sshTunnelManager.js +340 -0
  41. package/dist/agents/openclaw/sshTunnelManager.js.map +1 -0
  42. package/dist/agents/types.d.ts +64 -0
  43. package/dist/agents/types.d.ts.map +1 -0
  44. package/dist/agents/types.js +6 -0
  45. package/dist/agents/types.js.map +1 -0
  46. package/dist/connectors/airtable/connector.json +27 -0
  47. package/dist/connectors/alpha-vantage/connector.json +26 -0
  48. package/dist/connectors/brave-search/connector.json +26 -0
  49. package/dist/connectors/cloudinary/connector.json +27 -0
  50. package/dist/connectors/deepl/connector.json +28 -0
  51. package/dist/connectors/elevenlabs/connector.json +30 -0
  52. package/dist/connectors/giphy/connector.json +27 -0
  53. package/dist/connectors/github/connector.json +29 -0
  54. package/dist/connectors/huggingface/connector.json +27 -0
  55. package/dist/connectors/imgur/connector.json +29 -0
  56. package/dist/connectors/index.d.ts +1 -1
  57. package/dist/connectors/index.d.ts.map +1 -1
  58. package/dist/connectors/instagram/connector.json +43 -0
  59. package/dist/connectors/jira/connector.json +28 -0
  60. package/dist/connectors/mapbox/connector.json +26 -0
  61. package/dist/connectors/nasa/connector.json +27 -0
  62. package/dist/connectors/newsapi/connector.json +27 -0
  63. package/dist/connectors/notion/connector.json +28 -0
  64. package/dist/connectors/open-exchange-rates/connector.json +27 -0
  65. package/dist/connectors/openweathermap/connector.json +26 -0
  66. package/dist/connectors/pexels/connector.json +27 -0
  67. package/dist/connectors/registry.d.ts.map +1 -1
  68. package/dist/connectors/registry.js +42 -96
  69. package/dist/connectors/registry.js.map +1 -1
  70. package/dist/connectors/resend/connector.json +29 -0
  71. package/dist/connectors/rss2json/connector.json +27 -0
  72. package/dist/connectors/sendgrid/connector.json +27 -0
  73. package/dist/connectors/spoonacular/connector.json +28 -0
  74. package/dist/connectors/stability-ai/connector.json +27 -0
  75. package/dist/connectors/twilio/connector.json +28 -0
  76. package/dist/connectors/types.d.ts +23 -0
  77. package/dist/connectors/types.d.ts.map +1 -1
  78. package/dist/connectors/unsplash/connector.json +27 -0
  79. package/dist/connectors/wolfram-alpha/connector.json +26 -0
  80. package/dist/connectors/youtube-data/connector.json +30 -0
  81. package/dist/files.d.ts +1 -0
  82. package/dist/files.d.ts.map +1 -1
  83. package/dist/files.js +16 -1
  84. package/dist/files.js.map +1 -1
  85. package/dist/init.d.ts.map +1 -1
  86. package/dist/init.js +28 -0
  87. package/dist/init.js.map +1 -1
  88. package/dist/migrations.d.ts +3 -2
  89. package/dist/migrations.d.ts.map +1 -1
  90. package/dist/migrations.js +122 -138
  91. package/dist/migrations.js.map +1 -1
  92. package/dist/models/anthropic.d.ts +22 -0
  93. package/dist/models/anthropic.d.ts.map +1 -0
  94. package/dist/models/anthropic.js +76 -0
  95. package/dist/models/anthropic.js.map +1 -0
  96. package/dist/models/chainOfThought.d.ts +12 -0
  97. package/dist/models/chainOfThought.d.ts.map +1 -0
  98. package/dist/models/chainOfThought.js +45 -0
  99. package/dist/models/chainOfThought.js.map +1 -0
  100. package/dist/models/fireworksai.d.ts +30 -0
  101. package/dist/models/fireworksai.d.ts.map +1 -0
  102. package/dist/models/fireworksai.js +133 -0
  103. package/dist/models/fireworksai.js.map +1 -0
  104. package/dist/models/index.d.ts +7 -1
  105. package/dist/models/index.d.ts.map +1 -1
  106. package/dist/models/index.js +19 -1
  107. package/dist/models/index.js.map +1 -1
  108. package/dist/models/logCompletePrompt.d.ts +3 -0
  109. package/dist/models/logCompletePrompt.d.ts.map +1 -0
  110. package/dist/models/logCompletePrompt.js +23 -0
  111. package/dist/models/logCompletePrompt.js.map +1 -0
  112. package/dist/models/openai.d.ts +24 -0
  113. package/dist/models/openai.d.ts.map +1 -0
  114. package/dist/models/openai.js +80 -0
  115. package/dist/models/openai.js.map +1 -0
  116. package/dist/models/providers.d.ts +1 -0
  117. package/dist/models/providers.d.ts.map +1 -1
  118. package/dist/models/providers.js +12 -4
  119. package/dist/models/providers.js.map +1 -1
  120. package/dist/models/types.d.ts +34 -2
  121. package/dist/models/types.d.ts.map +1 -1
  122. package/dist/models/types.js +16 -0
  123. package/dist/models/types.js.map +1 -1
  124. package/dist/models/utils.d.ts +6 -0
  125. package/dist/models/utils.d.ts.map +1 -0
  126. package/dist/models/utils.js +21 -0
  127. package/dist/models/utils.js.map +1 -0
  128. package/dist/scripts.d.ts +2 -1
  129. package/dist/scripts.d.ts.map +1 -1
  130. package/dist/scripts.js +4 -3
  131. package/dist/scripts.js.map +1 -1
  132. package/dist/service/createCompletePrompt.d.ts +1 -1
  133. package/dist/service/createCompletePrompt.d.ts.map +1 -1
  134. package/dist/service/createCompletePrompt.js +9 -6
  135. package/dist/service/createCompletePrompt.js.map +1 -1
  136. package/dist/service/generateImage.d.ts +1 -1
  137. package/dist/service/generateImage.d.ts.map +1 -1
  138. package/dist/service/generateImage.js +3 -3
  139. package/dist/service/generateImage.js.map +1 -1
  140. package/dist/service/server.d.ts.map +1 -1
  141. package/dist/service/server.js +3 -0
  142. package/dist/service/server.js.map +1 -1
  143. package/dist/service/transformPage.d.ts +4 -2
  144. package/dist/service/transformPage.d.ts.map +1 -1
  145. package/dist/service/transformPage.js +74 -6
  146. package/dist/service/transformPage.js.map +1 -1
  147. package/dist/service/useAgentRoutes.d.ts +4 -0
  148. package/dist/service/useAgentRoutes.d.ts.map +1 -0
  149. package/dist/service/useAgentRoutes.js +389 -0
  150. package/dist/service/useAgentRoutes.js.map +1 -0
  151. package/dist/service/useApiRoutes.d.ts.map +1 -1
  152. package/dist/service/useApiRoutes.js +157 -16
  153. package/dist/service/useApiRoutes.js.map +1 -1
  154. package/dist/service/useConnectorRoutes.d.ts.map +1 -1
  155. package/dist/service/useConnectorRoutes.js +14 -3
  156. package/dist/service/useConnectorRoutes.js.map +1 -1
  157. package/dist/service/useGatewayRoutes.d.ts +4 -0
  158. package/dist/service/useGatewayRoutes.d.ts.map +1 -0
  159. package/dist/service/useGatewayRoutes.js +168 -0
  160. package/dist/service/useGatewayRoutes.js.map +1 -0
  161. package/dist/service/usePageRoutes.d.ts.map +1 -1
  162. package/dist/service/usePageRoutes.js +16 -5
  163. package/dist/service/usePageRoutes.js.map +1 -1
  164. package/dist/settings.d.ts +2 -1
  165. package/dist/settings.d.ts.map +1 -1
  166. package/dist/settings.js +4 -8
  167. package/dist/settings.js.map +1 -1
  168. package/dist/themes.d.ts +14 -0
  169. package/dist/themes.d.ts.map +1 -1
  170. package/dist/themes.js +86 -13
  171. package/dist/themes.js.map +1 -1
  172. package/package.json +10 -5
  173. package/page-scripts/helpers-v2.js +222 -0
  174. package/page-scripts/page-v2.js +656 -0
  175. package/required-pages/builder.html +1 -27
  176. package/required-pages/pages.html +745 -22
  177. package/required-pages/settings.html +819 -21
  178. package/required-pages/synthos_apis.html +56 -1
  179. package/src/agents/a2a/a2aProvider.ts +110 -0
  180. package/src/agents/discovery.ts +74 -0
  181. package/src/agents/index.ts +6 -0
  182. package/src/agents/openclaw/gatewayManager.ts +559 -0
  183. package/src/agents/openclaw/openclawProvider.ts +261 -0
  184. package/src/agents/openclaw/sshTunnelManager.ts +385 -0
  185. package/src/agents/types.ts +82 -0
  186. package/src/connectors/airtable/connector.json +27 -0
  187. package/src/connectors/alpha-vantage/connector.json +26 -0
  188. package/src/connectors/brave-search/connector.json +26 -0
  189. package/src/connectors/cloudinary/connector.json +27 -0
  190. package/src/connectors/deepl/connector.json +28 -0
  191. package/src/connectors/elevenlabs/connector.json +30 -0
  192. package/src/connectors/giphy/connector.json +27 -0
  193. package/src/connectors/github/connector.json +29 -0
  194. package/src/connectors/huggingface/connector.json +27 -0
  195. package/src/connectors/imgur/connector.json +29 -0
  196. package/src/connectors/index.ts +2 -0
  197. package/src/connectors/instagram/connector.json +43 -0
  198. package/src/connectors/jira/connector.json +28 -0
  199. package/src/connectors/mapbox/connector.json +26 -0
  200. package/src/connectors/nasa/connector.json +27 -0
  201. package/src/connectors/newsapi/connector.json +27 -0
  202. package/src/connectors/notion/connector.json +28 -0
  203. package/src/connectors/open-exchange-rates/connector.json +27 -0
  204. package/src/connectors/openweathermap/connector.json +26 -0
  205. package/src/connectors/pexels/connector.json +27 -0
  206. package/src/connectors/registry.ts +21 -97
  207. package/src/connectors/resend/connector.json +29 -0
  208. package/src/connectors/rss2json/connector.json +27 -0
  209. package/src/connectors/sendgrid/connector.json +27 -0
  210. package/src/connectors/spoonacular/connector.json +28 -0
  211. package/src/connectors/stability-ai/connector.json +27 -0
  212. package/src/connectors/twilio/connector.json +28 -0
  213. package/src/connectors/types.ts +25 -0
  214. package/src/connectors/unsplash/connector.json +27 -0
  215. package/src/connectors/wolfram-alpha/connector.json +26 -0
  216. package/src/connectors/youtube-data/connector.json +30 -0
  217. package/src/files.ts +14 -0
  218. package/src/init.ts +27 -0
  219. package/src/migrations.ts +121 -138
  220. package/src/models/anthropic.ts +89 -0
  221. package/src/models/chainOfThought.ts +56 -0
  222. package/src/models/fireworksai.ts +136 -0
  223. package/src/models/index.ts +7 -1
  224. package/src/models/logCompletePrompt.ts +25 -0
  225. package/src/models/openai.ts +90 -0
  226. package/src/models/providers.ts +12 -3
  227. package/src/models/types.ts +67 -2
  228. package/src/models/utils.ts +16 -0
  229. package/src/scripts.ts +2 -2
  230. package/src/service/createCompletePrompt.ts +3 -1
  231. package/src/service/generateImage.ts +2 -2
  232. package/src/service/server.ts +4 -0
  233. package/src/service/transformPage.ts +81 -8
  234. package/src/service/useAgentRoutes.ts +423 -0
  235. package/src/service/useApiRoutes.ts +173 -18
  236. package/src/service/useConnectorRoutes.ts +14 -3
  237. package/src/service/usePageRoutes.ts +20 -6
  238. package/src/settings.ts +6 -10
  239. package/src/themes.ts +84 -12
  240. package/tests/README.md +12 -0
  241. package/tests/anthropic.spec.ts +84 -0
  242. package/tests/chainOfThought.spec.ts +108 -0
  243. package/tests/ensureScripts.spec.ts +82 -0
  244. package/tests/files.spec.ts +233 -0
  245. package/tests/fireworksai.spec.ts +92 -0
  246. package/tests/logCompletePrompt.spec.ts +74 -0
  247. package/tests/migrations.spec.ts +169 -0
  248. package/tests/openai.spec.ts +71 -0
  249. package/tests/pages.spec.ts +328 -0
  250. package/tests/providers.spec.ts +144 -0
  251. package/tests/scripts.spec.ts +209 -0
  252. package/tests/transformPage.spec.ts +931 -0
  253. package/tests/types.spec.ts +23 -0
  254. package/default-pages/app_builder.json +0 -1
  255. package/default-pages/sidebar_builder.json +0 -1
  256. package/default-pages/two-panel_builder.json +0 -1
  257. package/images/home.png +0 -0
  258. package/images/page-management.png +0 -0
  259. package/images/settings.png +0 -0
  260. package/images/synthos-square.png +0 -0
  261. /package/default-pages/{app_builder.html → application.html} +0 -0
  262. /package/default-pages/{sidebar_builder.html → sidebar_page.html} +0 -0
  263. /package/default-pages/{two-panel_builder.html → two-panel_page.html} +0 -0
@@ -0,0 +1,233 @@
1
+ import assert from 'assert';
2
+ import * as fs from 'fs/promises';
3
+ import * as os from 'os';
4
+ import path from 'path';
5
+ import {
6
+ checkIfExists,
7
+ ensureFolderExists,
8
+ saveFile,
9
+ loadFile,
10
+ deleteFile,
11
+ listFiles,
12
+ listFolders,
13
+ copyFile,
14
+ copyFiles,
15
+ copyFolderRecursive,
16
+ deleteFolder,
17
+ } from '../src/files';
18
+
19
+ let tmpDir: string;
20
+
21
+ beforeEach(async () => {
22
+ tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'synthos-test-'));
23
+ });
24
+
25
+ afterEach(async () => {
26
+ await fs.rm(tmpDir, { recursive: true, force: true });
27
+ });
28
+
29
+ // ---------------------------------------------------------------------------
30
+ // checkIfExists
31
+ // ---------------------------------------------------------------------------
32
+
33
+ describe('checkIfExists', () => {
34
+ it('returns true for an existing file', async () => {
35
+ const file = path.join(tmpDir, 'exists.txt');
36
+ await fs.writeFile(file, 'hi');
37
+ assert.strictEqual(await checkIfExists(file), true);
38
+ });
39
+
40
+ it('returns false for a non-existing file', async () => {
41
+ assert.strictEqual(await checkIfExists(path.join(tmpDir, 'nope.txt')), false);
42
+ });
43
+
44
+ it('returns true for an existing directory', async () => {
45
+ assert.strictEqual(await checkIfExists(tmpDir), true);
46
+ });
47
+ });
48
+
49
+ // ---------------------------------------------------------------------------
50
+ // ensureFolderExists
51
+ // ---------------------------------------------------------------------------
52
+
53
+ describe('ensureFolderExists', () => {
54
+ it('creates a new folder', async () => {
55
+ const dir = path.join(tmpDir, 'new-folder');
56
+ await ensureFolderExists(dir);
57
+ const stat = await fs.stat(dir);
58
+ assert.ok(stat.isDirectory());
59
+ });
60
+
61
+ it('creates nested folders recursively', async () => {
62
+ const dir = path.join(tmpDir, 'a', 'b', 'c');
63
+ await ensureFolderExists(dir);
64
+ const stat = await fs.stat(dir);
65
+ assert.ok(stat.isDirectory());
66
+ });
67
+
68
+ it('does not throw if folder already exists', async () => {
69
+ await ensureFolderExists(tmpDir);
70
+ });
71
+ });
72
+
73
+ // ---------------------------------------------------------------------------
74
+ // saveFile / loadFile roundtrip
75
+ // ---------------------------------------------------------------------------
76
+
77
+ describe('saveFile / loadFile', () => {
78
+ it('writes and reads back the same content', async () => {
79
+ const file = path.join(tmpDir, 'round.txt');
80
+ await saveFile(file, 'hello world');
81
+ const content = await loadFile(file);
82
+ assert.strictEqual(content, 'hello world');
83
+ });
84
+
85
+ it('handles unicode content', async () => {
86
+ const file = path.join(tmpDir, 'unicode.txt');
87
+ const text = '日本語テスト 🎉';
88
+ await saveFile(file, text);
89
+ assert.strictEqual(await loadFile(file), text);
90
+ });
91
+ });
92
+
93
+ // ---------------------------------------------------------------------------
94
+ // deleteFile
95
+ // ---------------------------------------------------------------------------
96
+
97
+ describe('deleteFile', () => {
98
+ it('removes an existing file', async () => {
99
+ const file = path.join(tmpDir, 'del.txt');
100
+ await fs.writeFile(file, 'bye');
101
+ await deleteFile(file);
102
+ assert.strictEqual(await checkIfExists(file), false);
103
+ });
104
+ });
105
+
106
+ // ---------------------------------------------------------------------------
107
+ // listFiles
108
+ // ---------------------------------------------------------------------------
109
+
110
+ describe('listFiles', () => {
111
+ it('returns files with extensions', async () => {
112
+ await fs.writeFile(path.join(tmpDir, 'a.txt'), '');
113
+ await fs.writeFile(path.join(tmpDir, 'b.json'), '');
114
+ const files = await listFiles(tmpDir);
115
+ assert.ok(files.includes('a.txt'));
116
+ assert.ok(files.includes('b.json'));
117
+ });
118
+
119
+ it('filters out dot-files', async () => {
120
+ await fs.writeFile(path.join(tmpDir, '.hidden'), '');
121
+ await fs.writeFile(path.join(tmpDir, 'visible.txt'), '');
122
+ const files = await listFiles(tmpDir);
123
+ assert.ok(!files.includes('.hidden'));
124
+ assert.ok(files.includes('visible.txt'));
125
+ });
126
+
127
+ it('filters out directories (no dot in name)', async () => {
128
+ await fs.mkdir(path.join(tmpDir, 'subdir'));
129
+ await fs.writeFile(path.join(tmpDir, 'file.txt'), '');
130
+ const files = await listFiles(tmpDir);
131
+ assert.ok(!files.includes('subdir'));
132
+ assert.ok(files.includes('file.txt'));
133
+ });
134
+ });
135
+
136
+ // ---------------------------------------------------------------------------
137
+ // listFolders
138
+ // ---------------------------------------------------------------------------
139
+
140
+ describe('listFolders', () => {
141
+ it('returns only directories', async () => {
142
+ await fs.mkdir(path.join(tmpDir, 'dirA'));
143
+ await fs.mkdir(path.join(tmpDir, 'dirB'));
144
+ await fs.writeFile(path.join(tmpDir, 'file.txt'), '');
145
+ const folders = await listFolders(tmpDir);
146
+ assert.ok(folders.includes('dirA'));
147
+ assert.ok(folders.includes('dirB'));
148
+ assert.ok(!folders.includes('file.txt'));
149
+ });
150
+
151
+ it('filters out dot-directories', async () => {
152
+ await fs.mkdir(path.join(tmpDir, '.hidden'));
153
+ await fs.mkdir(path.join(tmpDir, 'visible'));
154
+ const folders = await listFolders(tmpDir);
155
+ assert.ok(!folders.includes('.hidden'));
156
+ assert.ok(folders.includes('visible'));
157
+ });
158
+ });
159
+
160
+ // ---------------------------------------------------------------------------
161
+ // copyFile
162
+ // ---------------------------------------------------------------------------
163
+
164
+ describe('copyFile', () => {
165
+ it('copies a file to a destination folder', async () => {
166
+ const src = path.join(tmpDir, 'src.txt');
167
+ const destDir = path.join(tmpDir, 'dest');
168
+ await fs.writeFile(src, 'content');
169
+ await copyFile(src, destDir);
170
+ const copied = await fs.readFile(path.join(destDir, 'src.txt'), 'utf8');
171
+ assert.strictEqual(copied, 'content');
172
+ });
173
+
174
+ it('creates the destination folder if it does not exist', async () => {
175
+ const src = path.join(tmpDir, 'src2.txt');
176
+ const destDir = path.join(tmpDir, 'new-dest');
177
+ await fs.writeFile(src, 'data');
178
+ await copyFile(src, destDir);
179
+ assert.strictEqual(await checkIfExists(destDir), true);
180
+ });
181
+ });
182
+
183
+ // ---------------------------------------------------------------------------
184
+ // copyFiles
185
+ // ---------------------------------------------------------------------------
186
+
187
+ describe('copyFiles', () => {
188
+ it('copies all files from source to destination folder', async () => {
189
+ const srcDir = path.join(tmpDir, 'src-dir');
190
+ const destDir = path.join(tmpDir, 'dest-dir');
191
+ await fs.mkdir(srcDir);
192
+ await fs.writeFile(path.join(srcDir, 'a.txt'), 'aaa');
193
+ await fs.writeFile(path.join(srcDir, 'b.txt'), 'bbb');
194
+
195
+ await copyFiles(srcDir, destDir);
196
+
197
+ assert.strictEqual(await fs.readFile(path.join(destDir, 'a.txt'), 'utf8'), 'aaa');
198
+ assert.strictEqual(await fs.readFile(path.join(destDir, 'b.txt'), 'utf8'), 'bbb');
199
+ });
200
+ });
201
+
202
+ // ---------------------------------------------------------------------------
203
+ // copyFolderRecursive
204
+ // ---------------------------------------------------------------------------
205
+
206
+ describe('copyFolderRecursive', () => {
207
+ it('copies a folder tree recursively', async () => {
208
+ const srcDir = path.join(tmpDir, 'tree');
209
+ await fs.mkdir(path.join(srcDir, 'sub'), { recursive: true });
210
+ await fs.writeFile(path.join(srcDir, 'root.txt'), 'root');
211
+ await fs.writeFile(path.join(srcDir, 'sub', 'child.txt'), 'child');
212
+
213
+ const destDir = path.join(tmpDir, 'tree-copy');
214
+ await copyFolderRecursive(srcDir, destDir);
215
+
216
+ assert.strictEqual(await fs.readFile(path.join(destDir, 'root.txt'), 'utf8'), 'root');
217
+ assert.strictEqual(await fs.readFile(path.join(destDir, 'sub', 'child.txt'), 'utf8'), 'child');
218
+ });
219
+ });
220
+
221
+ // ---------------------------------------------------------------------------
222
+ // deleteFolder
223
+ // ---------------------------------------------------------------------------
224
+
225
+ describe('deleteFolder', () => {
226
+ it('removes a folder and its contents', async () => {
227
+ const dir = path.join(tmpDir, 'to-delete');
228
+ await fs.mkdir(dir);
229
+ await fs.writeFile(path.join(dir, 'f.txt'), '');
230
+ await deleteFolder(dir);
231
+ assert.strictEqual(await checkIfExists(dir), false);
232
+ });
233
+ });
@@ -0,0 +1,92 @@
1
+ import assert from 'assert';
2
+ import { resolveFireworksModel, buildFireworksRequest, extractJSON } from '../src/models/fireworksai';
3
+ import { PromptCompletionArgs } from '../src/models/types';
4
+
5
+ // ---------------------------------------------------------------------------
6
+ // resolveFireworksModel
7
+ // ---------------------------------------------------------------------------
8
+
9
+ describe('resolveFireworksModel', () => {
10
+ it('maps known short names to full paths', () => {
11
+ assert.strictEqual(
12
+ resolveFireworksModel('fireworks-glm-5'),
13
+ 'accounts/fireworks/models/glm-5',
14
+ );
15
+ assert.strictEqual(
16
+ resolveFireworksModel('fireworks-minimax-m2p5'),
17
+ 'accounts/fireworks/models/minimax-m2p5',
18
+ );
19
+ });
20
+
21
+ it('passes through unknown model names unchanged', () => {
22
+ assert.strictEqual(resolveFireworksModel('custom-model'), 'custom-model');
23
+ });
24
+ });
25
+
26
+ // ---------------------------------------------------------------------------
27
+ // buildFireworksRequest
28
+ // ---------------------------------------------------------------------------
29
+
30
+ describe('buildFireworksRequest', () => {
31
+ const baseArgs: PromptCompletionArgs = {
32
+ prompt: { role: 'user', content: 'Hello' },
33
+ };
34
+
35
+ it('builds messages from system + history + prompt', () => {
36
+ const args: PromptCompletionArgs = {
37
+ ...baseArgs,
38
+ system: { role: 'system', content: 'Be helpful' },
39
+ history: [
40
+ { role: 'user', content: 'Hi' },
41
+ { role: 'assistant', content: 'Hey' },
42
+ ],
43
+ };
44
+ const { messages } = buildFireworksRequest(args, 0.0);
45
+ assert.strictEqual(messages.length, 4);
46
+ assert.strictEqual(messages[0].role, 'system');
47
+ assert.strictEqual(messages[0].content, 'Be helpful');
48
+ assert.strictEqual(messages[3].role, 'user');
49
+ assert.strictEqual(messages[3].content, 'Hello');
50
+ });
51
+
52
+ it('appends JSON instruction to user content in jsonMode', () => {
53
+ const args: PromptCompletionArgs = { ...baseArgs, jsonMode: true };
54
+ const { messages, useJson } = buildFireworksRequest(args, 0.0);
55
+ const last = messages[messages.length - 1];
56
+ assert.ok(last.content.includes('Respond with valid JSON only'));
57
+ assert.strictEqual(useJson, true);
58
+ });
59
+
60
+ it('does not append JSON instruction in plain mode', () => {
61
+ const { messages, useJson } = buildFireworksRequest(baseArgs, 0.0);
62
+ const last = messages[messages.length - 1];
63
+ assert.ok(!last.content.includes('JSON'));
64
+ assert.strictEqual(useJson, false);
65
+ });
66
+
67
+ it('uses default temperature when none specified', () => {
68
+ const { temperature } = buildFireworksRequest(baseArgs, 0.5);
69
+ assert.strictEqual(temperature, 0.5);
70
+ });
71
+
72
+ it('uses args temperature when specified', () => {
73
+ const args: PromptCompletionArgs = { ...baseArgs, temperature: 0.9 };
74
+ const { temperature } = buildFireworksRequest(args, 0.5);
75
+ assert.strictEqual(temperature, 0.9);
76
+ });
77
+ });
78
+
79
+ // ---------------------------------------------------------------------------
80
+ // extractJSON (moved from providers.spec.ts for completeness)
81
+ // ---------------------------------------------------------------------------
82
+
83
+ describe('extractJSON (fireworksai)', () => {
84
+ it('extracts JSON from markdown code fences', () => {
85
+ const input = '```json\n{"key": "value"}\n```';
86
+ assert.strictEqual(extractJSON(input), '{"key": "value"}');
87
+ });
88
+
89
+ it('returns original text when no JSON found', () => {
90
+ assert.strictEqual(extractJSON('no json here'), 'no json here');
91
+ });
92
+ });
@@ -0,0 +1,74 @@
1
+ import assert from 'assert';
2
+ import { logCompletePrompt } from '../src/models/logCompletePrompt';
3
+ import { AgentCompletion, PromptCompletionArgs } from '../src/models/types';
4
+
5
+ /** Build a minimal PromptCompletionArgs for testing. */
6
+ function makeArgs(content = 'test'): PromptCompletionArgs {
7
+ return { prompt: { role: 'user', content } };
8
+ }
9
+
10
+ describe('logCompletePrompt', () => {
11
+ // Capture console.log output during each test
12
+ let logs: string[];
13
+ let origLog: typeof console.log;
14
+
15
+ beforeEach(() => {
16
+ logs = [];
17
+ origLog = console.log;
18
+ console.log = (...args: any[]) => { logs.push(args.join(' ')); };
19
+ });
20
+
21
+ afterEach(() => {
22
+ console.log = origLog;
23
+ });
24
+
25
+ it('passes args through to inner function and returns its result', async () => {
26
+ const expected: AgentCompletion<string> = { completed: true, value: 'hello' };
27
+ let received: PromptCompletionArgs | undefined;
28
+ const inner = async (args: PromptCompletionArgs) => { received = args; return expected; };
29
+ const wrapped = logCompletePrompt(inner);
30
+ const args = makeArgs('my prompt');
31
+ const result = await wrapped(args);
32
+ assert.strictEqual(result, expected);
33
+ assert.strictEqual(received, args);
34
+ });
35
+
36
+ it('handles completed result with string value (logs green)', async () => {
37
+ const inner = async () => ({ completed: true, value: 'output text' } as AgentCompletion<string>);
38
+ const wrapped = logCompletePrompt(inner);
39
+ await wrapped(makeArgs());
40
+ assert.ok(logs.some(l => l.includes('output text')));
41
+ // Check for green ANSI code
42
+ assert.ok(logs.some(l => l.includes('\x1b[32m')));
43
+ });
44
+
45
+ it('handles completed result with object value (JSON.stringify)', async () => {
46
+ const inner = async () => ({ completed: true, value: { a: 1 } } as AgentCompletion<any>);
47
+ const wrapped = logCompletePrompt(inner);
48
+ await wrapped(makeArgs());
49
+ assert.ok(logs.some(l => l.includes('"a": 1')));
50
+ });
51
+
52
+ it('handles failed result with error (logs red)', async () => {
53
+ const inner = async () => ({ completed: false, error: new Error('boom') } as AgentCompletion<string>);
54
+ const wrapped = logCompletePrompt(inner);
55
+ await wrapped(makeArgs());
56
+ assert.ok(logs.some(l => l.includes('boom')));
57
+ // Check for red ANSI code
58
+ assert.ok(logs.some(l => l.includes('\x1b[31m')));
59
+ });
60
+
61
+ it('respects logDetails flag (separator line)', async () => {
62
+ const inner = async () => ({ completed: true, value: 'ok' } as AgentCompletion<string>);
63
+ const wrapped = logCompletePrompt(inner, true);
64
+ await wrapped(makeArgs());
65
+ assert.ok(logs.some(l => l.includes('─')));
66
+ });
67
+
68
+ it('does not log separator when logDetails is false', async () => {
69
+ const inner = async () => ({ completed: true, value: 'ok' } as AgentCompletion<string>);
70
+ const wrapped = logCompletePrompt(inner, false);
71
+ await wrapped(makeArgs());
72
+ assert.ok(!logs.some(l => l.includes('─')));
73
+ });
74
+ });
@@ -0,0 +1,169 @@
1
+ import assert from 'assert';
2
+ import { postProcessV2, migratePage } from '../src/migrations';
3
+ import { AgentCompletion, PromptCompletionArgs } from '../src/models/types';
4
+
5
+ // ---------------------------------------------------------------------------
6
+ // postProcessV2
7
+ // ---------------------------------------------------------------------------
8
+
9
+ describe('postProcessV2', () => {
10
+ const baseV2 = `<!DOCTYPE html>
11
+ <html lang="en">
12
+ <head>
13
+ <meta charset="UTF-8">
14
+ <title>Test</title>
15
+ <script src="/api/theme-info.js"></script>
16
+ <link rel="stylesheet" href="/api/theme.css">
17
+ <style>
18
+ .my-class { color: red; }
19
+ </style>
20
+ </head>
21
+ <body>
22
+ <div class="viewer-panel">Content</div>
23
+ </body>
24
+ </html>`;
25
+
26
+ it('injects theme-info.js when missing', () => {
27
+ const html = `<html><head><title>Test</title></head><body></body></html>`;
28
+ const result = postProcessV2(html);
29
+ assert.ok(result.includes('src="/api/theme-info.js"'));
30
+ });
31
+
32
+ it('does not duplicate theme-info.js when already present', () => {
33
+ const result = postProcessV2(baseV2);
34
+ const matches = result.match(/theme-info\.js/g);
35
+ assert.strictEqual(matches?.length, 1);
36
+ });
37
+
38
+ it('injects theme.css when missing', () => {
39
+ const html = `<html><head><title>Test</title></head><body></body></html>`;
40
+ const result = postProcessV2(html);
41
+ assert.ok(result.includes('href="/api/theme.css"'));
42
+ });
43
+
44
+ it('does not duplicate theme.css when already present', () => {
45
+ const result = postProcessV2(baseV2);
46
+ const matches = result.match(/theme\.css/g);
47
+ assert.strictEqual(matches?.length, 1);
48
+ });
49
+
50
+ it('strips shared CSS selectors from style blocks', () => {
51
+ const html = `<html><head><style>
52
+ .chat-panel { background: black; }
53
+ .viewer-panel { padding: 10px; }
54
+ .my-custom { color: blue; }
55
+ </style></head><body></body></html>`;
56
+ const result = postProcessV2(html);
57
+ assert.ok(!result.includes('.chat-panel {'));
58
+ assert.ok(!result.includes('.viewer-panel {'));
59
+ assert.ok(result.includes('.my-custom'));
60
+ });
61
+
62
+ it('removes empty style blocks after stripping', () => {
63
+ const html = `<html><head><style>
64
+ .chat-panel { background: black; }
65
+ </style></head><body></body></html>`;
66
+ const result = postProcessV2(html);
67
+ // The style block should be removed since it only had shared CSS
68
+ assert.ok(!result.includes('<style>'));
69
+ });
70
+
71
+ it('removes empty script blocks (no src)', () => {
72
+ const html = `<html><head></head><body><script> </script></body></html>`;
73
+ const result = postProcessV2(html);
74
+ // Empty script should be removed
75
+ assert.ok(!result.includes('<script> </script>'));
76
+ });
77
+
78
+ it('preserves script blocks with src attribute', () => {
79
+ const html = `<html><head><script src="/api/theme-info.js"></script></head><body></body></html>`;
80
+ const result = postProcessV2(html);
81
+ assert.ok(result.includes('src="/api/theme-info.js"'));
82
+ });
83
+
84
+ it('preserves page-specific CSS', () => {
85
+ const html = `<html><head><style>
86
+ .chat-panel { background: black; }
87
+ .game-canvas { width: 100%; height: 100%; }
88
+ </style></head><body></body></html>`;
89
+ const result = postProcessV2(html);
90
+ assert.ok(result.includes('.game-canvas'));
91
+ });
92
+
93
+ // -- Additional postProcessV2 edge cases --------------------------------
94
+
95
+ it('restores chat panel with default when entirely missing (no original)', () => {
96
+ const html = `<html><head><title>Test</title></head><body><div class="viewer-panel">Content</div></body></html>`;
97
+ const result = postProcessV2(html);
98
+ assert.ok(result.includes('id="chatForm"'));
99
+ assert.ok(result.includes('class="chat-panel"'));
100
+ assert.ok(result.includes('id="chatInput"'));
101
+ });
102
+
103
+ it('restores chat form when panel exists but form missing', () => {
104
+ const html = `<html><head><title>Test</title></head><body>
105
+ <div class="chat-panel"><div class="chat-messages"></div></div>
106
+ <div class="viewer-panel">Content</div>
107
+ </body></html>`;
108
+ const result = postProcessV2(html);
109
+ assert.ok(result.includes('id="chatForm"'));
110
+ });
111
+
112
+ it('injects #thoughts div when missing', () => {
113
+ const html = `<html><head><title>Test</title></head><body>
114
+ <div class="chat-panel">
115
+ <form id="chatForm"><input id="chatInput"></form>
116
+ </div>
117
+ </body></html>`;
118
+ const result = postProcessV2(html);
119
+ assert.ok(result.includes('id="thoughts"'));
120
+ });
121
+
122
+ it('ensures #loadingOverlay inside .viewer-panel', () => {
123
+ const html = `<html><head><title>Test</title></head><body>
124
+ <div class="chat-panel"><form id="chatForm"><input id="chatInput"></form></div>
125
+ <div class="viewer-panel">Content</div>
126
+ </body></html>`;
127
+ const result = postProcessV2(html);
128
+ // loadingOverlay should be present inside viewer-panel
129
+ assert.ok(result.includes('id="loadingOverlay"'));
130
+ // Quick structural check: loadingOverlay appears after viewer-panel opens
131
+ const vpIdx = result.indexOf('class="viewer-panel"');
132
+ const olIdx = result.indexOf('id="loadingOverlay"');
133
+ assert.ok(vpIdx < olIdx);
134
+ });
135
+
136
+ it('moves #loadingOverlay inside .viewer-panel when it is outside', () => {
137
+ const html = `<html><head><title>Test</title></head><body>
138
+ <div class="chat-panel"><form id="chatForm"><input id="chatInput"></form></div>
139
+ <div id="loadingOverlay" class="loading-overlay"><div class="spinner"></div></div>
140
+ <div class="viewer-panel">Content</div>
141
+ </body></html>`;
142
+ const result = postProcessV2(html);
143
+ // loadingOverlay should now be inside viewer-panel
144
+ const vpIdx = result.indexOf('class="viewer-panel"');
145
+ const olIdx = result.indexOf('id="loadingOverlay"');
146
+ assert.ok(vpIdx < olIdx);
147
+ });
148
+ });
149
+
150
+ // ---------------------------------------------------------------------------
151
+ // migratePage
152
+ // ---------------------------------------------------------------------------
153
+
154
+ describe('migratePage', () => {
155
+ it('returns input unchanged when fromVersion === toVersion', async () => {
156
+ const html = '<html>unchanged</html>';
157
+ const stub = async () => ({ completed: true, value: '' } as AgentCompletion<string>);
158
+ const result = await migratePage(html, 2, 2, stub);
159
+ assert.strictEqual(result, html);
160
+ });
161
+
162
+ it('throws when no migration exists for version', async () => {
163
+ const stub = async () => ({ completed: true, value: '' } as AgentCompletion<string>);
164
+ await assert.rejects(
165
+ () => migratePage('<html></html>', 99, 100, stub),
166
+ /No migration defined for version 99/,
167
+ );
168
+ });
169
+ });
@@ -0,0 +1,71 @@
1
+ import assert from 'assert';
2
+ import { buildOpenAIRequest } from '../src/models/openai';
3
+ import { PromptCompletionArgs } from '../src/models/types';
4
+
5
+ describe('buildOpenAIRequest', () => {
6
+ const baseArgs: PromptCompletionArgs = {
7
+ prompt: { role: 'user', content: 'Hello' },
8
+ };
9
+
10
+ it('builds input from history + prompt', () => {
11
+ const args: PromptCompletionArgs = {
12
+ ...baseArgs,
13
+ history: [
14
+ { role: 'user', content: 'Hi' },
15
+ { role: 'assistant', content: 'Hey' },
16
+ ],
17
+ };
18
+ const { input } = buildOpenAIRequest(args);
19
+ assert.strictEqual(input.length, 3);
20
+ assert.strictEqual(input[2].content, 'Hello');
21
+ });
22
+
23
+ it('appends "Return JSON." when jsonMode and no "json" in input', () => {
24
+ const args: PromptCompletionArgs = {
25
+ ...baseArgs,
26
+ jsonMode: true,
27
+ };
28
+ const { input } = buildOpenAIRequest(args);
29
+ const last = input[input.length - 1];
30
+ assert.ok(last.content.includes('Return JSON.'));
31
+ });
32
+
33
+ it('does not append "Return JSON." when input already contains "json"', () => {
34
+ const args: PromptCompletionArgs = {
35
+ prompt: { role: 'user', content: 'Return this as json please' },
36
+ jsonMode: true,
37
+ };
38
+ const { input } = buildOpenAIRequest(args);
39
+ const last = input[input.length - 1];
40
+ assert.ok(!last.content.includes('Return JSON.'));
41
+ });
42
+
43
+ it('returns json_schema text config when jsonSchema provided', () => {
44
+ const schema = { type: 'object', properties: { x: { type: 'number' } } };
45
+ const args: PromptCompletionArgs = {
46
+ ...baseArgs,
47
+ jsonSchema: schema,
48
+ };
49
+ const { text } = buildOpenAIRequest(args);
50
+ assert.ok(text);
51
+ assert.strictEqual(text.format.type, 'json_schema');
52
+ assert.strictEqual(text.format.name, 'response');
53
+ assert.strictEqual(text.format.strict, true);
54
+ assert.deepStrictEqual(text.format.schema, schema);
55
+ });
56
+
57
+ it('returns json_object text config when jsonMode (no schema)', () => {
58
+ const args: PromptCompletionArgs = {
59
+ ...baseArgs,
60
+ jsonMode: true,
61
+ };
62
+ const { text } = buildOpenAIRequest(args);
63
+ assert.ok(text);
64
+ assert.strictEqual(text.format.type, 'json_object');
65
+ });
66
+
67
+ it('returns no text config for plain text', () => {
68
+ const { text } = buildOpenAIRequest(baseArgs);
69
+ assert.strictEqual(text, undefined);
70
+ });
71
+ });