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,9 +1,7 @@
1
1
  import { generateText } from "ai";
2
- import fs from "node:fs/promises";
3
- import path from "node:path";
2
+ import { StaticTextFile } from "../fs/static-text-file";
4
3
  import { Resource } from "../resource";
5
- import { ignore } from "../util/ignore";
6
- import { createModel } from "./client";
4
+ import { createModel, withRateLimitRetry } from "./client";
7
5
  /**
8
6
  * Default system prompt for markdown document generation
9
7
  */
@@ -13,7 +11,22 @@ const DEFAULT_MD_SYSTEM_PROMPT = "You are a technical documentation writer. Crea
13
11
  * Supports powerful context handling through the alchemy template literal tag.
14
12
  *
15
13
  * @example
16
- * // Create a document using alchemy template literals for context
14
+ * // Create an in-memory document (no file created)
15
+ * const apiDocs = await Document("api-docs", {
16
+ * title: "API Documentation",
17
+ * prompt: await alchemy`
18
+ * Generate API documentation based on these source files:
19
+ * ${alchemy.file("src/api.ts")}
20
+ * ${alchemy.file("src/types.ts")}
21
+ * `,
22
+ * model: {
23
+ * id: "gpt-4o",
24
+ * provider: "openai"
25
+ * }
26
+ * });
27
+ *
28
+ * @example
29
+ * // Create a document and write it to disk
17
30
  * const apiDocs = await Document("api-docs", {
18
31
  * title: "API Documentation",
19
32
  * path: "./docs/api.md",
@@ -29,6 +42,23 @@ const DEFAULT_MD_SYSTEM_PROMPT = "You are a technical documentation writer. Crea
29
42
  * });
30
43
  *
31
44
  * @example
45
+ * // Use message history for iterative document generation
46
+ * const apiDocs = await Document("api-docs", {
47
+ * title: "API Documentation",
48
+ * path: "./docs/api.md",
49
+ * messages: [
50
+ * { role: "user", content: "Create API documentation for these files" },
51
+ * { role: "assistant", content: "I'll help you create API documentation. Please provide the files." },
52
+ * { role: "user", content: "Here are the files: [file contents]" }
53
+ * ],
54
+ * system: "You are a technical documentation writer. Generate clear and concise API documentation.",
55
+ * model: {
56
+ * id: "gpt-4o",
57
+ * provider: "openai"
58
+ * }
59
+ * });
60
+ *
61
+ * @example
32
62
  * // Use alchemy template literals with file collections and temperature control
33
63
  * const modelDocs = await Document("models", {
34
64
  * title: "Data Models",
@@ -61,67 +91,75 @@ const DEFAULT_MD_SYSTEM_PROMPT = "You are a technical documentation writer. Crea
61
91
  * });
62
92
  */
63
93
  export const Document = Resource("docs::Document", async function (id, props) {
64
- // Ensure directory exists
65
- await fs.mkdir(path.dirname(props.path), { recursive: true });
94
+ // Validate that either prompt or messages are provided
95
+ if (!props.prompt && !props.messages) {
96
+ throw new Error("Either prompt or messages must be provided");
97
+ }
98
+ // Handle deletion phase
66
99
  if (this.phase === "delete") {
67
- try {
68
- await fs.unlink(props.path);
69
- }
70
- catch (error) {
71
- // Ignore if file doesn't exist
72
- if (error.code !== "ENOENT") {
73
- throw error;
74
- }
75
- }
76
100
  return this.destroy();
77
101
  }
78
102
  // Use provided system prompt or default
79
103
  const system = props.system || DEFAULT_MD_SYSTEM_PROMPT;
80
- // Generate initial content
81
- const { text } = await generateText({
82
- model: createModel(props),
83
- prompt: props.prompt,
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 }),
104
+ // Generate initial content with rate limit retry
105
+ const { text } = await withRateLimitRetry(async () => {
106
+ return generateText({
107
+ model: createModel(props),
108
+ ...(props.messages
109
+ ? { messages: props.messages }
110
+ : { prompt: props.prompt }),
111
+ system,
112
+ maxTokens: props.maxTokens || 8192,
113
+ providerOptions: props.model?.options,
114
+ ...(props.temperature === undefined
115
+ ? {}
116
+ : // some models error if you provide it (rather than ignoring it)
117
+ { temperature: props.temperature }),
118
+ });
90
119
  });
91
120
  // Extract and validate markdown content
92
- let { content, error } = await extractMarkdownContent(text);
121
+ let { content, error } = extractMarkdownContent(text);
93
122
  // Re-prompt if there are validation errors
94
123
  if (error) {
95
124
  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 }),
125
+ const { text: retryText } = await withRateLimitRetry(async () => {
126
+ return generateText({
127
+ model: createModel(props),
128
+ ...(props.messages
129
+ ? { messages: props.messages }
130
+ : { prompt: props.prompt }),
131
+ system: errorSystem,
132
+ providerOptions: props.model?.options,
133
+ ...(props.temperature === undefined
134
+ ? {}
135
+ : { temperature: props.temperature }),
136
+ });
104
137
  });
105
- const retryResult = await extractMarkdownContent(retryText);
138
+ const retryResult = extractMarkdownContent(retryText);
106
139
  if (retryResult.error) {
107
140
  throw new Error(`Failed to generate valid markdown content: ${retryResult.error}`);
108
141
  }
109
142
  content = retryResult.content;
110
143
  }
