alchemy 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/README.md +8 -3
  2. package/lib/ai/document.d.ts +12 -1
  3. package/lib/ai/document.js +10 -1
  4. package/lib/ai/index.d.ts +0 -2
  5. package/lib/ai/index.js +0 -2
  6. package/lib/alchemy.d.ts +5 -0
  7. package/lib/alchemy.js +44 -1
  8. package/lib/apply.js +17 -10
  9. package/lib/aws/account-id.d.ts +4 -1
  10. package/lib/aws/account-id.js +1 -1
  11. package/lib/aws/credentials.d.ts +5 -0
  12. package/lib/aws/credentials.js +0 -0
  13. package/lib/aws/oidc/oidc-provider.js +2 -2
  14. package/lib/aws/role.js +35 -23
  15. package/lib/cloudflare/account-api-token.d.ts +168 -0
  16. package/lib/cloudflare/account-api-token.js +179 -0
  17. package/lib/cloudflare/account-id.d.ts +1 -0
  18. package/lib/cloudflare/account-id.js +0 -0
  19. package/lib/cloudflare/api.d.ts +38 -1
  20. package/lib/cloudflare/api.js +55 -4
  21. package/lib/cloudflare/bucket.d.ts +118 -3
  22. package/lib/cloudflare/bucket.js +269 -89
  23. package/lib/cloudflare/custom-domain.d.ts +75 -0
  24. package/lib/cloudflare/custom-domain.js +154 -0
  25. package/lib/cloudflare/{dns.js → dns-records.js} +14 -1
  26. package/lib/cloudflare/index.d.ts +5 -1
  27. package/lib/cloudflare/index.js +5 -1
  28. package/lib/cloudflare/permission-groups.d.ts +78 -0
  29. package/lib/cloudflare/permission-groups.js +48 -0
  30. package/lib/cloudflare/r2-rest-state-store.d.ts +2 -1
  31. package/lib/cloudflare/r2-rest-state-store.js +3 -2
  32. package/lib/cloudflare/static-site.d.ts +17 -18
  33. package/lib/cloudflare/static-site.js +19 -10
  34. package/lib/{util/encrypt.d.ts → encrypt.d.ts} +1 -1
  35. package/lib/{util/encrypt.js → encrypt.js} +1 -1
  36. package/lib/fs/file-system-state-store.js +1 -1
  37. package/lib/fs/file.d.ts +18 -0
  38. package/lib/fs/file.js +6 -0
  39. package/lib/internal/{providers.d.ts → docs/providers.d.ts} +10 -4
  40. package/lib/internal/docs/providers.js +196 -0
  41. package/lib/secret.d.ts +3 -0
  42. package/lib/secret.js +13 -0
  43. package/lib/{util/serde.d.ts → serde.d.ts} +1 -1
  44. package/lib/{util/serde.js → serde.js} +12 -4
  45. package/lib/test/bun.js +3 -2
  46. package/lib/util/sha256.d.ts +1 -0
  47. package/lib/util/sha256.js +4 -0
  48. package/lib/web/vitepress/config.d.ts +2 -1
  49. package/lib/web/vitepress/config.js +1 -0
  50. package/lib/web/vitepress/index.d.ts +1 -0
  51. package/lib/web/vitepress/index.js +1 -0
  52. package/lib/web/vitepress/process-front-matter-files.d.ts +18 -0
  53. package/lib/web/vitepress/process-front-matter-files.js +68 -0
  54. package/lib/web/vitepress/vitepress.js +3 -2
  55. package/package.json +8 -4
  56. package/src/ai/document.ts +26 -2
  57. package/src/ai/index.ts +0 -2
  58. package/src/alchemy.ts +56 -2
  59. package/src/apply.ts +21 -20
  60. package/src/aws/account-id.ts +6 -2
  61. package/src/aws/credentials.ts +6 -0
  62. package/src/aws/oidc/oidc-provider.ts +17 -17
  63. package/src/aws/role.ts +68 -54
  64. package/src/cloudflare/account-api-token.ts +365 -0
  65. package/src/cloudflare/account-id.ts +0 -0
  66. package/src/cloudflare/api.ts +88 -17
  67. package/src/cloudflare/bucket.ts +493 -133
  68. package/src/cloudflare/custom-domain.ts +318 -0
  69. package/src/cloudflare/{dns.ts → dns-records.ts} +36 -22
  70. package/src/cloudflare/index.ts +5 -1
  71. package/src/cloudflare/permission-groups.ts +137 -0
  72. package/src/cloudflare/r2-rest-state-store.ts +14 -13
  73. package/src/cloudflare/static-site.ts +20 -32
  74. package/src/dns/import-dns.ts +4 -4
  75. package/src/{util/encrypt.ts → encrypt.ts} +7 -10
  76. package/src/fs/file-system-state-store.ts +5 -5
  77. package/src/fs/file.ts +29 -0
  78. package/src/internal/docs/providers.ts +281 -0
  79. package/src/secret.ts +16 -0
  80. package/src/{util/serde.ts → serde.ts} +16 -10
  81. package/src/test/bun.ts +8 -7
  82. package/src/util/sha256.ts +5 -0
  83. package/src/web/vitepress/config.ts +3 -1
  84. package/src/web/vitepress/index.ts +1 -0
  85. package/src/web/vitepress/process-front-matter-files.ts +98 -0
  86. package/src/web/vitepress/vitepress.ts +3 -2
  87. package/lib/ai/approve.d.ts +0 -99
  88. package/lib/ai/approve.js +0 -76
  89. package/lib/ai/review.d.ts +0 -122
  90. package/lib/ai/review.js +0 -101
  91. package/lib/internal/getting-started.d.ts +0 -21
  92. package/lib/internal/getting-started.js +0 -87
  93. package/lib/internal/index.d.ts +0 -3
  94. package/lib/internal/index.js +0 -3
  95. package/lib/internal/providers.js +0 -172
  96. package/lib/internal/tutorial.d.ts +0 -104
  97. package/lib/internal/tutorial.js +0 -251
  98. package/src/ai/approve.ts +0 -163
  99. package/src/ai/review.ts +0 -213
  100. package/src/internal/getting-started.ts +0 -115
  101. package/src/internal/index.ts +0 -3
  102. package/src/internal/providers.ts +0 -241
  103. package/src/internal/tutorial.ts +0 -392
  104. /package/lib/cloudflare/{dns.d.ts → dns-records.d.ts} +0 -0
