alchemy 0.2.5 → 0.3.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 (151) hide show
  1. package/README.md +3 -3
  2. package/lib/ai/astro-file.d.ts +137 -0
  3. package/lib/ai/astro-file.js +173 -0
  4. package/lib/ai/client.d.ts +2 -10
  5. package/lib/ai/client.js +8 -24
  6. package/lib/ai/css-file.d.ts +131 -0
  7. package/lib/ai/css-file.js +158 -0
  8. package/lib/ai/data.d.ts +45 -5
  9. package/lib/ai/data.js +45 -10
  10. package/lib/ai/document.d.ts +44 -20
  11. package/lib/ai/document.js +95 -28
  12. package/lib/ai/html-file.d.ts +128 -0
  13. package/lib/ai/html-file.js +155 -0
  14. package/lib/ai/index.d.ts +7 -0
  15. package/lib/ai/index.js +7 -0
  16. package/lib/ai/json-file.d.ts +160 -0
  17. package/lib/ai/json-file.js +208 -0
  18. package/lib/ai/typescript-file.d.ts +140 -0
  19. package/lib/ai/typescript-file.js +176 -0
  20. package/lib/ai/vue-file.d.ts +126 -0
  21. package/lib/ai/vue-file.js +153 -0
  22. package/lib/ai/yaml-file.d.ts +169 -0
  23. package/lib/ai/yaml-file.js +236 -0
  24. package/lib/cloudflare/dns.d.ts +88 -0
  25. package/lib/cloudflare/dns.js +192 -0
  26. package/lib/cloudflare/index.d.ts +2 -1
  27. package/lib/cloudflare/index.js +2 -1
  28. package/lib/cloudflare/r2-rest-state-store.d.ts +125 -0
  29. package/lib/cloudflare/r2-rest-state-store.js +254 -0
  30. package/lib/cloudflare/response.d.ts +12 -0
  31. package/lib/cloudflare/response.js +0 -0
  32. package/lib/cloudflare/zone.d.ts +18 -0
  33. package/lib/cloudflare/zone.js +17 -4
  34. package/lib/dns/godaddy.d.ts +7 -0
  35. package/lib/dns/godaddy.js +21 -0
  36. package/lib/dns/import-dns.d.ts +82 -0
  37. package/lib/dns/import-dns.js +114 -0
  38. package/lib/dns/index.d.ts +2 -0
  39. package/lib/dns/index.js +2 -0
  40. package/lib/dns/record.d.ts +88 -0
  41. package/lib/dns/record.js +13 -0
  42. package/lib/fs/file-system-state-store.d.ts +17 -0
  43. package/lib/fs/file-system-state-store.js +87 -0
  44. package/lib/fs/folder.d.ts +10 -0
  45. package/lib/fs/folder.js +2 -2
  46. package/lib/fs/index.d.ts +4 -4
  47. package/lib/fs/index.js +4 -4
  48. package/lib/fs/{json-file.d.ts → static-json-file.d.ts} +2 -2
  49. package/lib/fs/static-json-file.js +14 -0
  50. package/lib/fs/{text-file.d.ts → static-text-file.d.ts} +2 -2
  51. package/lib/fs/{text-file.js → static-text-file.js} +1 -1
  52. package/lib/fs/{typescript-file.d.ts → static-typescript-file.d.ts} +2 -2
  53. package/lib/fs/{typescript-file.js → static-typescript-file.js} +1 -1
  54. package/lib/fs/{yaml-file.d.ts → static-yaml-file.d.ts} +2 -2
  55. package/lib/fs/{yaml-file.js → static-yaml-file.js} +1 -1
  56. package/lib/internal/getting-started.d.ts +10 -0
  57. package/lib/internal/getting-started.js +94 -0
  58. package/lib/internal/project.d.ts +13 -0
  59. package/lib/internal/project.js +101 -0
  60. package/lib/internal/providers.d.ts +23 -0
  61. package/lib/internal/providers.js +148 -0
  62. package/lib/resource.js +8 -1
  63. package/lib/scope.d.ts +1 -1
  64. package/lib/scope.js +4 -2
  65. package/lib/state.d.ts +1 -16
  66. package/lib/state.js +0 -87
  67. package/lib/test/bun.d.ts +11 -0
  68. package/lib/test/bun.js +36 -8
  69. package/lib/web/astro.d.ts +147 -0
  70. package/lib/web/astro.js +414 -0
  71. package/lib/{shadcn/component.js → web/shadcn-component.js} +2 -9
  72. package/lib/web/shadcn.d.ts +97 -0
  73. package/lib/web/shadcn.js +99 -0
  74. package/lib/web/tailwind.d.ts +64 -0
  75. package/lib/web/tailwind.js +97 -0
  76. package/lib/{vite → web}/vite.d.ts +5 -0
  77. package/lib/{vite → web}/vite.js +44 -44
  78. package/lib/web/vitepress/custom-theme.d.ts +173 -0
  79. package/lib/web/vitepress/custom-theme.js +336 -0
  80. package/lib/{vitepress → web/vitepress}/dependencies.d.ts +2 -2
  81. package/lib/{vitepress → web/vitepress}/dependencies.js +1 -1
  82. package/lib/web/vitepress/home-page.d.ts +244 -0
  83. package/lib/web/vitepress/home-page.js +134 -0
  84. package/lib/{vitepress → web/vitepress}/index.d.ts +1 -0
  85. package/lib/{vitepress → web/vitepress}/index.js +1 -0
  86. package/lib/{vitepress → web/vitepress}/vitepress.d.ts +10 -8
  87. package/lib/{vitepress → web/vitepress}/vitepress.js +11 -12
  88. package/package.json +6 -2
  89. package/src/ai/astro-file.ts +288 -0
  90. package/src/ai/client.ts +8 -28
  91. package/src/ai/css-file.ts +266 -0
  92. package/src/ai/data.ts +53 -16
  93. package/src/ai/document.ts +130 -33
  94. package/src/ai/html-file.ts +263 -0
  95. package/src/ai/index.ts +7 -0
  96. package/src/ai/json-file.ts +349 -0
  97. package/src/ai/typescript-file.ts +293 -0
  98. package/src/ai/vue-file.ts +261 -0
  99. package/src/ai/yaml-file.ts +368 -0
  100. package/src/apply.ts +0 -1
  101. package/src/cloudflare/dns.ts +344 -0
  102. package/src/cloudflare/index.ts +2 -1
  103. package/src/cloudflare/r2-rest-state-store.ts +353 -0
  104. package/src/cloudflare/response.ts +9 -0
  105. package/src/cloudflare/zone.ts +39 -23
  106. package/src/dns/godaddy.ts +29 -0
  107. package/src/dns/import-dns.ts +213 -0
  108. package/src/dns/index.ts +2 -0
  109. package/src/dns/record.ts +121 -0
  110. package/src/fs/file-system-state-store.ts +109 -0
  111. package/src/fs/folder.ts +14 -2
  112. package/src/fs/index.ts +4 -4
  113. package/src/fs/{json-file.ts → static-json-file.ts} +13 -3
  114. package/src/fs/{text-file.ts → static-text-file.ts} +5 -2
  115. package/src/fs/{typescript-file.ts → static-typescript-file.ts} +3 -3
  116. package/src/fs/{yaml-file.ts → static-yaml-file.ts} +5 -2
  117. package/src/internal/getting-started.ts +107 -0
  118. package/src/internal/project.ts +119 -0
  119. package/src/internal/providers.ts +209 -0
  120. package/src/resource.ts +10 -1
  121. package/src/scope.ts +5 -6
  122. package/src/state.ts +1 -111
  123. package/src/test/bun.ts +60 -10
  124. package/src/web/astro.ts +644 -0
  125. package/src/{shadcn/component.ts → web/shadcn-component.ts} +5 -13
  126. package/src/web/shadcn.ts +219 -0
  127. package/src/web/tailwind.ts +167 -0
  128. package/src/{vite → web}/vite.ts +54 -56
  129. package/src/web/vitepress/custom-theme.ts +514 -0
  130. package/src/{vitepress → web/vitepress}/dependencies.ts +2 -2
  131. package/src/web/vitepress/home-page.ts +363 -0
  132. package/src/{vitepress → web/vitepress}/index.ts +1 -0
  133. package/src/{vitepress → web/vitepress}/vitepress.ts +34 -27
  134. package/lib/cloudflare/state.d.ts +0 -82
  135. package/lib/cloudflare/state.js +0 -108
  136. package/lib/fs/json-file.js +0 -7
  137. package/lib/internal/docs.d.ts +0 -5
  138. package/lib/internal/docs.js +0 -287
  139. package/lib/shadcn/index.d.ts +0 -1
  140. package/lib/shadcn/index.js +0 -1
  141. package/lib/vite/index.d.ts +0 -1
  142. package/lib/vite/index.js +0 -1
  143. package/lib/vitepress/home-page.d.ts +0 -133
  144. package/lib/vitepress/home-page.js +0 -11
  145. package/src/cloudflare/state.ts +0 -138
  146. package/src/internal/docs.ts +0 -338
  147. package/src/shadcn/index.ts +0 -1
  148. package/src/vite/index.ts +0 -1
  149. package/src/vitepress/home-page.ts +0 -166
  150. /package/lib/{shadcn/component.d.ts → web/shadcn-component.d.ts} +0 -0
  151. /package/src/{vitepress → web/vitepress}/index.md +0 -0
