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
|
@@ -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
|
+
}
|
package/lib/ai/astro-file.js
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { generateText } from "ai";
|
|
2
|
-
import fs from "node:fs/promises";
|
|
3
|
-
import path from "node:path";
|
|
4
2
|
import prettier from "prettier";
|
|
3
|
+
import { StaticAstroFile } from "../fs/static-astro-file";
|
|
5
4
|
import { Resource } from "../resource";
|
|
6
|
-
import { ignore } from "../util/ignore";
|
|
7
5
|
import { createModel } from "./client";
|
|
8
6
|
/**
|
|
9
7
|
* Default system prompt for Astro file generation
|
|
@@ -72,18 +70,8 @@ const DEFAULT_ASTRO_SYSTEM_PROMPT = "You are an Astro component generator. Creat
|
|
|
72
70
|
* });
|
|
73
71
|
*/
|
|
74
72
|
export const AstroFile = Resource("ai::AstroFile", async function (id, props) {
|
|
75
|
-
//
|
|
76
|
-
await fs.mkdir(path.dirname(props.path), { recursive: true });
|
|
73
|
+
// Handle deletion phase
|
|
77
74
|
if (this.phase === "delete") {
|
|
78
|
-
try {
|
|
79
|
-
await fs.unlink(props.path);
|
|
80
|
-
}
|
|
81
|
-
catch (error) {
|
|
82
|
-
// Ignore if file doesn't exist
|
|
83
|
-
if (error.code !== "ENOENT") {
|
|
84
|
-
throw error;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
75
|
return this.destroy();
|
|
88
76
|
}
|
|
89
77
|
// Use provided system prompt or default
|
|
@@ -132,19 +120,14 @@ export const AstroFile = Resource("ai::AstroFile", async function (id, props) {
|
|
|
132
120
|
// If Prettier formatting fails, just use the unformatted code
|
|
133
121
|
console.warn("Failed to format Astro code with Prettier:", error);
|
|
134
122
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
}
|
|
138
|
-
// Write content to file
|
|
139
|
-
await fs.writeFile(props.path, code);
|
|
140
|
-
// Get file stats for timestamps
|
|
141
|
-
const stats = await fs.stat(props.path);
|
|
123
|
+
// Use StaticAstroFile to create/update the file
|
|
124
|
+
const file = await StaticAstroFile("file", props.path, code);
|
|
142
125
|
// Return the resource
|
|
143
126
|
return this({
|
|
144
127
|
...props,
|
|
145
|
-
content:
|
|
146
|
-
createdAt:
|
|
147
|
-
updatedAt:
|
|
128
|
+
content: file.content,
|
|
129
|
+
createdAt: Date.now(),
|
|
130
|
+
updatedAt: Date.now(),
|
|
148
131
|
});
|
|
149
132
|
});
|
|
150
133
|
/**
|
package/lib/ai/client.d.ts
CHANGED
|
@@ -41,3 +41,10 @@ export interface ClientConfig {
|
|
|
41
41
|
* Creates an OpenAI-compatible client with the given configuration
|
|
42
42
|
*/
|
|
43
43
|
export declare function createModel(config: ClientConfig): import("ai").LanguageModelV1;
|
|
44
|
+
/**
|
|
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
|
|
49
|
+
*/
|
|
50
|
+
export declare function withRateLimitRetry<T>(fn: () => Promise<T>): Promise<T>;
|
package/lib/ai/client.js
CHANGED
|
@@ -11,3 +11,48 @@ export function createModel(config) {
|
|
|
11
11
|
return openai(config.model?.id ?? "gpt-4o");
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
|
+
/**
|
|
15
|
+
* Maximum time to retry in milliseconds (5 minutes)
|
|
16
|
+
*/
|
|
17
|
+
const MAX_RETRY_TIME = 5 * 60 * 1000;
|
|
18
|
+
/**
|
|
19
|
+
* Initial delay between retries in milliseconds
|
|
20
|
+
*/
|
|
21
|
+
const INITIAL_RETRY_DELAY = 1000;
|
|
22
|
+
/**
|
|
23
|
+
* Maximum number of retries
|
|
24
|
+
*/
|
|
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
|
+
}
|
|
58
|
+
}
|
package/lib/ai/css-file.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { generateText } from "ai";
|
|
2
|
-
import
|
|
3
|
-
import path from "node:path";
|
|
2
|
+
import { StaticCSSFile } from "../fs/static-css-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 CSS file generation
|
|
@@ -71,18 +69,8 @@ const DEFAULT_CSS_SYSTEM_PROMPT = "You are a CSS code generator. Create CSS code
|
|
|
71
69
|
* });
|
|
72
70
|
*/
|
|
73
71
|
export const CSSFile = Resource("ai::CSSFile", async function (id, props) {
|
|
74
|
-
//
|
|
75
|
-
await fs.mkdir(path.dirname(props.path), { recursive: true });
|
|
72
|
+
// Handle deletion phase
|
|
76
73
|
if (this.phase === "delete") {
|
|
77
|
-
try {
|
|
78
|
-
await fs.unlink(props.path);
|
|
79
|
-
}
|
|
80
|
-
catch (error) {
|
|
81
|
-
// Ignore if file doesn't exist
|
|
82
|
-
if (error.code !== "ENOENT") {
|
|
83
|
-
throw error;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
74
|
return this.destroy();
|
|
87
75
|
}
|
|
88
76
|
// Use provided system prompt or default
|
|
@@ -117,19 +105,14 @@ export const CSSFile = Resource("ai::CSSFile", async function (id, props) {
|
|
|
117
105
|
}
|
|
118
106
|
code = retryResult.code;
|
|
119
107
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
}
|
|
123
|
-
// Write content to file
|
|
124
|
-
await fs.writeFile(props.path, code);
|
|
125
|
-
// Get file stats for timestamps
|
|
126
|
-
const stats = await fs.stat(props.path);
|
|
108
|
+
// Use StaticCSSFile to create/update the file
|
|
109
|
+
const file = await StaticCSSFile("file", props.path, code);
|
|
127
110
|
// Return the resource
|
|
128
111
|
return this({
|
|
129
112
|
...props,
|
|
130
|
-
content:
|
|
131
|
-
createdAt:
|
|
132
|
-
updatedAt:
|
|
113
|
+
content: file.content,
|
|
114
|
+
createdAt: Date.now(),
|
|
115
|
+
updatedAt: Date.now(),
|
|
133
116
|
});
|
|
134
117
|
});
|
|
135
118
|
/**
|
package/lib/ai/data.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type CoreMessage } from "ai";
|
|
1
2
|
import type { JsonSchema, Type, type } from "arktype";
|
|
2
3
|
import type { Context } from "../context";
|
|
3
4
|
import { Resource } from "../resource";
|
|
@@ -20,7 +21,12 @@ export interface DataProps<T extends Type<any, any>> {
|
|
|
20
21
|
* ${alchemy.file("src/data.ts")}
|
|
21
22
|
* `
|
|
22
23
|
*/
|
|
23
|
-
prompt
|
|
24
|
+
prompt?: string;
|
|
25
|
+
/**
|
|
26
|
+
* Message history for the conversation
|
|
27
|
+
* If provided, this will be used instead of the prompt
|
|
28
|
+
*/
|
|
29
|
+
messages?: CoreMessage[];
|
|
24
30
|
/**
|
|
25
31
|
* System prompt to guide the AI's behavior
|
|
26
32
|
* @example
|
|
@@ -58,6 +64,10 @@ export interface Data<T> extends Resource<"ai::Object"> {
|
|
|
58
64
|
* The generated content, typed according to the provided schema
|
|
59
65
|
*/
|
|
60
66
|
object: T;
|
|
67
|
+
/**
|
|
68
|
+
* Updated message history with the AI's response appended
|
|
69
|
+
*/
|
|
70
|
+
messages: CoreMessage[];
|
|
61
71
|
/**
|
|
62
72
|
* Time at which the content was generated
|
|
63
73
|
*/
|
|
@@ -114,28 +124,23 @@ export interface Data<T> extends Resource<"ai::Object"> {
|
|
|
114
124
|
* });
|
|
115
125
|
*
|
|
116
126
|
* @example
|
|
117
|
-
* // Using
|
|
118
|
-
* const
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
127
|
+
* // Using message history for iterative generation
|
|
128
|
+
* const feedbackSchema = type({
|
|
129
|
+
* rating: "number",
|
|
130
|
+
* positives: "string[]",
|
|
131
|
+
* improvements: "string[]",
|
|
132
|
+
* summary: "string"
|
|
122
133
|
* });
|
|
123
134
|
*
|
|
124
|
-
* const
|
|
125
|
-
* schema:
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
* provider: "openai",
|
|
134
|
-
* options: {
|
|
135
|
-
* reasoningEffort: "high"
|
|
136
|
-
* }
|
|
137
|
-
* },
|
|
138
|
-
* temperature: 0.1
|
|
135
|
+
* const feedback = await Data("product-feedback", {
|
|
136
|
+
* schema: feedbackSchema,
|
|
137
|
+
* messages: [
|
|
138
|
+
* { role: "user", content: "I'd like feedback on my product design" },
|
|
139
|
+
* { role: "assistant", content: "I'd be happy to provide feedback. What's your product?" },
|
|
140
|
+
* { role: "user", content: "It's a new smart home device that..." }
|
|
141
|
+
* ],
|
|
142
|
+
* system: "You are a product design expert providing structured feedback",
|
|
143
|
+
* temperature: 0.3
|
|
139
144
|
* });
|
|
140
145
|
*/
|
|
141
146
|
export declare const Data: (((this: any, id: string, props?: {}) => never) & (new (_: never) => never)) | (<const T extends Type<any, any>>(this: Context<Data<any>>, id: string, props: DataProps<T>) => Promise<Data<type.infer<T>>>);
|
package/lib/ai/data.js
CHANGED
|
@@ -53,34 +53,35 @@ import { createModel } from "./client";
|
|
|
53
53
|
* });
|
|
54
54
|
*
|
|
55
55
|
* @example
|
|
56
|
-
* // Using
|
|
57
|
-
* const
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
56
|
+
* // Using message history for iterative generation
|
|
57
|
+
* const feedbackSchema = type({
|
|
58
|
+
* rating: "number",
|
|
59
|
+
* positives: "string[]",
|
|
60
|
+
* improvements: "string[]",
|
|
61
|
+
* summary: "string"
|
|
61
62
|
* });
|
|
62
63
|
*
|
|
63
|
-
* const
|
|
64
|
-
* schema:
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
* provider: "openai",
|
|
73
|
-
* options: {
|
|
74
|
-
* reasoningEffort: "high"
|
|
75
|
-
* }
|
|
76
|
-
* },
|
|
77
|
-
* temperature: 0.1
|
|
64
|
+
* const feedback = await Data("product-feedback", {
|
|
65
|
+
* schema: feedbackSchema,
|
|
66
|
+
* messages: [
|
|
67
|
+
* { role: "user", content: "I'd like feedback on my product design" },
|
|
68
|
+
* { role: "assistant", content: "I'd be happy to provide feedback. What's your product?" },
|
|
69
|
+
* { role: "user", content: "It's a new smart home device that..." }
|
|
70
|
+
* ],
|
|
71
|
+
* system: "You are a product design expert providing structured feedback",
|
|
72
|
+
* temperature: 0.3
|
|
78
73
|
* });
|
|
79
74
|
*/
|
|
80
75
|
export const Data = Resource("ai::Object", async function (id, props) {
|
|
81
76
|
if (this.phase === "delete") {
|
|
82
77
|
return this.destroy();
|
|
83
78
|
}
|
|
79
|
+
// Validate that either prompt or messages is provided
|
|
80
|
+
if (!props.prompt && !props.messages) {
|
|
81
|
+
throw new Error("Either prompt or messages must be provided");
|
|
82
|
+
}
|
|
83
|
+
// Create messages array if only prompt is provided
|
|
84
|
+
const messages = props.messages || [{ role: "user", content: props.prompt }];
|
|
84
85
|
// Generate structured output using generateObject
|
|
85
86
|
const { object } = await generateObject({
|
|
86
87
|
model: createModel(props),
|
|
@@ -90,16 +91,26 @@ export const Data = Resource("ai::Object", async function (id, props) {
|
|
|
90
91
|
providerOptions: props.model?.options,
|
|
91
92
|
system: props.system ||
|
|
92
93
|
"You are an AI assistant tasked with generating structured content.",
|
|
93
|
-
|
|
94
|
+
messages,
|
|
94
95
|
...(props.temperature === undefined
|
|
95
96
|
? {}
|
|
96
97
|
: // some models error if you provide it (rather than ignoring it)
|
|
97
98
|
{ temperature: props.temperature }),
|
|
98
99
|
});
|
|
99
|
-
//
|
|
100
|
+
// Create updated message history with the structured response
|
|
101
|
+
const responseText = JSON.stringify(object);
|
|
102
|
+
const updatedMessages = [
|
|
103
|
+
...messages,
|
|
104
|
+
{
|
|
105
|
+
role: "assistant",
|
|
106
|
+
content: responseText,
|
|
107
|
+
},
|
|
108
|
+
];
|
|
109
|
+
// Return the resource with typed content and updated messages
|
|
100
110
|
return this({
|
|
101
111
|
type: props.schema,
|
|
102
112
|
object: object,
|
|
113
|
+
messages: updatedMessages,
|
|
103
114
|
createdAt: Date.now(),
|
|
104
115
|
});
|
|
105
116
|
});
|
package/lib/ai/document.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { type CoreMessage } from "ai";
|
|
1
2
|
import type { Context } from "../context";
|
|
3
|
+
import { StaticTextFile } from "../fs/static-text-file";
|
|
2
4
|
import { Resource } from "../resource";
|
|
3
5
|
import type { Secret } from "../secret";
|
|
4
6
|
import { type ModelConfig } from "./client";
|
|
@@ -11,9 +13,10 @@ export interface DocumentProps {
|
|
|
11
13
|
*/
|
|
12
14
|
title: string;
|
|
13
15
|
/**
|
|
14
|
-
*
|
|
16
|
+
* Optional path to the markdown document
|
|
17
|
+
* If provided, document will be written to this path
|
|
15
18
|
*/
|
|
16
|
-
path
|
|
19
|
+
path?: string;
|
|
17
20
|
/**
|
|
18
21
|
* Base URL for the OpenAI API
|
|
19
22
|
* @default 'https://api.openai.com/v1'
|
|
@@ -28,7 +31,18 @@ export interface DocumentProps {
|
|
|
28
31
|
* ${alchemy.file("src/api.ts")}
|
|
29
32
|
* `
|
|
30
33
|
*/
|
|
31
|
-
prompt
|
|
34
|
+
prompt?: string;
|
|
35
|
+
/**
|
|
36
|
+
* Message history for conversation-based generation
|
|
37
|
+
* If provided, this will be used instead of the prompt
|
|
38
|
+
* @example
|
|
39
|
+
* messages: [
|
|
40
|
+
* { role: "user", content: "Generate API documentation for this file" },
|
|
41
|
+
* { role: "assistant", content: "I'll create detailed API docs. What file should I document?" },
|
|
42
|
+
* { role: "user", content: "Please document src/api.ts" }
|
|
43
|
+
* ]
|
|
44
|
+
*/
|
|
45
|
+
messages?: CoreMessage[];
|
|
32
46
|
/**
|
|
33
47
|
* System prompt for the model
|
|
34
48
|
* This is used to provide instructions to the model about how to format the response
|
|
@@ -52,6 +66,12 @@ export interface DocumentProps {
|
|
|
52
66
|
* @default 0.7
|
|
53
67
|
*/
|
|
54
68
|
temperature?: number;
|
|
69
|
+
/**
|
|
70
|
+
* Maximum number of tokens to generate.
|
|
71
|
+
* Higher values allow for longer documents but may increase cost and generation time.
|
|
72
|
+
* @default 10000
|
|
73
|
+
*/
|
|
74
|
+
maxTokens?: number;
|
|
55
75
|
}
|
|
56
76
|
/**
|
|
57
77
|
* A markdown document that can be created, updated, and deleted
|
|
@@ -61,6 +81,10 @@ export interface Document extends DocumentProps, Resource<"docs::Document"> {
|
|
|
61
81
|
* Content of the document
|
|
62
82
|
*/
|
|
63
83
|
content: string;
|
|
84
|
+
/**
|
|
85
|
+
* Updated message history with the document response appended
|
|
86
|
+
*/
|
|
87
|
+
messages: CoreMessage[];
|
|
64
88
|
/**
|
|
65
89
|
* Time at which the document was created
|
|
66
90
|
*/
|
|
@@ -69,13 +93,32 @@ export interface Document extends DocumentProps, Resource<"docs::Document"> {
|
|
|
69
93
|
* Time at which the document was last updated
|
|
70
94
|
*/
|
|
71
95
|
updatedAt: number;
|
|
96
|
+
/**
|
|
97
|
+
* File resource if path was provided
|
|
98
|
+
*/
|
|
99
|
+
file?: StaticTextFile;
|
|
72
100
|
}
|
|
73
101
|
/**
|
|
74
102
|
* Resource for managing AI-generated markdown documents using the Vercel AI SDK.
|
|
75
103
|
* Supports powerful context handling through the alchemy template literal tag.
|
|
76
104
|
*
|
|
77
105
|
* @example
|
|
78
|
-
* // Create
|
|
106
|
+
* // Create an in-memory document (no file created)
|
|
107
|
+
* const apiDocs = await Document("api-docs", {
|
|
108
|
+
* title: "API Documentation",
|
|
109
|
+
* prompt: await alchemy`
|
|
110
|
+
* Generate API documentation based on these source files:
|
|
111
|
+
* ${alchemy.file("src/api.ts")}
|
|
112
|
+
* ${alchemy.file("src/types.ts")}
|
|
113
|
+
* `,
|
|
114
|
+
* model: {
|
|
115
|
+
* id: "gpt-4o",
|
|
116
|
+
* provider: "openai"
|
|
117
|
+
* }
|
|
118
|
+
* });
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
* // Create a document and write it to disk
|
|
79
122
|
* const apiDocs = await Document("api-docs", {
|
|
80
123
|
* title: "API Documentation",
|
|
81
124
|
* path: "./docs/api.md",
|
|
@@ -91,6 +134,23 @@ export interface Document extends DocumentProps, Resource<"docs::Document"> {
|
|
|
91
134
|
* });
|
|
92
135
|
*
|
|
93
136
|
* @example
|
|
137
|
+
* // Use message history for iterative document generation
|
|
138
|
+
* const apiDocs = await Document("api-docs", {
|
|
139
|
+
* title: "API Documentation",
|
|
140
|
+
* path: "./docs/api.md",
|
|
141
|
+
* messages: [
|
|
142
|
+
* { role: "user", content: "Create API documentation for these files" },
|
|
143
|
+
* { role: "assistant", content: "I'll help you create API documentation. Please provide the files." },
|
|
144
|
+
* { role: "user", content: "Here are the files: [file contents]" }
|
|
145
|
+
* ],
|
|
146
|
+
* system: "You are a technical documentation writer. Generate clear and concise API documentation.",
|
|
147
|
+
* model: {
|
|
148
|
+
* id: "gpt-4o",
|
|
149
|
+
* provider: "openai"
|
|
150
|
+
* }
|
|
151
|
+
* });
|
|
152
|
+
*
|
|
153
|
+
* @example
|
|
94
154
|
* // Use alchemy template literals with file collections and temperature control
|
|
95
155
|
* const modelDocs = await Document("models", {
|
|
96
156
|
* title: "Data Models",
|