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.
- package/lib/ai/approve.d.ts +99 -0
- package/lib/ai/approve.js +76 -0
- package/lib/ai/astro-file.js +7 -24
- package/lib/ai/client.d.ts +7 -0
- package/lib/ai/client.js +45 -0
- package/lib/ai/css-file.js +7 -24
- package/lib/ai/data.d.ts +26 -21
- package/lib/ai/data.js +33 -22
- package/lib/ai/document.d.ts +64 -4
- package/lib/ai/document.js +102 -55
- package/lib/ai/html-file.js +7 -24
- package/lib/ai/index.d.ts +2 -0
- package/lib/ai/index.js +2 -0
- package/lib/ai/json-file.js +11 -32
- package/lib/ai/review.d.ts +122 -0
- package/lib/ai/review.js +101 -0
- package/lib/ai/typescript-file.d.ts +1 -2
- package/lib/ai/typescript-file.js +8 -39
- package/lib/ai/vue-file.js +7 -24
- package/lib/ai/yaml-file.js +12 -48
- package/lib/alchemy.js +17 -2
- package/lib/apply.js +1 -1
- package/lib/cloudflare/generate-asset-manifest.js +1 -1
- package/lib/cloudflare/static-site-router.js +16 -7
- package/lib/cloudflare/static-site.js +3 -0
- package/lib/cloudflare/upload-asset-manifest.js +4 -2
- package/lib/fs/copy-file.d.ts +54 -0
- package/lib/fs/copy-file.js +63 -0
- package/lib/fs/file.d.ts +13 -0
- package/lib/fs/file.js +24 -5
- package/lib/fs/index.d.ts +6 -0
- package/lib/fs/index.js +6 -0
- package/lib/fs/static-astro-file.d.ts +34 -0
- package/lib/fs/static-astro-file.js +39 -0
- package/lib/fs/static-css-file.d.ts +24 -0
- package/lib/fs/static-css-file.js +29 -0
- package/lib/fs/static-html-file.d.ts +31 -0
- package/lib/fs/static-html-file.js +36 -0
- package/lib/fs/static-json-file.d.ts +2 -2
- package/lib/fs/static-json-file.js +3 -2
- package/lib/fs/static-text-file.d.ts +1 -1
- package/lib/fs/static-text-file.js +3 -2
- package/lib/fs/static-typescript-file.d.ts +2 -2
- package/lib/fs/static-typescript-file.js +3 -2
- package/lib/fs/static-vue-file.d.ts +28 -0
- package/lib/fs/static-vue-file.js +33 -0
- package/lib/fs/static-yaml-file.d.ts +11 -8
- package/lib/fs/static-yaml-file.js +3 -2
- package/lib/internal/getting-started.d.ts +15 -4
- package/lib/internal/getting-started.js +34 -41
- package/lib/internal/index.d.ts +3 -0
- package/lib/internal/index.js +3 -0
- package/lib/internal/providers.d.ts +7 -3
- package/lib/internal/providers.js +42 -18
- package/lib/internal/tutorial.d.ts +104 -0
- package/lib/internal/tutorial.js +251 -0
- package/lib/web/vitepress/config.d.ts +10 -0
- package/lib/web/vitepress/config.js +31 -0
- package/lib/web/vitepress/home-page.d.ts +5 -3
- package/lib/web/vitepress/home-page.js +48 -35
- package/lib/web/vitepress/index.d.ts +1 -0
- package/lib/web/vitepress/index.js +1 -0
- package/lib/web/vitepress/vitepress.d.ts +5 -4
- package/lib/web/vitepress/vitepress.js +17 -34
- package/package.json +1 -1
- package/src/ai/approve.ts +163 -0
- package/src/ai/astro-file.ts +11 -30
- package/src/ai/client.ts +59 -0
- package/src/ai/css-file.ts +11 -30
- package/src/ai/data.ts +50 -25
- package/src/ai/document.ts +146 -65
- package/src/ai/html-file.ts +11 -30
- package/src/ai/index.ts +2 -0
- package/src/ai/json-file.ts +13 -37
- package/src/ai/review.ts +213 -0
- package/src/ai/typescript-file.ts +14 -48
- package/src/ai/vue-file.ts +11 -30
- package/src/ai/yaml-file.ts +13 -53
- package/src/alchemy.ts +18 -7
- package/src/apply.ts +8 -8
- package/src/cloudflare/generate-asset-manifest.ts +4 -4
- package/src/cloudflare/static-site-router.ts +18 -8
- package/src/cloudflare/static-site.ts +11 -7
- package/src/cloudflare/upload-asset-manifest.ts +6 -4
- package/src/cloudflare/worker.ts +44 -43
- package/src/fs/copy-file.ts +115 -0
- package/src/fs/file.ts +35 -9
- package/src/fs/index.ts +6 -0
- package/src/fs/static-astro-file.ts +45 -0
- package/src/fs/static-css-file.ts +35 -0
- package/src/fs/static-html-file.ts +42 -0
- package/src/fs/static-json-file.ts +4 -3
- package/src/fs/static-text-file.ts +5 -2
- package/src/fs/static-typescript-file.ts +4 -3
- package/src/fs/static-vue-file.ts +39 -0
- package/src/fs/static-yaml-file.ts +13 -9
- package/src/internal/getting-started.ts +51 -43
- package/src/internal/index.ts +3 -0
- package/src/internal/providers.ts +59 -27
- package/src/internal/tutorial.ts +392 -0
- package/src/web/vitepress/config.ts +48 -0
- package/src/web/vitepress/home-page.ts +54 -42
- package/src/web/vitepress/index.ts +1 -0
- package/src/web/vitepress/vitepress.ts +31 -48
- package/lib/internal/project.d.ts +0 -13
- package/lib/internal/project.js +0 -101
- package/src/internal/project.ts +0 -119
package/lib/ai/document.js
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { generateText } from "ai";
|
|
2
|
-
import
|
|
3
|
-
import path from "node:path";
|
|
2
|
+
import { StaticTextFile } from "../fs/static-text-file";
|
|
4
3
|
import { Resource } from "../resource";
|
|
5
|
-
import {
|
|
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
|
|
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
|
-
//
|
|
65
|
-
|
|
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
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
:
|
|
89
|
-
|
|
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 } =
|
|
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
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
:
|
|
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 =
|
|
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
|
-
|
|
112
|
-
|
|
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
|
-
|
|
134
|
-
const
|
|
135
|
-
const
|
|
136
|
-
if (
|
|
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
|
-
|
|
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: "
|
|
187
|
+
error: "Markdown block was not closed properly.",
|
|
146
188
|
};
|
|
147
189
|
}
|
|
148
|
-
|
|
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
|
}
|
package/lib/ai/html-file.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { generateText } from "ai";
|
|
2
|
-
import
|
|
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
|
-
//
|
|
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
|
-
|
|
118
|
-
|
|
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:
|
|
128
|
-
createdAt:
|
|
129
|
-
updatedAt:
|
|
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";
|
package/lib/ai/json-file.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { generateObject, generateText } from "ai";
|
|
2
|
-
import
|
|
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
|
-
//
|
|
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
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
//
|
|
155
|
-
|
|
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:
|
|
171
|
-
updatedAt:
|
|
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>);
|
package/lib/ai/review.js
ADDED
|
@@ -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?:
|
|
55
|
+
prettierConfig?: object;
|
|
57
56
|
}
|
|
58
57
|
/**
|
|
59
58
|
* A TypeScript file that can be created, updated, and deleted
|