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