@@ -1,6 +1,6 @@
1
- import { Scope } from "../scope";
2
- import { Secret } from "../secret";
3
- import { decryptWithKey, encryptWithKey } from "./encrypt";
1
+ import { decryptWithKey, encrypt } from "./encrypt";
2
+ import { Scope } from "./scope";
3
+ import { Secret } from "./secret";
4
4
 
5
5
  import type { Type } from "arktype";
6
6
 
@@ -18,7 +18,7 @@ export async function serialize(
18
18
  value: any,
19
19
  options?: {
20
20
  encrypt?: boolean;
21
- },
21
+ }
22
22
  ): Promise<any> {
23
23
  if (Array.isArray(value)) {
24
24
  return Promise.all(value.map((value) => serialize(scope, value, options)));
@@ -29,13 +29,17 @@ export async function serialize(
29
29
  return {
30
30
  "@secret":
31
31
  options?.encrypt !== false
32
- ? await encryptWithKey(value.unencrypted, scope.password)
32
+ ? await encrypt(value.unencrypted, scope.password)
33
33
  : value.unencrypted,
34
34
  };
35
35
  } else if (isType(value)) {
36
36
  return {
37
37
  "@schema": value.toJSON(),
38
38
  };
39
+ } else if (value instanceof Date) {
40
+ return {
41
+ "@date": value.toISOString(),
42
+ };
39
43
  } else if (value instanceof Scope) {
40
44
  return undefined;
41
45
  } else if (value && typeof value === "object") {
@@ -44,8 +48,8 @@ export async function serialize(
44
48
  Object.entries(value).map(async ([key, value]) => [
45
49
  key,
46
50
  await serialize(scope, value, options),
47
- ]),
48
- ),
51
+ ])
52
+ )
49
53
  );
