alchemy 0.3.0 → 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 (107) hide show
  1. package/lib/ai/approve.d.ts +99 -0
  2. package/lib/ai/approve.js +76 -0
  3. package/lib/ai/astro-file.js +7 -24
  4. package/lib/ai/client.d.ts +7 -0
  5. package/lib/ai/client.js +45 -0
  6. package/lib/ai/css-file.js +7 -24
  7. package/lib/ai/data.d.ts +26 -21
  8. package/lib/ai/data.js +33 -22
  9. package/lib/ai/document.d.ts +64 -4
  10. package/lib/ai/document.js +102 -55
  11. package/lib/ai/html-file.js +7 -24
  12. package/lib/ai/index.d.ts +2 -0
  13. package/lib/ai/index.js +2 -0
  14. package/lib/ai/json-file.js +11 -32
  15. package/lib/ai/review.d.ts +122 -0
  16. package/lib/ai/review.js +101 -0
  17. package/lib/ai/typescript-file.d.ts +1 -2
  18. package/lib/ai/typescript-file.js +8 -39
  19. package/lib/ai/vue-file.js +7 -24
  20. package/lib/ai/yaml-file.js +12 -48
  21. package/lib/alchemy.js +17 -2
  22. package/lib/apply.js +1 -1
  23. package/lib/cloudflare/generate-asset-manifest.js +1 -1
  24. package/lib/cloudflare/static-site-router.js +16 -7
  25. package/lib/cloudflare/static-site.js +3 -0
  26. package/lib/cloudflare/upload-asset-manifest.js +4 -2
  27. package/lib/fs/copy-file.d.ts +54 -0
  28. package/lib/fs/copy-file.js +63 -0
  29. package/lib/fs/file.d.ts +13 -0
  30. package/lib/fs/file.js +24 -5
  31. package/lib/fs/index.d.ts +6 -0
  32. package/lib/fs/index.js +6 -0
  33. package/lib/fs/static-astro-file.d.ts +34 -0
  34. package/lib/fs/static-astro-file.js +39 -0
  35. package/lib/fs/static-css-file.d.ts +24 -0
  36. package/lib/fs/static-css-file.js +29 -0
  37. package/lib/fs/static-html-file.d.ts +31 -0
  38. package/lib/fs/static-html-file.js +36 -0
  39. package/lib/fs/static-json-file.d.ts +2 -2
  40. package/lib/fs/static-json-file.js +3 -2
  41. package/lib/fs/static-text-file.d.ts +1 -1
  42. package/lib/fs/static-text-file.js +3 -2
  43. package/lib/fs/static-typescript-file.d.ts +2 -2
  44. package/lib/fs/static-typescript-file.js +3 -2
  45. package/lib/fs/static-vue-file.d.ts +28 -0
  46. package/lib/fs/static-vue-file.js +33 -0
  47. package/lib/fs/static-yaml-file.d.ts +11 -8
  48. package/lib/fs/static-yaml-file.js +3 -2
  49. package/lib/internal/getting-started.d.ts +15 -4
  50. package/lib/internal/getting-started.js +34 -41
  51. package/lib/internal/index.d.ts +3 -0
  52. package/lib/internal/index.js +3 -0
  53. package/lib/internal/providers.d.ts +7 -3
  54. package/lib/internal/providers.js +42 -18
  55. package/lib/internal/tutorial.d.ts +104 -0
  56. package/lib/internal/tutorial.js +251 -0
  57. package/lib/web/vitepress/config.d.ts +10 -0
  58. package/lib/web/vitepress/config.js +31 -0
  59. package/lib/web/vitepress/home-page.d.ts +5 -3
  60. package/lib/web/vitepress/home-page.js +48 -35
  61. package/lib/web/vitepress/index.d.ts +1 -0
  62. package/lib/web/vitepress/index.js +1 -0
  63. package/lib/web/vitepress/vitepress.d.ts +5 -4
  64. package/lib/web/vitepress/vitepress.js +17 -34
  65. package/package.json +1 -1
  66. package/src/ai/approve.ts +163 -0
  67. package/src/ai/astro-file.ts +11 -30
  68. package/src/ai/client.ts +59 -0
  69. package/src/ai/css-file.ts +11 -30
  70. package/src/ai/data.ts +50 -25
  71. package/src/ai/document.ts +146 -65
  72. package/src/ai/html-file.ts +11 -30
  73. package/src/ai/index.ts +2 -0
  74. package/src/ai/json-file.ts +13 -37
  75. package/src/ai/review.ts +213 -0
  76. package/src/ai/typescript-file.ts +14 -48
  77. package/src/ai/vue-file.ts +11 -30
  78. package/src/ai/yaml-file.ts +13 -53
  79. package/src/alchemy.ts +18 -7
  80. package/src/apply.ts +8 -8
  81. package/src/cloudflare/generate-asset-manifest.ts +4 -4
  82. package/src/cloudflare/static-site-router.ts +18 -8
  83. package/src/cloudflare/static-site.ts +11 -7
  84. package/src/cloudflare/upload-asset-manifest.ts +6 -4
  85. package/src/cloudflare/worker.ts +44 -43
  86. package/src/fs/copy-file.ts +115 -0
  87. package/src/fs/file.ts +35 -9
  88. package/src/fs/index.ts +6 -0
  89. package/src/fs/static-astro-file.ts +45 -0
  90. package/src/fs/static-css-file.ts +35 -0
  91. package/src/fs/static-html-file.ts +42 -0
  92. package/src/fs/static-json-file.ts +4 -3
  93. package/src/fs/static-text-file.ts +5 -2
  94. package/src/fs/static-typescript-file.ts +4 -3
  95. package/src/fs/static-vue-file.ts +39 -0
  96. package/src/fs/static-yaml-file.ts +13 -9
  97. package/src/internal/getting-started.ts +51 -43
  98. package/src/internal/index.ts +3 -0
  99. package/src/internal/providers.ts +59 -27
  100. package/src/internal/tutorial.ts +392 -0
  101. package/src/web/vitepress/config.ts +48 -0
  102. package/src/web/vitepress/home-page.ts +54 -42
  103. package/src/web/vitepress/index.ts +1 -0
  104. package/src/web/vitepress/vitepress.ts +31 -48
  105. package/lib/internal/project.d.ts +0 -13
  106. package/lib/internal/project.js +0 -101
  107. package/src/internal/project.ts +0 -119
