alchemy 0.2.5 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/lib/ai/approve.d.ts +99 -0
- package/lib/ai/approve.js +76 -0
- package/lib/ai/astro-file.d.ts +137 -0
- package/lib/ai/astro-file.js +156 -0
- package/lib/ai/client.d.ts +7 -8
- package/lib/ai/client.js +48 -19
- package/lib/ai/css-file.d.ts +131 -0
- package/lib/ai/css-file.js +141 -0
- package/lib/ai/data.d.ts +51 -6
- package/lib/ai/data.js +58 -12
- package/lib/ai/document.d.ts +108 -24
- package/lib/ai/document.js +166 -52
- package/lib/ai/html-file.d.ts +128 -0
- package/lib/ai/html-file.js +138 -0
- package/lib/ai/index.d.ts +9 -0
- package/lib/ai/index.js +9 -0
- package/lib/ai/json-file.d.ts +160 -0
- package/lib/ai/json-file.js +187 -0
- package/lib/ai/review.d.ts +122 -0
- package/lib/ai/review.js +101 -0
- package/lib/ai/typescript-file.d.ts +139 -0
- package/lib/ai/typescript-file.js +145 -0
- package/lib/ai/vue-file.d.ts +126 -0
- package/lib/ai/vue-file.js +136 -0
- package/lib/ai/yaml-file.d.ts +169 -0
- package/lib/ai/yaml-file.js +200 -0
- package/lib/alchemy.js +17 -2
- package/lib/apply.js +1 -1
- package/lib/cloudflare/dns.d.ts +88 -0
- package/lib/cloudflare/dns.js +192 -0
- package/lib/cloudflare/generate-asset-manifest.js +1 -1
- package/lib/cloudflare/index.d.ts +2 -1
- package/lib/cloudflare/index.js +2 -1
- package/lib/cloudflare/r2-rest-state-store.d.ts +125 -0
- package/lib/cloudflare/r2-rest-state-store.js +254 -0
- package/lib/cloudflare/response.d.ts +12 -0
- package/lib/cloudflare/response.js +0 -0
- 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/cloudflare/zone.d.ts +18 -0
- package/lib/cloudflare/zone.js +17 -4
- package/lib/dns/godaddy.d.ts +7 -0
- package/lib/dns/godaddy.js +21 -0
- package/lib/dns/import-dns.d.ts +82 -0
- package/lib/dns/import-dns.js +114 -0
- package/lib/dns/index.d.ts +2 -0
- package/lib/dns/index.js +2 -0
- package/lib/dns/record.d.ts +88 -0
- package/lib/dns/record.js +13 -0
- package/lib/fs/copy-file.d.ts +54 -0
- package/lib/fs/copy-file.js +63 -0
- package/lib/fs/file-system-state-store.d.ts +17 -0
- package/lib/fs/file-system-state-store.js +87 -0
- package/lib/fs/file.d.ts +13 -0
- package/lib/fs/file.js +24 -5
- package/lib/fs/folder.d.ts +10 -0
- package/lib/fs/folder.js +2 -2
- package/lib/fs/index.d.ts +10 -4
- package/lib/fs/index.js +10 -4
- 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/{json-file.d.ts → static-json-file.d.ts} +3 -3
- package/lib/fs/static-json-file.js +15 -0
- package/lib/fs/{text-file.d.ts → static-text-file.d.ts} +2 -2
- package/lib/fs/static-text-file.js +8 -0
- package/lib/fs/{typescript-file.d.ts → static-typescript-file.d.ts} +3 -3
- package/lib/fs/{typescript-file.js → 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 +22 -0
- package/lib/fs/{yaml-file.js → static-yaml-file.js} +3 -2
- package/lib/internal/getting-started.d.ts +21 -0
- package/lib/internal/getting-started.js +87 -0
- package/lib/internal/index.d.ts +3 -0
- package/lib/internal/index.js +3 -0
- package/lib/internal/providers.d.ts +27 -0
- package/lib/internal/providers.js +172 -0
- package/lib/internal/tutorial.d.ts +104 -0
- package/lib/internal/tutorial.js +251 -0
- package/lib/resource.js +8 -1
- package/lib/scope.d.ts +1 -1
- package/lib/scope.js +4 -2
- package/lib/state.d.ts +1 -16
- package/lib/state.js +0 -87
- package/lib/test/bun.d.ts +11 -0
- package/lib/test/bun.js +36 -8
- package/lib/web/astro.d.ts +147 -0
- package/lib/web/astro.js +414 -0
- package/lib/{shadcn/component.js → web/shadcn-component.js} +2 -9
- package/lib/web/shadcn.d.ts +97 -0
- package/lib/web/shadcn.js +99 -0
- package/lib/web/tailwind.d.ts +64 -0
- package/lib/web/tailwind.js +97 -0
- package/lib/{vite → web}/vite.d.ts +5 -0
- package/lib/{vite → web}/vite.js +44 -44
- package/lib/web/vitepress/config.d.ts +10 -0
- package/lib/web/vitepress/config.js +31 -0
- package/lib/web/vitepress/custom-theme.d.ts +173 -0
- package/lib/web/vitepress/custom-theme.js +336 -0
- package/lib/{vitepress → web/vitepress}/dependencies.d.ts +2 -2
- package/lib/{vitepress → web/vitepress}/dependencies.js +1 -1
- package/lib/web/vitepress/home-page.d.ts +246 -0
- package/lib/web/vitepress/home-page.js +147 -0
- package/lib/{vitepress → web/vitepress}/index.d.ts +2 -0
- package/lib/{vitepress → web/vitepress}/index.js +2 -0
- package/lib/{vitepress → web/vitepress}/vitepress.d.ts +15 -12
- package/lib/{vitepress → web/vitepress}/vitepress.js +26 -44
- package/package.json +6 -2
- package/src/ai/approve.ts +163 -0
- package/src/ai/astro-file.ts +269 -0
- package/src/ai/client.ts +60 -21
- package/src/ai/css-file.ts +247 -0
- package/src/ai/data.ts +81 -19
- package/src/ai/document.ts +240 -62
- package/src/ai/html-file.ts +244 -0
- package/src/ai/index.ts +9 -0
- package/src/ai/json-file.ts +325 -0
- package/src/ai/review.ts +213 -0
- package/src/ai/typescript-file.ts +259 -0
- package/src/ai/vue-file.ts +242 -0
- package/src/ai/yaml-file.ts +328 -0
- package/src/alchemy.ts +18 -7
- package/src/apply.ts +8 -9
- package/src/cloudflare/dns.ts +344 -0
- package/src/cloudflare/generate-asset-manifest.ts +4 -4
- package/src/cloudflare/index.ts +2 -1
- package/src/cloudflare/r2-rest-state-store.ts +353 -0
- package/src/cloudflare/response.ts +9 -0
- 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/cloudflare/zone.ts +39 -23
- package/src/dns/godaddy.ts +29 -0
- package/src/dns/import-dns.ts +213 -0
- package/src/dns/index.ts +2 -0
- package/src/dns/record.ts +121 -0
- package/src/fs/copy-file.ts +115 -0
- package/src/fs/file-system-state-store.ts +109 -0
- package/src/fs/file.ts +35 -9
- package/src/fs/folder.ts +14 -2
- package/src/fs/index.ts +10 -4
- 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 +34 -0
- package/src/fs/{text-file.ts → static-text-file.ts} +9 -3
- package/src/fs/{typescript-file.ts → static-typescript-file.ts} +7 -6
- package/src/fs/static-vue-file.ts +39 -0
- package/src/fs/static-yaml-file.ts +33 -0
- package/src/internal/getting-started.ts +115 -0
- package/src/internal/index.ts +3 -0
- package/src/internal/providers.ts +241 -0
- package/src/internal/tutorial.ts +392 -0
- package/src/resource.ts +10 -1
- package/src/scope.ts +5 -6
- package/src/state.ts +1 -111
- package/src/test/bun.ts +60 -10
- package/src/web/astro.ts +644 -0
- package/src/{shadcn/component.ts → web/shadcn-component.ts} +5 -13
- package/src/web/shadcn.ts +219 -0
- package/src/web/tailwind.ts +167 -0
- package/src/{vite → web}/vite.ts +54 -56
- package/src/web/vitepress/config.ts +48 -0
- package/src/web/vitepress/custom-theme.ts +514 -0
- package/src/{vitepress → web/vitepress}/dependencies.ts +2 -2
- package/src/web/vitepress/home-page.ts +375 -0
- package/src/{vitepress → web/vitepress}/index.ts +2 -0
- package/src/{vitepress → web/vitepress}/vitepress.ts +62 -72
- package/lib/cloudflare/state.d.ts +0 -82
- package/lib/cloudflare/state.js +0 -108
- package/lib/fs/json-file.js +0 -7
- package/lib/fs/text-file.js +0 -7
- package/lib/fs/yaml-file.d.ts +0 -19
- package/lib/internal/docs.d.ts +0 -5
- package/lib/internal/docs.js +0 -287
- package/lib/shadcn/index.d.ts +0 -1
- package/lib/shadcn/index.js +0 -1
- package/lib/vite/index.d.ts +0 -1
- package/lib/vite/index.js +0 -1
- package/lib/vitepress/home-page.d.ts +0 -133
- package/lib/vitepress/home-page.js +0 -11
- package/src/cloudflare/state.ts +0 -138
- package/src/fs/json-file.ts +0 -23
- package/src/fs/yaml-file.ts +0 -26
- package/src/internal/docs.ts +0 -338
- package/src/shadcn/index.ts +0 -1
- package/src/vite/index.ts +0 -1
- package/src/vitepress/home-page.ts +0 -166
- /package/lib/{shadcn/component.d.ts → web/shadcn-component.d.ts} +0 -0
- /package/src/{vitepress → web/vitepress}/index.md +0 -0
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ Resources are simple memoized async functions that can run in any JavaScript run
|
|
|
9
9
|
```ts
|
|
10
10
|
import alchemy from "alchemy";
|
|
11
11
|
|
|
12
|
-
await using
|
|
12
|
+
await using app = alchemy("cloudflare-worker");
|
|
13
13
|
|
|
14
14
|
export const worker = await Worker("worker", {
|
|
15
15
|
name: "my-worker",
|
|
@@ -38,8 +38,8 @@ export const worker = await Worker("worker", {
|
|
|
38
38
|
|
|
39
39
|
# Examples
|
|
40
40
|
|
|
41
|
-
- CloudFlare ViteJS Website + API Backend with Durable Objects: [examples/cloudflare-vite/](./examples/cloudflare-vite/alchemy.
|
|
42
|
-
- Deploy an AWS Lambda Function with a DynamoDB Table and IAM Role: [examples/aws-app/](./examples/aws-app/alchemy.
|
|
41
|
+
- CloudFlare ViteJS Website + API Backend with Durable Objects: [examples/cloudflare-vite/](./examples/cloudflare-vite/alchemy.run.ts)
|
|
42
|
+
- Deploy an AWS Lambda Function with a DynamoDB Table and IAM Role: [examples/aws-app/](./examples/aws-app/alchemy.run.ts)
|
|
43
43
|
|
|
44
44
|
# Getting Started
|
|
45
45
|
|
|
@@ -0,0 +1,99 @@
|
|
|
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>;
|
|
@@ -0,0 +1,76 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import prettier from "prettier";
|
|
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 an AstroFile
|
|
8
|
+
*/
|
|
9
|
+
export interface AstroFileProps {
|
|
10
|
+
/**
|
|
11
|
+
* Path to the Astro file
|
|
12
|
+
*/
|
|
13
|
+
path: string;
|
|
14
|
+
/**
|
|
15
|
+
* Base URL for the OpenAI API
|
|
16
|
+
* @default 'https://api.openai.com/v1'
|
|
17
|
+
*/
|
|
18
|
+
baseURL?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Prompt for generating content
|
|
21
|
+
* Use alchemy template literals to include file context:
|
|
22
|
+
* @example
|
|
23
|
+
* prompt: await alchemy`
|
|
24
|
+
* Generate an Astro component using:
|
|
25
|
+
* ${alchemy.file("src/types.ts")}
|
|
26
|
+
* `
|
|
27
|
+
*/
|
|
28
|
+
prompt: string;
|
|
29
|
+
/**
|
|
30
|
+
* System prompt for the model
|
|
31
|
+
* This is used to provide instructions to the model about how to format the response
|
|
32
|
+
* The default system prompt instructs the model to return Astro code inside ```astro fences
|
|
33
|
+
* @default "You are an Astro component generator. Create Astro components based on the user's requirements. Your response MUST include only Astro code inside ```astro fences. Do not include any other text, explanations, or multiple code blocks."
|
|
34
|
+
*/
|
|
35
|
+
system?: string;
|
|
36
|
+
/**
|
|
37
|
+
* OpenAI API key to use for generating content
|
|
38
|
+
* If not provided, will use OPENAI_API_KEY environment variable
|
|
39
|
+
*/
|
|
40
|
+
apiKey?: Secret;
|
|
41
|
+
/**
|
|
42
|
+
* Model configuration
|
|
43
|
+
*/
|
|
44
|
+
model?: ModelConfig;
|
|
45
|
+
/**
|
|
46
|
+
* Temperature for controlling randomness in generation.
|
|
47
|
+
* Higher values (e.g., 0.8) make output more random,
|
|
48
|
+
* lower values (e.g., 0.2) make it more deterministic.
|
|
49
|
+
* @default 0.7
|
|
50
|
+
*/
|
|
51
|
+
temperature?: number;
|
|
52
|
+
/**
|
|
53
|
+
* Prettier configuration to use for formatting the Astro code
|
|
54
|
+
* If not provided, will use the default Prettier configuration
|
|
55
|
+
*/
|
|
56
|
+
prettierConfig?: prettier.Options;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* An Astro file that can be created, updated, and deleted
|
|
60
|
+
*/
|
|
61
|
+
export interface AstroFile extends AstroFileProps, Resource<"ai::AstroFile"> {
|
|
62
|
+
/**
|
|
63
|
+
* Content of the Astro file
|
|
64
|
+
*/
|
|
65
|
+
content: string;
|
|
66
|
+
/**
|
|
67
|
+
* Time at which the file was created
|
|
68
|
+
*/
|
|
69
|
+
createdAt: number;
|
|
70
|
+
/**
|
|
71
|
+
* Time at which the file was last updated
|
|
72
|
+
*/
|
|
73
|
+
updatedAt: number;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Resource for generating Astro files using AI models.
|
|
77
|
+
* Extracts Astro code from between ```astro fences, validates the response,
|
|
78
|
+
* and formats the code with Prettier.
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* // Create a simple Astro component
|
|
82
|
+
* const header = await AstroFile("header", {
|
|
83
|
+
* path: "./src/components/Header.astro",
|
|
84
|
+
* prompt: await alchemy`
|
|
85
|
+
* Generate an Astro header component with:
|
|
86
|
+
* - Site logo
|
|
87
|
+
* - Navigation menu with Home, About, Services, Contact links
|
|
88
|
+
* - Mobile responsive design
|
|
89
|
+
* - Dark/light mode toggle
|
|
90
|
+
* `,
|
|
91
|
+
* model: {
|
|
92
|
+
* id: "gpt-4o",
|
|
93
|
+
* provider: "openai"
|
|
94
|
+
* }
|
|
95
|
+
* });
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* // Generate an Astro page with data fetching
|
|
99
|
+
* const blogPost = await AstroFile("blog-post", {
|
|
100
|
+
* path: "./src/pages/blog/[slug].astro",
|
|
101
|
+
* prompt: await alchemy`
|
|
102
|
+
* Create an Astro blog post page that:
|
|
103
|
+
* - Uses getStaticPaths to generate pages from a CMS
|
|
104
|
+
* - Renders markdown content
|
|
105
|
+
* - Includes author info, publication date, and related posts
|
|
106
|
+
* - Has social sharing buttons
|
|
107
|
+
*
|
|
108
|
+
* Use the following types:
|
|
109
|
+
* ${alchemy.file("src/types/Blog.ts")}
|
|
110
|
+
* `,
|
|
111
|
+
* temperature: 0.2,
|
|
112
|
+
* prettierConfig: {
|
|
113
|
+
* semi: false,
|
|
114
|
+
* singleQuote: true,
|
|
115
|
+
* printWidth: 120
|
|
116
|
+
* }
|
|
117
|
+
* });
|
|
118
|
+
*
|
|
119
|
+
* @example
|
|
120
|
+
* // Generate a layout with custom system prompt
|
|
121
|
+
* const mainLayout = await AstroFile("main-layout", {
|
|
122
|
+
* path: "./src/layouts/MainLayout.astro",
|
|
123
|
+
* prompt: await alchemy`
|
|
124
|
+
* Create the main layout for an Astro site that:
|
|
125
|
+
* - Includes common head metadata and SEO optimization
|
|
126
|
+
* - Has slots for page content, header, and footer
|
|
127
|
+
* - Imports and uses the Header and Footer components
|
|
128
|
+
* - Sets up viewport and responsive configurations
|
|
129
|
+
* `,
|
|
130
|
+
* system: "You are an expert Astro developer. Create a single Astro layout file inside ```astro fences with no additional text. Follow Astro best practices and include proper typing in the frontmatter section.",
|
|
131
|
+
* model: {
|
|
132
|
+
* id: "claude-3-opus-20240229",
|
|
133
|
+
* provider: "anthropic"
|
|
134
|
+
* }
|
|
135
|
+
* });
|
|
136
|
+
*/
|
|
137
|
+
export declare const AstroFile: (((this: any, id: string, props?: {}) => never) & (new (_: never) => never)) | ((this: Context<AstroFile>, id: string, props: AstroFileProps) => Promise<AstroFile>);
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { generateText } from "ai";
|
|
2
|
+
import prettier from "prettier";
|
|
3
|
+
import { StaticAstroFile } from "../fs/static-astro-file";
|
|
4
|
+
import { Resource } from "../resource";
|
|
5
|
+
import { createModel } from "./client";
|
|
6
|
+
/**
|
|
7
|
+
* Default system prompt for Astro file generation
|
|
8
|
+
*/
|
|
9
|
+
const DEFAULT_ASTRO_SYSTEM_PROMPT = "You are an Astro component generator. Create Astro components based on the user's requirements. Your response MUST include only Astro code inside ```astro fences. Do not include any other text, explanations, or multiple code blocks.";
|
|
10
|
+
/**
|
|
11
|
+
* Resource for generating Astro files using AI models.
|
|
12
|
+
* Extracts Astro code from between ```astro fences, validates the response,
|
|
13
|
+
* and formats the code with Prettier.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* // Create a simple Astro component
|
|
17
|
+
* const header = await AstroFile("header", {
|
|
18
|
+
* path: "./src/components/Header.astro",
|
|
19
|
+
* prompt: await alchemy`
|
|
20
|
+
* Generate an Astro header component with:
|
|
21
|
+
* - Site logo
|
|
22
|
+
* - Navigation menu with Home, About, Services, Contact links
|
|
23
|
+
* - Mobile responsive design
|
|
24
|
+
* - Dark/light mode toggle
|
|
25
|
+
* `,
|
|
26
|
+
* model: {
|
|
27
|
+
* id: "gpt-4o",
|
|
28
|
+
* provider: "openai"
|
|
29
|
+
* }
|
|
30
|
+
* });
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* // Generate an Astro page with data fetching
|
|
34
|
+
* const blogPost = await AstroFile("blog-post", {
|
|
35
|
+
* path: "./src/pages/blog/[slug].astro",
|
|
36
|
+
* prompt: await alchemy`
|
|
37
|
+
* Create an Astro blog post page that:
|
|
38
|
+
* - Uses getStaticPaths to generate pages from a CMS
|
|
39
|
+
* - Renders markdown content
|
|
40
|
+
* - Includes author info, publication date, and related posts
|
|
41
|
+
* - Has social sharing buttons
|
|
42
|
+
*
|
|
43
|
+
* Use the following types:
|
|
44
|
+
* ${alchemy.file("src/types/Blog.ts")}
|
|
45
|
+
* `,
|
|
46
|
+
* temperature: 0.2,
|
|
47
|
+
* prettierConfig: {
|
|
48
|
+
* semi: false,
|
|
49
|
+
* singleQuote: true,
|
|
50
|
+
* printWidth: 120
|
|
51
|
+
* }
|
|
52
|
+
* });
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* // Generate a layout with custom system prompt
|
|
56
|
+
* const mainLayout = await AstroFile("main-layout", {
|
|
57
|
+
* path: "./src/layouts/MainLayout.astro",
|
|
58
|
+
* prompt: await alchemy`
|
|
59
|
+
* Create the main layout for an Astro site that:
|
|
60
|
+
* - Includes common head metadata and SEO optimization
|
|
61
|
+
* - Has slots for page content, header, and footer
|
|
62
|
+
* - Imports and uses the Header and Footer components
|
|
63
|
+
* - Sets up viewport and responsive configurations
|
|
64
|
+
* `,
|
|
65
|
+
* system: "You are an expert Astro developer. Create a single Astro layout file inside ```astro fences with no additional text. Follow Astro best practices and include proper typing in the frontmatter section.",
|
|
66
|
+
* model: {
|
|
67
|
+
* id: "claude-3-opus-20240229",
|
|
68
|
+
* provider: "anthropic"
|
|
69
|
+
* }
|
|
70
|
+
* });
|
|
71
|
+
*/
|
|
72
|
+
export const AstroFile = Resource("ai::AstroFile", async function (id, props) {
|
|
73
|
+
// Handle deletion phase
|
|
74
|
+
if (this.phase === "delete") {
|
|
75
|
+
return this.destroy();
|
|
76
|
+
}
|
|
77
|
+
// Use provided system prompt or default
|
|
78
|
+
const system = props.system || DEFAULT_ASTRO_SYSTEM_PROMPT;
|
|
79
|
+
// Generate initial content
|
|
80
|
+
const { text } = await generateText({
|
|
81
|
+
model: createModel(props),
|
|
82
|
+
prompt: props.prompt,
|
|
83
|
+
system,
|
|
84
|
+
providerOptions: props.model?.options,
|
|
85
|
+
...(props.temperature === undefined
|
|
86
|
+
? {}
|
|
87
|
+
: { temperature: props.temperature }),
|
|
88
|
+
});
|
|
89
|
+
// Extract and validate Astro code
|
|
90
|
+
let { code, error } = await extractAstroCode(text);
|
|
91
|
+
// Re-prompt if there are validation errors
|
|
92
|
+
if (error) {
|
|
93
|
+
const errorSystem = `${system}\n\nERROR: ${error}\n\nPlease try again and ensure your response contains exactly one Astro code block inside \`\`\`astro fences.`;
|
|
94
|
+
const { text: retryText } = await generateText({
|
|
95
|
+
model: createModel(props),
|
|
96
|
+
prompt: props.prompt,
|
|
97
|
+
system: errorSystem,
|
|
98
|
+
providerOptions: props.model?.options,
|
|
99
|
+
...(props.temperature === undefined
|
|
100
|
+
? {}
|
|
101
|
+
: { temperature: props.temperature }),
|
|
102
|
+
});
|
|
103
|
+
const retryResult = await extractAstroCode(retryText);
|
|
104
|
+
if (retryResult.error) {
|
|
105
|
+
throw new Error(`Failed to generate valid Astro code: ${retryResult.error}`);
|
|
106
|
+
}
|
|
107
|
+
code = retryResult.code;
|
|
108
|
+
}
|
|
109
|
+
// Format the code with Prettier
|
|
110
|
+
try {
|
|
111
|
+
// Set default parser to astro
|
|
112
|
+
const prettierOptions = {
|
|
113
|
+
parser: "astro",
|
|
114
|
+
...props.prettierConfig,
|
|
115
|
+
};
|
|
116
|
+
// Format the code
|
|
117
|
+
code = await prettier.format(code, prettierOptions);
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
// If Prettier formatting fails, just use the unformatted code
|
|
121
|
+
console.warn("Failed to format Astro code with Prettier:", error);
|
|
122
|
+
}
|
|
123
|
+
// Use StaticAstroFile to create/update the file
|
|
124
|
+
const file = await StaticAstroFile("file", props.path, code);
|
|
125
|
+
// Return the resource
|
|
126
|
+
return this({
|
|
127
|
+
...props,
|
|
128
|
+
content: file.content,
|
|
129
|
+
createdAt: Date.now(),
|
|
130
|
+
updatedAt: Date.now(),
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
/**
|
|
134
|
+
* Extracts Astro code from between ```astro fences
|
|
135
|
+
* Validates that exactly one Astro code block exists
|
|
136
|
+
*
|
|
137
|
+
* @param text The text to extract Astro code from
|
|
138
|
+
* @returns The extracted Astro code or error message
|
|
139
|
+
*/
|
|
140
|
+
async function extractAstroCode(text) {
|
|
141
|
+
const astroCodeRegex = /```astro\s*([\s\S]*?)```/g;
|
|
142
|
+
const matches = Array.from(text.matchAll(astroCodeRegex));
|
|
143
|
+
if (matches.length === 0) {
|
|
144
|
+
return {
|
|
145
|
+
code: "",
|
|
146
|
+
error: "No Astro code block found in the response. Please include your code within ```astro fences.",
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
if (matches.length > 1) {
|
|
150
|
+
return {
|
|
151
|
+
code: "",
|
|
152
|
+
error: "Multiple Astro code blocks found in the response. Please provide exactly one code block within ```astro fences.",
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
return { code: matches[0][1].trim() };
|
|
156
|
+
}
|
package/lib/ai/client.d.ts
CHANGED
|
@@ -16,7 +16,7 @@ export interface ModelConfig {
|
|
|
16
16
|
/**
|
|
17
17
|
* Model-specific options
|
|
18
18
|
*/
|
|
19
|
-
options?: Record<string,
|
|
19
|
+
options?: Record<string, any>;
|
|
20
20
|
}
|
|
21
21
|
/**
|
|
22
22
|
* Configuration for creating an OpenAI client
|
|
@@ -40,12 +40,11 @@ export interface ClientConfig {
|
|
|
40
40
|
/**
|
|
41
41
|
* Creates an OpenAI-compatible client with the given configuration
|
|
42
42
|
*/
|
|
43
|
-
export declare function
|
|
43
|
+
export declare function createModel(config: ClientConfig): import("ai").LanguageModelV1;
|
|
44
44
|
/**
|
|
45
|
-
*
|
|
45
|
+
* Handles rate limiting with exponential backoff
|
|
46
|
+
* @param fn Function to retry
|
|
47
|
+
* @returns Result of the function
|
|
48
|
+
* @throws Error if max retries or time is exceeded
|
|
46
49
|
*/
|
|
47
|
-
export declare function
|
|
48
|
-
/**
|
|
49
|
-
* Gets the model options from the configuration
|
|
50
|
-
*/
|
|
51
|
-
export declare function getModelOptions(config: ClientConfig): Record<string, unknown>;
|
|
50
|
+
export declare function withRateLimitRetry<T>(fn: () => Promise<T>): Promise<T>;
|
package/lib/ai/client.js
CHANGED
|
@@ -1,29 +1,58 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { anthropic } from "@ai-sdk/anthropic";
|
|
2
|
+
import { openai } from "@ai-sdk/openai";
|
|
2
3
|
/**
|
|
3
4
|
* Creates an OpenAI-compatible client with the given configuration
|
|
4
5
|
*/
|
|
5
|
-
export function
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
export function createModel(config) {
|
|
7
|
+
if (config.model?.provider === "anthropic") {
|
|
8
|
+
return anthropic(config.model?.id ?? "claude-3-7-sonnet-latest");
|
|
9
|
+
}
|
|
10
|
+
else {
|
|
11
|
+
return openai(config.model?.id ?? "gpt-4o");
|
|
10
12
|
}
|
|
11
|
-
// Initialize OpenAI compatible provider
|
|
12
|
-
return createOpenAICompatible({
|
|
13
|
-
name: config.model?.provider || "openai",
|
|
14
|
-
apiKey,
|
|
15
|
-
baseURL: config.baseURL || "https://api.openai.com/v1",
|
|
16
|
-
});
|
|
17
13
|
}
|
|
18
14
|
/**
|
|
19
|
-
*
|
|
15
|
+
* Maximum time to retry in milliseconds (5 minutes)
|
|
20
16
|
*/
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
17
|
+
const MAX_RETRY_TIME = 5 * 60 * 1000;
|
|
18
|
+
/**
|
|
19
|
+
* Initial delay between retries in milliseconds
|
|
20
|
+
*/
|
|
21
|
+
const INITIAL_RETRY_DELAY = 1000;
|
|
24
22
|
/**
|
|
25
|
-
*
|
|
23
|
+
* Maximum number of retries
|
|
26
24
|
*/
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
const MAX_RETRIES = 10;
|
|
26
|
+
/**
|
|
27
|
+
* Handles rate limiting with exponential backoff
|
|
28
|
+
* @param fn Function to retry
|
|
29
|
+
* @returns Result of the function
|
|
30
|
+
* @throws Error if max retries or time is exceeded
|
|
31
|
+
*/
|
|
32
|
+
export async function withRateLimitRetry(fn) {
|
|
33
|
+
let retryCount = 0;
|
|
34
|
+
let lastError = null;
|
|
35
|
+
let startTime = Date.now();
|
|
36
|
+
while (true) {
|
|
37
|
+
try {
|
|
38
|
+
return await fn();
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
lastError = error;
|
|
42
|
+
console.log("retry error", error);
|
|
43
|
+
// Check if we should retry
|
|
44
|
+
const isRateLimit = error.statusCode === 429;
|
|
45
|
+
const timeElapsed = Date.now() - startTime;
|
|
46
|
+
const shouldRetry = isRateLimit && retryCount < MAX_RETRIES && timeElapsed < MAX_RETRY_TIME;
|
|
47
|
+
if (!shouldRetry) {
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
// Calculate delay with exponential backoff
|
|
51
|
+
const delay = Math.min(INITIAL_RETRY_DELAY * Math.pow(2, retryCount), MAX_RETRY_TIME - timeElapsed);
|
|
52
|
+
console.log(`Retrying in ${delay}ms`);
|
|
53
|
+
// Wait before retrying
|
|
54
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
55
|
+
retryCount++;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
29
58
|
}
|