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.
Files changed (107) hide show
  1. package/lib/ai/approve.d.ts +99 -0
  2. package/lib/ai/approve.js +76 -0
  3. package/lib/ai/astro-file.js +7 -24
  4. package/lib/ai/client.d.ts +7 -0
  5. package/lib/ai/client.js +45 -0
  6. package/lib/ai/css-file.js +7 -24
  7. package/lib/ai/data.d.ts +26 -21
  8. package/lib/ai/data.js +33 -22
  9. package/lib/ai/document.d.ts +64 -4
  10. package/lib/ai/document.js +102 -55
  11. package/lib/ai/html-file.js +7 -24
  12. package/lib/ai/index.d.ts +2 -0
  13. package/lib/ai/index.js +2 -0
  14. package/lib/ai/json-file.js +11 -32
  15. package/lib/ai/review.d.ts +122 -0
  16. package/lib/ai/review.js +101 -0
  17. package/lib/ai/typescript-file.d.ts +1 -2
  18. package/lib/ai/typescript-file.js +8 -39
  19. package/lib/ai/vue-file.js +7 -24
  20. package/lib/ai/yaml-file.js +12 -48
  21. package/lib/alchemy.js +17 -2
  22. package/lib/apply.js +1 -1
  23. package/lib/cloudflare/generate-asset-manifest.js +1 -1
  24. package/lib/cloudflare/static-site-router.js +16 -7
  25. package/lib/cloudflare/static-site.js +3 -0
  26. package/lib/cloudflare/upload-asset-manifest.js +4 -2
  27. package/lib/fs/copy-file.d.ts +54 -0
  28. package/lib/fs/copy-file.js +63 -0
  29. package/lib/fs/file.d.ts +13 -0
  30. package/lib/fs/file.js +24 -5
  31. package/lib/fs/index.d.ts +6 -0
  32. package/lib/fs/index.js +6 -0
  33. package/lib/fs/static-astro-file.d.ts +34 -0
  34. package/lib/fs/static-astro-file.js +39 -0
  35. package/lib/fs/static-css-file.d.ts +24 -0
  36. package/lib/fs/static-css-file.js +29 -0
  37. package/lib/fs/static-html-file.d.ts +31 -0
  38. package/lib/fs/static-html-file.js +36 -0
  39. package/lib/fs/static-json-file.d.ts +2 -2
  40. package/lib/fs/static-json-file.js +3 -2
  41. package/lib/fs/static-text-file.d.ts +1 -1
  42. package/lib/fs/static-text-file.js +3 -2
  43. package/lib/fs/static-typescript-file.d.ts +2 -2
  44. package/lib/fs/static-typescript-file.js +3 -2
  45. package/lib/fs/static-vue-file.d.ts +28 -0
  46. package/lib/fs/static-vue-file.js +33 -0
  47. package/lib/fs/static-yaml-file.d.ts +11 -8
  48. package/lib/fs/static-yaml-file.js +3 -2
  49. package/lib/internal/getting-started.d.ts +15 -4
  50. package/lib/internal/getting-started.js +34 -41
  51. package/lib/internal/index.d.ts +3 -0
  52. package/lib/internal/index.js +3 -0
  53. package/lib/internal/providers.d.ts +7 -3
  54. package/lib/internal/providers.js +42 -18
  55. package/lib/internal/tutorial.d.ts +104 -0
  56. package/lib/internal/tutorial.js +251 -0
  57. package/lib/web/vitepress/config.d.ts +10 -0
  58. package/lib/web/vitepress/config.js +31 -0
  59. package/lib/web/vitepress/home-page.d.ts +5 -3
  60. package/lib/web/vitepress/home-page.js +48 -35
  61. package/lib/web/vitepress/index.d.ts +1 -0
  62. package/lib/web/vitepress/index.js +1 -0
  63. package/lib/web/vitepress/vitepress.d.ts +5 -4
  64. package/lib/web/vitepress/vitepress.js +17 -34
  65. package/package.json +1 -1
  66. package/src/ai/approve.ts +163 -0
  67. package/src/ai/astro-file.ts +11 -30
  68. package/src/ai/client.ts +59 -0
  69. package/src/ai/css-file.ts +11 -30
  70. package/src/ai/data.ts +50 -25
  71. package/src/ai/document.ts +146 -65
  72. package/src/ai/html-file.ts +11 -30
  73. package/src/ai/index.ts +2 -0
  74. package/src/ai/json-file.ts +13 -37
  75. package/src/ai/review.ts +213 -0
  76. package/src/ai/typescript-file.ts +14 -48
  77. package/src/ai/vue-file.ts +11 -30
  78. package/src/ai/yaml-file.ts +13 -53
  79. package/src/alchemy.ts +18 -7
  80. package/src/apply.ts +8 -8
  81. package/src/cloudflare/generate-asset-manifest.ts +4 -4
  82. package/src/cloudflare/static-site-router.ts +18 -8
  83. package/src/cloudflare/static-site.ts +11 -7
  84. package/src/cloudflare/upload-asset-manifest.ts +6 -4
  85. package/src/cloudflare/worker.ts +44 -43
  86. package/src/fs/copy-file.ts +115 -0
  87. package/src/fs/file.ts +35 -9
  88. package/src/fs/index.ts +6 -0
  89. package/src/fs/static-astro-file.ts +45 -0
  90. package/src/fs/static-css-file.ts +35 -0
  91. package/src/fs/static-html-file.ts +42 -0
  92. package/src/fs/static-json-file.ts +4 -3
  93. package/src/fs/static-text-file.ts +5 -2
  94. package/src/fs/static-typescript-file.ts +4 -3
  95. package/src/fs/static-vue-file.ts +39 -0
  96. package/src/fs/static-yaml-file.ts +13 -9
  97. package/src/internal/getting-started.ts +51 -43
  98. package/src/internal/index.ts +3 -0
  99. package/src/internal/providers.ts +59 -27
  100. package/src/internal/tutorial.ts +392 -0
  101. package/src/web/vitepress/config.ts +48 -0
  102. package/src/web/vitepress/home-page.ts +54 -42
  103. package/src/web/vitepress/index.ts +1 -0
  104. package/src/web/vitepress/vitepress.ts +31 -48
  105. package/lib/internal/project.d.ts +0 -13
  106. package/lib/internal/project.js +0 -101
  107. package/src/internal/project.ts +0 -119