@@ -1,11 +1,9 @@
1
- import { generateText } from "ai";
2
- import fs from "node:fs/promises";
3
- import path from "node:path";
1
+ import { generateText, type CoreMessage } from "ai";
4
2
  import type { Context } from "../context";
3
+ import { StaticTextFile } from "../fs/static-text-file";
5
4
  import { Resource } from "../resource";
6
5
  import type { Secret } from "../secret";
7
- import { ignore } from "../util/ignore";
8
- import { type ModelConfig, createModel } from "./client";
6
+ import { createModel, withRateLimitRetry, type ModelConfig } from "./client";
9
7
 
10
8
  /**
11
9
  * Properties for creating or updating a Document
@@ -17,9 +15,10 @@ export interface DocumentProps {
17
15
  title: string;
18
16
 
19
17
  /**
20
- * Path to the markdown document
18
+ * Optional path to the markdown document
19
+ * If provided, document will be written to this path
21
20
  */
22
- path: string;
21
+ path?: string;
23
22
 
24
23
  /**
25
24
  * Base URL for the OpenAI API
@@ -36,7 +35,19 @@ export interface DocumentProps {
36
35
  * ${alchemy.file("src/api.ts")}
37
36
  * `
38
37
  */
39
- prompt: string;
38
+ prompt?: string;
39
+
40
+ /**
41
+ * Message history for conversation-based generation
42
+ * If provided, this will be used instead of the prompt
43
+ * @example
44
+ * messages: [
45
+ * { role: "user", content: "Generate API documentation for this file" },
46
+ * { role: "assistant", content: "I'll create detailed API docs. What file should I document?" },
47
+ * { role: "user", content: "Please document src/api.ts" }
48
+ * ]
49
+ */
50
+ messages?: CoreMessage[];
40
51
 
41
52
  /**
42
53
  * System prompt for the model
@@ -64,6 +75,13 @@ export interface DocumentProps {
64
75
  * @default 0.7
65
76
  */
66
77
  temperature?: number;
78
+
79
+ /**
80
+ * Maximum number of tokens to generate.
81
+ * Higher values allow for longer documents but may increase cost and generation time.
82
+ * @default 10000
83
+ */
84
+ maxTokens?: number;
67
85
  }
68
86
 