50
54
  }
51
55
  return value;
@@ -54,7 +58,7 @@ export async function serialize(
54
58
  export async function deserialize(scope: Scope, value: any): Promise<any> {
55
59
  if (Array.isArray(value)) {
56
60
  return await Promise.all(
57
- value.map(async (item) => await deserialize(scope, item)),
61
+ value.map(async (item) => await deserialize(scope, item))
58
62
  );
59
63
  } else if (value && typeof value === "object") {
60
64
  if (typeof value["@secret"] === "string") {
@@ -64,14 +68,16 @@ export async function deserialize(scope: Scope, value: any): Promise<any> {
64
68
  return new Secret(await decryptWithKey(value["@secret"], scope.password));
65
69
  } else if ("@schema" in value) {
66
70
  return value["@schema"];
71
+ } else if ("@date" in value) {
72
+ return new Date(value["@date"]);
67
73
  } else {
68
74
  return Object.fromEntries(
69
75
  await Promise.all(
70
76
  Object.entries(value).map(async ([key, value]) => [
71
77
  key,
72
78
  await deserialize(scope, value),
73
- ]),
74
- ),
79
+ ])
80
+ )
75
81
  );
76
82
  }
77
83
  }
package/src/test/bun.ts CHANGED
@@ -78,7 +78,7 @@ type test = {
78
78
  name: string,
79
79
  options: TestOptions,
80
80
  fn: (scope: Scope) => Promise<any>,
81
- timeout?: number,
81
+ timeout?: number
82
82
  ): void;
83
83
 
84
84
  /**
@@ -121,6 +121,7 @@ type test = {
121
121
  * ```
122
122
  */
123
123
  export function test(meta: ImportMeta, defaultOptions?: TestOptions): test {
124
+ let isFailed = false;
124
125
  defaultOptions = defaultOptions ?? {};
125
126
  if (
126
127
  defaultOptions.stateStore === undefined &&
@@ -129,7 +130,7 @@ export function test(meta: ImportMeta, defaultOptions?: TestOptions): test {
129
130
  ) {
130
131
  defaultOptions.stateStore = (scope) =>
131
132
  new R2RestStateStore(scope, {
132
- apiKey: process.env.CLOUDFLARE_API_KEY,
133
+ apiKey: alchemy.secret(process.env.CLOUDFLARE_API_KEY),
133
134
  email: process.env.CLOUDFLARE_EMAIL,
134
135
  bucketName: process.env.CLOUDFLARE_BUCKET_NAME!,
135
136
  });
@@ -150,7 +151,7 @@ export function test(meta: ImportMeta, defaultOptions?: TestOptions): test {
150
151
  {
151
152
  // parent: globalTestScope,
152
153
  stateStore: defaultOptions?.stateStore,
153
- },
154
+ }
154
155
  );
155
156
  test.scope = localTestScope;
156
157
 
@@ -170,7 +171,7 @@ export function test(meta: ImportMeta, defaultOptions?: TestOptions): test {
170
171
 
171
172
  // Clean up test scope after all tests complete
172
173
  afterAll(async () => {
173
- if (defaultOptions?.destroy !== false) {
174
+ if (defaultOptions?.destroy !== false && !isFailed) {
174
175
  await alchemy.destroy(test.scope);
175
176
  }
176
177
  });
@@ -192,8 +193,8 @@ export function test(meta: ImportMeta, defaultOptions?: TestOptions): test {
192
193
  obj && typeof obj === "object"
193
194
  ? Object.fromEntries(
194
195
  Object.entries(obj).flatMap(([k, v]) =>
195
- v !== undefined ? [[k, v]] : [],
196
- ),
196
+ v !== undefined ? [[k, v]] : []
197
+ )
197
198
  )
198
199
  : {};
199
200
 
@@ -229,7 +230,7 @@ export function test(meta: ImportMeta, defaultOptions?: TestOptions): test {
229
230
  }
230
231
  }
231
232
  });
232
- },
233
+ }
233
234
  );
