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/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
|
});
|
package/src/ai/document.ts
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import { generateText } from "ai";
|
|
2
|
-
import fs from "node:fs/promises";
|
|
3
|
-
import path from "node:path";
|
|
1
|
+
import { generateText, type CoreMessage } from "ai";
|
|
4
2
|
import type { Context } from "../context";
|
|
3
|
+
import { StaticTextFile } from "../fs/static-text-file";
|
|
5
4
|
import { Resource } from "../resource";
|
|
6
5
|
import type { Secret } from "../secret";
|
|
7
|
-
import {
|
|
8
|
-
import { type ModelConfig, createModel } from "./client";
|
|
6
|
+
import { createModel, withRateLimitRetry, type ModelConfig } from "./client";
|
|
9
7
|
|
|
10
8
|
/**
|
|
11
9
|
* Properties for creating or updating a Document
|
|
@@ -13,13 +11,16 @@ import { type ModelConfig, createModel } from "./client";
|
|
|
13
11
|
export interface DocumentProps {
|
|
14
12
|
/**
|
|
15
13
|
* Title of the document
|
|
14
|
+
*
|
|
15
|
+
* @default id
|
|
16
16
|
*/
|
|
17
|
-
title
|
|
17
|
+
title?: string;
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
|
-
*
|
|
20
|
+
* Optional path to the markdown document
|
|
21
|
+
* If provided, document will be written to this path
|
|
21
22
|
*/
|
|
22
|
-
path
|
|
23
|
+
path?: string;
|
|
23
24
|
|
|
24
25
|
/**
|
|
25
26
|
* Base URL for the OpenAI API
|
|
@@ -36,7 +37,19 @@ export interface DocumentProps {
|
|
|
36
37
|
* ${alchemy.file("src/api.ts")}
|
|
37
38
|
* `
|
|
38
39
|
*/
|
|
39
|
-
prompt
|
|
40
|
+
prompt?: string;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Message history for conversation-based generation
|
|
44
|
+
* If provided, this will be used instead of the prompt
|
|
45
|
+
* @example
|
|
46
|
+
* messages: [
|
|
47
|
+
* { role: "user", content: "Generate API documentation for this file" },
|
|
48
|
+
* { role: "assistant", content: "I'll create detailed API docs. What file should I document?" },
|
|
49
|
+
* { role: "user", content: "Please document src/api.ts" }
|
|
50
|
+
* ]
|
|
51
|
+
*/
|
|
52
|
+
messages?: CoreMessage[];
|
|
40
53
|
|
|
41
54
|
/**
|
|
42
55
|
* System prompt for the model
|
|
@@ -64,17 +77,40 @@ export interface DocumentProps {
|
|
|
64
77
|
* @default 0.7
|
|
65
78
|
*/
|
|
66
79
|
temperature?: number;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Maximum number of tokens to generate.
|
|
83
|
+
* Higher values allow for longer documents but may increase cost and generation time.
|
|
84
|
+
* @default 10000
|
|
85
|
+
*/
|
|
86
|
+
maxTokens?: number;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Freeze the document after creation (do not re-generate on updates)
|
|
90
|
+
* @default false
|
|
91
|
+
*/
|
|
92
|
+
freeze?: boolean;
|
|
67
93
|
}
|
|
68
94
|
|
|
69
95
|
/**
|
|
70
96
|
* A markdown document that can be created, updated, and deleted
|
|
71
97
|
*/
|
|
72
98
|
export interface Document extends DocumentProps, Resource<"docs::Document"> {
|
|
99
|
+
/**
|
|
100
|
+
* The title of the document
|
|
101
|
+
*/
|
|
102
|
+
title: string;
|
|
103
|
+
|
|
73
104
|
/**
|
|
74
105
|
* Content of the document
|
|
75
106
|
*/
|
|
76
107
|
content: string;
|
|
77
108
|
|
|
109
|
+
/**
|
|
110
|
+
* Updated message history with the document response appended
|
|
111
|
+
*/
|
|
112
|
+
messages: CoreMessage[];
|
|
113
|
+
|
|
78
114
|
/**
|
|
79
115
|
* Time at which the document was created
|
|
80
116
|
*/
|
|
@@ -84,6 +120,11 @@ export interface Document extends DocumentProps, Resource<"docs::Document"> {
|
|
|
84
120
|
* Time at which the document was last updated
|
|
85
121
|
*/
|
|
86
122
|
updatedAt: number;
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* File resource if path was provided
|
|
126
|
+
*/
|
|
127
|
+
file?: StaticTextFile;
|
|
87
128
|
}
|
|
88
129
|
|
|
89
130
|
/**
|
|
@@ -97,7 +138,22 @@ const DEFAULT_MD_SYSTEM_PROMPT =
|
|
|
97
138
|
* Supports powerful context handling through the alchemy template literal tag.
|
|
98
139
|
*
|
|
99
140
|
* @example
|
|
100
|
-
* // Create
|
|
141
|
+
* // Create an in-memory document (no file created)
|
|
142
|
+
* const apiDocs = await Document("api-docs", {
|
|
143
|
+
* title: "API Documentation",
|
|
144
|
+
* prompt: await alchemy`
|
|
145
|
+
* Generate API documentation based on these source files:
|
|
146
|
+
* ${alchemy.file("src/api.ts")}
|
|
147
|
+
* ${alchemy.file("src/types.ts")}
|
|
148
|
+
* `,
|
|
149
|
+
* model: {
|
|
150
|
+
* id: "gpt-4o",
|
|
151
|
+
* provider: "openai"
|
|
152
|
+
* }
|
|
153
|
+
* });
|
|
154
|
+
*
|
|
155
|
+
* @example
|
|
156
|
+
* // Create a document and write it to disk
|
|
101
157
|
* const apiDocs = await Document("api-docs", {
|
|
102
158
|
* title: "API Documentation",
|
|
103
159
|
* path: "./docs/api.md",
|
|
@@ -113,6 +169,23 @@ const DEFAULT_MD_SYSTEM_PROMPT =
|
|
|
113
169
|
* });
|
|
114
170
|
*
|
|
115
171
|
* @example
|
|
172
|
+
* // Use message history for iterative document generation
|
|
173
|
+
* const apiDocs = await Document("api-docs", {
|
|
174
|
+
* title: "API Documentation",
|
|
175
|
+
* path: "./docs/api.md",
|
|
176
|
+
* messages: [
|
|
177
|
+
* { role: "user", content: "Create API documentation for these files" },
|
|
178
|
+
* { role: "assistant", content: "I'll help you create API documentation. Please provide the files." },
|
|
179
|
+
* { role: "user", content: "Here are the files: [file contents]" }
|
|
180
|
+
* ],
|
|
181
|
+
* system: "You are a technical documentation writer. Generate clear and concise API documentation.",
|
|
182
|
+
* model: {
|
|
183
|
+
* id: "gpt-4o",
|
|
184
|
+
* provider: "openai"
|
|
185
|
+
* }
|
|
186
|
+
* });
|
|
187
|
+
*
|
|
188
|
+
* @example
|
|
116
189
|
* // Use alchemy template literals with file collections and temperature control
|
|
117
190
|
* const modelDocs = await Document("models", {
|
|
118
191
|
* title: "Data Models",
|
|
@@ -149,84 +222,104 @@ export const Document = Resource(
|
|
|
149
222
|
async function (
|
|
150
223
|
this: Context<Document>,
|
|
151
224
|
id: string,
|
|
152
|
-
props: DocumentProps
|
|
225
|
+
props: DocumentProps
|
|
153
226
|
): Promise<Document> {
|
|
154
|
-
//
|
|
155
|
-
|
|
227
|
+
// Validate that either prompt or messages are provided
|
|
228
|
+
if (!props.prompt && !props.messages) {
|
|
229
|
+
throw new Error("Either prompt or messages must be provided");
|
|
230
|
+
}
|
|
156
231
|
|
|
232
|
+
// Handle deletion phase
|
|
157
233
|
if (this.phase === "delete") {
|
|
158
|
-
try {
|
|
159
|
-
await fs.unlink(props.path);
|
|
160
|
-
} catch (error: any) {
|
|
161
|
-
// Ignore if file doesn't exist
|
|
162
|
-
if (error.code !== "ENOENT") {
|
|
163
|
-
throw error;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
234
|
return this.destroy();
|
|
167
235
|
}
|
|
168
236
|
|
|
237
|
+
if (this.phase === "update" && props.freeze) {
|
|
238
|
+
if (props.path) {
|
|
239
|
+
const filePath = props.path;
|
|
240
|
+
const fileId = `${id}-file`;
|
|
241
|
+
|
|
242
|
+
await StaticTextFile(fileId, filePath, this.output!.content);
|
|
243
|
+
}
|
|
244
|
+
return this(this.output);
|
|
245
|
+
}
|
|
246
|
+
|
|
169
247
|
// Use provided system prompt or default
|
|
170
248
|
const system = props.system || DEFAULT_MD_SYSTEM_PROMPT;
|
|
171
249
|
|
|
172
|
-
// Generate initial content
|
|
173
|
-
const { text } = await
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
:
|
|
181
|
-
|
|
250
|
+
// Generate initial content with rate limit retry
|
|
251
|
+
const { text } = await withRateLimitRetry(async () => {
|
|
252
|
+
return generateText({
|
|
253
|
+
model: createModel(props),
|
|
254
|
+
...(props.messages
|
|
255
|
+
? { messages: props.messages }
|
|
256
|
+
: { prompt: props.prompt! }),
|
|
257
|
+
system,
|
|
258
|
+
maxTokens: props.maxTokens || 8192,
|
|
259
|
+
providerOptions: props.model?.options,
|
|
260
|
+
...(props.temperature === undefined
|
|
261
|
+
? {}
|
|
262
|
+
: // some models error if you provide it (rather than ignoring it)
|
|
263
|
+
{ temperature: props.temperature }),
|
|
264
|
+
});
|
|
182
265
|
});
|
|
183
266
|
|
|
184
267
|
// Extract and validate markdown content
|
|
185
|
-
let { content, error } =
|
|
268
|
+
let { content, error } = extractMarkdownContent(text);
|
|
186
269
|
|
|
187
270
|
// Re-prompt if there are validation errors
|
|
188
271
|
if (error) {
|
|
189
272
|
const errorSystem = `${system}\n\nERROR: ${error}\n\nPlease try again and ensure your response contains exactly one markdown document inside \`\`\`md fences.`;
|
|
190
273
|
|
|
191
|
-
const { text: retryText } = await
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
:
|
|
274
|
+
const { text: retryText } = await withRateLimitRetry(async () => {
|
|
275
|
+
return generateText({
|
|
276
|
+
model: createModel(props),
|
|
277
|
+
...(props.messages
|
|
278
|
+
? { messages: props.messages }
|
|
279
|
+
: { prompt: props.prompt! }),
|
|
280
|
+
system: errorSystem,
|
|
281
|
+
providerOptions: props.model?.options,
|
|
282
|
+
...(props.temperature === undefined
|
|
283
|
+
? {}
|
|
284
|
+
: { temperature: props.temperature }),
|
|
285
|
+
});
|
|
199
286
|
});
|
|
200
287
|
|
|
201
|
-
const retryResult =
|
|
288
|
+
const retryResult = extractMarkdownContent(retryText);
|
|
202
289
|
|
|
203
290
|
if (retryResult.error) {
|
|
204
291
|
throw new Error(
|
|
205
|
-
`Failed to generate valid markdown content: ${retryResult.error}
|
|
292
|
+
`Failed to generate valid markdown content: ${retryResult.error}\n${retryText}`
|
|
206
293
|
);
|
|
207
294
|
}
|
|
208
295
|
|
|
209
296
|
content = retryResult.content;
|
|
210
297
|
}
|
|
211
298
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
299
|
+
// Create result object
|
|
300
|
+
const result: Partial<Document> = {
|
|
301
|
+
...props,
|
|
302
|
+
content,
|
|
303
|
+
messages: [
|
|
304
|
+
...(props.messages || [{ role: "user", content: props.prompt! }]),
|
|
305
|
+
{ role: "assistant", content },
|
|
306
|
+
],
|
|
307
|
+
createdAt: this.output?.createdAt || Date.now(),
|
|
308
|
+
updatedAt: Date.now(),
|
|
309
|
+
title: props.title || id,
|
|
310
|
+
};
|
|
215
311
|
|
|
216
|
-
// Write
|
|
217
|
-
|
|
312
|
+
// Write file if path is provided
|
|
313
|
+
if (props.path) {
|
|
314
|
+
const filePath = props.path;
|
|
315
|
+
const fileId = `${id}-file`;
|
|
218
316
|
|
|
219
|
-
|
|
220
|
-
|
|
317
|
+
result.file = await StaticTextFile(fileId, filePath, content);
|
|
318
|
+
}
|
|
221
319
|
|
|
222
320
|
// Return the resource
|
|
223
|
-
return this(
|
|
224
|
-
|
|
225
|
-
content: content,
|
|
226
|
-
createdAt: stats.birthtimeMs,
|
|
227
|
-
updatedAt: stats.mtimeMs,
|
|
228
|
-
});
|
|
229
|
-
},
|
|
321
|
+
return this(result as Document);
|
|
322
|
+
}
|
|
230
323
|
);
|
|
231
324
|
|
|
232
325
|
/**
|
|
@@ -236,13 +329,14 @@ export const Document = Resource(
|
|
|
236
329
|
* @param text The text to extract markdown content from
|
|
237
330
|
* @returns The extracted markdown content or error message
|
|
238
331
|
*/
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
const
|
|
332
|
+
function extractMarkdownContent(text: string): {
|
|
333
|
+
content: string;
|
|
334
|
+
error?: string;
|
|
335
|
+
} {
|
|
336
|
+
const lines = text.split("\n");
|
|
337
|
+
const startIdx = lines.findIndex((line) => line.trim() === "```md");
|
|
244
338
|
|
|
245
|
-
if (
|
|
339
|
+
if (startIdx === -1) {
|
|
246
340
|
return {
|
|
247
341
|
content: "",
|
|
248
342
|
error:
|
|
@@ -250,13 +344,24 @@ async function extractMarkdownContent(
|
|
|
250
344
|
};
|
|
251
345
|
}
|
|
252
346
|
|
|
253
|
-
|
|
347
|
+
const rest = lines.slice(startIdx + 1);
|
|
348
|
+
const endRelativeIdx = rest
|
|
349
|
+
.map((line) => line.trim() === "```")
|
|
350
|
+
.lastIndexOf(true);
|
|
351
|
+
|
|
352
|
+
if (endRelativeIdx === -1) {
|
|
254
353
|
return {
|
|
255
354
|
content: "",
|
|
256
|
-
error:
|
|
257
|
-
"Multiple markdown code blocks found in the response. Please provide exactly one markdown block within ```md fences.",
|
|
355
|
+
error: "Markdown block was not closed properly.",
|
|
258
356
|
};
|
|
259
357
|
}
|
|
260
358
|
|
|
261
|
-
|
|
359
|
+
const endIdx = startIdx + 1 + endRelativeIdx;
|
|
360
|
+
|
|
361
|
+
const content = lines
|
|
362
|
+
.slice(startIdx + 1, endIdx)
|
|
363
|
+
.join("\n")
|
|
364
|
+
.trim();
|
|
365
|
+
|
|
366
|
+
return { content };
|
|
262
367
|
}
|
package/src/ai/html-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 { StaticHTMLFile } from "../fs/static-html-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
|
/**
|
|
@@ -151,20 +149,10 @@ export const HTMLFile = Resource(
|
|
|
151
149
|
async function (
|
|
152
150
|
this: Context<HTMLFile>,
|
|
153
151
|
id: string,
|
|
154
|
-
props: HTMLFileProps
|
|
152
|
+
props: HTMLFileProps
|
|
155
153
|
): Promise<HTMLFile> {
|
|
156
|
-
//
|
|
157
|
-
await fs.mkdir(path.dirname(props.path), { recursive: true });
|
|
158
|
-
|
|
154
|
+
// Handle deletion phase
|
|
159
155
|
if (this.phase === "delete") {
|
|
160
|
-
try {
|
|
161
|
-
await fs.unlink(props.path);
|
|
162
|
-
} catch (error: any) {
|
|
163
|
-
// Ignore if file doesn't exist
|
|
164
|
-
if (error.code !== "ENOENT") {
|
|
165
|
-
throw error;
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
156
|
return this.destroy();
|
|
169
157
|
}
|
|
170
158
|
|
|
@@ -203,31 +191,24 @@ export const HTMLFile = Resource(
|
|
|
203
191
|
|
|
204
192
|
if (retryResult.error) {
|
|
205
193
|
throw new Error(
|
|
206
|
-
`Failed to generate valid HTML code: ${retryResult.error}
|
|
194
|
+
`Failed to generate valid HTML code: ${retryResult.error}`
|
|
207
195
|
);
|
|
208
196
|
}
|
|
209
197
|
|
|
210
198
|
code = retryResult.code;
|
|
211
199
|
}
|
|
212
200
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
// Write content to file
|
|
218
|
-
await fs.writeFile(props.path, code);
|
|
219
|
-
|
|
220
|
-
// Get file stats for timestamps
|
|
221
|
-
const stats = await fs.stat(props.path);
|
|
201
|
+
// Use StaticHTMLFile to create/update the file
|
|
202
|
+
const file = await StaticHTMLFile("file", props.path, code);
|
|
222
203
|
|
|
223
204
|
// Return the resource
|
|
224
205
|
return this({
|
|
225
206
|
...props,
|
|
226
|
-
content:
|
|
227
|
-
createdAt:
|
|
228
|
-
updatedAt:
|
|
207
|
+
content: file.content,
|
|
208
|
+
createdAt: Date.now(),
|
|
209
|
+
updatedAt: Date.now(),
|
|
229
210
|
});
|
|
230
|
-
}
|
|
211
|
+
}
|
|
231
212
|
);
|
|
232
213
|
|
|
233
214
|
/**
|
|
@@ -238,7 +219,7 @@ export const HTMLFile = Resource(
|
|
|
238
219
|
* @returns The extracted HTML code or error message
|
|
239
220
|
*/
|
|
240
221
|
async function extractHTMLCode(
|
|
241
|
-
text: string
|
|
222
|
+
text: string
|
|
242
223
|
): Promise<{ code: string; error?: string }> {
|
|
243
224
|
const htmlCodeRegex = /```html\s*([\s\S]*?)```/g;
|
|
244
225
|
const matches = Array.from(text.matchAll(htmlCodeRegex));
|