alchemy 0.3.0 → 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.
- package/README.md +8 -3
- 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 +76 -5
- package/lib/ai/document.js +112 -56
- package/lib/ai/html-file.js +7 -24
- package/lib/ai/json-file.js +11 -32
- 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.d.ts +5 -0
- package/lib/alchemy.js +61 -3
- package/lib/apply.js +18 -11
- package/lib/aws/account-id.d.ts +4 -1
- package/lib/aws/account-id.js +1 -1
- package/lib/aws/credentials.d.ts +5 -0
- package/lib/aws/credentials.js +0 -0
- package/lib/aws/oidc/oidc-provider.js +2 -2
- package/lib/aws/role.js +35 -23
- package/lib/cloudflare/account-api-token.d.ts +168 -0
- package/lib/cloudflare/account-api-token.js +179 -0
- package/lib/cloudflare/account-id.d.ts +1 -0
- package/lib/cloudflare/account-id.js +0 -0
- package/lib/cloudflare/api.d.ts +38 -1
- package/lib/cloudflare/api.js +55 -4
- package/lib/cloudflare/bucket.d.ts +118 -3
- package/lib/cloudflare/bucket.js +269 -89
- package/lib/cloudflare/custom-domain.d.ts +75 -0
- package/lib/cloudflare/custom-domain.js +154 -0
- package/lib/cloudflare/{dns.js → dns-records.js} +14 -1
- package/lib/cloudflare/generate-asset-manifest.js +1 -1
- package/lib/cloudflare/index.d.ts +5 -1
- package/lib/cloudflare/index.js +5 -1
- package/lib/cloudflare/permission-groups.d.ts +78 -0
- package/lib/cloudflare/permission-groups.js +48 -0
- package/lib/cloudflare/r2-rest-state-store.d.ts +2 -1
- package/lib/cloudflare/r2-rest-state-store.js +3 -2
- package/lib/cloudflare/static-site-router.js +16 -7
- package/lib/cloudflare/static-site.d.ts +17 -18
- package/lib/cloudflare/static-site.js +19 -7
- package/lib/cloudflare/upload-asset-manifest.js +4 -2
- package/lib/{util/encrypt.d.ts → encrypt.d.ts} +1 -1
- package/lib/{util/encrypt.js → encrypt.js} +1 -1
- package/lib/fs/copy-file.d.ts +54 -0
- package/lib/fs/copy-file.js +63 -0
- package/lib/fs/file-system-state-store.js +1 -1
- package/lib/fs/file.d.ts +31 -0
- package/lib/fs/file.js +30 -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/{providers.d.ts → docs/providers.d.ts} +14 -4
- package/lib/internal/docs/providers.js +196 -0
- package/lib/secret.d.ts +3 -0
- package/lib/secret.js +13 -0
- package/lib/{util/serde.d.ts → serde.d.ts} +1 -1
- package/lib/{util/serde.js → serde.js} +12 -4
- package/lib/test/bun.js +3 -2
- package/lib/util/sha256.d.ts +1 -0
- package/lib/util/sha256.js +4 -0
- package/lib/web/vitepress/config.d.ts +11 -0
- package/lib/web/vitepress/config.js +32 -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 +2 -0
- package/lib/web/vitepress/index.js +2 -0
- package/lib/web/vitepress/process-front-matter-files.d.ts +18 -0
- package/lib/web/vitepress/process-front-matter-files.js +68 -0
- package/lib/web/vitepress/vitepress.d.ts +5 -4
- package/lib/web/vitepress/vitepress.js +20 -36
- package/package.json +8 -4
- 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 +171 -66
- package/src/ai/html-file.ts +11 -30
- package/src/ai/json-file.ts +13 -37
- 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 +74 -9
- package/src/apply.ts +27 -26
- package/src/aws/account-id.ts +6 -2
- package/src/aws/credentials.ts +6 -0
- package/src/aws/oidc/oidc-provider.ts +17 -17
- package/src/aws/role.ts +68 -54
- package/src/cloudflare/account-api-token.ts +365 -0
- package/src/cloudflare/account-id.ts +0 -0
- package/src/cloudflare/api.ts +88 -17
- package/src/cloudflare/bucket.ts +493 -133
- package/src/cloudflare/custom-domain.ts +318 -0
- package/src/cloudflare/{dns.ts → dns-records.ts} +36 -22
- package/src/cloudflare/generate-asset-manifest.ts +4 -4
- package/src/cloudflare/index.ts +5 -1
- package/src/cloudflare/permission-groups.ts +137 -0
- package/src/cloudflare/r2-rest-state-store.ts +14 -13
- package/src/cloudflare/static-site-router.ts +18 -8
- package/src/cloudflare/static-site.ts +27 -35
- package/src/cloudflare/upload-asset-manifest.ts +6 -4
- package/src/cloudflare/worker.ts +44 -43
- package/src/dns/import-dns.ts +4 -4
- package/src/{util/encrypt.ts → encrypt.ts} +7 -10
- package/src/fs/copy-file.ts +115 -0
- package/src/fs/file-system-state-store.ts +5 -5
- package/src/fs/file.ts +64 -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/docs/providers.ts +281 -0
- package/src/secret.ts +16 -0
- package/src/{util/serde.ts → serde.ts} +16 -10
- package/src/test/bun.ts +8 -7
- package/src/util/sha256.ts +5 -0
- package/src/web/vitepress/config.ts +50 -0
- package/src/web/vitepress/home-page.ts +54 -42
- package/src/web/vitepress/index.ts +2 -0
- package/src/web/vitepress/process-front-matter-files.ts +98 -0
- package/src/web/vitepress/vitepress.ts +34 -50
- package/lib/internal/getting-started.d.ts +0 -10
- package/lib/internal/getting-started.js +0 -94
- package/lib/internal/project.d.ts +0 -13
- package/lib/internal/project.js +0 -101
- package/lib/internal/providers.js +0 -148
- package/src/internal/getting-started.ts +0 -107
- package/src/internal/project.ts +0 -119
- package/src/internal/providers.ts +0 -209
- /package/lib/cloudflare/{dns.d.ts → dns-records.d.ts} +0 -0
package/README.md
CHANGED
|
@@ -9,8 +9,10 @@ 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
|
-
|
|
12
|
+
// initialize the app (with default state $USER)
|
|
13
|
+
const app = alchemy("cloudflare-worker");
|
|
13
14
|
|
|
15
|
+
// create a Cloudflare Worker
|
|
14
16
|
export const worker = await Worker("worker", {
|
|
15
17
|
name: "my-worker",
|
|
16
18
|
entrypoint: "./src/index.ts",
|
|
@@ -18,10 +20,13 @@ export const worker = await Worker("worker", {
|
|
|
18
20
|
COUNTER: counter,
|
|
19
21
|
STORAGE: storage, // Bind the R2 bucket to the worker
|
|
20
22
|
AUTH_STORE: authStore,
|
|
21
|
-
GITHUB_CLIENT_ID: secret(process.env.GITHUB_CLIENT_ID),
|
|
22
|
-
GITHUB_CLIENT_SECRET: secret(process.env.GITHUB_CLIENT_SECRET),
|
|
23
|
+
GITHUB_CLIENT_ID: alchemy.secret(process.env.GITHUB_CLIENT_ID),
|
|
24
|
+
GITHUB_CLIENT_SECRET: alchemy.secret(process.env.GITHUB_CLIENT_SECRET),
|
|
23
25
|
},
|
|
24
26
|
});
|
|
27
|
+
|
|
28
|
+
// finalize the alchemy app (triggering deletion of orphaned resources)
|
|
29
|
+
await app.finalize();
|
|
25
30
|
```
|
|
26
31
|
|
|
27
32
|
# Features
|
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";
|
|
@@ -8,12 +10,15 @@ import { type ModelConfig } from "./client";
|
|
|
8
10
|
export interface DocumentProps {
|
|
9
11
|
/**
|
|
10
12
|
* Title of the document
|
|
13
|
+
*
|
|
14
|
+
* @default id
|
|
11
15
|
*/
|
|
12
|
-
title
|
|
16
|
+
title?: string;
|
|
13
17
|
/**
|
|
14
|
-
*
|
|
18
|
+
* Optional path to the markdown document
|
|
19
|
+
* If provided, document will be written to this path
|
|
15
20
|
*/
|
|
16
|
-
path
|
|
21
|
+
path?: string;
|
|
17
22
|
/**
|
|
18
23
|
* Base URL for the OpenAI API
|
|
19
24
|
* @default 'https://api.openai.com/v1'
|
|
@@ -28,7 +33,18 @@ export interface DocumentProps {
|
|
|
28
33
|
* ${alchemy.file("src/api.ts")}
|
|
29
34
|
* `
|
|
30
35
|
*/
|
|
31
|
-
prompt
|
|
36
|
+
prompt?: string;
|
|
37
|
+
/**
|
|
38
|
+
* Message history for conversation-based generation
|
|
39
|
+
* If provided, this will be used instead of the prompt
|
|
40
|
+
* @example
|
|
41
|
+
* messages: [
|
|
42
|
+
* { role: "user", content: "Generate API documentation for this file" },
|
|
43
|
+
* { role: "assistant", content: "I'll create detailed API docs. What file should I document?" },
|
|
44
|
+
* { role: "user", content: "Please document src/api.ts" }
|
|
45
|
+
* ]
|
|
46
|
+
*/
|
|
47
|
+
messages?: CoreMessage[];
|
|
32
48
|
/**
|
|
33
49
|
* System prompt for the model
|
|
34
50
|
* This is used to provide instructions to the model about how to format the response
|
|
@@ -52,15 +68,34 @@ export interface DocumentProps {
|
|
|
52
68
|
* @default 0.7
|
|
53
69
|
*/
|
|
54
70
|
temperature?: number;
|
|
71
|
+
/**
|
|
72
|
+
* Maximum number of tokens to generate.
|
|
73
|
+
* Higher values allow for longer documents but may increase cost and generation time.
|
|
74
|
+
* @default 10000
|
|
75
|
+
*/
|
|
76
|
+
maxTokens?: number;
|
|
77
|
+
/**
|
|
78
|
+
* Freeze the document after creation (do not re-generate on updates)
|
|
79
|
+
* @default false
|
|
80
|
+
*/
|
|
81
|
+
freeze?: boolean;
|
|
55
82
|
}
|
|
56
83
|
/**
|
|
57
84
|
* A markdown document that can be created, updated, and deleted
|
|
58
85
|
*/
|
|
59
86
|
export interface Document extends DocumentProps, Resource<"docs::Document"> {
|
|
87
|
+
/**
|
|
88
|
+
* The title of the document
|
|
89
|
+
*/
|
|
90
|
+
title: string;
|
|
60
91
|
/**
|
|
61
92
|
* Content of the document
|
|
62
93
|
*/
|
|
63
94
|
content: string;
|
|
95
|
+
/**
|
|
96
|
+
* Updated message history with the document response appended
|
|
97
|
+
*/
|
|
98
|
+
messages: CoreMessage[];
|
|
64
99
|
/**
|
|
65
100
|
* Time at which the document was created
|
|
66
101
|
*/
|
|
@@ -69,13 +104,32 @@ export interface Document extends DocumentProps, Resource<"docs::Document"> {
|
|
|
69
104
|
* Time at which the document was last updated
|
|
70
105
|
*/
|
|
71
106
|
updatedAt: number;
|
|
107
|
+
/**
|
|
108
|
+
* File resource if path was provided
|
|
109
|
+
*/
|
|
110
|
+
file?: StaticTextFile;
|
|
72
111
|
}
|
|
73
112
|
/**
|
|
74
113
|
* Resource for managing AI-generated markdown documents using the Vercel AI SDK.
|
|
75
114
|
* Supports powerful context handling through the alchemy template literal tag.
|
|
76
115
|
*
|
|
77
116
|
* @example
|
|
78
|
-
* // Create
|
|
117
|
+
* // Create an in-memory document (no file created)
|
|
118
|
+
* const apiDocs = await Document("api-docs", {
|
|
119
|
+
* title: "API Documentation",
|
|
120
|
+
* prompt: await alchemy`
|
|
121
|
+
* Generate API documentation based on these source files:
|
|
122
|
+
* ${alchemy.file("src/api.ts")}
|
|
123
|
+
* ${alchemy.file("src/types.ts")}
|
|
124
|
+
* `,
|
|
125
|
+
* model: {
|
|
126
|
+
* id: "gpt-4o",
|
|
127
|
+
* provider: "openai"
|
|
128
|
+
* }
|
|
129
|
+
* });
|
|
130
|
+
*
|
|
131
|
+
* @example
|
|
132
|
+
* // Create a document and write it to disk
|
|
79
133
|
* const apiDocs = await Document("api-docs", {
|
|
80
134
|
* title: "API Documentation",
|
|
81
135
|
* path: "./docs/api.md",
|
|
@@ -91,6 +145,23 @@ export interface Document extends DocumentProps, Resource<"docs::Document"> {
|
|
|
91
145
|
* });
|
|
92
146
|
*
|
|
93
147
|
* @example
|
|
148
|
+
* // Use message history for iterative document generation
|
|
149
|
+
* const apiDocs = await Document("api-docs", {
|
|
150
|
+
* title: "API Documentation",
|
|
151
|
+
* path: "./docs/api.md",
|
|
152
|
+
* messages: [
|
|
153
|
+
* { role: "user", content: "Create API documentation for these files" },
|
|
154
|
+
* { role: "assistant", content: "I'll help you create API documentation. Please provide the files." },
|
|
155
|
+
* { role: "user", content: "Here are the files: [file contents]" }
|
|
156
|
+
* ],
|
|
157
|
+
* system: "You are a technical documentation writer. Generate clear and concise API documentation.",
|
|
158
|
+
* model: {
|
|
159
|
+
* id: "gpt-4o",
|
|
160
|
+
* provider: "openai"
|
|
161
|
+
* }
|
|
162
|
+
* });
|
|
163
|
+
*
|
|
164
|
+
* @example
|
|
94
165
|
* // Use alchemy template literals with file collections and temperature control
|
|
95
166
|
* const modelDocs = await Document("models", {
|
|
96
167
|
* title: "Data Models",
|