69
87
  /**
@@ -75,6 +93,11 @@ export interface Document extends DocumentProps, Resource<"docs::Document"> {
75
93
  */
76
94
  content: string;
77
95
 
96
+ /**
97
+ * Updated message history with the document response appended
98
+ */
99
+ messages: CoreMessage[];
100
+
78
101
  /**
79
102
  * Time at which the document was created
80
103
  */
@@ -84,6 +107,11 @@ export interface Document extends DocumentProps, Resource<"docs::Document"> {
84
107
  * Time at which the document was last updated
85
108
  */
86
109
  updatedAt: number;
110
+
111
+ /**
112
+ * File resource if path was provided
113
+ */
114
+ file?: StaticTextFile;
87
115
  }
88
116
 
89
117
  /**
@@ -97,7 +125,22 @@ const DEFAULT_MD_SYSTEM_PROMPT =
97
125
  * Supports powerful context handling through the alchemy template literal tag.
98
126
  *
99
127
  * @example
100
- * // Create a document using alchemy template literals for context
128
+ * // Create an in-memory document (no file created)
129
+ * const apiDocs = await Document("api-docs", {
130
+ * title: "API Documentation",
131
+ * prompt: await alchemy`
132
+ * Generate API documentation based on these source files:
133
+ * ${alchemy.file("src/api.ts")}
134
+ * ${alchemy.file("src/types.ts")}
135
+ * `,
136
+ * model: {
137
+ * id: "gpt-4o",
138
+ * provider: "openai"
139
+ * }
140
+ * });
141
+ *
142
+ * @example
143
+ * // Create a document and write it to disk
101
144
  * const apiDocs = await Document("api-docs", {
102
145
  * title: "API Documentation",
103
146
  * path: "./docs/api.md",
@@ -113,6 +156,23 @@ const DEFAULT_MD_SYSTEM_PROMPT =
113
156
  * });
114
157
  *
115
158
  * @example
159
+ * // Use message history for iterative document generation
160
+ * const apiDocs = await Document("api-docs", {
161
+ * title: "API Documentation",
162
+ * path: "./docs/api.md",
163
+ * messages: [
164
+ * { role: "user", content: "Create API documentation for these files" },
165
+ * { role: "assistant", content: "I'll help you create API documentation. Please provide the files." },
166
+ * { role: "user", content: "Here are the files: [file contents]" }
167
+ * ],
168
+ * system: "You are a technical documentation writer. Generate clear and concise API documentation.",
169
+ * model: {
170
+ * id: "gpt-4o",
171
+ * provider: "openai"
172
+ * }
173
+ * });
174
+ *
175
+ * @example
116
176
  * // Use alchemy template literals with file collections and temperature control
117
177
  * const modelDocs = await Document("models", {
118
178
  * title: "Data Models",
@@ -149,84 +209,93 @@ export const Document = Resource(
149
209
  async function (
150
210
  this: Context<Document>,
151
211
  id: string,
152
- props: DocumentProps,
212
+ props: DocumentProps
153
213
  ): Promise<Document> {
154
- // Ensure directory exists
155
- await fs.mkdir(path.dirname(props.path), { recursive: true });
214
+ // Validate that either prompt or messages are provided
215
+ if (!props.prompt && !props.messages) {
216
+ throw new Error("Either prompt or messages must be provided");
217
+ }
156
218
 
219
+ // Handle deletion phase
157
220
  if (this.phase === "delete") {
158
- try {
159
- await fs.unlink(props.path);
160
- } catch (error: any) {
161
- // Ignore if file doesn't exist
162
- if (error.code !== "ENOENT") {
163
- throw error;
164
- }
165
- }
166
221
  return this.destroy();
167
222
  }
168
223
 
169
224
  // Use provided system prompt or default
170
225
  const system = props.system || DEFAULT_MD_SYSTEM_PROMPT;
171
226
 
172
- // Generate initial content
173
- const { text } = await generateText({
174
- model: createModel(props),
175
- prompt: props.prompt,
176
- system,
177
- providerOptions: props.model?.options,
178
- ...(props.temperature === undefined
179
- ? {}
180
- : // some models error if you provide it (rather than ignoring it)
181
- { temperature: props.temperature }),
227
+ // Generate initial content with rate limit retry
228
+ const { text } = await withRateLimitRetry(async () => {
229
+ return generateText({
230
+ model: createModel(props),
231
+ ...(props.messages
232
+ ? { messages: props.messages }
233
+ : { prompt: props.prompt! }),
234
+ system,
235
+ maxTokens: props.maxTokens || 8192,
236
+ providerOptions: props.model?.options,
237
+ ...(props.temperature === undefined
238
+ ? {}
239
+ : // some models error if you provide it (rather than ignoring it)
240
+ { temperature: props.temperature }),
241
+ });
182
242
  });
183
243
 
184
244
  // Extract and validate markdown content
185
- let { content, error } = await extractMarkdownContent(text);
245
+ let { content, error } = extractMarkdownContent(text);
186
246
 
187
247
  // Re-prompt if there are validation errors
188
248
  if (error) {
189
249
  const errorSystem = `${system}\n\nERROR: ${error}\n\nPlease try again and ensure your response contains exactly one markdown document inside \`\`\`md fences.`;
190
250
 
191
- const { text: retryText } = await generateText({
192
- model: createModel(props),
193
- prompt: props.prompt,
194
- system: errorSystem,
195
- providerOptions: props.model?.options,
196
- ...(props.temperature === undefined
197
- ? {}
198
- : { temperature: props.temperature }),
251
+ const { text: retryText } = await withRateLimitRetry(async () => {
252
+ return generateText({
253
+ model: createModel(props),
254
+ ...(props.messages
255
+ ? { messages: props.messages }
256
+ : { prompt: props.prompt! }),
257
+ system: errorSystem,
258
+ providerOptions: props.model?.options,
259
+ ...(props.temperature === undefined
260
+ ? {}
261
+ : { temperature: props.temperature }),
262
+ });
199
263
  });
200
264
 
201
- const retryResult = await extractMarkdownContent(retryText);
265
+ const retryResult = extractMarkdownContent(retryText);
202
266
 
203
267
  if (retryResult.error) {
204
268
  throw new Error(
205
- `Failed to generate valid markdown content: ${retryResult.error}`,
269
+ `Failed to generate valid markdown content: ${retryResult.error}`
206
270
  );
207
271
  }
208
272
 
209
273
  content = retryResult.content;
210
274
  }
211
275
 
212
- if (this.phase === "update" && props.path !== this.props.path) {
213
- await ignore("ENOENT", () => fs.unlink(this.props.path));
214
- }
276
+ // Create result object
277
+ const result: Partial<Document> = {
278
+ ...props,
279
+ content,
280
+ messages: [
281
+ ...(props.messages || [{ role: "user", content: props.prompt! }]),
282
+ { role: "assistant", content },
283
+ ],
284
+ createdAt: this.output?.createdAt || Date.now(),
285
+ updatedAt: Date.now(),
286
+ };
215
287
 
216
- // Write content to file
217
- await fs.writeFile(props.path, content);
288
+ // Write file if path is provided
289
+ if (props.path) {
290
+ const filePath = props.path;
291
+ const fileId = `${id}-file`;
218
292
 
219
- // Get file stats for timestamps
220
- const stats = await fs.stat(props.path);
293
+ result.file = await StaticTextFile(fileId, filePath, content);
294
+ }
221
295
 
222
296
  // Return the resource
223
- return this({
224
- ...props,
225
- content: content,
226
- createdAt: stats.birthtimeMs,
227
- updatedAt: stats.mtimeMs,
228
- });
229
- },
297
+ return this(result as Document);
298
+ }
230
299
  );
231
300
 
232
301
  /**
@@ -236,13 +305,14 @@ export const Document = Resource(
236
305
  * @param text The text to extract markdown content from
237
306
  * @returns The extracted markdown content or error message
238
307
  */