@@ -0,0 +1,99 @@
1
+ import type { CoreMessage } from "ai";
2
+ /**
3
+ * Properties for approving or denying content
4
+ */
5
+ export interface ApproveProps {
6
+ /**
7
+ * Content to be reviewed
8
+ * Use alchemy template literals to include file context:
9
+ * @example
10
+ * content: await alchemy`
11
+ * Review this code:
12
+ * ${alchemy.file("src/api.ts")}
13
+ * `
14
+ *
15
+ * Required unless messages are provided
16
+ */
17
+ content?: string;
18
+ /**
19
+ * Prompt for the approval decision
20
+ * This should include specific criteria for approval or denial
21
+ * @example
22
+ * prompt: "Approve this code if it follows security best practices and has proper error handling."
23
+ *
24
+ * Required unless messages are provided
25
+ */
26
+ prompt?: string;
27
+ /**
28
+ * System prompt for the model
29
+ * This is used to provide instructions to the model about how to make the decision
30
+ */
31
+ system?: string;
32
+ /**
33
+ * Message history for the conversation
34
+ * If provided, this will be used instead of the prompt and content
35
+ */
36
+ messages?: CoreMessage[];
37
+ /**
38
+ * Temperature for controlling randomness in generation.
39
+ * Higher values (e.g., 0.8) make output more random,
40
+ * lower values (e.g., 0.2) make it more deterministic.
41
+ * @default 0.2 (lower for more consistent decisions)
42
+ */
43
+ temperature?: number;
44
+ }
45
+ /**
46
+ * Result of the approval process
47
+ */
48
+ export interface ApprovalResult {
49
+ /**
50
+ * Whether the content was approved
51
+ */
52
+ approved: boolean;
53
+ /**
54
+ * Explanation for the decision
55
+ */
56
+ explanation: string;
57
+ /**
58
+ * Suggestions for improvement if denied
59
+ */
60
+ suggestions?: string[];
61
+ /**
62
+ * Updated message history with the approval response appended
63
+ */
64
+ messages: CoreMessage[];
65
+ }
66
+ export type Approve = ApprovalResult;
67
+ /**
68
+ * Approves or denies content based on a prompt or message history
69
+ *
70
+ * @example
71
+ * // Approve or deny a code file
72
+ * const result = await Approve("code-approval", {
73
+ * content: await alchemy`
74
+ * Review this API implementation:
75
+ * ${alchemy.file("src/api.ts")}
76
+ * `,
77
+ * prompt: "Approve this code if it follows security best practices and has proper error handling."
78
+ * });
79
+ *
80
+ * if (result.approved) {
81
+ * console.log("Content approved:", result.explanation);
82
+ * } else {
83
+ * console.log("Content denied:", result.explanation);
84
+ * console.log("Suggestions:", result.suggestions);
85
+ * }
86
+ *
87
+ * @example
88
+ * // Approve or deny using message history
89
+ * const result = await Approve("doc-approval-with-history", {
90
+ * content: "This is the content to review",
91
+ * prompt: "Approve this documentation if it is clear and accurate",
92
+ * messages: [
93
+ * { role: "user", content: "Can you review this documentation?" },
94
+ * { role: "assistant", content: "Yes, I'd be happy to review it." },
95
+ * { role: "user", content: "Please check for clarity and accuracy." }
96
+ * ]
97
+ * });
98
+ */
99
+ export declare function Approve(id: string, props: ApproveProps): Promise<ApprovalResult>;
@@ -0,0 +1,76 @@
1
+ import { type } from "arktype";
2
+ import { Data } from "./data";
3
+ /**
4
+ * Default system prompt for approval decisions
5
+ */
6
+ const DEFAULT_APPROVE_SYSTEM_PROMPT = `You are an expert content reviewer tasked with approving or denying content based on specific criteria.
7
+
8
+ Your role is to:
9
+ 1. Carefully evaluate the content against the provided criteria
10
+ 2. Make a clear decision to approve or deny the content
11
+ 3. Provide a detailed explanation for your decision
12
+ 4. If denying, offer specific suggestions for improvement
13
+
14
+ Be objective, thorough, and fair in your assessment.`;
15
+ /**
16
+ * Approves or denies content based on a prompt or message history
17
+ *
18
+ * @example
19
+ * // Approve or deny a code file
20
+ * const result = await Approve("code-approval", {
21
+ * content: await alchemy`
22
+ * Review this API implementation:
23
+ * ${alchemy.file("src/api.ts")}
24
+ * `,
25
+ * prompt: "Approve this code if it follows security best practices and has proper error handling."
26
+ * });
27
+ *
28
+ * if (result.approved) {
29
+ * console.log("Content approved:", result.explanation);
30
+ * } else {
31
+ * console.log("Content denied:", result.explanation);
32
+ * console.log("Suggestions:", result.suggestions);
33
+ * }
34
+ *
35
+ * @example
36
+ * // Approve or deny using message history
37
+ * const result = await Approve("doc-approval-with-history", {
38
+ * content: "This is the content to review",
39
+ * prompt: "Approve this documentation if it is clear and accurate",
40
+ * messages: [
41
+ * { role: "user", content: "Can you review this documentation?" },
42
+ * { role: "assistant", content: "Yes, I'd be happy to review it." },
43
+ * { role: "user", content: "Please check for clarity and accuracy." }
44
+ * ]
45
+ * });
46
+ */
47
+ export async function Approve(id, props) {
48
+ // Create messages array if not provided
49
+ const messages = props.messages ||
50
+ (props.content && props.prompt
51
+ ? [
52
+ {
53
+ role: "user",
54
+ content: `${props.content}\n\n${props.prompt}`,
55
+ },
56
+ ]
57
+ : []);
58
+ if (messages.length === 0) {
59
+ throw new Error("Either messages or both content and prompt must be provided");
60
+ }
61
+ const data = await Data(id, {
62
+ schema: type({
63
+ approved: "boolean",
64
+ explanation: "string",
65
+ suggestions: "string[]?",
66
+ }),
67
+ messages,
68
+ system: props.system || DEFAULT_APPROVE_SYSTEM_PROMPT,
69
+ temperature: props.temperature ?? 0.2,
70
+ });
71
+ // Return the result with updated messages from Data
72
+ return {
73
+ ...data.object,
74
+ messages: data.messages,
75
+ };
76
+ }
@@ -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
- // Ensure directory exists
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
- if (this.phase === "update" && props.path !== this.props.path) {
136
- await ignore("ENOENT", () => fs.unlink(this.props.path));
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: code,
146
- createdAt: stats.birthtimeMs,
147
- updatedAt: stats.mtimeMs,
128
+ content: file.content,
129
+ createdAt: Date.now(),
130
+ updatedAt: Date.now(),
148
131
  });