111
- if (this.phase === "update" && props.path !== this.props.path) {
112
- await ignore("ENOENT", () => fs.unlink(this.props.path));
144
+ // Create result object
145
+ const result = {
146
+ ...props,
147
+ content,
148
+ messages: [
149
+ ...(props.messages || [{ role: "user", content: props.prompt }]),
150
+ { role: "assistant", content },
151
+ ],
152
+ createdAt: this.output?.createdAt || Date.now(),
153
+ updatedAt: Date.now(),
154
+ };
155
+ // Write file if path is provided
156
+ if (props.path) {
157
+ const filePath = props.path;
158
+ const fileId = `${id}-file`;
159
+ result.file = await StaticTextFile(fileId, filePath, content);
113
160
  }
114
- // Write content to file
115
- await fs.writeFile(props.path, content);
116
- // Get file stats for timestamps
117
- const stats = await fs.stat(props.path);
118
161
  // Return the resource
119
- return this({
120
- ...props,
121
- content: content,
122
- createdAt: stats.birthtimeMs,
123
- updatedAt: stats.mtimeMs,
124
- });
162
+ return this(result);
125
163
  });
126
164
  /**
127
165
  * Extracts markdown content from between ```md fences
@@ -130,20 +168,29 @@ export const Document = Resource("docs::Document", async function (id, props) {
130
168
  * @param text The text to extract markdown content from
131
169
  * @returns The extracted markdown content or error message
132
170
  */
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) {
171
+ function extractMarkdownContent(text) {
172
+ const lines = text.split("\n");
173
+ const startIdx = lines.findIndex((line) => line.trim() === "```md");
174
+ if (startIdx === -1) {
137
175
  return {
138
176
  content: "",
139
177
  error: "No markdown code block found in the response. Please include your markdown content within ```md fences.",
140
178
  };
141
179
  }
142
- if (matches.length > 1) {
180
+ const rest = lines.slice(startIdx + 1);
181
+ const endRelativeIdx = rest
182
+ .map((line) => line.trim() === "```")
183
+ .lastIndexOf(true);
184
+ if (endRelativeIdx === -1) {
143
185
  return {
144
186
  content: "",
145
- error: "Multiple markdown code blocks found in the response. Please provide exactly one markdown block within ```md fences.",
187
+ error: "Markdown block was not closed properly.",
146
188
  };
147
189
  }
148
- return { content: matches[0][1].trim() };
190
+ const endIdx = startIdx + 1 + endRelativeIdx;
191
+ const content = lines
192
+ .slice(startIdx + 1, endIdx)
193
+ .join("\n")
194
+ .trim();
195
+ return { content };
149
196
  }
@@ -1,8 +1,6 @@
1
1
  import { generateText } from "ai";
2
- import fs from "node:fs/promises";
3
- import path from "node:path";
2
+ import { StaticHTMLFile } from "../fs/static-html-file";
4
3
  import { Resource } from "../resource";
5
- import { ignore } from "../util/ignore";
6
4
  import { createModel } from "./client";
7
5
  /**
8
6
  * Default system prompt for HTML file generation
@@ -68,18 +66,8 @@ const DEFAULT_HTML_SYSTEM_PROMPT = "You are an HTML code generator. Create HTML
68
66
  * });
69
67
  */
70
68
  export const HTMLFile = Resource("ai::HTMLFile", async function (id, props) {
71
- // Ensure directory exists
72
- await fs.mkdir(path.dirname(props.path), { recursive: true });
69
+ // Handle deletion phase
73
70
  if (this.phase === "delete") {
74
- try {
75
- await fs.unlink(props.path);
76
- }
77
- catch (error) {
78
- // Ignore if file doesn't exist
79
- if (error.code !== "ENOENT") {
80
- throw error;
81
- }
82
- }
83
71
  return this.destroy();
84
72
  }
85
73
  // Use provided system prompt or default
@@ -114,19 +102,14 @@ export const HTMLFile = Resource("ai::HTMLFile", async function (id, props) {
114
102
  }
115
103
  code = retryResult.code;
116
104
  }
117
- if (this.phase === "update" && props.path !== this.props.path) {
118
- await ignore("ENOENT", () => fs.unlink(this.props.path));
119
- }
120
- // Write content to file
121
- await fs.writeFile(props.path, code);
122
- // Get file stats for timestamps
123
- const stats = await fs.stat(props.path);
105
+ // Use StaticHTMLFile to create/update the file
106
+ const file = await StaticHTMLFile("file", props.path, code);
124
107
  // Return the resource
125
108
  return this({
126
109
  ...props,
127
- content: code,
128
- createdAt: stats.birthtimeMs,
129
- updatedAt: stats.mtimeMs,
110
+ content: file.content,
111
+ createdAt: Date.now(),
112
+ updatedAt: Date.now(),
130
113
  });
131
114
  });
132
115
  /**
package/lib/ai/index.d.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";
package/lib/ai/index.js 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,8 +1,6 @@
1
1
  import { generateObject, generateText } from "ai";
2
- import fs from "node:fs/promises";
3
- import path from "node:path";
2
+ import { StaticJsonFile } from "../fs/static-json-file";
4
3
  import { Resource } from "../resource";
5
- import { ignore } from "../util/ignore";
6
4
  import { ark } from "./ark";
7
5
  import { createModel } from "./client";
8
6
  /**
@@ -77,23 +75,10 @@ const DEFAULT_JSON_SYSTEM_PROMPT = "You are a JSON generator. Create valid JSON
77
75
  * });
78
76
  */
79
77
  export const JSONFile = Resource("ai::JSONFile", async function (id, props) {
80
- // Ensure directory exists
81
- await fs.mkdir(path.dirname(props.path), { recursive: true });
78
+ // Handle deletion phase
82
79
  if (this.phase === "delete") {
83
- try {
84
- await fs.unlink(props.path);
85
- }
86
- catch (error) {
87
- // Ignore if file doesn't exist
88
- if (error.code !== "ENOENT") {
89
- throw error;
90
- }
91
- }
92
80
  return this.destroy();
93
81
  }
94
- // Determine if we should use pretty printing
95
- const pretty = props.pretty !== false;
96
- const indent = props.indent ?? 2;
97
82
  let jsonContent;
98
83
  let jsonObject;
99
84
  // Check if schema is provided
@@ -111,9 +96,9 @@ export const JSONFile = Resource("ai::JSONFile", async function (id, props) {
111
96
  : { temperature: props.temperature }),
112
97
  });
113
98
  jsonObject = object;
114
- jsonContent = pretty
115
- ? JSON.stringify(jsonObject, null, indent)
116
- : JSON.stringify(jsonObject);
99
+ // Use StaticJsonFile to create the file
100
+ const file = await StaticJsonFile("file", props.path, jsonObject);
101
+ jsonContent = file.content;
117
102
  }
118
103
  else {
119
104
  // Use fence-based extraction
@@ -149,26 +134,20 @@ export const JSONFile = Resource("ai::JSONFile", async function (id, props) {
149
134
  }
150
135
  content = retryResult.content;
151
136
  }
152
- // Parse JSON
137
+ // Parse JSON to get the object representation
153
138
  jsonObject = JSON.parse(content);
154
- // Format with or without indentation based on pretty option
155
- jsonContent = pretty ? JSON.stringify(jsonObject, null, indent) : content;
156
- }
157
- if (this.phase === "update" && props.path !== this.props.path) {
158
- await ignore("ENOENT", () => fs.unlink(this.props.path));
139
+ // Use StaticJsonFile to create the file
140
+ const file = await StaticJsonFile("file", props.path, jsonObject);
141
+ jsonContent = file.content;
159
142
  }
160
- // Write content to file
161
- await fs.writeFile(props.path, jsonContent);
162
- // Get file stats for timestamps
163
- const stats = await fs.stat(props.path);
164
143
  // Return the resource
165
144
  return this({
166
145
  ...props,
167
146
  schema: props.schema,
168
147
  content: jsonContent,
169
148
  json: jsonObject,
170
- createdAt: stats.birthtimeMs,
171
- updatedAt: stats.mtimeMs,
149
+ createdAt: Date.now(),
150
+ updatedAt: Date.now(),
172
151
  });
173
152
  });
