synthos 0.8.0 → 0.10.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.
- package/README.md +1 -1
- package/default-pages/application/page.html +42 -0
- package/default-pages/application/page.json +10 -0
- package/default-pages/elevenlabs_effects_studio/page.html +1363 -0
- package/default-pages/elevenlabs_effects_studio/page.json +11 -0
- package/default-pages/elevenlabs_voice_studio/page.html +801 -0
- package/default-pages/elevenlabs_voice_studio/page.json +11 -0
- package/default-pages/{json_tools.html → json_tools/page.html} +13 -11
- package/default-pages/json_tools/page.json +10 -0
- package/default-pages/my_notes/notes/a1b2c3d4-e5f6-7890-abcd-ef1234567890.json +5 -0
- package/default-pages/my_notes/page.html +132 -0
- package/default-pages/{my_notes.json → my_notes/page.json} +2 -2
- package/default-pages/neon_asteroids/files/Ambient_Space.mp3 +0 -0
- package/default-pages/neon_asteroids/files/Ambient_Space2.mp3 +0 -0
- package/default-pages/neon_asteroids/files/Ambient_Space3.mp3 +0 -0
- package/default-pages/neon_asteroids/files/Asteroid_Explosion.mp3 +0 -0
- package/default-pages/neon_asteroids/files/Hyperspace_Jump.mp3 +0 -0
- package/default-pages/neon_asteroids/files/Laser_Fire.mp3 +0 -0
- package/default-pages/neon_asteroids/files/Menu_Navigate.mp3 +0 -0
- package/default-pages/neon_asteroids/files/Power_Up_Collect.mp3 +0 -0
- package/default-pages/neon_asteroids/files/Saucer_Alert.mp3 +0 -0
- package/default-pages/neon_asteroids/files/Ship_Thrust.mp3 +0 -0
- package/default-pages/neon_asteroids/files/effects.json +74 -0
- package/default-pages/neon_asteroids/page.html +1803 -0
- package/default-pages/{neon_asteroids.json → neon_asteroids/page.json} +3 -3
- package/default-pages/{oregon_trail.html → oregon_trail/page.html} +16 -30
- package/default-pages/{oregon_trail.json → oregon_trail/page.json} +2 -2
- package/default-pages/retro_game_starter/page.html +1308 -0
- package/default-pages/retro_game_starter/page.json +12 -0
- package/default-pages/{sidebar_page.html → sidebar_page/page.html} +12 -10
- package/default-pages/sidebar_page/page.json +10 -0
- package/default-pages/{solar_explorer.html → solar_explorer/page.html} +15 -12
- package/default-pages/{solar_explorer.json → solar_explorer/page.json} +2 -2
- package/default-pages/{solar_tutorial.html → solar_tutorial/page.html} +12 -10
- package/default-pages/solar_tutorial/page.json +10 -0
- package/default-pages/{two-panel_page.html → two-panel_page/page.html} +13 -11
- package/default-pages/two-panel_page/page.json +10 -0
- package/default-pages/{us_map.html → us_map/page.html} +193 -192
- package/default-pages/{us_map.json → us_map/page.json} +12 -12
- package/default-pages/{us_map_1850.html → us_map_1850/page.html} +326 -325
- package/default-pages/{us_map_1850.json → us_map_1850/page.json} +12 -12
- package/default-pages/{western_cities_1850.html → western_cities_1850/page.html} +527 -526
- package/default-pages/{western_cities_1850.json → western_cities_1850/page.json} +12 -12
- package/default-themes/aurora-dawn.json +19 -0
- package/default-themes/aurora-dawn.v3.css +198 -0
- package/default-themes/aurora-dusk.json +19 -0
- package/default-themes/aurora-dusk.v3.css +200 -0
- package/default-themes/cosmos-dawn.json +19 -0
- package/default-themes/cosmos-dawn.v3.css +198 -0
- package/default-themes/cosmos-dusk.json +19 -0
- package/default-themes/cosmos-dusk.v3.css +200 -0
- package/default-themes/high-contrast-dark.json +19 -0
- package/default-themes/high-contrast-dark.v3.css +200 -0
- package/default-themes/high-contrast-light.json +19 -0
- package/default-themes/high-contrast-light.v3.css +198 -0
- package/default-themes/nebula-dawn.v2.css +110 -0
- package/default-themes/nebula-dawn.v3.css +199 -0
- package/default-themes/nebula-dusk.v2.css +104 -0
- package/default-themes/nebula-dusk.v3.css +201 -0
- package/default-themes/solar-flare-dawn.json +19 -0
- package/default-themes/solar-flare-dawn.v3.css +198 -0
- package/default-themes/solar-flare-dusk.json +19 -0
- package/default-themes/solar-flare-dusk.v3.css +200 -0
- package/dist/agents/index.d.ts +1 -1
- package/dist/agents/index.d.ts.map +1 -1
- package/dist/agents/index.js +2 -1
- package/dist/agents/index.js.map +1 -1
- package/dist/agents/openclaw/gatewayManager.d.ts +4 -0
- package/dist/agents/openclaw/gatewayManager.d.ts.map +1 -1
- package/dist/agents/openclaw/gatewayManager.js +27 -11
- package/dist/agents/openclaw/gatewayManager.js.map +1 -1
- package/dist/agents/openclaw/openclawProvider.d.ts.map +1 -1
- package/dist/agents/openclaw/openclawProvider.js +2 -4
- package/dist/agents/openclaw/openclawProvider.js.map +1 -1
- package/dist/agents/openclaw/sshTunnelManager.d.ts +2 -0
- package/dist/agents/openclaw/sshTunnelManager.d.ts.map +1 -1
- package/dist/agents/openclaw/sshTunnelManager.js +31 -12
- package/dist/agents/openclaw/sshTunnelManager.js.map +1 -1
- package/dist/builders/anthropic.d.ts +31 -0
- package/dist/builders/anthropic.d.ts.map +1 -0
- package/dist/builders/anthropic.js +227 -0
- package/dist/builders/anthropic.js.map +1 -0
- package/dist/builders/fireworksai.d.ts +9 -0
- package/dist/builders/fireworksai.d.ts.map +1 -0
- package/dist/builders/fireworksai.js +57 -0
- package/dist/builders/fireworksai.js.map +1 -0
- package/dist/builders/index.d.ts +13 -0
- package/dist/builders/index.d.ts.map +1 -0
- package/dist/builders/index.js +31 -0
- package/dist/builders/index.js.map +1 -0
- package/dist/builders/openai.d.ts +8 -0
- package/dist/builders/openai.d.ts.map +1 -0
- package/dist/builders/openai.js +87 -0
- package/dist/builders/openai.js.map +1 -0
- package/dist/builders/types.d.ts +54 -0
- package/dist/builders/types.d.ts.map +1 -0
- package/dist/builders/types.js +211 -0
- package/dist/builders/types.js.map +1 -0
- package/dist/connectors/index.d.ts +1 -1
- package/dist/connectors/index.d.ts.map +1 -1
- package/dist/connectors/index.js +3 -2
- package/dist/connectors/index.js.map +1 -1
- package/dist/connectors/registry.d.ts +2 -1
- package/dist/connectors/registry.d.ts.map +1 -1
- package/dist/connectors/registry.js +31 -8
- package/dist/connectors/registry.js.map +1 -1
- package/dist/customizer/Customizer.d.ts +62 -0
- package/dist/customizer/Customizer.d.ts.map +1 -0
- package/dist/customizer/Customizer.js +134 -0
- package/dist/customizer/Customizer.js.map +1 -0
- package/dist/customizer/index.d.ts +4 -0
- package/dist/customizer/index.d.ts.map +1 -0
- package/dist/customizer/index.js +9 -0
- package/dist/customizer/index.js.map +1 -0
- package/dist/files.d.ts +16 -0
- package/dist/files.d.ts.map +1 -1
- package/dist/files.js +60 -1
- package/dist/files.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts +12 -6
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +150 -133
- package/dist/init.js.map +1 -1
- package/dist/migrations.d.ts.map +1 -1
- package/dist/migrations.js +23 -10
- package/dist/migrations.js.map +1 -1
- package/dist/models/anthropic.d.ts +4 -2
- package/dist/models/anthropic.d.ts.map +1 -1
- package/dist/models/anthropic.js +33 -6
- package/dist/models/anthropic.js.map +1 -1
- package/dist/models/fireworksai.d.ts.map +1 -1
- package/dist/models/fireworksai.js +9 -1
- package/dist/models/fireworksai.js.map +1 -1
- package/dist/models/index.d.ts +1 -1
- package/dist/models/index.d.ts.map +1 -1
- package/dist/models/index.js +2 -1
- package/dist/models/index.js.map +1 -1
- package/dist/models/openai.d.ts +1 -1
- package/dist/models/openai.d.ts.map +1 -1
- package/dist/models/openai.js +24 -3
- package/dist/models/openai.js.map +1 -1
- package/dist/models/types.d.ts +20 -1
- package/dist/models/types.d.ts.map +1 -1
- package/dist/models/types.js +6 -1
- package/dist/models/types.js.map +1 -1
- package/dist/pages.d.ts +34 -10
- package/dist/pages.d.ts.map +1 -1
- package/dist/pages.js +229 -79
- package/dist/pages.js.map +1 -1
- package/dist/service/createCompletePrompt.d.ts +2 -1
- package/dist/service/createCompletePrompt.d.ts.map +1 -1
- package/dist/service/createCompletePrompt.js +2 -2
- package/dist/service/createCompletePrompt.js.map +1 -1
- package/dist/service/requiresSettings.d.ts +2 -1
- package/dist/service/requiresSettings.d.ts.map +1 -1
- package/dist/service/requiresSettings.js +3 -3
- package/dist/service/requiresSettings.js.map +1 -1
- package/dist/service/server.d.ts +2 -1
- package/dist/service/server.d.ts.map +1 -1
- package/dist/service/server.js +37 -8
- package/dist/service/server.js.map +1 -1
- package/dist/service/transformPage.d.ts +47 -20
- package/dist/service/transformPage.d.ts.map +1 -1
- package/dist/service/transformPage.js +514 -293
- package/dist/service/transformPage.js.map +1 -1
- package/dist/service/useAgentRoutes.d.ts +2 -1
- package/dist/service/useAgentRoutes.d.ts.map +1 -1
- package/dist/service/useAgentRoutes.js +17 -14
- package/dist/service/useAgentRoutes.js.map +1 -1
- package/dist/service/useApiRoutes.d.ts +2 -1
- package/dist/service/useApiRoutes.d.ts.map +1 -1
- package/dist/service/useApiRoutes.js +287 -172
- package/dist/service/useApiRoutes.js.map +1 -1
- package/dist/service/useConnectorRoutes.js +17 -17
- package/dist/service/useConnectorRoutes.js.map +1 -1
- package/dist/service/useDataRoutes.d.ts.map +1 -1
- package/dist/service/useDataRoutes.js +13 -10
- package/dist/service/useDataRoutes.js.map +1 -1
- package/dist/service/useFileRoutes.d.ts +4 -0
- package/dist/service/useFileRoutes.d.ts.map +1 -0
- package/dist/service/useFileRoutes.js +122 -0
- package/dist/service/useFileRoutes.js.map +1 -0
- package/dist/service/usePageRoutes.d.ts +2 -1
- package/dist/service/usePageRoutes.d.ts.map +1 -1
- package/dist/service/usePageRoutes.js +671 -74
- package/dist/service/usePageRoutes.js.map +1 -1
- package/dist/service/useSharedDataRoutes.d.ts +4 -0
- package/dist/service/useSharedDataRoutes.d.ts.map +1 -0
- package/dist/service/useSharedDataRoutes.js +107 -0
- package/dist/service/useSharedDataRoutes.js.map +1 -0
- package/dist/service/useSharedFileRoutes.d.ts +4 -0
- package/dist/service/useSharedFileRoutes.d.ts.map +1 -0
- package/dist/service/useSharedFileRoutes.js +121 -0
- package/dist/service/useSharedFileRoutes.js.map +1 -0
- package/dist/settings.d.ts +5 -3
- package/dist/settings.d.ts.map +1 -1
- package/dist/settings.js +12 -10
- package/dist/settings.js.map +1 -1
- package/dist/storage/FsStorageProvider.d.ts +25 -0
- package/dist/storage/FsStorageProvider.d.ts.map +1 -0
- package/dist/storage/FsStorageProvider.js +103 -0
- package/dist/storage/FsStorageProvider.js.map +1 -0
- package/dist/storage/StorageProvider.d.ts +31 -0
- package/dist/storage/StorageProvider.d.ts.map +1 -0
- package/dist/storage/StorageProvider.js +3 -0
- package/dist/storage/StorageProvider.js.map +1 -0
- package/dist/storage/index.d.ts +3 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +6 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/synthos-cli.d.ts.map +1 -1
- package/dist/synthos-cli.js +4 -3
- package/dist/synthos-cli.js.map +1 -1
- package/dist/themes.d.ts +1 -0
- package/dist/themes.d.ts.map +1 -1
- package/dist/themes.js +65 -28
- package/dist/themes.js.map +1 -1
- package/migration-rules/v1-to-v2.md +193 -0
- package/migration-rules/v2-to-v3.md +481 -0
- package/package.json +11 -10
- package/required-pages/builder/page.html +43 -0
- package/required-pages/builder/page.json +10 -0
- package/required-pages/{pages.html → pages/page.html} +238 -233
- package/required-pages/pages/page.json +10 -0
- package/required-pages/{settings.html → settings/page.html} +389 -275
- package/required-pages/settings/page.json +10 -0
- package/required-pages/synthos_apis/page.html +846 -0
- package/required-pages/synthos_apis/page.json +10 -0
- package/required-pages/{synthos_scripts.html → synthos_scripts/page.html} +13 -11
- package/required-pages/synthos_scripts/page.json +10 -0
- package/src/agents/index.ts +1 -1
- package/src/agents/openclaw/gatewayManager.ts +22 -11
- package/src/agents/openclaw/openclawProvider.ts +2 -4
- package/src/agents/openclaw/sshTunnelManager.ts +19 -11
- package/src/builders/anthropic.ts +283 -0
- package/src/builders/fireworksai.ts +59 -0
- package/src/builders/index.ts +33 -0
- package/src/builders/openai.ts +89 -0
- package/src/builders/types.ts +261 -0
- package/src/connectors/index.ts +1 -1
- package/src/connectors/registry.ts +28 -8
- package/src/customizer/Customizer.ts +163 -0
- package/src/customizer/index.ts +5 -0
- package/src/files.ts +57 -0
- package/src/index.ts +3 -1
- package/src/init.ts +195 -145
- package/src/migrations.ts +30 -10
- package/src/models/anthropic.ts +40 -10
- package/src/models/fireworksai.ts +9 -2
- package/src/models/index.ts +1 -1
- package/src/models/openai.ts +26 -6
- package/src/models/types.ts +31 -1
- package/src/pages.ts +230 -77
- package/src/service/createCompletePrompt.ts +3 -2
- package/src/service/requiresSettings.ts +4 -3
- package/src/service/server.ts +36 -9
- package/src/service/transformPage.ts +557 -326
- package/src/service/useAgentRoutes.ts +19 -14
- package/src/service/useApiRoutes.ts +208 -84
- package/src/service/useConnectorRoutes.ts +18 -18
- package/src/service/useDataRoutes.ts +13 -10
- package/src/service/useFileRoutes.ts +128 -0
- package/src/service/usePageRoutes.ts +730 -81
- package/src/service/useSharedDataRoutes.ts +109 -0
- package/src/service/useSharedFileRoutes.ts +127 -0
- package/src/settings.ts +14 -10
- package/src/storage/FsStorageProvider.ts +87 -0
- package/src/storage/StorageProvider.ts +34 -0
- package/src/storage/index.ts +2 -0
- package/src/synthos-cli.ts +4 -3
- package/src/themes.ts +64 -27
- package/static-files/favicon.svg +12 -0
- package/static-files/fluentlm-instructions.llmd +868 -0
- package/static-files/fluentlm-instructions.md +1595 -0
- package/static-files/fluentlm.css +4844 -0
- package/static-files/fluentlm.js +3602 -0
- package/static-files/fluentlm.min.css +1 -0
- package/static-files/fluentlm.min.js +1 -0
- package/{page-scripts/helpers-v2.js → static-files/helpers.v3.js} +82 -0
- package/static-files/page.v3.js +1290 -0
- package/static-files/recommended-frameworks.llmd +81 -0
- package/static-files/recommended-frameworks.md +137 -0
- package/static-files/retro-game.js +877 -0
- package/static-files/shell.css +797 -0
- package/static-files/theme-dark.css +169 -0
- package/static-files/theme-light.css +169 -0
- package/tests/builders.spec.ts +139 -0
- package/tests/pages.spec.ts +54 -84
- package/tests/transformPage.spec.ts +299 -360
- package/default-pages/application.html +0 -40
- package/default-pages/application.json +0 -1
- package/default-pages/json_tools.json +0 -1
- package/default-pages/my_notes.html +0 -33
- package/default-pages/neon_asteroids.html +0 -77
- package/default-pages/sidebar_page.json +0 -1
- package/default-pages/solar_tutorial.json +0 -1
- package/default-pages/two-panel_page.json +0 -1
- package/dist/service/useGatewayRoutes.d.ts +0 -4
- package/dist/service/useGatewayRoutes.d.ts.map +0 -1
- package/dist/service/useGatewayRoutes.js +0 -168
- package/dist/service/useGatewayRoutes.js.map +0 -1
- package/page-scripts/page-v2.js +0 -656
- package/required-pages/builder.html +0 -48
- package/required-pages/builder.json +0 -1
- package/required-pages/pages.json +0 -1
- package/required-pages/settings.json +0 -1
- package/required-pages/synthos_apis.html +0 -327
- package/required-pages/synthos_apis.json +0 -1
- package/required-pages/synthos_scripts.json +0 -1
- package/src/connectors/airtable/connector.json +0 -27
- package/src/connectors/alpha-vantage/connector.json +0 -26
- package/src/connectors/brave-search/connector.json +0 -26
- package/src/connectors/cloudinary/connector.json +0 -27
- package/src/connectors/deepl/connector.json +0 -28
- package/src/connectors/elevenlabs/connector.json +0 -30
- package/src/connectors/giphy/connector.json +0 -27
- package/src/connectors/github/connector.json +0 -29
- package/src/connectors/huggingface/connector.json +0 -27
- package/src/connectors/imgur/connector.json +0 -29
- package/src/connectors/instagram/connector.json +0 -43
- package/src/connectors/jira/connector.json +0 -28
- package/src/connectors/mapbox/connector.json +0 -26
- package/src/connectors/nasa/connector.json +0 -27
- package/src/connectors/newsapi/connector.json +0 -27
- package/src/connectors/notion/connector.json +0 -28
- package/src/connectors/open-exchange-rates/connector.json +0 -27
- package/src/connectors/openweathermap/connector.json +0 -26
- package/src/connectors/pexels/connector.json +0 -27
- package/src/connectors/resend/connector.json +0 -29
- package/src/connectors/rss2json/connector.json +0 -27
- package/src/connectors/sendgrid/connector.json +0 -27
- package/src/connectors/spoonacular/connector.json +0 -28
- package/src/connectors/stability-ai/connector.json +0 -27
- package/src/connectors/twilio/connector.json +0 -28
- package/src/connectors/unsplash/connector.json +0 -27
- package/src/connectors/wolfram-alpha/connector.json +0 -26
- package/src/connectors/youtube-data/connector.json +0 -30
- /package/{dist/connectors → service-connectors}/airtable/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/alpha-vantage/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/brave-search/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/cloudinary/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/deepl/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/elevenlabs/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/giphy/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/github/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/huggingface/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/imgur/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/instagram/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/jira/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/mapbox/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/nasa/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/newsapi/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/notion/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/open-exchange-rates/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/openweathermap/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/pexels/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/resend/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/rss2json/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/sendgrid/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/spoonacular/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/stability-ai/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/twilio/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/unsplash/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/wolfram-alpha/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/youtube-data/connector.json +0 -0
package/src/models/anthropic.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import Anthropic from '@anthropic-ai/sdk';
|
|
2
|
-
import { AgentCompletion, completePrompt, PromptCompletionArgs, RequestError } from './types';
|
|
2
|
+
import { AgentCompletion, completePrompt, PromptCompletionArgs, RequestError, isMultimodalContent } from './types';
|
|
3
3
|
|
|
4
4
|
export interface AnthropicArgs {
|
|
5
5
|
apiKey: string;
|
|
@@ -14,25 +14,44 @@ export interface AnthropicArgs {
|
|
|
14
14
|
* Pure function — no SDK dependency.
|
|
15
15
|
*/
|
|
16
16
|
export function buildAnthropicRequest(args: PromptCompletionArgs, defaultTemp: number): {
|
|
17
|
-
messages: { role: string; content: string }[];
|
|
18
|
-
system: string | undefined;
|
|
17
|
+
messages: { role: string; content: string | Anthropic.ContentBlockParam[] }[];
|
|
18
|
+
system: string | Anthropic.TextBlockParam[] | undefined;
|
|
19
19
|
temperature: number;
|
|
20
|
+
outputConfig?: Anthropic.OutputConfig;
|
|
20
21
|
} {
|
|
21
22
|
const reqTemp = args.temperature ?? defaultTemp;
|
|
22
23
|
|
|
23
|
-
const messages: { role: string; content: string }[] = [];
|
|
24
|
+
const messages: { role: string; content: string | Anthropic.ContentBlockParam[] }[] = [];
|
|
24
25
|
if (args.history) {
|
|
25
26
|
for (const msg of args.history) {
|
|
26
27
|
messages.push({ role: msg.role, content: msg.content });
|
|
27
28
|
}
|
|
28
29
|
}
|
|
29
30
|
|
|
30
|
-
|
|
31
|
+
// Build user content — multimodal when ContentBlock[] is provided
|
|
32
|
+
const promptContent = args.prompt.content;
|
|
33
|
+
let userContent: string | Anthropic.ContentBlockParam[];
|
|
34
|
+
if (isMultimodalContent(promptContent)) {
|
|
35
|
+
userContent = promptContent.map(block => {
|
|
36
|
+
if (block.type === 'text') {
|
|
37
|
+
return { type: 'text' as const, text: block.text };
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
type: 'image' as const,
|
|
41
|
+
source: { type: 'base64' as const, media_type: block.mediaType as 'image/jpeg' | 'image/png' | 'image/gif' | 'image/webp', data: block.data },
|
|
42
|
+
};
|
|
43
|
+
});
|
|
44
|
+
} else {
|
|
45
|
+
userContent = promptContent;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Structured output via output_config is incompatible with prefilling
|
|
49
|
+
const useJsonPrefill = !args.outputSchema && (args.jsonMode || args.jsonSchema);
|
|
31
50
|
if (useJsonPrefill) {
|
|
32
|
-
messages.push({ role: 'user', content:
|
|
51
|
+
messages.push({ role: 'user', content: userContent });
|
|
33
52
|
messages.push({ role: 'assistant', content: '{' });
|
|
34
53
|
} else {
|
|
35
|
-
messages.push({ role: 'user', content:
|
|
54
|
+
messages.push({ role: 'user', content: userContent });
|
|
36
55
|
}
|
|
37
56
|
|
|
38
57
|
let system = args.system?.content;
|
|
@@ -41,7 +60,17 @@ export function buildAnthropicRequest(args: PromptCompletionArgs, defaultTemp: n
|
|
|
41
60
|
system = system ? system + schemaInstruction : schemaInstruction;
|
|
42
61
|
}
|
|
43
62
|
|
|
44
|
-
|
|
63
|
+
// Wrap system content with cache_control for prompt caching
|
|
64
|
+
const finalSystem: string | Anthropic.TextBlockParam[] | undefined = (system && args.cacheSystem)
|
|
65
|
+
? [{ type: 'text' as const, text: system, cache_control: { type: 'ephemeral' as const } }]
|
|
66
|
+
: system;
|
|
67
|
+
|
|
68
|
+
// Structured output config for constrained decoding
|
|
69
|
+
const outputConfig: Anthropic.OutputConfig | undefined = args.outputSchema
|
|
70
|
+
? { format: { type: 'json_schema', schema: args.outputSchema } }
|
|
71
|
+
: undefined;
|
|
72
|
+
|
|
73
|
+
return { messages, system: finalSystem, temperature: reqTemp, outputConfig };
|
|
45
74
|
}
|
|
46
75
|
|
|
47
76
|
export function anthropic(args: AnthropicArgs): completePrompt {
|
|
@@ -50,9 +79,9 @@ export function anthropic(args: AnthropicArgs): completePrompt {
|
|
|
50
79
|
const client = new Anthropic({ apiKey, baseURL, maxRetries });
|
|
51
80
|
|
|
52
81
|
return async (completionArgs: PromptCompletionArgs): Promise<AgentCompletion<string>> => {
|
|
53
|
-
const { messages, system: systemContent, temperature: reqTemp } = buildAnthropicRequest(completionArgs, temperature);
|
|
82
|
+
const { messages, system: systemContent, temperature: reqTemp, outputConfig } = buildAnthropicRequest(completionArgs, temperature);
|
|
54
83
|
|
|
55
|
-
const useJsonPrefill = completionArgs.jsonMode || completionArgs.jsonSchema;
|
|
84
|
+
const useJsonPrefill = !completionArgs.outputSchema && (completionArgs.jsonMode || completionArgs.jsonSchema);
|
|
56
85
|
|
|
57
86
|
try {
|
|
58
87
|
const stream = await client.messages.create({
|
|
@@ -62,6 +91,7 @@ export function anthropic(args: AnthropicArgs): completePrompt {
|
|
|
62
91
|
system: systemContent,
|
|
63
92
|
messages: messages as Anthropic.MessageParam[],
|
|
64
93
|
stream: true,
|
|
94
|
+
...(outputConfig && { output_config: outputConfig }),
|
|
65
95
|
});
|
|
66
96
|
|
|
67
97
|
let text = '';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import OpenAI from 'openai';
|
|
2
|
-
import { AgentCompletion, completePrompt, PromptCompletionArgs, RequestError } from './types';
|
|
2
|
+
import { AgentCompletion, completePrompt, PromptCompletionArgs, RequestError, isMultimodalContent } from './types';
|
|
3
3
|
|
|
4
4
|
export interface FireworksAIArgs {
|
|
5
5
|
apiKey: string;
|
|
@@ -83,7 +83,14 @@ export function buildFireworksRequest(args: PromptCompletionArgs, defaultTemp: n
|
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
|
|
86
|
+
// Strip images — FireworksAI has no vision support; keep text only
|
|
87
|
+
const promptContent = args.prompt.content;
|
|
88
|
+
let userContent: string;
|
|
89
|
+
if (isMultimodalContent(promptContent)) {
|
|
90
|
+
userContent = promptContent.filter(b => b.type === 'text').map(b => (b as { text: string }).text).join('\n');
|
|
91
|
+
} else {
|
|
92
|
+
userContent = promptContent;
|
|
93
|
+
}
|
|
87
94
|
if (useJson) {
|
|
88
95
|
userContent += '\n\nRespond with valid JSON only. No markdown fences.';
|
|
89
96
|
}
|
package/src/models/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { ProviderName, ProviderConfig, ModelEntry, Provider, SystemMessage, UserMessage, Message, AgentCompletion, completePrompt, PromptCompletionArgs, AgentArgs, RequestError } from './types';
|
|
1
|
+
export { ProviderName, ProviderConfig, ModelEntry, Provider, SystemMessage, UserMessage, Message, AgentCompletion, completePrompt, PromptCompletionArgs, AgentArgs, RequestError, TextBlock, ImageBlock, ContentBlock, MessageContent, isMultimodalContent } from './types';
|
|
2
2
|
export { AnthropicProvider, OpenAIProvider, PROVIDERS, getProvider, detectProvider } from './providers';
|
|
3
3
|
export { anthropic, AnthropicArgs, buildAnthropicRequest } from './anthropic';
|
|
4
4
|
export { openai, OpenaiArgs, buildOpenAIRequest } from './openai';
|
package/src/models/openai.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import OpenAI from 'openai';
|
|
2
|
-
import { AgentCompletion, completePrompt, PromptCompletionArgs, RequestError } from './types';
|
|
2
|
+
import { AgentCompletion, completePrompt, PromptCompletionArgs, RequestError, isMultimodalContent } from './types';
|
|
3
3
|
|
|
4
4
|
export interface OpenaiArgs {
|
|
5
5
|
apiKey: string;
|
|
@@ -15,23 +15,43 @@ export interface OpenaiArgs {
|
|
|
15
15
|
* Pure function — no SDK dependency.
|
|
16
16
|
*/
|
|
17
17
|
export function buildOpenAIRequest(args: PromptCompletionArgs): {
|
|
18
|
-
input: { role: string; content: string }[];
|
|
18
|
+
input: { role: string; content: string | any[] }[];
|
|
19
19
|
text?: { format: any };
|
|
20
20
|
} {
|
|
21
|
-
const input: { role: string; content: string }[] = [];
|
|
21
|
+
const input: { role: string; content: string | any[] }[] = [];
|
|
22
22
|
if (args.history) {
|
|
23
23
|
for (const msg of args.history) {
|
|
24
24
|
input.push({ role: msg.role, content: msg.content });
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
|
-
|
|
27
|
+
|
|
28
|
+
// Build user content — multimodal when ContentBlock[] is provided
|
|
29
|
+
const promptContent = args.prompt.content;
|
|
30
|
+
if (isMultimodalContent(promptContent)) {
|
|
31
|
+
const parts: any[] = promptContent.map(block => {
|
|
32
|
+
if (block.type === 'text') {
|
|
33
|
+
return { type: 'input_text', text: block.text };
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
type: 'input_image',
|
|
37
|
+
image_url: `data:${block.mediaType};base64,${block.data}`,
|
|
38
|
+
};
|
|
39
|
+
});
|
|
40
|
+
input.push({ role: 'user', content: parts });
|
|
41
|
+
} else {
|
|
42
|
+
input.push({ role: 'user', content: promptContent });
|
|
43
|
+
}
|
|
28
44
|
|
|
29
45
|
if (args.jsonMode || args.jsonSchema) {
|
|
30
|
-
const inputText = input.map(m => m.content).join(' ');
|
|
46
|
+
const inputText = input.map(m => typeof m.content === 'string' ? m.content : '').join(' ');
|
|
31
47
|
if (!/json/i.test(inputText)) {
|
|
32
48
|
const last = input[input.length - 1];
|
|
33
49
|
if (last) {
|
|
34
|
-
last.content
|
|
50
|
+
if (typeof last.content === 'string') {
|
|
51
|
+
last.content += '\nReturn JSON.';
|
|
52
|
+
} else if (Array.isArray(last.content)) {
|
|
53
|
+
last.content.push({ type: 'input_text', text: '\nReturn JSON.' });
|
|
54
|
+
}
|
|
35
55
|
}
|
|
36
56
|
}
|
|
37
57
|
}
|
package/src/models/types.ts
CHANGED
|
@@ -25,6 +25,30 @@ export interface Provider {
|
|
|
25
25
|
detectModel(model: string): boolean;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
// Multimodal content
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
export interface TextBlock {
|
|
33
|
+
type: 'text';
|
|
34
|
+
text: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface ImageBlock {
|
|
38
|
+
type: 'image';
|
|
39
|
+
mediaType: string;
|
|
40
|
+
data: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export type ContentBlock = TextBlock | ImageBlock;
|
|
44
|
+
|
|
45
|
+
export type MessageContent = string | ContentBlock[];
|
|
46
|
+
|
|
47
|
+
/** Type guard: returns true when content is a multimodal ContentBlock array. */
|
|
48
|
+
export function isMultimodalContent(content: MessageContent): content is ContentBlock[] {
|
|
49
|
+
return Array.isArray(content);
|
|
50
|
+
}
|
|
51
|
+
|
|
28
52
|
// ---------------------------------------------------------------------------
|
|
29
53
|
// Messages
|
|
30
54
|
// ---------------------------------------------------------------------------
|
|
@@ -39,8 +63,10 @@ export interface SystemMessage extends Message {
|
|
|
39
63
|
role: 'system';
|
|
40
64
|
}
|
|
41
65
|
|
|
42
|
-
export interface UserMessage
|
|
66
|
+
export interface UserMessage {
|
|
43
67
|
role: 'user';
|
|
68
|
+
content: MessageContent;
|
|
69
|
+
name?: string;
|
|
44
70
|
}
|
|
45
71
|
|
|
46
72
|
// ---------------------------------------------------------------------------
|
|
@@ -61,6 +87,10 @@ export interface PromptCompletionArgs {
|
|
|
61
87
|
jsonMode?: boolean;
|
|
62
88
|
/** JSON schema for structured output. When provided, the model is asked to return JSON conforming to this schema. */
|
|
63
89
|
jsonSchema?: Record<string, unknown>;
|
|
90
|
+
/** When true, system content is wrapped with cache_control for Anthropic prompt caching. */
|
|
91
|
+
cacheSystem?: boolean;
|
|
92
|
+
/** JSON schema for structured output via constrained decoding (Anthropic output_config). */
|
|
93
|
+
outputSchema?: Record<string, unknown>;
|
|
64
94
|
}
|
|
65
95
|
|
|
66
96
|
export type completePrompt<TValue = string> = (args: PromptCompletionArgs) => Promise<AgentCompletion<TValue>>;
|
package/src/pages.ts
CHANGED
|
@@ -1,12 +1,29 @@
|
|
|
1
|
-
import {checkIfExists,
|
|
1
|
+
import {checkIfExists, listFolders, loadFile} from './files';
|
|
2
2
|
import path from 'path';
|
|
3
|
+
import { SynthOSConfig } from './init';
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Derive the list of required page names by scanning *.html files
|
|
7
|
+
* across one or more requiredPages folders.
|
|
8
|
+
*/
|
|
9
|
+
export async function getRequiredPages(requiredPagesFolders: string[]): Promise<string[]> {
|
|
10
|
+
const result: string[] = [];
|
|
11
|
+
const seen = new Set<string>();
|
|
12
|
+
for (const folder of requiredPagesFolders) {
|
|
13
|
+
if (!await checkIfExists(folder)) continue;
|
|
14
|
+
const entries = await listFolders(folder);
|
|
15
|
+
for (const entry of entries) {
|
|
16
|
+
if (seen.has(entry)) continue;
|
|
17
|
+
if (await checkIfExists(path.join(folder, entry, 'page.html'))) {
|
|
18
|
+
seen.add(entry);
|
|
19
|
+
result.push(entry);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return result;
|
|
24
|
+
}
|
|
8
25
|
|
|
9
|
-
export const PAGE_VERSION =
|
|
26
|
+
export const PAGE_VERSION = 3;
|
|
10
27
|
|
|
11
28
|
export interface PageInfo {
|
|
12
29
|
name: string;
|
|
@@ -22,12 +39,15 @@ export interface PageInfo {
|
|
|
22
39
|
|
|
23
40
|
export type PageMetadata = Omit<PageInfo, 'name'>;
|
|
24
41
|
|
|
25
|
-
export async function loadPageMetadata(
|
|
26
|
-
|
|
42
|
+
export async function loadPageMetadata(config: SynthOSConfig, name: string, fallbackFolders?: string[]): Promise<PageMetadata | undefined> {
|
|
43
|
+
const pagesFolder = config.pagesFolder;
|
|
44
|
+
const sp = config.storageProvider;
|
|
45
|
+
|
|
46
|
+
// 1. Try user override: <localFolder>/pages/<name>/page.json
|
|
27
47
|
const metadataPath = path.join(pagesFolder, 'pages', name, 'page.json');
|
|
28
|
-
if (await checkIfExists(metadataPath)) {
|
|
48
|
+
if (await sp.checkIfExists(metadataPath)) {
|
|
29
49
|
try {
|
|
30
|
-
const raw = await loadFile(metadataPath);
|
|
50
|
+
const raw = await sp.loadFile(metadataPath);
|
|
31
51
|
const parsed = JSON.parse(raw);
|
|
32
52
|
return parseMetadata(parsed);
|
|
33
53
|
} catch {
|
|
@@ -35,16 +55,18 @@ export async function loadPageMetadata(pagesFolder: string, name: string, fallba
|
|
|
35
55
|
}
|
|
36
56
|
}
|
|
37
57
|
|
|
38
|
-
// 2. Try fallback: fallbackFolder/<name
|
|
39
|
-
if (
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
58
|
+
// 2. Try fallback folders: fallbackFolder/<name>/page.json (package content, always local fs)
|
|
59
|
+
if (fallbackFolders) {
|
|
60
|
+
for (const folder of fallbackFolders) {
|
|
61
|
+
const candidate = path.join(folder, name, 'page.json');
|
|
62
|
+
if (await checkIfExists(candidate)) {
|
|
63
|
+
try {
|
|
64
|
+
const raw = await loadFile(candidate);
|
|
65
|
+
const parsed = JSON.parse(raw);
|
|
66
|
+
return parseMetadata(parsed);
|
|
67
|
+
} catch {
|
|
68
|
+
// fall through
|
|
69
|
+
}
|
|
48
70
|
}
|
|
49
71
|
}
|
|
50
72
|
}
|
|
@@ -66,10 +88,11 @@ export function parseMetadata(parsed: Record<string, unknown>): PageMetadata {
|
|
|
66
88
|
};
|
|
67
89
|
}
|
|
68
90
|
|
|
69
|
-
export async function savePageMetadata(
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
await
|
|
91
|
+
export async function savePageMetadata(config: SynthOSConfig, name: string, metadata: PageMetadata): Promise<void> {
|
|
92
|
+
const sp = config.storageProvider;
|
|
93
|
+
const pageFolder = path.join(config.pagesFolder, 'pages', name);
|
|
94
|
+
await sp.ensureFolderExists(pageFolder);
|
|
95
|
+
await sp.saveFile(path.join(pageFolder, 'page.json'), JSON.stringify(metadata, null, 4));
|
|
73
96
|
}
|
|
74
97
|
|
|
75
98
|
const DEFAULT_METADATA: PageMetadata = {
|
|
@@ -83,16 +106,18 @@ const DEFAULT_METADATA: PageMetadata = {
|
|
|
83
106
|
mode: 'unlocked',
|
|
84
107
|
};
|
|
85
108
|
|
|
86
|
-
export async function listPages(
|
|
109
|
+
export async function listPages(config: SynthOSConfig, fallbackPagesFolders: string[]): Promise<PageInfo[]> {
|
|
110
|
+
const pagesFolder = config.pagesFolder;
|
|
111
|
+
const sp = config.storageProvider;
|
|
87
112
|
const pageMap = new Map<string, PageInfo>();
|
|
88
113
|
|
|
89
114
|
// Folder-based pages under pages/ subdirectory
|
|
90
115
|
const pagesSubdir = path.join(pagesFolder, 'pages');
|
|
91
|
-
if (await checkIfExists(pagesSubdir)) {
|
|
92
|
-
const folders = await listFolders(pagesSubdir);
|
|
116
|
+
if (await sp.checkIfExists(pagesSubdir)) {
|
|
117
|
+
const folders = await sp.listFolders(pagesSubdir);
|
|
93
118
|
for (const folder of folders) {
|
|
94
|
-
if (await checkIfExists(path.join(pagesSubdir, folder, 'page.html'))) {
|
|
95
|
-
const metadata = await loadPageMetadata(
|
|
119
|
+
if (await sp.checkIfExists(path.join(pagesSubdir, folder, 'page.html'))) {
|
|
120
|
+
const metadata = await loadPageMetadata(config, folder);
|
|
96
121
|
pageMap.set(folder, {
|
|
97
122
|
name: folder,
|
|
98
123
|
...(metadata ?? DEFAULT_METADATA),
|
|
@@ -102,7 +127,7 @@ export async function listPages(pagesFolder: string, fallbackPagesFolder: string
|
|
|
102
127
|
}
|
|
103
128
|
|
|
104
129
|
// Legacy flat .html files in root (v1 pages)
|
|
105
|
-
const flatFiles = (await listFiles(pagesFolder)).filter(file => file.endsWith('.html'));
|
|
130
|
+
const flatFiles = (await sp.listFiles(pagesFolder)).filter(file => file.endsWith('.html'));
|
|
106
131
|
for (const file of flatFiles) {
|
|
107
132
|
const name = file.replace(/\.html$/, '');
|
|
108
133
|
if (!pageMap.has(name)) {
|
|
@@ -128,13 +153,15 @@ export async function listPages(pagesFolder: string, fallbackPagesFolder: string
|
|
|
128
153
|
}
|
|
129
154
|
}
|
|
130
155
|
|
|
131
|
-
// Add pages from fallback (required) pages
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
156
|
+
// Add pages from fallback (required) pages folders (package content, always local fs)
|
|
157
|
+
for (const folder of fallbackPagesFolders) {
|
|
158
|
+
if (!await checkIfExists(folder)) continue;
|
|
159
|
+
const dirs = await listFolders(folder);
|
|
160
|
+
for (const name of dirs) {
|
|
161
|
+
if (pageMap.has(name)) continue;
|
|
162
|
+
if (!await checkIfExists(path.join(folder, name, 'page.html'))) continue;
|
|
163
|
+
// System page not yet in map — check for user override, then fallback page.json
|
|
164
|
+
const metadata = await loadPageMetadata(config, name, fallbackPagesFolders);
|
|
138
165
|
pageMap.set(name, {
|
|
139
166
|
name,
|
|
140
167
|
title: metadata?.title ?? '',
|
|
@@ -156,37 +183,45 @@ export async function listPages(pagesFolder: string, fallbackPagesFolder: string
|
|
|
156
183
|
return entries;
|
|
157
184
|
}
|
|
158
185
|
|
|
159
|
-
export async function loadPageState(
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
const folderPath = path.join(pagesFolder, 'pages', name, 'page.html');
|
|
163
|
-
const flatPath = path.join(pagesFolder, `${name}.html`);
|
|
186
|
+
export async function loadPageState(config: SynthOSConfig, name: string): Promise<string|undefined> {
|
|
187
|
+
const pagesFolder = config.pagesFolder;
|
|
188
|
+
const sp = config.storageProvider;
|
|
164
189
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
return undefined;
|
|
171
|
-
}
|
|
190
|
+
// Check for working-state version files first
|
|
191
|
+
const latestVersion = await getLatestVersion(config, name);
|
|
192
|
+
if (latestVersion > 0) {
|
|
193
|
+
const versionHtml = await loadPageVersion(config, name, latestVersion);
|
|
194
|
+
if (versionHtml) return versionHtml;
|
|
172
195
|
}
|
|
173
196
|
|
|
174
|
-
|
|
197
|
+
// Fall back to saved baseline
|
|
198
|
+
const folderPath = path.join(pagesFolder, 'pages', name, 'page.html');
|
|
199
|
+
const directFolderPath = path.join(pagesFolder, name, 'page.html');
|
|
200
|
+
const flatPath = path.join(pagesFolder, `${name}.html`);
|
|
201
|
+
|
|
202
|
+
if (await sp.checkIfExists(folderPath)) {
|
|
203
|
+
return sp.loadFile(folderPath);
|
|
204
|
+
} else if (await sp.checkIfExists(directFolderPath)) {
|
|
205
|
+
return sp.loadFile(directFolderPath);
|
|
206
|
+
} else if (await sp.checkIfExists(flatPath)) {
|
|
207
|
+
return sp.loadFile(flatPath);
|
|
208
|
+
}
|
|
209
|
+
return undefined;
|
|
175
210
|
}
|
|
176
211
|
|
|
177
212
|
export function normalizePageName(name: string|undefined): string|undefined {
|
|
178
213
|
return typeof name == 'string' && name.length > 0 ? name.replace(/[^a-z0-9\-_\[\]\(\)\{\}@#\$%&]/gi, '_').toLowerCase() : undefined;
|
|
179
214
|
}
|
|
180
215
|
|
|
181
|
-
export async function savePageState(
|
|
182
|
-
|
|
183
|
-
const pageFolder = path.join(pagesFolder, 'pages', name);
|
|
184
|
-
await ensureFolderExists(pageFolder);
|
|
185
|
-
await saveFile(path.join(pageFolder, 'page.html'), content);
|
|
216
|
+
export async function savePageState(config: SynthOSConfig, name: string, content: string, title?: string, categories?: string[]): Promise<void> {
|
|
217
|
+
const sp = config.storageProvider;
|
|
218
|
+
const pageFolder = path.join(config.pagesFolder, 'pages', name);
|
|
219
|
+
await sp.ensureFolderExists(pageFolder);
|
|
220
|
+
await sp.saveFile(path.join(pageFolder, 'page.html'), content);
|
|
186
221
|
|
|
187
222
|
// Create page.json with full metadata if it doesn't exist
|
|
188
223
|
const metadataPath = path.join(pageFolder, 'page.json');
|
|
189
|
-
if (!(await checkIfExists(metadataPath))) {
|
|
224
|
+
if (!(await sp.checkIfExists(metadataPath))) {
|
|
190
225
|
const now = new Date().toISOString();
|
|
191
226
|
const metadata: PageMetadata = {
|
|
192
227
|
title: title ?? '',
|
|
@@ -198,45 +233,134 @@ export async function savePageState(pagesFolder: string, name: string, content:
|
|
|
198
233
|
pageVersion: PAGE_VERSION,
|
|
199
234
|
mode: 'unlocked',
|
|
200
235
|
};
|
|
201
|
-
await saveFile(metadataPath, JSON.stringify(metadata, null, 4));
|
|
236
|
+
await sp.saveFile(metadataPath, JSON.stringify(metadata, null, 4));
|
|
202
237
|
}
|
|
203
238
|
}
|
|
204
239
|
|
|
205
|
-
export function
|
|
206
|
-
|
|
207
|
-
|
|
240
|
+
export async function deletePage(config: SynthOSConfig, name: string): Promise<void> {
|
|
241
|
+
const pagesFolder = config.pagesFolder;
|
|
242
|
+
const sp = config.storageProvider;
|
|
208
243
|
|
|
209
|
-
export async function deletePage(pagesFolder: string, name: string): Promise<void> {
|
|
210
244
|
// Delete folder-based page: <pagesFolder>/pages/<name>/
|
|
211
245
|
const folderPath = path.join(pagesFolder, 'pages', name);
|
|
212
|
-
if (await checkIfExists(folderPath)) {
|
|
213
|
-
await deleteFolder(folderPath);
|
|
246
|
+
if (await sp.checkIfExists(folderPath)) {
|
|
247
|
+
await sp.deleteFolder(folderPath);
|
|
214
248
|
}
|
|
215
249
|
|
|
216
250
|
// Delete legacy flat file: <pagesFolder>/<name>.html
|
|
217
251
|
const flatPath = path.join(pagesFolder, `${name}.html`);
|
|
218
|
-
if (await checkIfExists(flatPath)) {
|
|
219
|
-
await deleteFile(flatPath);
|
|
252
|
+
if (await sp.checkIfExists(flatPath)) {
|
|
253
|
+
await sp.deleteFile(flatPath);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// ---------------------------------------------------------------------------
|
|
259
|
+
// Page versioning — per-edit version snapshots for undo support
|
|
260
|
+
// ---------------------------------------------------------------------------
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Save a version snapshot: <pagesFolder>/pages/<name>/page.v<version>.html
|
|
264
|
+
*/
|
|
265
|
+
export async function savePageVersion(config: SynthOSConfig, name: string, version: number, html: string): Promise<void> {
|
|
266
|
+
const sp = config.storageProvider;
|
|
267
|
+
const pageFolder = path.join(config.pagesFolder, 'pages', name);
|
|
268
|
+
await sp.ensureFolderExists(pageFolder);
|
|
269
|
+
await sp.saveFile(path.join(pageFolder, `page.v${version}.html`), html);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Load a version snapshot (returns undefined if the file doesn't exist).
|
|
274
|
+
*/
|
|
275
|
+
export async function loadPageVersion(config: SynthOSConfig, name: string, version: number): Promise<string | undefined> {
|
|
276
|
+
const sp = config.storageProvider;
|
|
277
|
+
const filePath = path.join(config.pagesFolder, 'pages', name, `page.v${version}.html`);
|
|
278
|
+
if (!await sp.checkIfExists(filePath)) return undefined;
|
|
279
|
+
return sp.loadFile(filePath);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Scan page.v*.html files and return the highest version number (0 if none).
|
|
284
|
+
*/
|
|
285
|
+
export async function getLatestVersion(config: SynthOSConfig, name: string): Promise<number> {
|
|
286
|
+
const sp = config.storageProvider;
|
|
287
|
+
const pageFolder = path.join(config.pagesFolder, 'pages', name);
|
|
288
|
+
if (!await sp.checkIfExists(pageFolder)) return 0;
|
|
289
|
+
const files = await sp.listFiles(pageFolder);
|
|
290
|
+
let max = 0;
|
|
291
|
+
for (const file of files) {
|
|
292
|
+
const match = file.match(/^page\.v(\d+)\.html$/);
|
|
293
|
+
if (match) {
|
|
294
|
+
const v = parseInt(match[1], 10);
|
|
295
|
+
if (v > max) max = v;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return max;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Delete all page.v*.html version files for a page.
|
|
303
|
+
*/
|
|
304
|
+
export async function clearVersions(config: SynthOSConfig, name: string): Promise<void> {
|
|
305
|
+
const sp = config.storageProvider;
|
|
306
|
+
const pageFolder = path.join(config.pagesFolder, 'pages', name);
|
|
307
|
+
if (!await sp.checkIfExists(pageFolder)) return;
|
|
308
|
+
const files = await sp.listFiles(pageFolder);
|
|
309
|
+
for (const file of files) {
|
|
310
|
+
if (/^page\.v\d+\.html$/.test(file)) {
|
|
311
|
+
await sp.deleteFile(path.join(pageFolder, file));
|
|
312
|
+
}
|
|
220
313
|
}
|
|
314
|
+
}
|
|
221
315
|
|
|
222
|
-
|
|
223
|
-
|
|
316
|
+
export interface CopyPageOptions {
|
|
317
|
+
copyTables?: boolean; // default false
|
|
318
|
+
copyFiles?: boolean; // default true
|
|
224
319
|
}
|
|
225
320
|
|
|
226
321
|
export async function copyPage(
|
|
227
|
-
|
|
322
|
+
config: SynthOSConfig,
|
|
228
323
|
sourceName: string,
|
|
229
324
|
targetName: string,
|
|
230
325
|
title: string,
|
|
231
326
|
categories: string[],
|
|
232
|
-
|
|
327
|
+
requiredPagesFolders: string[],
|
|
328
|
+
options?: CopyPageOptions
|
|
233
329
|
): Promise<void> {
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
330
|
+
const pagesFolder = config.pagesFolder;
|
|
331
|
+
const sp = config.storageProvider;
|
|
332
|
+
const copyTables = options?.copyTables ?? false;
|
|
333
|
+
const cpFiles = options?.copyFiles ?? true;
|
|
334
|
+
|
|
335
|
+
// Resolve source page folder: user pages first, then required pages
|
|
336
|
+
let sourceFolder: string | undefined;
|
|
337
|
+
const userSourceFolder = path.join(pagesFolder, 'pages', sourceName);
|
|
338
|
+
if (await sp.checkIfExists(path.join(userSourceFolder, 'page.html'))) {
|
|
339
|
+
sourceFolder = userSourceFolder;
|
|
340
|
+
} else {
|
|
341
|
+
for (const folder of requiredPagesFolders) {
|
|
342
|
+
const candidate = path.join(folder, sourceName);
|
|
343
|
+
if (await checkIfExists(path.join(candidate, 'page.html'))) {
|
|
344
|
+
sourceFolder = candidate;
|
|
345
|
+
break;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Load source HTML
|
|
351
|
+
let html: string | undefined;
|
|
352
|
+
if (sourceFolder) {
|
|
353
|
+
// Source could be user storage or package — try user first, fall back to local fs
|
|
354
|
+
if (sourceFolder === userSourceFolder) {
|
|
355
|
+
html = await sp.loadFile(path.join(sourceFolder, 'page.html'));
|
|
356
|
+
} else {
|
|
357
|
+
html = await loadFile(path.join(sourceFolder, 'page.html'));
|
|
358
|
+
}
|
|
359
|
+
} else {
|
|
360
|
+
// Try legacy flat file
|
|
361
|
+
const flatPath = path.join(pagesFolder, `${sourceName}.html`);
|
|
362
|
+
if (await sp.checkIfExists(flatPath)) {
|
|
363
|
+
html = await sp.loadFile(flatPath);
|
|
240
364
|
}
|
|
241
365
|
}
|
|
242
366
|
|
|
@@ -245,7 +369,7 @@ export async function copyPage(
|
|
|
245
369
|
}
|
|
246
370
|
|
|
247
371
|
// Save HTML to target (creates folder + page.html + page.json)
|
|
248
|
-
await savePageState(
|
|
372
|
+
await savePageState(config, targetName, html, title);
|
|
249
373
|
|
|
250
374
|
// Overwrite the generated metadata with provided title + categories
|
|
251
375
|
const now = new Date().toISOString();
|
|
@@ -259,5 +383,34 @@ export async function copyPage(
|
|
|
259
383
|
pageVersion: PAGE_VERSION,
|
|
260
384
|
mode: 'unlocked',
|
|
261
385
|
};
|
|
262
|
-
await savePageMetadata(
|
|
386
|
+
await savePageMetadata(config, targetName, metadata);
|
|
387
|
+
|
|
388
|
+
// Copy additional content from source if a folder was resolved
|
|
389
|
+
if (sourceFolder) {
|
|
390
|
+
const targetFolder = path.join(pagesFolder, 'pages', targetName);
|
|
391
|
+
|
|
392
|
+
if (copyTables) {
|
|
393
|
+
// Source could be user or package folder — use appropriate listing
|
|
394
|
+
const entries = sourceFolder === userSourceFolder
|
|
395
|
+
? await sp.listFolders(sourceFolder)
|
|
396
|
+
: await listFolders(sourceFolder);
|
|
397
|
+
for (const entry of entries) {
|
|
398
|
+
if (entry === 'files') continue; // handled separately
|
|
399
|
+
await sp.copyFolderRecursive(
|
|
400
|
+
path.join(sourceFolder, entry),
|
|
401
|
+
path.join(targetFolder, entry)
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
if (cpFiles) {
|
|
407
|
+
const filesDir = path.join(sourceFolder, 'files');
|
|
408
|
+
const filesDirExists = sourceFolder === userSourceFolder
|
|
409
|
+
? await sp.checkIfExists(filesDir)
|
|
410
|
+
: await checkIfExists(filesDir);
|
|
411
|
+
if (filesDirExists) {
|
|
412
|
+
await sp.copyFolderRecursive(filesDir, path.join(targetFolder, 'files'));
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
263
416
|
}
|