149
132
  });
150
133
  /**
@@ -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
+ }
@@ -1,8 +1,6 @@
1
1
  import { generateText } from "ai";
2
- import fs from "node:fs/promises";
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
- // Ensure directory exists
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
- if (this.phase === "update" && props.path !== this.props.path) {
121
- await ignore("ENOENT", () => fs.unlink(this.props.path));
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: code,
131
- createdAt: stats.birthtimeMs,
132
- updatedAt: stats.mtimeMs,
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: string;
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 specific model configuration with advanced options
118
- * const analysisSchema = type({
119
- * insights: "string[]",
120
- * recommendations: "string[]",
121
- * risk: "'low'|'medium'|'high'"
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 analysis = await Data("code-analysis", {
125
- * schema: analysisSchema,
126
- * prompt: await alchemy`
127
- * Analyze this code for security issues:
128
- * ${alchemy.file("src/auth/login.ts")}
129
- * `,
130
- * system: "You are a security expert specializing in code analysis",
131
- * model: {
132
- * id: "o3-mini",
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 specific model configuration with advanced options
57
- * const analysisSchema = type({
58
- * insights: "string[]",
59
- * recommendations: "string[]",
60
- * risk: "'low'|'medium'|'high'"
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 analysis = await Data("code-analysis", {
64
- * schema: analysisSchema,
65
- * prompt: await alchemy`
66
- * Analyze this code for security issues:
67
- * ${alchemy.file("src/auth/login.ts")}
68
- * `,
69
- * system: "You are a security expert specializing in code analysis",
70
- * model: {
71
- * id: "o3-mini",
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
- prompt: props.prompt,
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
- // Return the resource with typed content
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
  });
@@ -1,4 +1,6 @@
1
+ import { type CoreMessage } from "ai";
1
2
  import type { Context } from "../context";
3
+ import { StaticTextFile } from "../fs/static-text-file";
2
4
  import { Resource } from "../resource";
3
5
  import type { Secret } from "../secret";
4
6
  import { type ModelConfig } from "./client";
@@ -11,9 +13,10 @@ export interface DocumentProps {
11
13
  */