174
153
  /**
@@ -0,0 +1,122 @@
1
+ import { type CoreMessage } from "ai";
2
+ import type { Context } from "../context";
3
+ import { Resource } from "../resource";
4
+ import type { Secret } from "../secret";
5
+ import { type ModelConfig } from "./client";
6
+ /**
7
+ * Properties for creating or updating a Review
8
+ */
9
+ export interface ReviewProps {
10
+ /**
11
+ * Content to be reviewed
12
+ * Use alchemy template literals to include file context:
13
+ * @example
14
+ * content: await alchemy`
15
+ * Review this code:
16
+ * ${alchemy.file("src/api.ts")}
17
+ * `
18
+ *
19
+ * Required unless messages are provided
20
+ */
21
+ content?: string;
22
+ /**
23
+ * Prompt for the review
24
+ * This should include specific instructions for the review
25
+ * @example
26
+ * prompt: "Review this code for security vulnerabilities, performance issues, and best practices."
27
+ *
28
+ * Required unless messages are provided
29
+ */
30
+ prompt?: string;
31
+ /**
32
+ * Message history for the conversation
33
+ * If provided, this will be used instead of the prompt and content
34
+ */
35
+ messages?: CoreMessage[];
36
+ /**
37
+ * System prompt for the model
38
+ * This is used to provide instructions to the model about how to format the response
39
+ * The default system prompt instructs the model to act as a reviewer
40
+ */
41
+ system?: string;
42
+ /**
43
+ * AI API key to use for generating content
44
+ * If not provided, will use OPENAI_API_KEY environment variable
45
+ */
46
+ apiKey?: Secret;
47
+ /**
48
+ * Model configuration
49
+ */
50
+ model?: ModelConfig;
51
+ /**
52
+ * Temperature for controlling randomness in generation.
53
+ * Higher values (e.g., 0.8) make output more random,
54
+ * lower values (e.g., 0.2) make it more deterministic.
55
+ * @default 0.7
56
+ */
57
+ temperature?: number;
58
+ /**
59
+ * Maximum number of tokens to generate.
60
+ * Higher values allow for longer documents but may increase cost and generation time.
61
+ * @default 10000
62
+ */
63
+ maxTokens?: number;
64
+ }
65
+ /**
66
+ * Output returned after Review creation/update
67
+ */
68
+ export interface Review extends ReviewProps, Resource<"ai::Review"> {
69
+ /**
70
+ * Content of the review
71
+ */
72
+ content: string;
73
+ /**
74
+ * Updated message history with the review response appended
75
+ */
76
+ messages: CoreMessage[];
77
+ /**
78
+ * Time at which the review was created
79
+ */
80
+ createdAt: number;
81
+ /**
82
+ * Time at which the review was last updated
83
+ */
84
+ updatedAt: number;
85
+ }
86
+ /**
87
+ * Creates or updates a Review resource
88
+ *
89
+ * @example
90
+ * // Create a review of a code file
91
+ * const codeReview = await Review("api-review", {
92
+ * content: await alchemy`
93
+ * Review this API implementation:
94
+ * ${alchemy.file("src/api.ts")}
95
+ * `,
96
+ * prompt: "Review this API implementation for security vulnerabilities, performance issues, and best practices. Suggest improvements and alternative approaches."
97
+ * });
98
+ *
99
+ * @example
100
+ * // Create a review of a document
101
+ * const docReview = await Review("tutorial-review", {
102
+ * content: await alchemy`
103
+ * Review this tutorial:
104
+ * ${alchemy.file("docs/tutorials/getting-started.md")}
105
+ * `,
106
+ * prompt: "Review this tutorial for clarity, accuracy, and completeness. Suggest improvements to make it more user-friendly."
107
+ * });
108
+ *
109
+ * @example
110
+ * // Create a review with message history
111
+ * const reviewWithHistory = await Review("code-review-iteration-2", {
112
+ * content: "Updated code implementation",
113
+ * prompt: "Review this updated implementation",
114
+ * messages: [
115
+ * { role: "user", content: "Can you review my code?" },
116
+ * { role: "assistant", content: "I'll review your code. Please share it." },
117
+ * { role: "user", content: "Here's my implementation: [code]" },
118
+ * { role: "assistant", content: "Here's my review of your code: [previous review]" }
119
+ * ]
120
+ * });
121
+ */
122
+ export declare const Review: (((this: any, id: string, props?: {}) => never) & (new (_: never) => never)) | ((this: Context<Review>, id: string, props: ReviewProps) => Promise<Review>);
@@ -0,0 +1,101 @@
1
+ import { generateText } from "ai";
2
+ import { Resource } from "../resource";
3
+ import { createModel, withRateLimitRetry } from "./client";
4
+ /**
5
+ * Default system prompt for review generation
6
+ */
7
+ 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:
8
+
9
+ 1. A high-level summary of the content
10
+ 2. Detailed feedback on the content's quality, accuracy, and completeness
11
+ 3. Specific suggestions for improvement
12
+ 4. Alternative approaches or solutions where applicable
13
+ 5. Best practices that should be followed
14
+ 6. Potential issues or concerns that should be addressed
15
+
16
+ Provide your review in a clear, well-structured format.`;
17
+ /**
18
+ * Creates or updates a Review resource
19
+ *
20
+ * @example
21
+ * // Create a review of a code file
22
+ * const codeReview = await Review("api-review", {
23
+ * content: await alchemy`
24
+ * Review this API implementation:
25
+ * ${alchemy.file("src/api.ts")}
26
+ * `,
27
+ * prompt: "Review this API implementation for security vulnerabilities, performance issues, and best practices. Suggest improvements and alternative approaches."
28
+ * });
29
+ *
30
+ * @example
31
+ * // Create a review of a document
32
+ * const docReview = await Review("tutorial-review", {
33
+ * content: await alchemy`
34
+ * Review this tutorial:
35
+ * ${alchemy.file("docs/tutorials/getting-started.md")}
36
+ * `,
37
+ * prompt: "Review this tutorial for clarity, accuracy, and completeness. Suggest improvements to make it more user-friendly."
38
+ * });
39
+ *
40
+ * @example
41
+ * // Create a review with message history
42
+ * const reviewWithHistory = await Review("code-review-iteration-2", {
43
+ * content: "Updated code implementation",
44
+ * prompt: "Review this updated implementation",
45
+ * messages: [
46
+ * { role: "user", content: "Can you review my code?" },
47
+ * { role: "assistant", content: "I'll review your code. Please share it." },
48
+ * { role: "user", content: "Here's my implementation: [code]" },
49
+ * { role: "assistant", content: "Here's my review of your code: [previous review]" }
50
+ * ]
51
+ * });
52
+ */
53
+ export const Review = Resource("ai::Review", async function (id, props) {
54
+ if (this.phase === "delete") {
55
+ return this.destroy();
56
+ }
57
+ // Use provided system prompt or default
58
+ const system = props.system || DEFAULT_REVIEW_SYSTEM_PROMPT;
59
+ // Create messages array if not provided
60
+ const messages = props.messages ||
61
+ (props.content && props.prompt
62
+ ? [
63
+ {
64
+ role: "user",
65
+ content: `${props.content}\n\n${props.prompt}`,
66
+ },
67
+ ]
68
+ : []);
69
+ if (messages.length === 0) {
70
+ throw new Error("Either messages or both content and prompt must be provided");
71
+ }
72
+ // Generate review content with rate limit retry
73
+ const { text } = await withRateLimitRetry(async () => {
74
+ return generateText({
75
+ model: createModel(props),
76
+ messages,
77
+ system,
78
+ maxTokens: props.maxTokens || 8192,
79
+ providerOptions: props.model?.options,
80
+ ...(props.temperature === undefined
81
+ ? {}
82
+ : { temperature: props.temperature }),
83
+ });
84
+ });
85
+ // Create updated message history with the AI's response
86
+ const updatedMessages = [
87
+ ...messages,
88
+ {
89
+ role: "assistant",
90
+ content: text,
91
+ },
92
+ ];
93
+ // Return the review with the direct text from the LLM and updated messages
94
+ return this({
95
+ ...props,
96
+ content: text,
97
+ messages: updatedMessages,
98
+ createdAt: Date.now(),
99
+ updatedAt: Date.now(),
100
+ });
101
+ });
@@ -1,4 +1,3 @@
1
- import prettier from "prettier";
2
1
  import type { Context } from "../context";
3
2
  import { Resource } from "../resource";
4
3
  import type { Secret } from "../secret";
@@ -53,7 +52,7 @@ export interface TypeScriptFileProps {
53
52
  * Prettier configuration to use for formatting the TypeScript code
54
53
  * If not provided, will use the default Prettier configuration
55
54
  */
56
- prettierConfig?: prettier.Options;
55
+ prettierConfig?: object;
57
56
  }
58
57
  /**
59
58
  * A TypeScript file that can be created, updated, and deleted