alchemy 0.2.5 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (197) hide show
  1. package/README.md +3 -3
  2. package/lib/ai/approve.d.ts +99 -0
  3. package/lib/ai/approve.js +76 -0
  4. package/lib/ai/astro-file.d.ts +137 -0
  5. package/lib/ai/astro-file.js +156 -0
  6. package/lib/ai/client.d.ts +7 -8
  7. package/lib/ai/client.js +48 -19
  8. package/lib/ai/css-file.d.ts +131 -0
  9. package/lib/ai/css-file.js +141 -0
  10. package/lib/ai/data.d.ts +51 -6
  11. package/lib/ai/data.js +58 -12
  12. package/lib/ai/document.d.ts +108 -24
  13. package/lib/ai/document.js +166 -52
  14. package/lib/ai/html-file.d.ts +128 -0
  15. package/lib/ai/html-file.js +138 -0
  16. package/lib/ai/index.d.ts +9 -0
  17. package/lib/ai/index.js +9 -0
  18. package/lib/ai/json-file.d.ts +160 -0
  19. package/lib/ai/json-file.js +187 -0
  20. package/lib/ai/review.d.ts +122 -0
  21. package/lib/ai/review.js +101 -0
  22. package/lib/ai/typescript-file.d.ts +139 -0
  23. package/lib/ai/typescript-file.js +145 -0
  24. package/lib/ai/vue-file.d.ts +126 -0
  25. package/lib/ai/vue-file.js +136 -0
  26. package/lib/ai/yaml-file.d.ts +169 -0
  27. package/lib/ai/yaml-file.js +200 -0
  28. package/lib/alchemy.js +17 -2
  29. package/lib/apply.js +1 -1
  30. package/lib/cloudflare/dns.d.ts +88 -0
  31. package/lib/cloudflare/dns.js +192 -0
  32. package/lib/cloudflare/generate-asset-manifest.js +1 -1
  33. package/lib/cloudflare/index.d.ts +2 -1
  34. package/lib/cloudflare/index.js +2 -1
  35. package/lib/cloudflare/r2-rest-state-store.d.ts +125 -0
  36. package/lib/cloudflare/r2-rest-state-store.js +254 -0
  37. package/lib/cloudflare/response.d.ts +12 -0
  38. package/lib/cloudflare/response.js +0 -0
  39. package/lib/cloudflare/static-site-router.js +16 -7
  40. package/lib/cloudflare/static-site.js +3 -0
  41. package/lib/cloudflare/upload-asset-manifest.js +4 -2
  42. package/lib/cloudflare/zone.d.ts +18 -0
  43. package/lib/cloudflare/zone.js +17 -4
  44. package/lib/dns/godaddy.d.ts +7 -0
  45. package/lib/dns/godaddy.js +21 -0
  46. package/lib/dns/import-dns.d.ts +82 -0
  47. package/lib/dns/import-dns.js +114 -0
  48. package/lib/dns/index.d.ts +2 -0
  49. package/lib/dns/index.js +2 -0
  50. package/lib/dns/record.d.ts +88 -0
  51. package/lib/dns/record.js +13 -0
  52. package/lib/fs/copy-file.d.ts +54 -0
  53. package/lib/fs/copy-file.js +63 -0
  54. package/lib/fs/file-system-state-store.d.ts +17 -0
  55. package/lib/fs/file-system-state-store.js +87 -0
  56. package/lib/fs/file.d.ts +13 -0
  57. package/lib/fs/file.js +24 -5
  58. package/lib/fs/folder.d.ts +10 -0
  59. package/lib/fs/folder.js +2 -2
  60. package/lib/fs/index.d.ts +10 -4
  61. package/lib/fs/index.js +10 -4
  62. package/lib/fs/static-astro-file.d.ts +34 -0
  63. package/lib/fs/static-astro-file.js +39 -0
  64. package/lib/fs/static-css-file.d.ts +24 -0
  65. package/lib/fs/static-css-file.js +29 -0
  66. package/lib/fs/static-html-file.d.ts +31 -0
  67. package/lib/fs/static-html-file.js +36 -0
  68. package/lib/fs/{json-file.d.ts → static-json-file.d.ts} +3 -3
  69. package/lib/fs/static-json-file.js +15 -0
  70. package/lib/fs/{text-file.d.ts → static-text-file.d.ts} +2 -2
  71. package/lib/fs/static-text-file.js +8 -0
  72. package/lib/fs/{typescript-file.d.ts → static-typescript-file.d.ts} +3 -3
  73. package/lib/fs/{typescript-file.js → static-typescript-file.js} +3 -2
  74. package/lib/fs/static-vue-file.d.ts +28 -0
  75. package/lib/fs/static-vue-file.js +33 -0
  76. package/lib/fs/static-yaml-file.d.ts +22 -0
  77. package/lib/fs/{yaml-file.js → static-yaml-file.js} +3 -2
  78. package/lib/internal/getting-started.d.ts +21 -0
  79. package/lib/internal/getting-started.js +87 -0
  80. package/lib/internal/index.d.ts +3 -0
  81. package/lib/internal/index.js +3 -0
  82. package/lib/internal/providers.d.ts +27 -0
  83. package/lib/internal/providers.js +172 -0
  84. package/lib/internal/tutorial.d.ts +104 -0
  85. package/lib/internal/tutorial.js +251 -0
  86. package/lib/resource.js +8 -1
  87. package/lib/scope.d.ts +1 -1
  88. package/lib/scope.js +4 -2
  89. package/lib/state.d.ts +1 -16
  90. package/lib/state.js +0 -87
  91. package/lib/test/bun.d.ts +11 -0
  92. package/lib/test/bun.js +36 -8
  93. package/lib/web/astro.d.ts +147 -0
  94. package/lib/web/astro.js +414 -0
  95. package/lib/{shadcn/component.js → web/shadcn-component.js} +2 -9
  96. package/lib/web/shadcn.d.ts +97 -0
  97. package/lib/web/shadcn.js +99 -0
  98. package/lib/web/tailwind.d.ts +64 -0
  99. package/lib/web/tailwind.js +97 -0
  100. package/lib/{vite → web}/vite.d.ts +5 -0
  101. package/lib/{vite → web}/vite.js +44 -44
  102. package/lib/web/vitepress/config.d.ts +10 -0
  103. package/lib/web/vitepress/config.js +31 -0
  104. package/lib/web/vitepress/custom-theme.d.ts +173 -0
  105. package/lib/web/vitepress/custom-theme.js +336 -0
  106. package/lib/{vitepress → web/vitepress}/dependencies.d.ts +2 -2
  107. package/lib/{vitepress → web/vitepress}/dependencies.js +1 -1
  108. package/lib/web/vitepress/home-page.d.ts +246 -0
  109. package/lib/web/vitepress/home-page.js +147 -0
  110. package/lib/{vitepress → web/vitepress}/index.d.ts +2 -0
  111. package/lib/{vitepress → web/vitepress}/index.js +2 -0
  112. package/lib/{vitepress → web/vitepress}/vitepress.d.ts +15 -12
  113. package/lib/{vitepress → web/vitepress}/vitepress.js +26 -44
  114. package/package.json +6 -2
  115. package/src/ai/approve.ts +163 -0
  116. package/src/ai/astro-file.ts +269 -0
  117. package/src/ai/client.ts +60 -21
  118. package/src/ai/css-file.ts +247 -0
  119. package/src/ai/data.ts +81 -19
  120. package/src/ai/document.ts +240 -62
  121. package/src/ai/html-file.ts +244 -0
  122. package/src/ai/index.ts +9 -0
  123. package/src/ai/json-file.ts +325 -0
  124. package/src/ai/review.ts +213 -0
  125. package/src/ai/typescript-file.ts +259 -0
  126. package/src/ai/vue-file.ts +242 -0
  127. package/src/ai/yaml-file.ts +328 -0
  128. package/src/alchemy.ts +18 -7
  129. package/src/apply.ts +8 -9
  130. package/src/cloudflare/dns.ts +344 -0
  131. package/src/cloudflare/generate-asset-manifest.ts +4 -4
  132. package/src/cloudflare/index.ts +2 -1
  133. package/src/cloudflare/r2-rest-state-store.ts +353 -0
  134. package/src/cloudflare/response.ts +9 -0
  135. package/src/cloudflare/static-site-router.ts +18 -8
  136. package/src/cloudflare/static-site.ts +11 -7
  137. package/src/cloudflare/upload-asset-manifest.ts +6 -4
  138. package/src/cloudflare/worker.ts +44 -43
  139. package/src/cloudflare/zone.ts +39 -23
  140. package/src/dns/godaddy.ts +29 -0
  141. package/src/dns/import-dns.ts +213 -0
  142. package/src/dns/index.ts +2 -0
  143. package/src/dns/record.ts +121 -0
  144. package/src/fs/copy-file.ts +115 -0
  145. package/src/fs/file-system-state-store.ts +109 -0
  146. package/src/fs/file.ts +35 -9
  147. package/src/fs/folder.ts +14 -2
  148. package/src/fs/index.ts +10 -4
  149. package/src/fs/static-astro-file.ts +45 -0
  150. package/src/fs/static-css-file.ts +35 -0
  151. package/src/fs/static-html-file.ts +42 -0
  152. package/src/fs/static-json-file.ts +34 -0
  153. package/src/fs/{text-file.ts → static-text-file.ts} +9 -3
  154. package/src/fs/{typescript-file.ts → static-typescript-file.ts} +7 -6
  155. package/src/fs/static-vue-file.ts +39 -0
  156. package/src/fs/static-yaml-file.ts +33 -0
  157. package/src/internal/getting-started.ts +115 -0
  158. package/src/internal/index.ts +3 -0
  159. package/src/internal/providers.ts +241 -0
  160. package/src/internal/tutorial.ts +392 -0
  161. package/src/resource.ts +10 -1
  162. package/src/scope.ts +5 -6
  163. package/src/state.ts +1 -111
  164. package/src/test/bun.ts +60 -10
  165. package/src/web/astro.ts +644 -0
  166. package/src/{shadcn/component.ts → web/shadcn-component.ts} +5 -13
  167. package/src/web/shadcn.ts +219 -0
  168. package/src/web/tailwind.ts +167 -0
  169. package/src/{vite → web}/vite.ts +54 -56
  170. package/src/web/vitepress/config.ts +48 -0
  171. package/src/web/vitepress/custom-theme.ts +514 -0
  172. package/src/{vitepress → web/vitepress}/dependencies.ts +2 -2
  173. package/src/web/vitepress/home-page.ts +375 -0
  174. package/src/{vitepress → web/vitepress}/index.ts +2 -0
  175. package/src/{vitepress → web/vitepress}/vitepress.ts +62 -72
  176. package/lib/cloudflare/state.d.ts +0 -82
  177. package/lib/cloudflare/state.js +0 -108
  178. package/lib/fs/json-file.js +0 -7
  179. package/lib/fs/text-file.js +0 -7
  180. package/lib/fs/yaml-file.d.ts +0 -19
  181. package/lib/internal/docs.d.ts +0 -5
  182. package/lib/internal/docs.js +0 -287
  183. package/lib/shadcn/index.d.ts +0 -1
  184. package/lib/shadcn/index.js +0 -1
  185. package/lib/vite/index.d.ts +0 -1
  186. package/lib/vite/index.js +0 -1
  187. package/lib/vitepress/home-page.d.ts +0 -133
  188. package/lib/vitepress/home-page.js +0 -11
  189. package/src/cloudflare/state.ts +0 -138
  190. package/src/fs/json-file.ts +0 -23
  191. package/src/fs/yaml-file.ts +0 -26
  192. package/src/internal/docs.ts +0 -338
  193. package/src/shadcn/index.ts +0 -1
  194. package/src/vite/index.ts +0 -1
  195. package/src/vitepress/home-page.ts +0 -166
  196. /package/lib/{shadcn/component.d.ts → web/shadcn-component.d.ts} +0 -0
  197. /package/src/{vitepress → web/vitepress}/index.md +0 -0