234
235
  }
235
236
  }
@@ -0,0 +1,5 @@
1
+ import { createHash } from "node:crypto";
2
+
3
+ export function sha256(value: string): string {
4
+ return createHash("sha256").update(value).digest("hex");
5
+ }
@@ -1,11 +1,12 @@
1
1
  import path from "path";
2
- import type { DefaultTheme, ThemeOptions } from "vitepress";
2
+ import type { DefaultTheme, HeadConfig, ThemeOptions } from "vitepress";
3
3
  import { StaticTypeScriptFile } from "../../fs/static-typescript-file";
4
4
 
5
5
  export type VitePressConfigProps = {
6
6
  cwd: string;
7
7
  title?: string;
8
8
  description?: string;
9
+ head?: HeadConfig[];
9
10
  theme?: ThemeOptions;
10
11
  themeConfig: DefaultTheme.Config;
11
12
  };
@@ -23,6 +24,7 @@ import { defineConfig } from "vitepress";
23
24
  export default defineConfig({
24
25
  title: ${JSON.stringify(props.title || "Alchemy")},
25
26
  description: ${JSON.stringify(props.description || "Alchemy Docs")},
27
+ head: ${JSON.stringify(props.head || [])},
26
28
  markdown: {
27
29
  // @ts-ignore
28
30
  codeTransformers: [transformerTwoslash()],
@@ -1,4 +1,5 @@
1
1
  export * from "./config";
2
2
  export * from "./custom-theme";
3
3
  export * from "./home-page";
4
+ export * from "./process-front-matter-files";
4
5
  export * from "./vitepress";
@@ -0,0 +1,98 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ /**
4
+ * Process markdown files with frontmatter to generate navigation items
5
+ * @param directoryPath The directory containing markdown files
6
+ * @param linkPrefix The prefix for navigation links
7
+ * @returns Sorted array of navigation items with text and link properties
8
+ */
9
+ export async function processFrontmatterFiles(
10
+ directoryPath: string,
11
+ linkPrefix: string
12
+ ): Promise<
13
+ {
14
+ text: string;
15
+ link: string;
16
+ items?: {
17
+ text: string;
18
+ link: string;
19
+ items?: { text: string; link: string }[];
20
+ }[];
21
+ }[]
22
+ > {
23
+ const entries = await fs.readdir(directoryPath, { withFileTypes: true });
24
+
25
+ const processedEntries = await Promise.all(
26
+ entries
27
+ .filter((entry) => !entry.name.endsWith("index.md"))
28
+ .map(async (entry) => {
29
+ const fullPath = path.join(directoryPath, entry.name);
30
+
31
+ if (entry.isDirectory()) {
32
+ // Process subdirectory recursively
33
+ const items = await processFrontmatterFiles(
34
+ fullPath,
35
+ `${linkPrefix}/${entry.name}`
36
+ );
37
+
38
+ // Create section for subdirectory
39
+ return {
40
+ text: entry.name.charAt(0).toUpperCase() + entry.name.slice(1),
41
+ collapsed: false,
42
+ items,
43
+ order: 10000, // Default order for directories
44
+ };
45
+ }
46
+
47
+ // Process markdown file
48
+ if (!entry.name.endsWith(".md")) {
49
+ return null;
50
+ }
51
+
52
+ const content = await fs.readFile(fullPath, "utf-8");
53
+ const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
54
+ let order = 10000;
55
+ let title;
56
+
57
+ if (frontmatterMatch) {
58
+ const frontmatter = frontmatterMatch[1];
59
+ const orderMatch = frontmatter.match(/order:\s*(\d+)/);
60
+ if (orderMatch) {
61
+ order = parseInt(orderMatch[1]);
62
+ }
63
+ const titleMatch = frontmatter.match(/title:\s*(.+)/);
64
+ if (titleMatch) {
65
+ title = titleMatch[1].trim();
66
+ }
67
+ }
68
+
69
+ // If no title in frontmatter, try to get first heading
70
+ if (!title) {
71
+ const headingMatch = content.match(/^#\s+(.+)$/m);
72
+ if (headingMatch) {
73
+ title = headingMatch[1].trim();
74
+ }
75
+ }
76
+
77
+ // Fall back to filename if no title found
78
+ if (!title) {
79
+ const name = entry.name.replace(".md", "");
80
+ title = name.charAt(0).toUpperCase() + name.slice(1);
81
+ }
82
+
83
+ return {
84
+ text: title,
85
+ link: `${linkPrefix}/${entry.name}`,
86
+ order,
87
+ };
88
+ })
89
+ );
90
+
91
+ // Filter out null entries and sort by order
92
+ return processedEntries
93
+ .filter((entry) => entry !== null)
94
+ .sort((a, b) => a.order - b.order)
95
+ .map(({ text, link, items }) =>
96
+ items ? { text, items, collapsed: true } : { text, link }
97
+ ) as any;
98
+ }
@@ -117,8 +117,9 @@ export const VitepressProject = Resource(
117
117
  try {
118
118
  if (props.delete !== false) {
119
119
  if (await fs.exists(dir)) {
120
- // Delete the entire project directory
121
- await execAsync(`rm -rf ${dir}`);
120
+ await fs.rm(dir, { recursive: true, force: true });
121
+ // // Delete the entire project directory
122
+ // await execAsync(`rm -rf ${dir}`);
122
123
  }
123
124
  }
124
125
  } catch (error) {
@@ -1,99 +0,0 @@
1
- import type { CoreMessage } from "ai";
2
- /**
3
- * Properties for approving or denying content
4
- */
5
- export interface ApproveProps {
6
- /**
7
- * Content to be reviewed
8
- * Use alchemy template literals to include file context:
9
- * @example
10
- * content: await alchemy`
11
- * Review this code:
12
- * ${alchemy.file("src/api.ts")}
13
- * `
14
- *
15
- * Required unless messages are provided
16
- */
17
- content?: string;
18
- /**
19
- * Prompt for the approval decision
20
- * This should include specific criteria for approval or denial
21
- * @example
22
- * prompt: "Approve this code if it follows security best practices and has proper error handling."
23
- *
24
- * Required unless messages are provided
25
- */
26
- prompt?: string;
27
- /**
28
- * System prompt for the model
29
- * This is used to provide instructions to the model about how to make the decision
30
- */
31
- system?: string;
32
- /**
33
- * Message history for the conversation
34
- * If provided, this will be used instead of the prompt and content
35
- */
36
- messages?: CoreMessage[];
37
- /**
38
- * Temperature for controlling randomness in generation.
39
- * Higher values (e.g., 0.8) make output more random,
40
- * lower values (e.g., 0.2) make it more deterministic.
41
- * @default 0.2 (lower for more consistent decisions)
42
- */
43
- temperature?: number;
44
- }
45
- /**
46
- * Result of the approval process
47
- */
48
- export interface ApprovalResult {
49
- /**
50
- * Whether the content was approved
51
- */
52
- approved: boolean;
53
- /**
54
- * Explanation for the decision
55
- */
56
- explanation: string;
57
- /**
58
- * Suggestions for improvement if denied
59
- */
60
- suggestions?: string[];
61
- /**
62
- * Updated message history with the approval response appended
63
- */
64
- messages: CoreMessage[];
65
- }
66
- export type Approve = ApprovalResult;
67
- /**
68
- * Approves or denies content based on a prompt or message history
69
- *
70
- * @example
71
- * // Approve or deny a code file
72
- * const result = await Approve("code-approval", {
73
- * content: await alchemy`
74
- * Review this API implementation:
75
- * ${alchemy.file("src/api.ts")}
76
- * `,
77
- * prompt: "Approve this code if it follows security best practices and has proper error handling."
78
- * });
79
- *
80
- * if (result.approved) {
81
- * console.log("Content approved:", result.explanation);
82
- * } else {
83
- * console.log("Content denied:", result.explanation);
84
- * console.log("Suggestions:", result.suggestions);
85
- * }
86
- *
87
- * @example
88
- * // Approve or deny using message history
89
- * const result = await Approve("doc-approval-with-history", {
90
- * content: "This is the content to review",
91
- * prompt: "Approve this documentation if it is clear and accurate",
92
- * messages: [
93
- * { role: "user", content: "Can you review this documentation?" },
94
- * { role: "assistant", content: "Yes, I'd be happy to review it." },
95
- * { role: "user", content: "Please check for clarity and accuracy." }
96
- * ]
97
- * });
98
- */
99
- export declare function Approve(id: string, props: ApproveProps): Promise<ApprovalResult>;
package/lib/ai/approve.js DELETED
@@ -1,76 +0,0 @@
1
- import { type } from "arktype";
2
- import { Data } from "./data";
3
- /**
4
- * Default system prompt for approval decisions
5
- */
6
- const DEFAULT_APPROVE_SYSTEM_PROMPT = `You are an expert content reviewer tasked with approving or denying content based on specific criteria.
7
-
8
- Your role is to:
9
- 1. Carefully evaluate the content against the provided criteria
10
- 2. Make a clear decision to approve or deny the content
11
- 3. Provide a detailed explanation for your decision
12
- 4. If denying, offer specific suggestions for improvement
13
-
14
- Be objective, thorough, and fair in your assessment.`;
15
- /**
16
- * Approves or denies content based on a prompt or message history
17
- *
18
- * @example
19
- * // Approve or deny a code file
20
- * const result = await Approve("code-approval", {
21
- * content: await alchemy`
22
- * Review this API implementation:
23
- * ${alchemy.file("src/api.ts")}
24
- * `,
25
- * prompt: "Approve this code if it follows security best practices and has proper error handling."
26
- * });
27
- *
28
- * if (result.approved) {
29
- * console.log("Content approved:", result.explanation);
30
- * } else {
31
- * console.log("Content denied:", result.explanation);
32
- * console.log("Suggestions:", result.suggestions);
33
- * }
34
- *
35
- * @example
36
- * // Approve or deny using message history
37
- * const result = await Approve("doc-approval-with-history", {
38
- * content: "This is the content to review",
39
- * prompt: "Approve this documentation if it is clear and accurate",
40
- * messages: [
41
- * { role: "user", content: "Can you review this documentation?" },
42
- * { role: "assistant", content: "Yes, I'd be happy to review it." },
43
- * { role: "user", content: "Please check for clarity and accuracy." }
44
- * ]
45
- * });
46
- */
47
- export async function Approve(id, props) {
48
- // Create messages array if not provided
49
- const messages = props.messages ||
50
- (props.content && props.prompt
51
- ? [
52
- {
53
- role: "user",
54
- content: `${props.content}\n\n${props.prompt}`,
55
- },
56
- ]
57
- : []);
58
- if (messages.length === 0) {
59
- throw new Error("Either messages or both content and prompt must be provided");
60
- }
61
- const data = await Data(id, {
62
- schema: type({
63
- approved: "boolean",
64
- explanation: "string",
65
- suggestions: "string[]?",
66
- }),
67
- messages,
68
- system: props.system || DEFAULT_APPROVE_SYSTEM_PROMPT,
69
- temperature: props.temperature ?? 0.2,
70
- });
71
- // Return the result with updated messages from Data
72
- return {
73
- ...data.object,
74
- messages: data.messages,
75
- };
76
- }
@@ -1,122 +0,0 @@
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 DELETED
@@ -1,101 +0,0 @@
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
- });