package/lib/ai/data.d.ts CHANGED
@@ -27,6 +27,13 @@ export interface DataProps<T extends Type<any, any>> {
27
27
  * system: "You are a technical writer tasked with describing code"
28
28
  */
29
29
  system?: string;
30
+ /**
31
+ * Temperature for controlling randomness in generation.
32
+ * Higher values (e.g., 0.8) make output more random,
33
+ * lower values (e.g., 0.2) make it more deterministic.
34
+ * @default 0.7
35
+ */
36
+ temperature?: number;
30
37
  /**
31
38
  * Base URL for the OpenAI API
32
39
  * @default 'https://api.openai.com/v1'
@@ -69,13 +76,20 @@ export interface Data<T> extends Resource<"ai::Object"> {
69
76
  * price: "number"
70
77
  * });
71
78
  *
72
- * const product = await Object("new-product", {
79
+ * const product = await Data("new-product", {
73
80
  * schema: productSchema,
74
81
  * prompt: "Generate a product description for a new smartphone",
75
- * system: "You are a product copywriter specializing in tech products"
82
+ * system: "You are a product copywriter specializing in tech products",
83
+ * model: {
84
+ * id: "gpt-4o",
85
+ * provider: "openai",
86
+ * options: {
87
+ * temperature: 0.7
88
+ * }
89
+ * }
76
90
  * });
77
91
  *
78
- * console.log(product.content); // Typed as per schema
92
+ * console.log(product.object); // Typed as per schema
79
93
  *
80
94
  * @example
81
95
  * // Generate code documentation with context
@@ -89,13 +103,39 @@ export interface Data<T> extends Resource<"ai::Object"> {
89
103
  * returns: "string"
90
104
  * });
91
105
  *
92
- * const docs = await Object("function-docs", {
106
+ * const docs = await Data("function-docs", {
93
107
  * schema: docSchema,
94
108
  * prompt: await alchemy`
95
109
  * Generate documentation for this function:
96
110
  * ${alchemy.file("src/utils/format.ts")}
97
111
  * `,
98
- * system: "You are a technical documentation writer"
112
+ * system: "You are a technical documentation writer",
113
+ * temperature: 0.2
114
+ * });
115
+ *
116
+ * @example
117
+ * // Using specific model configuration with advanced options
118
+ * const analysisSchema = type({
119
+ * insights: "string[]",
120
+ * recommendations: "string[]",
121
+ * risk: "'low'|'medium'|'high'"
122
+ * });
123
+ *
124
+ * const analysis = await Data("code-analysis", {
125
+ * schema: analysisSchema,
126
+ * prompt: await alchemy`
127
+ * Analyze this code for security issues:
128
+ * ${alchemy.file("src/auth/login.ts")}
129
+ * `,
130
+ * system: "You are a security expert specializing in code analysis",
131
+ * model: {
132
+ * id: "o3-mini",
133
+ * provider: "openai",
134
+ * options: {
135
+ * reasoningEffort: "high"
136
+ * }
137
+ * },
138
+ * temperature: 0.1
99
139
  * });
100
140
  */
101
141
  export declare const Data: (((this: any, id: string, props?: {}) => never) & (new (_: never) => never)) | (<const T extends Type<any, any>>(this: Context<Data<any>>, id: string, props: DataProps<T>) => Promise<Data<type.infer<T>>>);
package/lib/ai/data.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { generateObject } from "ai";
2
2
  import { Resource } from "../resource";
3
3
  import { ark } from "./ark";
4
- import { createClient, getModelId, getModelOptions, } from "./client";
4
+ import { createModel } from "./client";
5
5
  /**
6
6
  * Resource for generating structured content using the Vercel AI SDK.
7
7
  * Supports powerful context handling through the alchemy template literal tag.
@@ -15,13 +15,20 @@ import { createClient, getModelId, getModelOptions, } from "./client";
15
15
  * price: "number"
16
16
  * });
17
17
  *
18
- * const product = await Object("new-product", {
18
+ * const product = await Data("new-product", {
19
19
  * schema: productSchema,
20
20
  * prompt: "Generate a product description for a new smartphone",
21
- * system: "You are a product copywriter specializing in tech products"
21
+ * system: "You are a product copywriter specializing in tech products",
22
+ * model: {
23
+ * id: "gpt-4o",
24
+ * provider: "openai",
25
+ * options: {
26
+ * temperature: 0.7
27
+ * }
28
+ * }
22
29
  * });
23
30
  *
24
- * console.log(product.content); // Typed as per schema
31
+ * console.log(product.object); // Typed as per schema
25
32
  *
26
33
  * @example
27
34
  * // Generate code documentation with context
@@ -35,31 +42,59 @@ import { createClient, getModelId, getModelOptions, } from "./client";
35
42
  * returns: "string"
36
43
  * });
37
44
  *
38
- * const docs = await Object("function-docs", {
45
+ * const docs = await Data("function-docs", {
39
46
  * schema: docSchema,
40
47
  * prompt: await alchemy`
41
48
  * Generate documentation for this function:
42
49
  * ${alchemy.file("src/utils/format.ts")}
43
50
  * `,
44
- * system: "You are a technical documentation writer"
51
+ * system: "You are a technical documentation writer",
52
+ * temperature: 0.2
53
+ * });
54
+ *
55
+ * @example
56
+ * // Using specific model configuration with advanced options
57
+ * const analysisSchema = type({
58
+ * insights: "string[]",
59
+ * recommendations: "string[]",
60
+ * risk: "'low'|'medium'|'high'"
61
+ * });
62
+ *
63
+ * const analysis = await Data("code-analysis", {
64
+ * schema: analysisSchema,
65
+ * prompt: await alchemy`
66
+ * Analyze this code for security issues:
67
+ * ${alchemy.file("src/auth/login.ts")}
68
+ * `,
69
+ * system: "You are a security expert specializing in code analysis",
70
+ * model: {
71
+ * id: "o3-mini",
72
+ * provider: "openai",
73
+ * options: {
74
+ * reasoningEffort: "high"
75
+ * }
76
+ * },
77
+ * temperature: 0.1
45
78
  * });
46
79
  */
47
80
  export const Data = Resource("ai::Object", async function (id, props) {
48
81
  if (this.phase === "delete") {
49
82
  return this.destroy();
50
83
  }
51
- // Initialize OpenAI compatible provider using shared client
52
- const provider = createClient(props);
53
84
  // Generate structured output using generateObject
54
85
  const { object } = await generateObject({
55
- model: provider(getModelId(props)),
86
+ model: createModel(props),
56
87
  // Convert ArkType schema to Zod schema for generateObject
57
88
  // This is needed because generateObject expects a Zod schema
58
89
  schema: ark.schema(props.schema),
90
+ providerOptions: props.model?.options,
59
91
  system: props.system ||
60
92
  "You are an AI assistant tasked with generating structured content.",
61
93
  prompt: props.prompt,
62
- ...getModelOptions(props),
94
+ ...(props.temperature === undefined
95
+ ? {}
96
+ : // some models error if you provide it (rather than ignoring it)
97
+ { temperature: props.temperature }),
63
98
  });
64
99
  // Return the resource with typed content
65
100
  return this({
@@ -29,6 +29,13 @@ export interface DocumentProps {
29
29
  * `
30
30
  */
31
31
  prompt: string;
32
+ /**
33
+ * System prompt for the model
34
+ * This is used to provide instructions to the model about how to format the response
35
+ * The default system prompt instructs the model to return a single markdown document inside ```md fences
36
+ * @default "You are a technical documentation writer. Create a single markdown document based on the user's requirements. Your response MUST include only a single markdown document inside ```md fences. Do not include any other text, explanations, or multiple code blocks."
37
+ */
38
+ system?: string;
32
39
  /**
33
40
  * OpenAI API key to use for generating content
34
41
  * If not provided, will use OPENAI_API_KEY environment variable
@@ -38,6 +45,13 @@ export interface DocumentProps {
38
45
  * Model configuration
39
46
  */
40
47
  model?: ModelConfig;
48
+ /**
49
+ * Temperature for controlling randomness in generation.
50
+ * Higher values (e.g., 0.8) make output more random,
51
+ * lower values (e.g., 0.2) make it more deterministic.
52
+ * @default 0.7
53
+ */
54
+ temperature?: number;
41
55
  }
42
56
  /**
43
57
  * A markdown document that can be created, updated, and deleted
@@ -63,39 +77,49 @@ export interface Document extends DocumentProps, Resource<"docs::Document"> {
63
77
  * @example
64
78
  * // Create a document using alchemy template literals for context
65
79
  * const apiDocs = await Document("api-docs", {
80
+ * title: "API Documentation",
66
81
  * path: "./docs/api.md",
67
82
  * prompt: await alchemy`
68
83
  * Generate API documentation based on these source files:
69
84
  * ${alchemy.file("src/api.ts")}
70
85
  * ${alchemy.file("src/types.ts")}
71
- * `
72
- * // The above will automatically append the file contents as code blocks:
73
- * //
74
- * // Generate API documentation based on these source files:
75
- * // [api.ts](src/api.ts)
76
- * // [types.ts](src/types.ts)
77
- * //
78
- * // // src/api.ts
79
- * // ```ts
80
- * // ... contents of api.ts ...
81
- * // ```
82
- * //
83
- * // // src/types.ts
84
- * // ```ts
85
- * // ... contents of types.ts ...
86
- * // ```
86
+ * `,
87
+ * model: {
88
+ * id: "gpt-4o",
89
+ * provider: "openai"
90
+ * }
87
91
  * });
88
92
  *
89
93
  * @example
90
- * // Use alchemy template literals with file collections
94
+ * // Use alchemy template literals with file collections and temperature control
91
95
  * const modelDocs = await Document("models", {
96
+ * title: "Data Models",
92
97
  * path: "./docs/models.md",
93
98
  * prompt: await alchemy`
94
99
  * Write documentation for these data models:
95
100
  * ${alchemy.files("src/models/user.ts", "src/models/post.ts")}
96
- * `
97
- * // This creates a prompt with all files appended as code blocks,
98
- * // automatically handling syntax highlighting based on file extensions
101
+ * `,
102
+ * temperature: 0.2 // Lower temperature for more deterministic output
103
+ * });
104
+ *
105
+ * @example
106
+ * // Advanced model configuration with custom provider options and custom system prompt
107
+ * const techDocs = await Document("tech-specs", {
108
+ * title: "Technical Specifications",
109
+ * path: "./docs/tech-specs.md",
110
+ * prompt: await alchemy`
111
+ * Create detailed technical specifications based on these requirements:
112
+ * ${alchemy.file("requirements/system.md")}
113
+ * `,
114
+ * system: "You are an expert technical writer specializing in system specifications. Create a single markdown document inside ```md fences with no additional text.",
115
+ * model: {
116
+ * id: "o3-mini",
117
+ * provider: "openai",
118
+ * options: {
119
+ * reasoningEffort: "high"
120
+ * }
121
+ * },
122
+ * temperature: 0.1
99
123
  * });
100
124
  */
101
125
  export declare const Document: (((this: any, id: string, props?: {}) => never) & (new (_: never) => never)) | ((this: Context<Document>, id: string, props: DocumentProps) => Promise<Document>);
@@ -2,7 +2,12 @@ import { generateText } from "ai";
2
2
  import fs from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { Resource } from "../resource";
5
- import { createClient, getModelId, getModelOptions, } from "./client";
5
+ import { ignore } from "../util/ignore";
6
+ import { createModel } from "./client";
7
+ /**
8
+ * Default system prompt for markdown document generation
9
+ */
10
+ const DEFAULT_MD_SYSTEM_PROMPT = "You are a technical documentation writer. Create a single markdown document based on the user's requirements. Your response MUST include only a single markdown document inside ```md fences. Do not include any other text, explanations, or multiple code blocks.";
6
11
  /**
7
12
  * Resource for managing AI-generated markdown documents using the Vercel AI SDK.
8
13
  * Supports powerful context handling through the alchemy template literal tag.
@@ -10,39 +15,49 @@ import { createClient, getModelId, getModelOptions, } from "./client";
10
15
  * @example
11
16
  * // Create a document using alchemy template literals for context
12
17
  * const apiDocs = await Document("api-docs", {
18
+ * title: "API Documentation",
13
19
  * path: "./docs/api.md",
14
20
  * prompt: await alchemy`
15
21
  * Generate API documentation based on these source files:
16
22
  * ${alchemy.file("src/api.ts")}
17
23
  * ${alchemy.file("src/types.ts")}
18
- * `
19
- * // The above will automatically append the file contents as code blocks:
20
- * //
21
- * // Generate API documentation based on these source files:
22
- * // [api.ts](src/api.ts)
23
- * // [types.ts](src/types.ts)
24
- * //
25
- * // // src/api.ts
26
- * // ```ts
27
- * // ... contents of api.ts ...
28
- * // ```
29
- * //
30
- * // // src/types.ts
31
- * // ```ts
32
- * // ... contents of types.ts ...
33
- * // ```
24
+ * `,
25
+ * model: {
26
+ * id: "gpt-4o",
27
+ * provider: "openai"
28
+ * }
34
29
  * });
35
30
  *
36
31
  * @example
37
- * // Use alchemy template literals with file collections
32
+ * // Use alchemy template literals with file collections and temperature control
38
33
  * const modelDocs = await Document("models", {
34
+ * title: "Data Models",
39
35
  * path: "./docs/models.md",
40
36
  * prompt: await alchemy`
41
37
  * Write documentation for these data models:
42
38
  * ${alchemy.files("src/models/user.ts", "src/models/post.ts")}
43
- * `
44
- * // This creates a prompt with all files appended as code blocks,
45
- * // automatically handling syntax highlighting based on file extensions
39
+ * `,
40
+ * temperature: 0.2 // Lower temperature for more deterministic output
41
+ * });
42
+ *
43
+ * @example
44
+ * // Advanced model configuration with custom provider options and custom system prompt
45
+ * const techDocs = await Document("tech-specs", {
46
+ * title: "Technical Specifications",
47
+ * path: "./docs/tech-specs.md",
48
+ * prompt: await alchemy`
49
+ * Create detailed technical specifications based on these requirements:
50
+ * ${alchemy.file("requirements/system.md")}
51
+ * `,
52
+ * system: "You are an expert technical writer specializing in system specifications. Create a single markdown document inside ```md fences with no additional text.",
53
+ * model: {
54
+ * id: "o3-mini",
55
+ * provider: "openai",
56
+ * options: {
57
+ * reasoningEffort: "high"
58
+ * }
59
+ * },
60
+ * temperature: 0.1
46
61
  * });
47
62
  */
48
63
  export const Document = Resource("docs::Document", async function (id, props) {
@@ -60,23 +75,75 @@ export const Document = Resource("docs::Document", async function (id, props) {
60
75
  }
61
76
  return this.destroy();
62
77
  }
63
- // Initialize OpenAI compatible provider using shared client
64
- const provider = createClient(props);
65
- // Generate content
78
+ // Use provided system prompt or default
79
+ const system = props.system || DEFAULT_MD_SYSTEM_PROMPT;
80
+ // Generate initial content
66
81
  const { text } = await generateText({
67
- model: provider(getModelId(props)),
82
+ model: createModel(props),
68
83
  prompt: props.prompt,
69
- ...getModelOptions(props),
84
+ system,
85
+ providerOptions: props.model?.options,
86
+ ...(props.temperature === undefined
87
+ ? {}
88
+ : // some models error if you provide it (rather than ignoring it)
89
+ { temperature: props.temperature }),
70
90
  });
91
+ // Extract and validate markdown content
92
+ let { content, error } = await extractMarkdownContent(text);
93
+ // Re-prompt if there are validation errors
94
+ if (error) {
95
+ const errorSystem = `${system}\n\nERROR: ${error}\n\nPlease try again and ensure your response contains exactly one markdown document inside \`\`\`md fences.`;
96
+ const { text: retryText } = await generateText({
97
+ model: createModel(props),
98
+ prompt: props.prompt,
99
+ system: errorSystem,
100
+ providerOptions: props.model?.options,
101
+ ...(props.temperature === undefined
102
+ ? {}
103
+ : { temperature: props.temperature }),
104
+ });
105
+ const retryResult = await extractMarkdownContent(retryText);
106
+ if (retryResult.error) {
107
+ throw new Error(`Failed to generate valid markdown content: ${retryResult.error}`);
108
+ }
109
+ content = retryResult.content;
110
+ }
111
+ if (this.phase === "update" && props.path !== this.props.path) {
112
+ await ignore("ENOENT", () => fs.unlink(this.props.path));
113
+ }
71
114
  // Write content to file
72
- await fs.writeFile(props.path, text);
115
+ await fs.writeFile(props.path, content);
73
116
  // Get file stats for timestamps
74
117
  const stats = await fs.stat(props.path);
75
118
  // Return the resource
76
119
  return this({
77
120
  ...props,
78
- content: text,
121
+ content: content,
79
122
  createdAt: stats.birthtimeMs,
80
123
  updatedAt: stats.mtimeMs,
81
124
  });
82
125
  });
126
+ /**
127
+ * Extracts markdown content from between ```md fences
128
+ * Validates that exactly one markdown code block exists
129
+ *
130
+ * @param text The text to extract markdown content from
131
+ * @returns The extracted markdown content or error message
132
+ */
133
+ async function extractMarkdownContent(text) {
134
+ const mdCodeRegex = /```md\s*([\s\S]*?)```/g;
135
+ const matches = Array.from(text.matchAll(mdCodeRegex));
136
+ if (matches.length === 0) {
137
+ return {
138
+ content: "",
139
+ error: "No markdown code block found in the response. Please include your markdown content within ```md fences.",
140
+ };
141
+ }
142
+ if (matches.length > 1) {
143
+ return {
144
+ content: "",
145
+ error: "Multiple markdown code blocks found in the response. Please provide exactly one markdown block within ```md fences.",
146
+ };
147
+ }
148
+ return { content: matches[0][1].trim() };
149
+ }
@@ -0,0 +1,128 @@
1
+ import type { Context } from "../context";
2
+ import { Resource } from "../resource";
3
+ import type { Secret } from "../secret";
4
+ import { type ModelConfig } from "./client";
5
+ /**
6
+ * Properties for creating or updating an HTMLFile
7
+ */
8
+ export interface HTMLFileProps {
9
+ /**
10
+ * Path to the HTML file
11
+ */
12
+ path: string;
13
+ /**
14
+ * Base URL for the OpenAI API
15
+ * @default 'https://api.openai.com/v1'
16
+ */
17
+ baseURL?: string;
18
+ /**
19
+ * Prompt for generating content
20
+ * Use alchemy template literals to include file context:
21
+ * @example
22
+ * prompt: await alchemy`
23
+ * Generate an HTML page using:
24
+ * ${alchemy.file("src/templates/base.html")}
25
+ * `
26
+ */
27
+ prompt: string;
28
+ /**
29
+ * System prompt for the model
30
+ * This is used to provide instructions to the model about how to format the response
31
+ * The default system prompt instructs the model to return HTML code inside ```html fences
32
+ * @default "You are an HTML code generator. Create HTML code based on the user's requirements. Your response MUST include only HTML code inside ```html fences. Do not include any other text, explanations, or multiple code blocks."
33
+ */
34
+ system?: string;
35
+ /**
36
+ * OpenAI API key to use for generating content
37
+ * If not provided, will use OPENAI_API_KEY environment variable
38
+ */
39
+ apiKey?: Secret;
40
+ /**
41
+ * Model configuration
42
+ */
43
+ model?: ModelConfig;
44
+ /**
45
+ * Temperature for controlling randomness in generation.
46
+ * Higher values (e.g., 0.8) make output more random,
47
+ * lower values (e.g., 0.2) make it more deterministic.
48
+ * @default 0.7
49
+ */
50
+ temperature?: number;
51
+ }
52
+ /**
53
+ * An HTML file that can be created, updated, and deleted
54
+ */
55
+ export interface HTMLFile extends HTMLFileProps, Resource<"ai::HTMLFile"> {
56
+ /**
57
+ * Content of the HTML file
58
+ */
59
+ content: string;
60
+ /**
61
+ * Time at which the file was created
62
+ */
63
+ createdAt: number;
64
+ /**
65
+ * Time at which the file was last updated
66
+ */
67
+ updatedAt: number;
68
+ }
69
+ /**
70
+ * Resource for generating HTML files using AI models.
71
+ * Extracts HTML code from between ```html fences and validates the response.
72
+ *
73
+ * @example
74
+ * // Create a simple landing page
75
+ * const landingPage = await HTMLFile("landing-page", {
76
+ * path: "./public/index.html",
77
+ * prompt: await alchemy`
78
+ * Generate a modern landing page for a SaaS product with:
79
+ * - Hero section with headline and call-to-action
80
+ * - Features section with 3 key features
81
+ * - Pricing section with 3 tiers
82
+ * - Testimonials section with 2 customer quotes
83
+ * - Contact form and footer
84
+ * `,
85
+ * model: {
86
+ * id: "gpt-4o",
87
+ * provider: "openai"
88
+ * }
89
+ * });
90
+ *
91
+ * @example
92
+ * // Generate an HTML email template
93
+ * const emailTemplate = await HTMLFile("welcome-email", {
94
+ * path: "./emails/welcome.html",
95
+ * prompt: await alchemy`
96
+ * Create an HTML email template for welcoming new users to our platform.
97
+ * The email should include:
98
+ * - Company logo and branding
99
+ * - Personalized welcome message (use {{name}} placeholder)
100
+ * - Three steps to get started
101
+ * - Support contact information
102
+ * - Unsubscribe footer
103
+ *
104
+ * Make sure it's responsive and works in all major email clients.
105
+ * `,
106
+ * temperature: 0.2
107
+ * });
108
+ *
109
+ * @example
110
+ * // Generate an HTML component with custom system prompt
111
+ * const navComponent = await HTMLFile("navigation", {
112
+ * path: "./components/nav.html",
113
+ * prompt: await alchemy`
114
+ * Create a responsive navigation component with:
115
+ * - Logo in the left corner
116
+ * - Navigation links: Home, Products, Services, About, Contact
117
+ * - Mobile hamburger menu that expands/collapses
118
+ * - Login/signup buttons on the right side
119
+ * - Dark/light mode toggle
120
+ * `,
121
+ * system: "You are an expert HTML/CSS developer specializing in responsive components. Create a single HTML file inside ```html fences with no additional text. Use modern HTML5 semantic elements and inline CSS if needed.",
122
+ * model: {
123
+ * id: "claude-3-opus-20240229",
124
+ * provider: "anthropic"
125
+ * }
126
+ * });
127
+ */
128
+ export declare const HTMLFile: (((this: any, id: string, props?: {}) => never) & (new (_: never) => never)) | ((this: Context<HTMLFile>, id: string, props: HTMLFileProps) => Promise<HTMLFile>);