@@ -0,0 +1,325 @@
1
+ import { generateObject, generateText } from "ai";
2
+ import type { JsonSchema, Type, type } from "arktype";
3
+ import type { Context } from "../context";
4
+ import { StaticJsonFile } from "../fs/static-json-file";
5
+ import { Resource } from "../resource";
6
+ import type { Secret } from "../secret";
7
+ import { ark } from "./ark";
8
+ import { type ModelConfig, createModel } from "./client";
9
+
10
+ /**
11
+ * Properties for creating or updating a JSONFile
12
+ */
13
+ export interface JSONFileProps<
14
+ T extends Type<any, any> | undefined = undefined,
15
+ > {
16
+ /**
17
+ * Path to the JSON file
18
+ */
19
+ path: string;
20
+
21
+ /**
22
+ * Optional ArkType schema to validate and structure the generated JSON
23
+ * When provided, the resource will use generateObject with schema validation
24
+ * When not provided, it will extract JSON from between ```json fences
25
+ */
26
+ schema?: T;
27
+
28
+ /**
29
+ * Base URL for the OpenAI API
30
+ * @default 'https://api.openai.com/v1'
31
+ */
32
+ baseURL?: string;
33
+
34
+ /**
35
+ * Prompt for generating content
36
+ * Use alchemy template literals to include file context:
37
+ * @example
38
+ * prompt: await alchemy`
39
+ * Generate a JSON configuration for:
40
+ * ${alchemy.file("src/config.ts")}
41
+ * `
42
+ */
43
+ prompt: string;
44
+
45
+ /**
46
+ * System prompt for the model
47
+ * This is used to provide instructions to the model about how to format the response
48
+ * @default Depends on whether schema is provided
49
+ */
50
+ system?: string;
51
+
52
+ /**
53
+ * OpenAI API key to use for generating content
54
+ * If not provided, will use OPENAI_API_KEY environment variable
55
+ */
56
+ apiKey?: Secret;
57
+
58
+ /**
59
+ * Model configuration
60
+ */
61
+ model?: ModelConfig;
62
+
63
+ /**
64
+ * Temperature for controlling randomness in generation.
65
+ * Higher values (e.g., 0.8) make output more random,
66
+ * lower values (e.g., 0.2) make it more deterministic.
67
+ * @default 0.7
68
+ */
69
+ temperature?: number;
70
+
71
+ /**
72
+ * Whether to pretty-print the JSON with indentation
73
+ * @default true
74
+ */
75
+ pretty?: boolean;
76
+
77
+ /**
78
+ * Number of spaces to use for indentation when pretty-printing
79
+ * @default 2
80
+ */
81
+ indent?: number;
82
+ }
83
+
84
+ /**
85
+ * A JSON file that can be created, updated, and deleted
86
+ */
87
+ export interface JSONFile<T = any>
88
+ extends Omit<JSONFileProps, "schema">,
89
+ Resource<"ai::JSONFile"> {
90
+ /**
91
+ * Content of the JSON file as a string
92
+ */
93
+ content: string;
94
+
95
+ /**
96
+ * Parsed JSON object
97
+ */
98
+ json: T;
99
+
100
+ /**
101
+ * Schema used to validate the JSON (if provided)
102
+ */
103
+ schema?: JsonSchema;
104
+
105
+ /**
106
+ * Time at which the file was created
107
+ */
108
+ createdAt: number;
109
+
110
+ /**
111
+ * Time at which the file was last updated
112
+ */
113
+ updatedAt: number;
114
+ }
115
+
116
+ /**
117
+ * Default system prompt for JSON file generation without schema
118
+ */
119
+ const DEFAULT_JSON_SYSTEM_PROMPT =
120
+ "You are a JSON generator. Create valid JSON based on the user's requirements. Your response MUST include only JSON inside ```json fences. Do not include any other text, explanations, or multiple code blocks.";
121
+
122
+ /**
123
+ * Resource for generating JSON files using AI models.
124
+ * Can operate in two modes:
125
+ * 1. With schema: Uses generateObject with type validation
126
+ * 2. Without schema: Extracts JSON from between ```json fences
127
+ *
128
+ * @example
129
+ * // Generate a configuration file with freeform JSON
130
+ * const config = await JSONFile("app-config", {
131
+ * path: "./config/app.json",
132
+ * prompt: await alchemy`
133
+ * Generate a configuration for a web application with:
134
+ * - Server settings (port, host, timeout)
135
+ * - Database connection details (redact any passwords)
136
+ * - Logging configuration
137
+ * - Feature flags
138
+ * `,
139
+ * model: {
140
+ * id: "gpt-4o",
141
+ * provider: "openai"
142
+ * }
143
+ * });
144
+ *
145
+ * @example
146
+ * // Generate JSON with schema validation
147
+ * import { type } from "arktype";
148
+ *
149
+ * const userSchema = type({
150
+ * users: [{
151
+ * id: "string",
152
+ * name: "string",
153
+ * email: "string",
154
+ * role: "'admin' | 'user' | 'guest'",
155
+ * permissions: "string[]",
156
+ * active: "boolean"
157
+ * }]
158
+ * });
159
+ *
160
+ * const userData = await JSONFile("user-data", {
161
+ * path: "./data/users.json",
162
+ * schema: userSchema,
163
+ * prompt: "Generate sample user data for an application with various roles and permissions",
164
+ * temperature: 0.2
165
+ * });
166
+ *
167
+ * // Type-safe access to the generated data
168
+ * console.log(userData.json.users[0].role); // Typed as 'admin' | 'user' | 'guest'
169
+ *
170
+ * @example
171
+ * // Generate API mock data with custom system prompt
172
+ * const apiMock = await JSONFile("api-mock", {
173
+ * path: "./mocks/products-api.json",
174
+ * prompt: await alchemy`
175
+ * Create mock data for a product catalog API response with:
176
+ * - 10 products with different categories
177
+ * - Each product should have id, name, price, category, inventory, and image_url
178
+ * - Include pagination metadata (total, page, limit)
179
+ * `,
180
+ * system: "You are an API design expert. Create realistic mock JSON data that follows REST API best practices. Your response must be valid JSON inside ```json fences.",
181
+ * model: {
182
+ * id: "claude-3-opus-20240229",
183
+ * provider: "anthropic"
184
+ * },
185
+ * pretty: true,
186
+ * indent: 4
187
+ * });
188
+ */
189
+ export const JSONFile = Resource("ai::JSONFile", async function <
190
+ const T extends Type<any, any> | undefined = undefined,
191
+ >(this: Context<JSONFile<T extends Type<any, any> ? type.infer<T> : any>>, id: string, props: JSONFileProps<T>): Promise<
192
+ JSONFile<T extends Type<any, any> ? type.infer<T> : any>
193
+ > {
194
+ // Handle deletion phase
195
+ if (this.phase === "delete") {
196
+ return this.destroy();
197
+ }
198
+
199
+ let jsonContent: string;
200
+ let jsonObject: any;
201
+
202
+ // Check if schema is provided
203
+ if (props.schema) {
204
+ // Use schema-based generation
205
+ const { object } = await generateObject({
206
+ model: createModel(props),
207
+ schema: ark.schema<type.infer<typeof props.schema>>(props.schema),
208
+ providerOptions: props.model?.options,
209
+ system:
210
+ props.system ||
211
+ "Generate a valid JSON object based on the provided requirements.",
212
+ prompt: props.prompt,
213
+ ...(props.temperature === undefined
214
+ ? {}
215
+ : { temperature: props.temperature }),
216
+ });
217
+
218
+ jsonObject = object;
219
+
220
+ // Use StaticJsonFile to create the file
221
+ const file = await StaticJsonFile("file", props.path, jsonObject);
222
+ jsonContent = file.content;
223
+ } else {
224
+ // Use fence-based extraction
225
+ // Use provided system prompt or default
226
+ const system = props.system || DEFAULT_JSON_SYSTEM_PROMPT;
227
+
228
+ // Generate initial content
229
+ const { text } = await generateText({
230
+ model: createModel(props),
231
+ prompt: props.prompt,
232
+ system,
233
+ providerOptions: props.model?.options,
234
+ ...(props.temperature === undefined
235
+ ? {}
236
+ : { temperature: props.temperature }),
237
+ });
238
+
239
+ // Extract and validate JSON content
240
+ let { content, error } = await extractJSONContent(text);
241
+
242
+ // Re-prompt if there are validation errors
243
+ if (error) {
244
+ const errorSystem = `${system}\n\nERROR: ${error}\n\nPlease try again and ensure your response contains exactly one valid JSON block inside \`\`\`json fences.`;
245
+
246
+ const { text: retryText } = await generateText({
247
+ model: createModel(props),
248
+ prompt: props.prompt,
249
+ system: errorSystem,
250
+ providerOptions: props.model?.options,
251
+ ...(props.temperature === undefined
252
+ ? {}
253
+ : { temperature: props.temperature }),
254
+ });
255
+
256
+ const retryResult = await extractJSONContent(retryText);
257
+
258
+ if (retryResult.error) {
259
+ throw new Error(`Failed to generate valid JSON: ${retryResult.error}`);
260
+ }
261
+
262
+ content = retryResult.content;
263
+ }
264
+
265
+ // Parse JSON to get the object representation
266
+ jsonObject = JSON.parse(content);
267
+
268
+ // Use StaticJsonFile to create the file
269
+ const file = await StaticJsonFile("file", props.path, jsonObject);
270
+ jsonContent = file.content;
271
+ }
272
+
273
+ // Return the resource
274
+ return this({
275
+ ...props,
276
+ schema: props.schema,
277
+ content: jsonContent,
278
+ json: jsonObject,
279
+ createdAt: Date.now(),
280
+ updatedAt: Date.now(),
281
+ });
282
+ });
283
+
284
+ /**
285
+ * Extracts JSON content from between ```json fences
286
+ * Validates that exactly one JSON code block exists
287
+ *
288
+ * @param text The text to extract JSON from
289
+ * @returns The extracted JSON or error message
290
+ */
291
+ async function extractJSONContent(
292
+ text: string
293
+ ): Promise<{ content: string; error?: string }> {
294
+ const jsonCodeRegex = /```json\s*([\s\S]*?)```/g;
295
+ const matches = Array.from(text.matchAll(jsonCodeRegex));
296
+
297
+ if (matches.length === 0) {
298
+ return {
299
+ content: "",
300
+ error:
301
+ "No JSON code block found in the response. Please include your JSON within ```json fences.",
302
+ };
303
+ }
304
+
305
+ if (matches.length > 1) {
306
+ return {
307
+ content: "",
308
+ error:
309
+ "Multiple JSON code blocks found in the response. Please provide exactly one JSON block within ```json fences.",
310
+ };
311
+ }
312
+
313
+ const content = matches[0][1].trim();
314
+
315
+ // Validate JSON can be parsed
316
+ try {
317
+ JSON.parse(content);
318
+ return { content };
319
+ } catch (e) {
320
+ return {
321
+ content: "",
322
+ error: `Invalid JSON: ${(e as Error).message}. Please provide valid JSON syntax.`,
323
+ };
324
+ }
325
+ }
@@ -0,0 +1,213 @@
1
+ import { generateText, type CoreMessage } from "ai";
2
+ import type { Context } from "../context";
3
+ import { Resource } from "../resource";
4
+ import type { Secret } from "../secret";
5
+ import { createModel, withRateLimitRetry, type ModelConfig } from "./client";
6
+
7
+ /**
8
+ * Properties for creating or updating a Review
9
+ */
10
+ export interface ReviewProps {
11
+ /**
12
+ * Content to be reviewed
13
+ * Use alchemy template literals to include file context:
14
+ * @example
15
+ * content: await alchemy`
16
+ * Review this code:
17
+ * ${alchemy.file("src/api.ts")}
18
+ * `
19
+ *
20
+ * Required unless messages are provided
21
+ */
22
+ content?: string;
23
+
24
+ /**
25
+ * Prompt for the review
26
+ * This should include specific instructions for the review
27
+ * @example
28
+ * prompt: "Review this code for security vulnerabilities, performance issues, and best practices."
29
+ *
30
+ * Required unless messages are provided
31
+ */
32
+ prompt?: string;
33
+
34
+ /**
35
+ * Message history for the conversation
36
+ * If provided, this will be used instead of the prompt and content
37
+ */
38
+ messages?: CoreMessage[];
39
+
40
+ /**
41
+ * System prompt for the model
42
+ * This is used to provide instructions to the model about how to format the response
43
+ * The default system prompt instructs the model to act as a reviewer
44
+ */
45
+ system?: string;
46
+
47
+ /**
48
+ * AI API key to use for generating content
49
+ * If not provided, will use OPENAI_API_KEY environment variable
50
+ */
51
+ apiKey?: Secret;
52
+
53
+ /**
54
+ * Model configuration
55
+ */
56
+ model?: ModelConfig;
57
+
58
+ /**
59
+ * Temperature for controlling randomness in generation.
60
+ * Higher values (e.g., 0.8) make output more random,
61
+ * lower values (e.g., 0.2) make it more deterministic.
62
+ * @default 0.7
63
+ */
64
+ temperature?: number;
65
+
66
+ /**
67
+ * Maximum number of tokens to generate.
68
+ * Higher values allow for longer documents but may increase cost and generation time.
69
+ * @default 10000
70
+ */
71
+ maxTokens?: number;
72
+ }
73
+
74
+ /**
75
+ * Output returned after Review creation/update
76
+ */
77
+ export interface Review extends ReviewProps, Resource<"ai::Review"> {
78
+ /**
79
+ * Content of the review
80
+ */
81
+ content: string;
82
+
83
+ /**
84
+ * Updated message history with the review response appended
85
+ */
86
+ messages: CoreMessage[];
87
+
88
+ /**
89
+ * Time at which the review was created
90
+ */
91
+ createdAt: number;
92
+
93
+ /**
94
+ * Time at which the review was last updated
95
+ */
96
+ updatedAt: number;
97
+ }
98
+
99
+ /**
100
+ * Default system prompt for review generation
101
+ */
102
+ const DEFAULT_REVIEW_SYSTEM_PROMPT = `You are an expert code reviewer and technical writer. Your task is to provide a thorough review of the provided content. Your review should include:
103
+
104
+ 1. A high-level summary of the content
105
+ 2. Detailed feedback on the content's quality, accuracy, and completeness
106
+ 3. Specific suggestions for improvement
107
+ 4. Alternative approaches or solutions where applicable
108
+ 5. Best practices that should be followed
109
+ 6. Potential issues or concerns that should be addressed
110
+
111
+ Provide your review in a clear, well-structured format.`;
112
+
113
+ /**
114
+ * Creates or updates a Review resource
115
+ *
116
+ * @example
117
+ * // Create a review of a code file
118
+ * const codeReview = await Review("api-review", {
119
+ * content: await alchemy`
120
+ * Review this API implementation:
121
+ * ${alchemy.file("src/api.ts")}
122
+ * `,
123
+ * prompt: "Review this API implementation for security vulnerabilities, performance issues, and best practices. Suggest improvements and alternative approaches."
124
+ * });
125
+ *
126
+ * @example
127
+ * // Create a review of a document
128
+ * const docReview = await Review("tutorial-review", {
129
+ * content: await alchemy`
130
+ * Review this tutorial:
131
+ * ${alchemy.file("docs/tutorials/getting-started.md")}
132
+ * `,
133
+ * prompt: "Review this tutorial for clarity, accuracy, and completeness. Suggest improvements to make it more user-friendly."
134
+ * });
135
+ *
136
+ * @example
137
+ * // Create a review with message history
138
+ * const reviewWithHistory = await Review("code-review-iteration-2", {
139
+ * content: "Updated code implementation",
140
+ * prompt: "Review this updated implementation",
141
+ * messages: [
142
+ * { role: "user", content: "Can you review my code?" },
143
+ * { role: "assistant", content: "I'll review your code. Please share it." },
144
+ * { role: "user", content: "Here's my implementation: [code]" },
145
+ * { role: "assistant", content: "Here's my review of your code: [previous review]" }
146
+ * ]
147
+ * });
148
+ */
149
+ export const Review = Resource(
150
+ "ai::Review",
151
+ async function (
152
+ this: Context<Review>,
153
+ id: string,
154
+ props: ReviewProps
155
+ ): Promise<Review> {
156
+ if (this.phase === "delete") {
157
+ return this.destroy();
158
+ }
159
+
160
+ // Use provided system prompt or default
161
+ const system = props.system || DEFAULT_REVIEW_SYSTEM_PROMPT;
162
+
163
+ // Create messages array if not provided
164
+ const messages =
165
+ props.messages ||
166
+ (props.content && props.prompt
167
+ ? [
168
+ {
169
+ role: "user",
170
+ content: `${props.content}\n\n${props.prompt}`,
171
+ },
172
+ ]
173
+ : []);
174
+
175
+ if (messages.length === 0) {
176
+ throw new Error(
177
+ "Either messages or both content and prompt must be provided"
178
+ );
179
+ }
180
+
181
+ // Generate review content with rate limit retry
182
+ const { text } = await withRateLimitRetry(async () => {
183
+ return generateText({
184
+ model: createModel(props),
185
+ messages,
186
+ system,
187
+ maxTokens: props.maxTokens || 8192,
188
+ providerOptions: props.model?.options,
189
+ ...(props.temperature === undefined
190
+ ? {}
191
+ : { temperature: props.temperature }),
192
+ });
193
+ });
194
+
195
+ // Create updated message history with the AI's response
196
+ const updatedMessages = [
197
+ ...messages,
198
+ {
199
+ role: "assistant" as const,
200
+ content: text,
201
+ },
202
+ ];
203
+
204
+ // Return the review with the direct text from the LLM and updated messages
205
+ return this({
206
+ ...props,
207
+ content: text,
208
+ messages: updatedMessages,
209
+ createdAt: Date.now(),
210
+ updatedAt: Date.now(),
211
+ });
212
+ }
213
+ );