12
14
  title: string;
13
15
  /**
14
- * Path to the markdown document
16
+ * Optional path to the markdown document
17
+ * If provided, document will be written to this path
15
18
  */
16
- path: string;
19
+ path?: string;
17
20
  /**
18
21
  * Base URL for the OpenAI API
19
22
  * @default 'https://api.openai.com/v1'
@@ -28,7 +31,18 @@ export interface DocumentProps {
28
31
  * ${alchemy.file("src/api.ts")}
29
32
  * `
30
33
  */
31
- prompt: string;
34
+ prompt?: string;
35
+ /**
36
+ * Message history for conversation-based generation
37
+ * If provided, this will be used instead of the prompt
38
+ * @example
39
+ * messages: [
40
+ * { role: "user", content: "Generate API documentation for this file" },
41
+ * { role: "assistant", content: "I'll create detailed API docs. What file should I document?" },
42
+ * { role: "user", content: "Please document src/api.ts" }
43
+ * ]
44
+ */
45
+ messages?: CoreMessage[];
32
46
  /**
33
47
  * System prompt for the model
34
48
  * This is used to provide instructions to the model about how to format the response
@@ -52,6 +66,12 @@ export interface DocumentProps {
52
66
  * @default 0.7
53
67
  */
54
68
  temperature?: number;