239
- async function extractMarkdownContent(
240
- text: string,
241
- ): Promise<{ content: string; error?: string }> {
242
- const mdCodeRegex = /```md\s*([\s\S]*?)```/g;
243
- const matches = Array.from(text.matchAll(mdCodeRegex));
308
+ function extractMarkdownContent(text: string): {
309
+ content: string;
310
+ error?: string;
311
+ } {
312
+ const lines = text.split("\n");
313
+ const startIdx = lines.findIndex((line) => line.trim() === "```md");
244
314
 
245
- if (matches.length === 0) {
315
+ if (startIdx === -1) {
246
316
  return {
247
317
  content: "",
248
318
  error:
@@ -250,13 +320,24 @@ async function extractMarkdownContent(
250
320
  };
251
321
  }
252
322
 
253
- if (matches.length > 1) {
323
+ const rest = lines.slice(startIdx + 1);
324
+ const endRelativeIdx = rest
325
+ .map((line) => line.trim() === "```")
326
+ .lastIndexOf(true);
327
+
328
+ if (endRelativeIdx === -1) {
254
329
  return {
255
330
  content: "",
256
- error:
257
- "Multiple markdown code blocks found in the response. Please provide exactly one markdown block within ```md fences.",
331
+ error: "Markdown block was not closed properly.",
258
332
  };
259
333
  }
260
334
 
261
- return { content: matches[0][1].trim() };
335
+ const endIdx = startIdx + 1 + endRelativeIdx;
336
+
337
+ const content = lines
338
+ .slice(startIdx + 1, endIdx)
339
+ .join("\n")
340
+ .trim();
341
+
342
+ return { content };
262
343
  }
@@ -1,10 +1,8 @@
1
1
  import { generateText } from "ai";
2
- import fs from "node:fs/promises";
3
- import path from "node:path";
4
2
  import type { Context } from "../context";
3
+ import { StaticHTMLFile } from "../fs/static-html-file";
5
4
  import { Resource } from "../resource";
6
5
  import type { Secret } from "../secret";
7
- import { ignore } from "../util/ignore";
8
6
  import { type ModelConfig, createModel } from "./client";
9
7
 
10
8
  /**
@@ -151,20 +149,10 @@ export const HTMLFile = Resource(
151
149
  async function (
152
150
  this: Context<HTMLFile>,
153
151
  id: string,
154
- props: HTMLFileProps,
152
+ props: HTMLFileProps
155
153
  ): Promise<HTMLFile> {
156
- // Ensure directory exists
157
- await fs.mkdir(path.dirname(props.path), { recursive: true });
158
-
154
+ // Handle deletion phase
159
155
  if (this.phase === "delete") {
160
- try {
161
- await fs.unlink(props.path);
162
- } catch (error: any) {
163
- // Ignore if file doesn't exist
164
- if (error.code !== "ENOENT") {
165
- throw error;
166
- }
167
- }
168
156
  return this.destroy();
169
157
  }
170
158
 
@@ -203,31 +191,24 @@ export const HTMLFile = Resource(
203
191
 
204
192
  if (retryResult.error) {
205
193
  throw new Error(
206
- `Failed to generate valid HTML code: ${retryResult.error}`,
194
+ `Failed to generate valid HTML code: ${retryResult.error}`
207
195
  );
208
196
  }
209
197
 
210
198
  code = retryResult.code;
211
199
  }
212
200
 
213
- if (this.phase === "update" && props.path !== this.props.path) {
214
- await ignore("ENOENT", () => fs.unlink(this.props.path));
215
- }
216
-
217
- // Write content to file
218
- await fs.writeFile(props.path, code);
219
-
220
- // Get file stats for timestamps
221
- const stats = await fs.stat(props.path);
201
+ // Use StaticHTMLFile to create/update the file
202
+ const file = await StaticHTMLFile("file", props.path, code);
222
203
 
223
204
  // Return the resource
224
205
  return this({
225
206
  ...props,
226
- content: code,
227
- createdAt: stats.birthtimeMs,
228
- updatedAt: stats.mtimeMs,
207
+ content: file.content,
208
+ createdAt: Date.now(),
209
+ updatedAt: Date.now(),
229
210
  });
230
- },
211
+ }
231
212
  );
232
213
 
233
214
  /**
@@ -238,7 +219,7 @@ export const HTMLFile = Resource(
238
219
  * @returns The extracted HTML code or error message
239
220
  */
240
221
  async function extractHTMLCode(
241
- text: string,
222
+ text: string
242
223
  ): Promise<{ code: string; error?: string }> {
243
224
  const htmlCodeRegex = /```html\s*([\s\S]*?)```/g;
244
225
  const matches = Array.from(text.matchAll(htmlCodeRegex));
package/src/ai/index.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export * from "./approve";
1
2
  export * from "./ark";
2
3
  export * from "./astro-file";
3
4
  export * from "./css-file";
@@ -5,6 +6,7 @@ export * from "./data";
5
6
  export * from "./document";
6
7
  export * from "./html-file";
7
8
  export * from "./json-file";
9
+ export * from "./review";
8
10
  export * from "./typescript-file";
9
11
  export * from "./vue-file";
10
12
  export * from "./yaml-file";
@@ -1,11 +1,9 @@
1
1
  import { generateObject, generateText } from "ai";
2
2
  import type { JsonSchema, Type, type } from "arktype";
3
- import fs from "node:fs/promises";
4
- import path from "node:path";
5
3
  import type { Context } from "../context";
4
+ import { StaticJsonFile } from "../fs/static-json-file";
6
5
  import { Resource } from "../resource";
7
6
  import type { Secret } from "../secret";
8
- import { ignore } from "../util/ignore";
9
7
  import { ark } from "./ark";
10
8
  import { type ModelConfig, createModel } from "./client";
11
9
 
@@ -193,25 +191,11 @@ export const JSONFile = Resource("ai::JSONFile", async function <
193
191
  >(this: Context<JSONFile<T extends Type<any, any> ? type.infer<T> : any>>, id: string, props: JSONFileProps<T>): Promise<
194
192
  JSONFile<T extends Type<any, any> ? type.infer<T> : any>
195
193
  > {
196
- // Ensure directory exists
197
- await fs.mkdir(path.dirname(props.path), { recursive: true });
198
-
194
+ // Handle deletion phase
199
195
  if (this.phase === "delete") {
200
- try {
201
- await fs.unlink(props.path);
202
- } catch (error: any) {
203
- // Ignore if file doesn't exist
204
- if (error.code !== "ENOENT") {
205
- throw error;
206
- }
207
- }
208
196
  return this.destroy();
209
197
  }
210
198
 
211
- // Determine if we should use pretty printing
212
- const pretty = props.pretty !== false;
213
- const indent = props.indent ?? 2;
214
-
215
199
  let jsonContent: string;
216
200
  let jsonObject: any;
217
201
 
@@ -232,9 +216,10 @@ export const JSONFile = Resource("ai::JSONFile", async function <
232
216
  });
233
217
 
234
218
  jsonObject = object;
235
- jsonContent = pretty
236
- ? JSON.stringify(jsonObject, null, indent)
237
- : JSON.stringify(jsonObject);
219
+
220
+ // Use StaticJsonFile to create the file
221
+ const file = await StaticJsonFile("file", props.path, jsonObject);
222
+ jsonContent = file.content;
238
223
  } else {
239
224
  // Use fence-based extraction
240
225
  // Use provided system prompt or default
@@ -277,31 +262,22 @@ export const JSONFile = Resource("ai::JSONFile", async function <
277
262
  content = retryResult.content;
278
263
  }
279
264
 
280
- // Parse JSON
265
+ // Parse JSON to get the object representation
281
266
  jsonObject = JSON.parse(content);
282
267
 
283
- // Format with or without indentation based on pretty option
284
- jsonContent = pretty ? JSON.stringify(jsonObject, null, indent) : content;
268
+ // Use StaticJsonFile to create the file
269
+ const file = await StaticJsonFile("file", props.path, jsonObject);
270
+ jsonContent = file.content;
285
271
  }
286
272
 
287
- if (this.phase === "update" && props.path !== this.props.path) {
288
- await ignore("ENOENT", () => fs.unlink(this.props.path));
289
- }
290
-
291
- // Write content to file
292
- await fs.writeFile(props.path, jsonContent);
293
-
294
- // Get file stats for timestamps
295
- const stats = await fs.stat(props.path);
296
-
297
273
  // Return the resource
298
274
  return this({
299
275
  ...props,
300
276
  schema: props.schema,
301
277
  content: jsonContent,
302
278
  json: jsonObject,
303
- createdAt: stats.birthtimeMs,
304
- updatedAt: stats.mtimeMs,
279
+ createdAt: Date.now(),
280
+ updatedAt: Date.now(),
305
281
  });
306
282
  });
307
283
 
@@ -313,7 +289,7 @@ export const JSONFile = Resource("ai::JSONFile", async function <
313
289
  * @returns The extracted JSON or error message
314
290
  */
315
291
  async function extractJSONContent(
316
- text: string,
292
+ text: string
317
293
  ): Promise<{ content: string; error?: string }> {
318
294
  const jsonCodeRegex = /```json\s*([\s\S]*?)```/g;
319
295
  const matches = Array.from(text.matchAll(jsonCodeRegex));