alchemy 0.1.2 → 0.1.3
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 -39
- package/package.json +12 -8
- package/src/$.ts +36 -0
- package/src/agent/ai.ts +215 -0
- package/src/{components/agent → agent}/anthropic.ts +6 -2
- package/src/agent/dependencies.ts +62 -0
- package/src/agent/file-context.ts +8 -0
- package/src/agent/index.ts +9 -0
- package/src/agent/model.ts +39 -0
- package/src/agent/module.ts +418 -0
- package/src/{components/agent → agent}/openai.ts +6 -0
- package/src/agent/program.md +38 -0
- package/src/agent/program.ts +37 -0
- package/src/agent/prompts.ts +20 -0
- package/src/alchemize.ts +45 -14
- package/src/apply.ts +25 -10
- package/src/{components/aws → aws}/bucket.ts +2 -2
- package/src/{components/aws → aws}/function.ts +2 -2
- package/src/{components/aws → aws}/policy.ts +2 -2
- package/src/{components/aws → aws}/queue.ts +1 -1
- package/src/{components/aws → aws}/role.ts +2 -2
- package/src/{components/aws → aws}/table.ts +2 -2
- package/src/destroy.ts +36 -12
- package/src/{components/esbuild.ts → esbuild.ts} +1 -1
- package/src/{components/fs.ts → fs.ts} +20 -18
- package/src/global.ts +7 -14
- package/src/index.ts +11 -2
- package/src/input.ts +1 -1
- package/src/markdown/design.ts +125 -0
- package/src/markdown/extract.ts +9 -0
- package/src/markdown/index.ts +3 -0
- package/src/{components/agent → markdown}/requirements.ts +53 -29
- package/src/output.ts +6 -0
- package/src/resource.ts +208 -75
- package/src/scope.ts +56 -0
- package/src/slug.ts +3 -0
- package/src/state.ts +66 -38
- package/src/typescript/check-omission.ts +47 -0
- package/src/typescript/debug-type-errors.ts +42 -0
- package/src/typescript/extract.ts +9 -0
- package/src/{components/agent → typescript}/index.ts +3 -1
- package/src/typescript/install-packages.ts +50 -0
- package/src/{components/agent → typescript}/package.ts +44 -27
- package/src/typescript/repair-omissions.ts +31 -0
- package/src/typescript/repair.ts +45 -0
- package/src/{components/agent → typescript}/tsconfig.ts +37 -17
- package/src/typescript/typescript.ts +217 -0
- package/src/typescript/validate.ts +73 -0
- package/test/scope.test.ts +67 -0
- package/src/components/agent/agent.ts +0 -45
- package/src/components/agent/model.ts +0 -22
- package/src/components/agent/typescript.ts +0 -177
- /package/src/{components/agent → agent}/scrape.ts +0 -0
- /package/src/{components/aws → aws}/index.ts +0 -0
package/src/apply.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defaultStage,
|
|
1
|
+
import { defaultStage, defaultStateStore } from "./global";
|
|
2
2
|
import { Output } from "./output";
|
|
3
3
|
import {
|
|
4
4
|
Input,
|
|
@@ -7,11 +7,14 @@ import {
|
|
|
7
7
|
ResourceID,
|
|
8
8
|
isResource,
|
|
9
9
|
} from "./resource";
|
|
10
|
+
import { type Scope as IScope, getScope } from "./scope";
|
|
10
11
|
import type { StateStore } from "./state";
|
|
11
12
|
|
|
12
|
-
interface ApplyOptions {
|
|
13
|
-
stage
|
|
14
|
-
|
|
13
|
+
export interface ApplyOptions {
|
|
14
|
+
stage: string;
|
|
15
|
+
scope: IScope;
|
|
16
|
+
stateStore: StateStore;
|
|
17
|
+
quiet?: boolean;
|
|
15
18
|
}
|
|
16
19
|
|
|
17
20
|
/**
|
|
@@ -21,9 +24,16 @@ interface ApplyOptions {
|
|
|
21
24
|
*/
|
|
22
25
|
export async function apply<T>(
|
|
23
26
|
output: T | Output<T>,
|
|
24
|
-
options?: ApplyOptions
|
|
27
|
+
options?: Partial<ApplyOptions>,
|
|
25
28
|
): Promise<T> {
|
|
26
|
-
|
|
29
|
+
const stage = options?.stage ?? defaultStage;
|
|
30
|
+
const scope = options?.scope ?? getScope();
|
|
31
|
+
const statePath = scope.getScopePath(stage);
|
|
32
|
+
const stateStore = options?.stateStore ?? new defaultStateStore(statePath);
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
await evaluate(output, { stage, scope, stateStore, quiet: options?.quiet })
|
|
36
|
+
).value;
|
|
27
37
|
}
|
|
28
38
|
|
|
29
39
|
class Evaluated<T> {
|
|
@@ -37,9 +47,10 @@ const cache = new WeakMap<Resource, Promise<Evaluated<any>>>();
|
|
|
37
47
|
|
|
38
48
|
export async function evaluate<T>(
|
|
39
49
|
output: T | Output<T>,
|
|
40
|
-
options: ApplyOptions
|
|
50
|
+
options: ApplyOptions,
|
|
41
51
|
): Promise<Evaluated<T>> {
|
|
42
|
-
const
|
|
52
|
+
const stage = options.stage;
|
|
53
|
+
const stateStore = options.stateStore;
|
|
43
54
|
if (isResource(output)) {
|
|
44
55
|
const resource = output;
|
|
45
56
|
const resourceID = resource[ResourceID];
|
|
@@ -63,14 +74,16 @@ export async function evaluate<T>(
|
|
|
63
74
|
const inputs = evaluated.map((input) => input.value);
|
|
64
75
|
try {
|
|
65
76
|
const result: T = await resource[Provider].update(
|
|
66
|
-
|
|
77
|
+
stage,
|
|
67
78
|
resource,
|
|
68
79
|
deps,
|
|
69
80
|
inputs as [],
|
|
70
|
-
|
|
81
|
+
stateStore,
|
|
82
|
+
options,
|
|
71
83
|
);
|
|
72
84
|
resolve!(new Evaluated(result, [resourceID, ...deps]));
|
|
73
85
|
} catch (error) {
|
|
86
|
+
console.error(error);
|
|
74
87
|
reject!(error);
|
|
75
88
|
}
|
|
76
89
|
return promise;
|
|
@@ -95,6 +108,8 @@ export async function evaluate<T>(
|
|
|
95
108
|
evaluatedItems.map((e) => e.value) as unknown as T,
|
|
96
109
|
evaluatedItems.flatMap((e) => e.deps),
|
|
97
110
|
);
|
|
111
|
+
} else if (output instanceof Date) {
|
|
112
|
+
return new Evaluated(output as T);
|
|
98
113
|
} else if (output && typeof output === "object") {
|
|
99
114
|
const entries = Object.entries(output);
|
|
100
115
|
const evaluatedEntries = await Promise.all(
|
|
@@ -10,8 +10,8 @@ import {
|
|
|
10
10
|
PutBucketTaggingCommand,
|
|
11
11
|
S3Client,
|
|
12
12
|
} from "@aws-sdk/client-s3";
|
|
13
|
-
import { ignore } from "
|
|
14
|
-
import { Resource } from "
|
|
13
|
+
import { ignore } from "../error";
|
|
14
|
+
import { Resource } from "../resource";
|
|
15
15
|
|
|
16
16
|
export interface BucketProps {
|
|
17
17
|
bucketName: string;
|
|
@@ -12,8 +12,8 @@ import {
|
|
|
12
12
|
} from "@aws-sdk/client-lambda";
|
|
13
13
|
import fs from "node:fs";
|
|
14
14
|
import path from "node:path";
|
|
15
|
-
import { ignore } from "
|
|
16
|
-
import {
|
|
15
|
+
import { ignore } from "../error";
|
|
16
|
+
import { type Context, Resource } from "../resource";
|
|
17
17
|
|
|
18
18
|
async function resolveRegion(client: LambdaClient): Promise<string> {
|
|
19
19
|
const region = client.config.region;
|
|
@@ -11,8 +11,8 @@ import {
|
|
|
11
11
|
ListPolicyVersionsCommand,
|
|
12
12
|
NoSuchEntityException,
|
|
13
13
|
} from "@aws-sdk/client-iam";
|
|
14
|
-
import { ignore } from "
|
|
15
|
-
import { Resource } from "
|
|
14
|
+
import { ignore } from "../error";
|
|
15
|
+
import { Resource } from "../resource";
|
|
16
16
|
|
|
17
17
|
// Type-safe policy document types
|
|
18
18
|
export type Effect = "Allow" | "Deny";
|
|
@@ -12,8 +12,8 @@ import {
|
|
|
12
12
|
UpdateAssumeRolePolicyCommand,
|
|
13
13
|
UpdateRoleCommand,
|
|
14
14
|
} from "@aws-sdk/client-iam";
|
|
15
|
-
import { ignore } from "
|
|
16
|
-
import { type Context, Resource } from "
|
|
15
|
+
import { ignore } from "../error";
|
|
16
|
+
import { type Context, Resource } from "../resource";
|
|
17
17
|
import type { PolicyDocument } from "./policy";
|
|
18
18
|
|
|
19
19
|
export interface RoleProps {
|
|
@@ -7,8 +7,8 @@ import {
|
|
|
7
7
|
ResourceInUseException,
|
|
8
8
|
ResourceNotFoundException,
|
|
9
9
|
} from "@aws-sdk/client-dynamodb";
|
|
10
|
-
import { ignore } from "
|
|
11
|
-
import { type Context, Resource } from "
|
|
10
|
+
import { ignore } from "../error";
|
|
11
|
+
import { type Context, Resource } from "../resource";
|
|
12
12
|
|
|
13
13
|
export interface TableProps {
|
|
14
14
|
tableName: string;
|
package/src/destroy.ts
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
import { defaultStage,
|
|
1
|
+
import { defaultStage, defaultStateStore, rootScope } from "./global";
|
|
2
2
|
import type { Output } from "./output";
|
|
3
3
|
import { Provider, ResourceID, isResource } from "./resource";
|
|
4
|
-
import type {
|
|
4
|
+
import type { Scope } from "./scope";
|
|
5
|
+
import type { State, StateStore } from "./state";
|
|
5
6
|
|
|
6
|
-
interface DestroyOptions {
|
|
7
|
+
export interface DestroyOptions {
|
|
7
8
|
stage?: string;
|
|
9
|
+
stateStore?: StateStore;
|
|
10
|
+
scope?: Scope;
|
|
11
|
+
quiet?: boolean;
|
|
8
12
|
}
|
|
9
13
|
|
|
10
14
|
/**
|
|
@@ -20,44 +24,64 @@ export async function destroy<T>(
|
|
|
20
24
|
*/
|
|
21
25
|
export async function destroy<T>(
|
|
22
26
|
stage: string,
|
|
27
|
+
scope: Scope,
|
|
23
28
|
resourceID: ResourceID,
|
|
24
29
|
resourceState: State,
|
|
25
30
|
resourceProvider: Provider,
|
|
31
|
+
stateStore: StateStore,
|
|
32
|
+
options: DestroyOptions,
|
|
26
33
|
): Promise<void>;
|
|
27
34
|
|
|
28
35
|
export async function destroy<T>(
|
|
29
|
-
...args:
|
|
36
|
+
...args:
|
|
37
|
+
| [Output<T>, DestroyOptions?]
|
|
38
|
+
| [string, Scope, ResourceID, State, Provider, StateStore, DestroyOptions]
|
|
30
39
|
): Promise<void> {
|
|
31
40
|
let resourceID: ResourceID;
|
|
32
41
|
let resourceState: State;
|
|
33
42
|
let resourceProvider: Provider;
|
|
34
43
|
let stage: string;
|
|
35
|
-
|
|
36
|
-
|
|
44
|
+
let stateStore: StateStore;
|
|
45
|
+
let scope: Scope | undefined = undefined;
|
|
46
|
+
let options: DestroyOptions | undefined = undefined;
|
|
47
|
+
if (args.length === 7) {
|
|
37
48
|
stage = args[0];
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
49
|
+
scope = args[1];
|
|
50
|
+
resourceID = args[2];
|
|
51
|
+
resourceState = args[3];
|
|
52
|
+
resourceProvider = args[4];
|
|
53
|
+
stateStore = args[5];
|
|
54
|
+
options = args[6];
|
|
41
55
|
} else if (isResource(args[0])) {
|
|
42
56
|
const resource = args[0];
|
|
43
|
-
stage = args[1]?.stage ?? defaultStage;
|
|
57
|
+
// stage = args[1]?.stage ?? defaultStage;
|
|
44
58
|
resourceID = resource[ResourceID];
|
|
59
|
+
stage = args[1]?.stage ?? defaultStage;
|
|
60
|
+
scope = args[1]?.scope ?? rootScope;
|
|
61
|
+
const statePath = scope.getScopePath(stage);
|
|
62
|
+
stateStore = args[1]?.stateStore ?? new defaultStateStore(statePath);
|
|
45
63
|
// First destroy all dependencies
|
|
46
|
-
const _resourceState = await stateStore.get(
|
|
64
|
+
const _resourceState = await stateStore.get(resourceID);
|
|
47
65
|
if (_resourceState === undefined) {
|
|
48
66
|
// we have no record of this resource, we must assume it's already deleted
|
|
49
67
|
return;
|
|
50
68
|
}
|
|
51
69
|
resourceState = _resourceState;
|
|
52
70
|
resourceProvider = resource[Provider];
|
|
71
|
+
options = args[1];
|
|
53
72
|
} else {
|
|
73
|
+
console.log(args[0]);
|
|
54
74
|
throw new Error("Not implemented: must handle destroy a Output chain");
|
|
55
75
|
}
|
|
56
76
|
await resourceProvider.delete(
|
|
57
77
|
stage,
|
|
78
|
+
scope,
|
|
58
79
|
resourceID,
|
|
59
80
|
resourceState,
|
|
60
81
|
resourceState.inputs as [],
|
|
82
|
+
options ?? {
|
|
83
|
+
quiet: false,
|
|
84
|
+
},
|
|
61
85
|
);
|
|
62
|
-
await stateStore.delete(
|
|
86
|
+
await stateStore.delete(resourceID);
|
|
63
87
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
|
+
import { unlink } from "node:fs/promises";
|
|
2
3
|
import path from "node:path";
|
|
3
|
-
import { ignore } from "
|
|
4
|
-
import { Resource } from "
|
|
4
|
+
import { ignore } from "./error";
|
|
5
|
+
import { Resource } from "./resource";
|
|
5
6
|
|
|
6
7
|
export class File extends Resource(
|
|
7
8
|
"fs::File",
|
|
@@ -18,28 +19,29 @@ export class File extends Resource(
|
|
|
18
19
|
|
|
19
20
|
export class Folder extends Resource(
|
|
20
21
|
"fs::Folder",
|
|
21
|
-
async (ctx, dirPath: string) => {
|
|
22
|
+
async (ctx, dirPath: string): Promise<{ path: string }> => {
|
|
22
23
|
if (ctx.event === "delete") {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
await ignore("ENOENT", () => fs.promises.rmdir(dirPath));
|
|
26
|
-
}
|
|
24
|
+
// we just do a best effort attempt
|
|
25
|
+
await ignore(["ENOENT", "ENOTEMPTY"], () => fs.promises.rmdir(dirPath));
|
|
27
26
|
} else {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
if (error.code === "EEXIST") {
|
|
32
|
-
// record that we own the directory
|
|
33
|
-
await ctx.set("isOwned", true);
|
|
34
|
-
} else {
|
|
35
|
-
throw error;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
27
|
+
await ignore("EEXIST", () =>
|
|
28
|
+
fs.promises.mkdir(dirPath, { recursive: true }),
|
|
29
|
+
);
|
|
38
30
|
}
|
|
39
|
-
return dirPath;
|
|
31
|
+
return { path: dirPath };
|
|
40
32
|
},
|
|
41
33
|
) {
|
|
42
34
|
public file(filePath: string, content: string) {
|
|
43
35
|
return new File(path.basename(filePath), filePath, content);
|
|
44
36
|
}
|
|
45
37
|
}
|
|
38
|
+
|
|
39
|
+
export async function rm(filePath: string) {
|
|
40
|
+
try {
|
|
41
|
+
await unlink(filePath);
|
|
42
|
+
} catch (error) {
|
|
43
|
+
if ((error as NodeJS.ErrnoException).code !== "ENOENT") {
|
|
44
|
+
throw error;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
package/src/global.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import type { Provider,
|
|
4
|
-
import {
|
|
3
|
+
import type { Provider, ResourceType } from "./resource";
|
|
4
|
+
import { Scope } from "./scope";
|
|
5
|
+
import { FileSystemStateStore } from "./state";
|
|
5
6
|
|
|
6
7
|
export interface Config {
|
|
7
8
|
defaultStage?: string;
|
|
8
|
-
stateStore?:
|
|
9
|
+
stateStore?: new (dir: string) => FileSystemStateStore;
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
const alchemy = path.resolve(process.cwd(), "alchemy.ts");
|
|
@@ -43,20 +44,14 @@ const _stage = process.env.ALCHEMY_STAGE ?? process.env.USER ?? "dev";
|
|
|
43
44
|
|
|
44
45
|
export const config: Config = _config ?? {
|
|
45
46
|
defaultStage: _stage,
|
|
46
|
-
stateStore:
|
|
47
|
+
stateStore: FileSystemStateStore,
|
|
47
48
|
};
|
|
48
49
|
|
|
49
50
|
export const defaultStage = config.defaultStage ?? _stage;
|
|
50
51
|
|
|
51
|
-
export const
|
|
52
|
+
export const defaultStateStore = config.stateStore ?? FileSystemStateStore;
|
|
52
53
|
|
|
53
|
-
export const
|
|
54
|
-
ResourceID,
|
|
55
|
-
{
|
|
56
|
-
provider: Provider<any, any[], any>;
|
|
57
|
-
resource: Resource<any, any>;
|
|
58
|
-
}
|
|
59
|
-
>();
|
|
54
|
+
export const rootScope = new Scope(null);
|
|
60
55
|
|
|
61
56
|
export const providers = new Map<
|
|
62
57
|
ResourceType,
|
|
@@ -68,5 +63,3 @@ export const deletions: {
|
|
|
68
63
|
data: Record<string, any>;
|
|
69
64
|
inputs: any[];
|
|
70
65
|
}[] = [];
|
|
71
|
-
|
|
72
|
-
await stateStore.init?.();
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
|
+
export * from "./$.ts";
|
|
1
2
|
export * from "./alchemize";
|
|
3
|
+
export * from "./error";
|
|
2
4
|
export * from "./global";
|
|
5
|
+
export * from "./input";
|
|
3
6
|
export * from "./output";
|
|
4
|
-
export
|
|
7
|
+
export {
|
|
8
|
+
Resource,
|
|
9
|
+
type Context,
|
|
10
|
+
type CreateContext,
|
|
11
|
+
type DeleteContext,
|
|
12
|
+
type UpdateContext,
|
|
13
|
+
} from "./resource";
|
|
5
14
|
|
|
6
|
-
export type { StateStore } from "./state";
|
|
15
|
+
export type { FileSystemStateStore as StateStore } from "./state";
|
package/src/input.ts
CHANGED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { tool } from "ai";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import {
|
|
6
|
+
type FileContext,
|
|
7
|
+
type ModelId,
|
|
8
|
+
dependenciesAsMessages,
|
|
9
|
+
generateText,
|
|
10
|
+
resolveModel,
|
|
11
|
+
} from "../agent";
|
|
12
|
+
import { Prompts } from "../agent/prompts";
|
|
13
|
+
import { rm } from "../fs";
|
|
14
|
+
import { extractMarkdown } from "../markdown/extract";
|
|
15
|
+
import { type Context, Resource } from "../resource";
|
|
16
|
+
|
|
17
|
+
export interface DesignInput {
|
|
18
|
+
prompt: string;
|
|
19
|
+
path: string;
|
|
20
|
+
deps?: any[];
|
|
21
|
+
model?: ModelId;
|
|
22
|
+
dependencies: FileContext[];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface DesignOutput {
|
|
26
|
+
path: string;
|
|
27
|
+
content: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export class Design extends Resource(
|
|
31
|
+
"design",
|
|
32
|
+
async (ctx: Context<DesignOutput>, props: DesignInput) => {
|
|
33
|
+
if (ctx.event === "delete") {
|
|
34
|
+
await rm(props.path);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const model = await resolveModel(props.model ?? "gpt-4o");
|
|
38
|
+
|
|
39
|
+
const importFile = tool({
|
|
40
|
+
description: "Imports a relevant file into context",
|
|
41
|
+
parameters: z.object({
|
|
42
|
+
file: z.string(),
|
|
43
|
+
}),
|
|
44
|
+
execute: async ({ file }) => fs.readFile(file, "utf-8"),
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
let existingContent: string | undefined = undefined;
|
|
48
|
+
if (ctx.event === "update") {
|
|
49
|
+
if (props.path) {
|
|
50
|
+
try {
|
|
51
|
+
existingContent = await fs.readFile(props.path, "utf-8");
|
|
52
|
+
} catch {
|
|
53
|
+
// File doesn't exist yet, continue with empty content
|
|
54
|
+
}
|
|
55
|
+
} else {
|
|
56
|
+
existingContent = ctx.output.content;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
console.log(
|
|
61
|
+
ctx.event === "create" ? "Designing" : "Revising design",
|
|
62
|
+
":",
|
|
63
|
+
props.path,
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
const result = await generateText({
|
|
67
|
+
model,
|
|
68
|
+
// tools: {
|
|
69
|
+
// import: importFile,
|
|
70
|
+
// },
|
|
71
|
+
providerOptions:
|
|
72
|
+
props.model === "o3-mini"
|
|
73
|
+
? {
|
|
74
|
+
openai: {
|
|
75
|
+
reasoningEffort: "high",
|
|
76
|
+
},
|
|
77
|
+
}
|
|
78
|
+
: undefined,
|
|
79
|
+
messages: [
|
|
80
|
+
{
|
|
81
|
+
role: "system",
|
|
82
|
+
content: Prompts.program,
|
|
83
|
+
},
|
|
84
|
+
...dependenciesAsMessages(props.dependencies),
|
|
85
|
+
...(existingContent
|
|
86
|
+
? [
|
|
87
|
+
{
|
|
88
|
+
role: "user" as const,
|
|
89
|
+
content: `Here is the prior version of ${props.path}:\n${existingContent}`,
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
role: "assistant" as const,
|
|
93
|
+
content:
|
|
94
|
+
"Thanks, I'll make sure to consider this when producing the next revision.",
|
|
95
|
+
},
|
|
96
|
+
]
|
|
97
|
+
: []),
|
|
98
|
+
{
|
|
99
|
+
role: "user",
|
|
100
|
+
content: props.prompt,
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
role: "user",
|
|
104
|
+
content: [
|
|
105
|
+
`Make sure to list each of the files that need to be implemented and`,
|
|
106
|
+
"and to follow best practices in term of separation of concerns.",
|
|
107
|
+
"Ideally, each component has its own dedicated file.",
|
|
108
|
+
"Each file should be referenced using markdown links, e.g. [file.ts](./file.ts).",
|
|
109
|
+
].join(" "),
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const content = extractMarkdown(result.text);
|
|
115
|
+
|
|
116
|
+
await fs.mkdir(path.dirname(props.path), { recursive: true });
|
|
117
|
+
|
|
118
|
+
await fs.writeFile(props.path, content);
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
path: props.path,
|
|
122
|
+
content,
|
|
123
|
+
};
|
|
124
|
+
},
|
|
125
|
+
) {}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extracts markdown content from a code block or returns the full content
|
|
3
|
+
* @param text Response text that may contain markdown code blocks
|
|
4
|
+
* @returns The markdown content from code block if found, otherwise returns the full text
|
|
5
|
+
*/
|
|
6
|
+
export function extractMarkdown(text: string): string {
|
|
7
|
+
const matches = text.match(/```md\n([\s\S]*?)```/);
|
|
8
|
+
return matches?.[1]?.trim() ?? text.trim();
|
|
9
|
+
}
|
|
@@ -1,17 +1,29 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { mkdir, unlink, writeFile } from "fs/promises";
|
|
1
|
+
import { mkdir, writeFile } from "fs/promises";
|
|
3
2
|
import { dirname } from "path";
|
|
4
3
|
import { z } from "zod";
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
import {
|
|
5
|
+
FileContext,
|
|
6
|
+
ModelId,
|
|
7
|
+
dependenciesAsMessages,
|
|
8
|
+
generateText,
|
|
9
|
+
resolveModel,
|
|
10
|
+
} from "../agent";
|
|
11
|
+
import { rm } from "../fs";
|
|
12
|
+
import { type Context, Resource } from "../resource";
|
|
13
|
+
|
|
14
|
+
export const ReasoningEffort = z.enum(["low", "medium", "high"]);
|
|
8
15
|
|
|
9
16
|
export const RequirementsInput = z.object({
|
|
10
17
|
/**
|
|
11
18
|
* The ID of the model to use for generating requirements documentation
|
|
12
19
|
* @default "gpt-4o"
|
|
13
20
|
*/
|
|
14
|
-
modelId:
|
|
21
|
+
modelId: ModelId.optional(),
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The effort to put into reasoning about the requirements
|
|
25
|
+
*/
|
|
26
|
+
reasoningEffort: ReasoningEffort.optional(),
|
|
15
27
|
|
|
16
28
|
/**
|
|
17
29
|
* List of requirements to document and analyze
|
|
@@ -19,9 +31,9 @@ export const RequirementsInput = z.object({
|
|
|
19
31
|
requirements: z.array(z.string()),
|
|
20
32
|
|
|
21
33
|
/**
|
|
22
|
-
*
|
|
34
|
+
* List of dependencies for the requirements document
|
|
23
35
|
*/
|
|
24
|
-
|
|
36
|
+
dependencies: z.array(FileContext).optional(),
|
|
25
37
|
|
|
26
38
|
/**
|
|
27
39
|
* Temperature setting for model generation (higher = more creative, lower = more focused)
|
|
@@ -32,7 +44,7 @@ export const RequirementsInput = z.object({
|
|
|
32
44
|
/**
|
|
33
45
|
* File path where the requirements document should be written
|
|
34
46
|
*/
|
|
35
|
-
|
|
47
|
+
file: z.string().optional(),
|
|
36
48
|
});
|
|
37
49
|
|
|
38
50
|
export const RequirementsOutput = z.object({
|
|
@@ -42,33 +54,44 @@ export const RequirementsOutput = z.object({
|
|
|
42
54
|
content: z.string(),
|
|
43
55
|
});
|
|
44
56
|
|
|
45
|
-
export
|
|
57
|
+
export interface RequirementsInput extends z.infer<typeof RequirementsInput> {}
|
|
46
58
|
export type RequirementsOutput = z.infer<typeof RequirementsOutput>;
|
|
47
59
|
|
|
48
|
-
export class Requirements extends
|
|
60
|
+
export class Requirements extends Resource(
|
|
49
61
|
"code::Requirements",
|
|
50
|
-
{
|
|
51
|
-
description:
|
|
52
|
-
"This Agent is responsible for writing and refining a detailed requirements document from a list of requirements.",
|
|
53
|
-
input: RequirementsInput,
|
|
54
|
-
output: RequirementsOutput,
|
|
55
|
-
},
|
|
56
|
-
async (ctx, input) => {
|
|
62
|
+
async (ctx: Context<RequirementsOutput>, props: RequirementsInput) => {
|
|
57
63
|
if (ctx.event === "delete") {
|
|
58
|
-
|
|
64
|
+
if (props.file) {
|
|
65
|
+
await rm(props.file);
|
|
66
|
+
}
|
|
59
67
|
return;
|
|
60
68
|
}
|
|
61
69
|
|
|
70
|
+
if (props.file) {
|
|
71
|
+
console.log(
|
|
72
|
+
ctx.event === "create" ? "Designing" : "Revising",
|
|
73
|
+
"requirements",
|
|
74
|
+
props.file,
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
62
78
|
// Get the appropriate model based on the ID
|
|
63
|
-
const
|
|
79
|
+
const model = await resolveModel(props.modelId ?? "gpt-4o");
|
|
64
80
|
|
|
65
81
|
// Generate the requirements document
|
|
66
82
|
const { text } = await generateText({
|
|
67
83
|
model,
|
|
68
|
-
temperature:
|
|
84
|
+
temperature: props.temperature ?? 0.7,
|
|
69
85
|
maxSteps: 3, // Allow multiple steps for potential tool usage
|
|
86
|
+
providerOptions: props.reasoningEffort
|
|
87
|
+
? {
|
|
88
|
+
openai: {
|
|
89
|
+
reasoningEffort: props.reasoningEffort,
|
|
90
|
+
},
|
|
91
|
+
}
|
|
92
|
+
: undefined,
|
|
70
93
|
tools: {
|
|
71
|
-
scrapeWebPage,
|
|
94
|
+
// scrapeWebPage,
|
|
72
95
|
},
|
|
73
96
|
messages: [
|
|
74
97
|
{
|
|
@@ -76,14 +99,13 @@ export class Requirements extends Agent(
|
|
|
76
99
|
content:
|
|
77
100
|
"You are a helpful assistant that creates markdown documents from requirements. You can use the scrapeWebPage tool to get additional requirements from relevant URLs if needed.",
|
|
78
101
|
},
|
|
102
|
+
...dependenciesAsMessages(props.dependencies ?? []),
|
|
79
103
|
{
|
|
80
104
|
role: "user",
|
|
81
105
|
content: `Please analyze the following system requirements and create a comprehensive, unambiguous markdown document that covers all aspects:
|
|
82
106
|
|
|
83
|
-
Title: ${input.title}
|
|
84
|
-
|
|
85
107
|
Requirements:
|
|
86
|
-
${
|
|
108
|
+
${props.requirements.map((req) => `- ${req}`).join("\n")}
|
|
87
109
|
|
|
88
110
|
Guidelines:
|
|
89
111
|
1. Make explicit decisions for any ambiguous requirements - do not leave open questions
|
|
@@ -98,11 +120,13 @@ Include technical considerations and implementation details, ensuring every requ
|
|
|
98
120
|
],
|
|
99
121
|
});
|
|
100
122
|
|
|
101
|
-
|
|
102
|
-
|
|
123
|
+
if (props.file) {
|
|
124
|
+
// Ensure the directory exists
|
|
125
|
+
await mkdir(dirname(props.file), { recursive: true });
|
|
103
126
|
|
|
104
|
-
|
|
105
|
-
|
|
127
|
+
// Write the requirements to the file
|
|
128
|
+
await writeFile(props.file, text);
|
|
129
|
+
}
|
|
106
130
|
|
|
107
131
|
return {
|
|
108
132
|
content: text,
|