69
+ /**
70
+ * Maximum number of tokens to generate.
71
+ * Higher values allow for longer documents but may increase cost and generation time.
72
+ * @default 10000
73
+ */
74
+ maxTokens?: number;
55
75
  }
56
76
  /**
57
77
  * A markdown document that can be created, updated, and deleted
@@ -61,6 +81,10 @@ export interface Document extends DocumentProps, Resource<"docs::Document"> {
61
81
  * Content of the document
62
82
  */
63
83
  content: string;
84
+ /**
85
+ * Updated message history with the document response appended
86
+ */
87
+ messages: CoreMessage[];
64
88
  /**
65
89
  * Time at which the document was created
66
90
  */
@@ -69,13 +93,32 @@ export interface Document extends DocumentProps, Resource<"docs::Document"> {
69
93
  * Time at which the document was last updated
70
94
  */
71
95
  updatedAt: number;
96
+ /**
97
+ * File resource if path was provided
98
+ */
99
+ file?: StaticTextFile;
72
100
  }
73
101
  /**
74
102
  * Resource for managing AI-generated markdown documents using the Vercel AI SDK.
75
103
  * Supports powerful context handling through the alchemy template literal tag.
76
104
  *
77
105
  * @example
78
- * // Create a document using alchemy template literals for context
106
+ * // Create an in-memory document (no file created)
107
+ * const apiDocs = await Document("api-docs", {
108
+ * title: "API Documentation",
109
+ * prompt: await alchemy`
110
+ * Generate API documentation based on these source files:
111
+ * ${alchemy.file("src/api.ts")}
112
+ * ${alchemy.file("src/types.ts")}
113
+ * `,
114
+ * model: {
115
+ * id: "gpt-4o",
116
+ * provider: "openai"
117
+ * }
118
+ * });
119
+ *
120
+ * @example
121
+ * // Create a document and write it to disk
79
122
  * const apiDocs = await Document("api-docs", {
80
123
  * title: "API Documentation",
81
124
  * path: "./docs/api.md",
@@ -91,6 +134,23 @@ export interface Document extends DocumentProps, Resource<"docs::Document"> {
91
134
  * });
92
135
  *
93
136
  * @example
137
+ * // Use message history for iterative document generation
138
+ * const apiDocs = await Document("api-docs", {
139
+ * title: "API Documentation",
140
+ * path: "./docs/api.md",
141
+ * messages: [
142
+ * { role: "user", content: "Create API documentation for these files" },
143
+ * { role: "assistant", content: "I'll help you create API documentation. Please provide the files." },
144
+ * { role: "user", content: "Here are the files: [file contents]" }
145
+ * ],
146
+ * system: "You are a technical documentation writer. Generate clear and concise API documentation.",
147
+ * model: {
148
+ * id: "gpt-4o",
149
+ * provider: "openai"
150
+ * }
151
+ * });
152
+ *
153
+ * @example
94
154
  * // Use alchemy template literals with file collections and temperature control
95
155
  * const modelDocs = await Document("models", {
96
156
  * title: "Data Models",