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,163 @@
|
|
|
1
|
+
import type { CoreMessage } from "ai";
|
|
2
|
+
import { type } from "arktype";
|
|
3
|
+
import { Data } from "./data";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Properties for approving or denying content
|
|
7
|
+
*/
|
|
8
|
+
export interface ApproveProps {
|
|
9
|
+
/**
|
|
10
|
+
* Content to be reviewed
|
|
11
|
+
* Use alchemy template literals to include file context:
|
|
12
|
+
* @example
|
|
13
|
+
* content: await alchemy`
|
|
14
|
+
* Review this code:
|
|
15
|
+
* ${alchemy.file("src/api.ts")}
|
|
16
|
+
* `
|
|
17
|
+
*
|
|
18
|
+
* Required unless messages are provided
|
|
19
|
+
*/
|
|
20
|
+
content?: string;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Prompt for the approval decision
|
|
24
|
+
* This should include specific criteria for approval or denial
|
|
25
|
+
* @example
|
|
26
|
+
* prompt: "Approve this code if it follows security best practices and has proper error handling."
|
|
27
|
+
*
|
|
28
|
+
* Required unless messages are provided
|
|
29
|
+
*/
|
|
30
|
+
prompt?: string;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* System prompt for the model
|
|
34
|
+
* This is used to provide instructions to the model about how to make the decision
|
|
35
|
+
*/
|
|
36
|
+
system?: string;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Message history for the conversation
|
|
40
|
+
* If provided, this will be used instead of the prompt and content
|
|
41
|
+
*/
|
|
42
|
+
messages?: CoreMessage[];
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Temperature for controlling randomness in generation.
|
|
46
|
+
* Higher values (e.g., 0.8) make output more random,
|
|
47
|
+
* lower values (e.g., 0.2) make it more deterministic.
|
|
48
|
+
* @default 0.2 (lower for more consistent decisions)
|
|
49
|
+
*/
|
|
50
|
+
temperature?: number;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Result of the approval process
|
|
55
|
+
*/
|
|
56
|
+
export interface ApprovalResult {
|
|
57
|
+
/**
|
|
58
|
+
* Whether the content was approved
|
|
59
|
+
*/
|
|
60
|
+
approved: boolean;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Explanation for the decision
|
|
64
|
+
*/
|
|
65
|
+
explanation: string;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Suggestions for improvement if denied
|
|
69
|
+
*/
|
|
70
|
+
suggestions?: string[];
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Updated message history with the approval response appended
|
|
74
|
+
*/
|
|
75
|
+
messages: CoreMessage[];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export type Approve = ApprovalResult;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Default system prompt for approval decisions
|
|
82
|
+
*/
|
|
83
|
+
const DEFAULT_APPROVE_SYSTEM_PROMPT = `You are an expert content reviewer tasked with approving or denying content based on specific criteria.
|
|
84
|
+
|
|
85
|
+
Your role is to:
|
|
86
|
+
1. Carefully evaluate the content against the provided criteria
|
|
87
|
+
2. Make a clear decision to approve or deny the content
|
|
88
|
+
3. Provide a detailed explanation for your decision
|
|
89
|
+
4. If denying, offer specific suggestions for improvement
|
|
90
|
+
|
|
91
|
+
Be objective, thorough, and fair in your assessment.`;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Approves or denies content based on a prompt or message history
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* // Approve or deny a code file
|
|
98
|
+
* const result = await Approve("code-approval", {
|
|
99
|
+
* content: await alchemy`
|
|
100
|
+
* Review this API implementation:
|
|
101
|
+
* ${alchemy.file("src/api.ts")}
|
|
102
|
+
* `,
|
|
103
|
+
* prompt: "Approve this code if it follows security best practices and has proper error handling."
|
|
104
|
+
* });
|
|
105
|
+
*
|
|
106
|
+
* if (result.approved) {
|
|
107
|
+
* console.log("Content approved:", result.explanation);
|
|
108
|
+
* } else {
|
|
109
|
+
* console.log("Content denied:", result.explanation);
|
|
110
|
+
* console.log("Suggestions:", result.suggestions);
|
|
111
|
+
* }
|
|
112
|
+
*
|
|
113
|
+
* @example
|
|
114
|
+
* // Approve or deny using message history
|
|
115
|
+
* const result = await Approve("doc-approval-with-history", {
|
|
116
|
+
* content: "This is the content to review",
|
|
117
|
+
* prompt: "Approve this documentation if it is clear and accurate",
|
|
118
|
+
* messages: [
|
|
119
|
+
* { role: "user", content: "Can you review this documentation?" },
|
|
120
|
+
* { role: "assistant", content: "Yes, I'd be happy to review it." },
|
|
121
|
+
* { role: "user", content: "Please check for clarity and accuracy." }
|
|
122
|
+
* ]
|
|
123
|
+
* });
|
|
124
|
+
*/
|
|
125
|
+
export async function Approve(
|
|
126
|
+
id: string,
|
|
127
|
+
props: ApproveProps
|
|
128
|
+
): Promise<ApprovalResult> {
|
|
129
|
+
// Create messages array if not provided
|
|
130
|
+
const messages =
|
|
131
|
+
props.messages ||
|
|
132
|
+
(props.content && props.prompt
|
|
133
|
+
? [
|
|
134
|
+
{
|
|
135
|
+
role: "user" as const,
|
|
136
|
+
content: `${props.content}\n\n${props.prompt}`,
|
|
137
|
+
},
|
|
138
|
+
]
|
|
139
|
+
: []);
|
|
140
|
+
|
|
141
|
+
if (messages.length === 0) {
|
|
142
|
+
throw new Error(
|
|
143
|
+
"Either messages or both content and prompt must be provided"
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const data = await Data(id, {
|
|
148
|
+
schema: type({
|
|
149
|
+
approved: "boolean",
|
|
150
|
+
explanation: "string",
|
|
151
|
+
suggestions: "string[]?",
|
|
152
|
+
}),
|
|
153
|
+
messages,
|
|
154
|
+
system: props.system || DEFAULT_APPROVE_SYSTEM_PROMPT,
|
|
155
|
+
temperature: props.temperature ?? 0.2,
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// Return the result with updated messages from Data
|
|
159
|
+
return {
|
|
160
|
+
...data.object,
|
|
161
|
+
messages: data.messages,
|
|
162
|
+
};
|
|
163
|
+
}
|
package/src/ai/astro-file.ts
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
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";
|
|
5
3
|
import type { Context } from "../context";
|
|
4
|
+
import { StaticAstroFile } from "../fs/static-astro-file";
|
|
6
5
|
import { Resource } from "../resource";
|
|
7
6
|
import type { Secret } from "../secret";
|
|
8
|
-
import { ignore } from "../util/ignore";
|
|
9
7
|
import { type ModelConfig, createModel } from "./client";
|
|
10
8
|
|
|
11
9
|
/**
|
|
@@ -161,20 +159,10 @@ export const AstroFile = Resource(
|
|
|
161
159
|
async function (
|
|
162
160
|
this: Context<AstroFile>,
|
|
163
161
|
id: string,
|
|
164
|
-
props: AstroFileProps
|
|
162
|
+
props: AstroFileProps
|
|
165
163
|
): Promise<AstroFile> {
|
|
166
|
-
//
|
|
167
|
-
await fs.mkdir(path.dirname(props.path), { recursive: true });
|
|
168
|
-
|
|
164
|
+
// Handle deletion phase
|
|
169
165
|
if (this.phase === "delete") {
|
|
170
|
-
try {
|
|
171
|
-
await fs.unlink(props.path);
|
|
172
|
-
} catch (error: any) {
|
|
173
|
-
// Ignore if file doesn't exist
|
|
174
|
-
if (error.code !== "ENOENT") {
|
|
175
|
-
throw error;
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
166
|
return this.destroy();
|
|
179
167
|
}
|
|
180
168
|
|
|
@@ -213,7 +201,7 @@ export const AstroFile = Resource(
|
|
|
213
201
|
|
|
214
202
|
if (retryResult.error) {
|
|
215
203
|
throw new Error(
|
|
216
|
-
`Failed to generate valid Astro code: ${retryResult.error}
|
|
204
|
+
`Failed to generate valid Astro code: ${retryResult.error}`
|
|
217
205
|
);
|
|
218
206
|
}
|
|
219
207
|
|
|
@@ -235,24 +223,17 @@ export const AstroFile = Resource(
|
|
|
235
223
|
console.warn("Failed to format Astro code with Prettier:", error);
|
|
236
224
|
}
|
|
237
225
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
// Write content to file
|
|
243
|
-
await fs.writeFile(props.path, code);
|
|
244
|
-
|
|
245
|
-
// Get file stats for timestamps
|
|
246
|
-
const stats = await fs.stat(props.path);
|
|
226
|
+
// Use StaticAstroFile to create/update the file
|
|
227
|
+
const file = await StaticAstroFile("file", props.path, code);
|
|
247
228
|
|
|
248
229
|
// Return the resource
|
|
249
230
|
return this({
|
|
250
231
|
...props,
|
|
251
|
-
content:
|
|
252
|
-
createdAt:
|
|
253
|
-
updatedAt:
|
|
232
|
+
content: file.content,
|
|
233
|
+
createdAt: Date.now(),
|
|
234
|
+
updatedAt: Date.now(),
|
|
254
235
|
});
|
|
255
|
-
}
|
|
236
|
+
}
|
|
256
237
|
);
|
|
257
238
|
|
|
258
239
|
/**
|
|
@@ -263,7 +244,7 @@ export const AstroFile = Resource(
|
|
|
263
244
|
* @returns The extracted Astro code or error message
|
|
264
245
|
*/
|
|
265
246
|
async function extractAstroCode(
|
|
266
|
-
text: string
|
|
247
|
+
text: string
|
|
267
248
|
): Promise<{ code: string; error?: string }> {
|
|
268
249
|
const astroCodeRegex = /```astro\s*([\s\S]*?)```/g;
|
|
269
250
|
const matches = Array.from(text.matchAll(astroCodeRegex));
|
package/src/ai/client.ts
CHANGED
|
@@ -56,3 +56,62 @@ export function createModel(config: ClientConfig) {
|
|
|
56
56
|
return openai(config.model?.id ?? "gpt-4o");
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Maximum time to retry in milliseconds (5 minutes)
|
|
62
|
+
*/
|
|
63
|
+
const MAX_RETRY_TIME = 5 * 60 * 1000;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Initial delay between retries in milliseconds
|
|
67
|
+
*/
|
|
68
|
+
const INITIAL_RETRY_DELAY = 1000;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Maximum number of retries
|
|
72
|
+
*/
|
|
73
|
+
const MAX_RETRIES = 10;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Handles rate limiting with exponential backoff
|
|
77
|
+
* @param fn Function to retry
|
|
78
|
+
* @returns Result of the function
|
|
79
|
+
* @throws Error if max retries or time is exceeded
|
|
80
|
+
*/
|
|
81
|
+
export async function withRateLimitRetry<T>(fn: () => Promise<T>): Promise<T> {
|
|
82
|
+
let retryCount = 0;
|
|
83
|
+
let lastError: Error | null = null;
|
|
84
|
+
let startTime = Date.now();
|
|
85
|
+
|
|
86
|
+
while (true) {
|
|
87
|
+
try {
|
|
88
|
+
return await fn();
|
|
89
|
+
} catch (error: any) {
|
|
90
|
+
lastError = error;
|
|
91
|
+
|
|
92
|
+
console.log("retry error", error);
|
|
93
|
+
|
|
94
|
+
// Check if we should retry
|
|
95
|
+
const isRateLimit = error.statusCode === 429;
|
|
96
|
+
const timeElapsed = Date.now() - startTime;
|
|
97
|
+
const shouldRetry =
|
|
98
|
+
isRateLimit && retryCount < MAX_RETRIES && timeElapsed < MAX_RETRY_TIME;
|
|
99
|
+
|
|
100
|
+
if (!shouldRetry) {
|
|
101
|
+
throw error;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Calculate delay with exponential backoff
|
|
105
|
+
const delay = Math.min(
|
|
106
|
+
INITIAL_RETRY_DELAY * Math.pow(2, retryCount),
|
|
107
|
+
MAX_RETRY_TIME - timeElapsed
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
console.log(`Retrying in ${delay}ms`);
|
|
111
|
+
|
|
112
|
+
// Wait before retrying
|
|
113
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
114
|
+
retryCount++;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
package/src/ai/css-file.ts
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import { generateText } from "ai";
|
|
2
|
-
import fs from "node:fs/promises";
|
|
3
|
-
import path from "node:path";
|
|
4
2
|
import type { Context } from "../context";
|
|
3
|
+
import { StaticCSSFile } from "../fs/static-css-file";
|
|
5
4
|
import { Resource } from "../resource";
|
|
6
5
|
import type { Secret } from "../secret";
|
|
7
|
-
import { ignore } from "../util/ignore";
|
|
8
6
|
import { type ModelConfig, createModel } from "./client";
|
|
9
7
|
|
|
10
8
|
/**
|
|
@@ -154,20 +152,10 @@ export const CSSFile = Resource(
|
|
|
154
152
|
async function (
|
|
155
153
|
this: Context<CSSFile>,
|
|
156
154
|
id: string,
|
|
157
|
-
props: CSSFileProps
|
|
155
|
+
props: CSSFileProps
|
|
158
156
|
): Promise<CSSFile> {
|
|
159
|
-
//
|
|
160
|
-
await fs.mkdir(path.dirname(props.path), { recursive: true });
|
|
161
|
-
|
|
157
|
+
// Handle deletion phase
|
|
162
158
|
if (this.phase === "delete") {
|
|
163
|
-
try {
|
|
164
|
-
await fs.unlink(props.path);
|
|
165
|
-
} catch (error: any) {
|
|
166
|
-
// Ignore if file doesn't exist
|
|
167
|
-
if (error.code !== "ENOENT") {
|
|
168
|
-
throw error;
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
159
|
return this.destroy();
|
|
172
160
|
}
|
|
173
161
|
|
|
@@ -206,31 +194,24 @@ export const CSSFile = Resource(
|
|
|
206
194
|
|
|
207
195
|
if (retryResult.error) {
|
|
208
196
|
throw new Error(
|
|
209
|
-
`Failed to generate valid CSS code: ${retryResult.error}\n${retryText}
|
|
197
|
+
`Failed to generate valid CSS code: ${retryResult.error}\n${retryText}`
|
|
210
198
|
);
|
|
211
199
|
}
|
|
212
200
|
|
|
213
201
|
code = retryResult.code;
|
|
214
202
|
}
|
|
215
203
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// Write content to file
|
|
221
|
-
await fs.writeFile(props.path, code);
|
|
222
|
-
|
|
223
|
-
// Get file stats for timestamps
|
|
224
|
-
const stats = await fs.stat(props.path);
|
|
204
|
+
// Use StaticCSSFile to create/update the file
|
|
205
|
+
const file = await StaticCSSFile("file", props.path, code);
|
|
225
206
|
|
|
226
207
|
// Return the resource
|
|
227
208
|
return this({
|
|
228
209
|
...props,
|
|
229
|
-
content:
|
|
230
|
-
createdAt:
|
|
231
|
-
updatedAt:
|
|
210
|
+
content: file.content,
|
|
211
|
+
createdAt: Date.now(),
|
|
212
|
+
updatedAt: Date.now(),
|
|
232
213
|
});
|
|
233
|
-
}
|
|
214
|
+
}
|
|
234
215
|
);
|
|
235
216
|
|
|
236
217
|
/**
|
|
@@ -241,7 +222,7 @@ export const CSSFile = Resource(
|
|
|
241
222
|
* @returns The extracted CSS code or error message
|
|
242
223
|
*/
|
|
243
224
|
async function extractCSSCode(
|
|
244
|
-
text: string
|
|
225
|
+
text: string
|
|
245
226
|
): Promise<{ code: string; error?: string }> {
|
|
246
227
|
const cssCodeRegex = /```css\s*([\s\S]*?)```/g;
|
|
247
228
|
const matches = Array.from(text.matchAll(cssCodeRegex));
|
package/src/ai/data.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { generateObject } from "ai";
|
|
1
|
+
import { generateObject, type CoreMessage } from "ai";
|
|
2
2
|
import type { JsonSchema, Type, type } from "arktype";
|
|
3
3
|
import type { Context } from "../context";
|
|
4
4
|
import { Resource } from "../resource";
|
|
5
5
|
import type { Secret } from "../secret";
|
|
6
6
|
import { ark } from "./ark";
|
|
7
|
-
import { type ModelConfig
|
|
7
|
+
import { createModel, type ModelConfig } from "./client";
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Properties for creating or updating an AI Object
|
|
@@ -24,7 +24,13 @@ export interface DataProps<T extends Type<any, any>> {
|
|
|
24
24
|
* ${alchemy.file("src/data.ts")}
|
|
25
25
|
* `
|
|
26
26
|
*/
|
|
27
|
-
prompt
|
|
27
|
+
prompt?: string;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Message history for the conversation
|
|
31
|
+
* If provided, this will be used instead of the prompt
|
|
32
|
+
*/
|
|
33
|
+
messages?: CoreMessage[];
|
|
28
34
|
|
|
29
35
|
/**
|
|
30
36
|
* System prompt to guide the AI's behavior
|
|
@@ -70,6 +76,11 @@ export interface Data<T> extends Resource<"ai::Object"> {
|
|
|
70
76
|
*/
|
|
71
77
|
object: T;
|
|
72
78
|
|
|
79
|
+
/**
|
|
80
|
+
* Updated message history with the AI's response appended
|
|
81
|
+
*/
|
|
82
|
+
messages: CoreMessage[];
|
|
83
|
+
|
|
73
84
|
/**
|
|
74
85
|
* Time at which the content was generated
|
|
75
86
|
*/
|
|
@@ -127,28 +138,23 @@ export interface Data<T> extends Resource<"ai::Object"> {
|
|
|
127
138
|
* });
|
|
128
139
|
*
|
|
129
140
|
* @example
|
|
130
|
-
* // Using
|
|
131
|
-
* const
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
141
|
+
* // Using message history for iterative generation
|
|
142
|
+
* const feedbackSchema = type({
|
|
143
|
+
* rating: "number",
|
|
144
|
+
* positives: "string[]",
|
|
145
|
+
* improvements: "string[]",
|
|
146
|
+
* summary: "string"
|
|
135
147
|
* });
|
|
136
148
|
*
|
|
137
|
-
* const
|
|
138
|
-
* schema:
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
* provider: "openai",
|
|
147
|
-
* options: {
|
|
148
|
-
* reasoningEffort: "high"
|
|
149
|
-
* }
|
|
150
|
-
* },
|
|
151
|
-
* temperature: 0.1
|
|
149
|
+
* const feedback = await Data("product-feedback", {
|
|
150
|
+
* schema: feedbackSchema,
|
|
151
|
+
* messages: [
|
|
152
|
+
* { role: "user", content: "I'd like feedback on my product design" },
|
|
153
|
+
* { role: "assistant", content: "I'd be happy to provide feedback. What's your product?" },
|
|
154
|
+
* { role: "user", content: "It's a new smart home device that..." }
|
|
155
|
+
* ],
|
|
156
|
+
* system: "You are a product design expert providing structured feedback",
|
|
157
|
+
* temperature: 0.3
|
|
152
158
|
* });
|
|
153
159
|
*/
|
|
154
160
|
export const Data = Resource("ai::Object", async function <
|
|
@@ -160,6 +166,14 @@ export const Data = Resource("ai::Object", async function <
|
|
|
160
166
|
return this.destroy();
|
|
161
167
|
}
|
|
162
168
|
|
|
169
|
+
// Validate that either prompt or messages is provided
|
|
170
|
+
if (!props.prompt && !props.messages) {
|
|
171
|
+
throw new Error("Either prompt or messages must be provided");
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Create messages array if only prompt is provided
|
|
175
|
+
const messages = props.messages || [{ role: "user", content: props.prompt! }];
|
|
176
|
+
|
|
163
177
|
// Generate structured output using generateObject
|
|
164
178
|
const { object } = await generateObject({
|
|
165
179
|
model: createModel(props),
|
|
@@ -170,17 +184,28 @@ export const Data = Resource("ai::Object", async function <
|
|
|
170
184
|
system:
|
|
171
185
|
props.system ||
|
|
172
186
|
"You are an AI assistant tasked with generating structured content.",
|
|
173
|
-
|
|
187
|
+
messages,
|
|
174
188
|
...(props.temperature === undefined
|
|
175
189
|
? {}
|
|
176
190
|
: // some models error if you provide it (rather than ignoring it)
|
|
177
191
|
{ temperature: props.temperature }),
|
|
178
192
|
});
|
|
179
193
|
|
|
180
|
-
//
|
|
194
|
+
// Create updated message history with the structured response
|
|
195
|
+
const responseText = JSON.stringify(object);
|
|
196
|
+
const updatedMessages = [
|
|
197
|
+
...messages,
|
|
198
|
+
{
|
|
199
|
+
role: "assistant" as const,
|
|
200
|
+
content: responseText,
|
|
201
|
+
},
|
|
202
|
+
];
|
|
203
|
+
|
|
204
|
+
// Return the resource with typed content and updated messages
|
|
181
205
|
return this({
|
|
182
206
|
type: props.schema,
|
|
183
207
|
object: object,
|
|
208
|
+
messages: updatedMessages,
|
|
184
209
|
createdAt: Date.now(),
|
|
185
210
|
});
|
|
186
211
|
});
|