@autobe/agent 0.9.0 → 0.9.2
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/AutoBeAgent.js +7 -1
- package/lib/AutoBeAgent.js.map +1 -1
- package/lib/constants/AutoBeSystemPromptConstant.d.ts +5 -3
- package/lib/constants/AutoBeSystemPromptConstant.js.map +1 -1
- package/lib/context/IAutoBeApplicationProps.d.ts +0 -61
- package/lib/factory/createAutoBeApplication.js +15 -135
- package/lib/factory/createAutoBeApplication.js.map +1 -1
- package/lib/index.mjs +1054 -1037
- package/lib/index.mjs.map +1 -1
- package/lib/orchestrate/analyze/orchestrateAnalyze.js +4 -30
- package/lib/orchestrate/analyze/orchestrateAnalyze.js.map +1 -1
- package/lib/orchestrate/interface/orchestrateInterface.js +9 -3
- package/lib/orchestrate/interface/orchestrateInterface.js.map +1 -1
- package/lib/orchestrate/test/compileTestScenario.d.ts +5 -0
- package/lib/orchestrate/test/compileTestScenario.js +57 -0
- package/lib/orchestrate/test/compileTestScenario.js.map +1 -0
- package/lib/orchestrate/test/filterTestFileName.d.ts +1 -0
- package/lib/orchestrate/test/filterTestFileName.js +13 -0
- package/lib/orchestrate/test/filterTestFileName.js.map +1 -0
- package/lib/orchestrate/test/orchestrateTest.js +6 -3
- package/lib/orchestrate/test/orchestrateTest.js.map +1 -1
- package/lib/orchestrate/test/orchestrateTestCorrect.d.ts +2 -2
- package/lib/orchestrate/test/orchestrateTestCorrect.js +172 -87
- package/lib/orchestrate/test/orchestrateTestCorrect.js.map +1 -1
- package/lib/orchestrate/test/orchestrateTestScenario.js +98 -83
- package/lib/orchestrate/test/orchestrateTestScenario.js.map +1 -1
- package/lib/orchestrate/test/orchestrateTestWrite.d.ts +4 -0
- package/lib/orchestrate/test/{orchestrateTestProgress.js → orchestrateTestWrite.js} +27 -54
- package/lib/orchestrate/test/orchestrateTestWrite.js.map +1 -0
- package/lib/orchestrate/test/structures/IAutoBeTestScenarioApplication.d.ts +18 -20
- package/lib/orchestrate/test/structures/IAutoBeTestScenarioArtifacts.d.ts +7 -0
- package/lib/orchestrate/test/structures/IAutoBeTestScenarioArtifacts.js +3 -0
- package/lib/orchestrate/test/structures/IAutoBeTestScenarioArtifacts.js.map +1 -0
- package/lib/orchestrate/test/transformTestCorrectHistories.d.ts +2 -2
- package/lib/orchestrate/test/transformTestCorrectHistories.js +38 -16
- package/lib/orchestrate/test/transformTestCorrectHistories.js.map +1 -1
- package/lib/orchestrate/test/transformTestWriteHistories.d.ts +7 -0
- package/lib/orchestrate/test/transformTestWriteHistories.js +59 -0
- package/lib/orchestrate/test/transformTestWriteHistories.js.map +1 -0
- package/lib/structures/IAutoBeProps.d.ts +12 -1
- package/package.json +4 -5
- package/src/AutoBeAgent.ts +14 -2
- package/src/constants/AutoBeSystemPromptConstant.ts +5 -3
- package/src/context/IAutoBeApplicationProps.ts +0 -62
- package/src/orchestrate/analyze/orchestrateAnalyze.ts +4 -34
- package/src/orchestrate/interface/orchestrateInterface.ts +7 -0
- package/src/orchestrate/test/compileTestScenario.ts +64 -0
- package/src/orchestrate/test/filterTestFileName.ts +9 -0
- package/src/orchestrate/test/orchestrateTest.ts +15 -9
- package/src/orchestrate/test/orchestrateTestCorrect.ts +288 -128
- package/src/orchestrate/test/orchestrateTestScenario.ts +23 -6
- package/src/orchestrate/test/{orchestrateTestProgress.ts → orchestrateTestWrite.ts} +56 -87
- package/src/orchestrate/test/structures/IAutoBeTestScenarioApplication.ts +18 -20
- package/src/orchestrate/test/structures/IAutoBeTestScenarioArtifacts.ts +8 -0
- package/src/orchestrate/test/transformTestCorrectHistories.ts +41 -17
- package/src/orchestrate/test/{transformTestProgressHistories.ts → transformTestWriteHistories.ts} +21 -10
- package/src/structures/IAutoBeProps.ts +17 -1
- package/lib/orchestrate/test/orchestrateTestProgress.d.ts +0 -5
- package/lib/orchestrate/test/orchestrateTestProgress.js.map +0 -1
- package/lib/orchestrate/test/transformTestProgressHistories.d.ts +0 -8
- package/lib/orchestrate/test/transformTestProgressHistories.js +0 -47
- package/lib/orchestrate/test/transformTestProgressHistories.js.map +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { IAgenticaController, MicroAgentica } from "@agentica/core";
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
AutoBeTestFile,
|
|
4
|
+
AutoBeTestScenario,
|
|
5
5
|
AutoBeTestValidateEvent,
|
|
6
6
|
AutoBeTestWriteEvent,
|
|
7
7
|
IAutoBeTypeScriptCompilerResult,
|
|
@@ -14,59 +14,57 @@ import { AutoBeContext } from "../../context/AutoBeContext";
|
|
|
14
14
|
import { assertSchemaModel } from "../../context/assertSchemaModel";
|
|
15
15
|
import { randomBackoffRetry } from "../../utils/backoffRetry";
|
|
16
16
|
import { enforceToolCall } from "../../utils/enforceToolCall";
|
|
17
|
-
import {
|
|
17
|
+
import { compileTestScenario } from "./compileTestScenario";
|
|
18
|
+
import { filterTestFileName } from "./filterTestFileName";
|
|
19
|
+
import { IAutoBeTestScenarioArtifacts } from "./structures/IAutoBeTestScenarioArtifacts";
|
|
18
20
|
import { transformTestCorrectHistories } from "./transformTestCorrectHistories";
|
|
19
21
|
|
|
20
22
|
export async function orchestrateTestCorrect<Model extends ILlmSchema.Model>(
|
|
21
23
|
ctx: AutoBeContext<Model>,
|
|
22
24
|
codes: AutoBeTestWriteEvent[],
|
|
23
|
-
scenarios:
|
|
25
|
+
scenarios: AutoBeTestScenario[],
|
|
24
26
|
life: number = 4,
|
|
25
27
|
): Promise<AutoBeTestValidateEvent> {
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
const files: AutoBeTestFile[] = codes.map(
|
|
29
|
+
({ filename, content }, index): AutoBeTestFile => {
|
|
30
|
+
const scenario: AutoBeTestScenario = scenarios[index];
|
|
31
|
+
return { location: filename, content, scenario };
|
|
32
|
+
},
|
|
33
|
+
);
|
|
30
34
|
|
|
31
35
|
// 1) Build map of new test files from progress events
|
|
32
|
-
const testFiles: Record<string, string> =
|
|
33
|
-
.map((
|
|
34
|
-
|
|
35
|
-
[`test/features/api/${filename}`]: content,
|
|
36
|
-
};
|
|
37
|
-
})
|
|
38
|
-
.reduce<Record<string, string>>((acc, cur) => Object.assign(acc, cur), {});
|
|
36
|
+
const testFiles: Record<string, string> = Object.fromEntries(
|
|
37
|
+
codes.map((c) => [c.filename, c.content]),
|
|
38
|
+
);
|
|
39
39
|
|
|
40
40
|
// 2) Keep only files outside the test directory from current state
|
|
41
|
-
const retainedFiles: Record<string, string> = Object.
|
|
42
|
-
ctx.state().interface?.files ?? {}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
})
|
|
47
|
-
.map(([filename, content]) => {
|
|
48
|
-
return { [filename]: content };
|
|
49
|
-
})
|
|
50
|
-
.reduce<Record<string, string>>((acc, cur) => Object.assign(acc, cur), {});
|
|
41
|
+
const retainedFiles: Record<string, string> = Object.fromEntries(
|
|
42
|
+
Object.entries(ctx.state().interface?.files ?? {}).filter(([key]) =>
|
|
43
|
+
filterTestFileName(key),
|
|
44
|
+
),
|
|
45
|
+
);
|
|
51
46
|
|
|
52
47
|
// 3) Merge and filter: keep .ts/.json, drop anything under "benchmark"
|
|
48
|
+
const external = async (
|
|
49
|
+
location: string,
|
|
50
|
+
): Promise<Record<string, string>> => {
|
|
51
|
+
const content: string | undefined =
|
|
52
|
+
await ctx.compiler.typescript.getExternal(location);
|
|
53
|
+
if (content === undefined) throw new Error(`File not found: ${location}`);
|
|
54
|
+
return { [location]: content };
|
|
55
|
+
};
|
|
53
56
|
const mergedFiles: Record<string, string> = {
|
|
54
57
|
...retainedFiles,
|
|
55
58
|
...testFiles,
|
|
59
|
+
...(await external("node_modules/@nestia/e2e/lib/TestValidator.d.ts")),
|
|
60
|
+
...(await external("node_modules/@nestia/fetcher/lib/IConnection.d.ts")),
|
|
56
61
|
};
|
|
57
|
-
const files: Record<string, string> = Object.fromEntries(
|
|
58
|
-
Object.entries(mergedFiles).filter(
|
|
59
|
-
([filename]) =>
|
|
60
|
-
(filename.endsWith(".ts") && !filename.startsWith("test/benchmark/")) ||
|
|
61
|
-
filename.endsWith(".json"),
|
|
62
|
-
),
|
|
63
|
-
);
|
|
64
62
|
|
|
65
63
|
// 4) Ask the LLM to correct the filtered file set
|
|
66
64
|
const response: AutoBeTestValidateEvent = await step(
|
|
67
65
|
ctx,
|
|
66
|
+
mergedFiles,
|
|
68
67
|
files,
|
|
69
|
-
scenarioMap,
|
|
70
68
|
life,
|
|
71
69
|
);
|
|
72
70
|
|
|
@@ -74,7 +72,17 @@ export async function orchestrateTestCorrect<Model extends ILlmSchema.Model>(
|
|
|
74
72
|
const event: AutoBeTestValidateEvent = {
|
|
75
73
|
...response,
|
|
76
74
|
type: "testValidate",
|
|
77
|
-
files:
|
|
75
|
+
files: [
|
|
76
|
+
...Object.entries(mergedFiles).map(
|
|
77
|
+
([filename, content]): AutoBeTestFile => {
|
|
78
|
+
return {
|
|
79
|
+
location: filename,
|
|
80
|
+
content,
|
|
81
|
+
};
|
|
82
|
+
},
|
|
83
|
+
),
|
|
84
|
+
...response.files,
|
|
85
|
+
],
|
|
78
86
|
};
|
|
79
87
|
return event;
|
|
80
88
|
}
|
|
@@ -89,29 +97,35 @@ export async function orchestrateTestCorrect<Model extends ILlmSchema.Model>(
|
|
|
89
97
|
* all generated test files are syntactically correct and compilable.
|
|
90
98
|
*
|
|
91
99
|
* @param ctx AutoBe context object
|
|
92
|
-
* @param
|
|
100
|
+
* @param entireFiles Map of all files to compile (filename: content)
|
|
101
|
+
* @param testFiles Map of files to compile (filename: content)
|
|
93
102
|
* @param life Number of remaining retry attempts
|
|
94
103
|
* @returns Event object containing successful compilation result and modified
|
|
95
104
|
* files
|
|
96
105
|
*/
|
|
97
106
|
async function step<Model extends ILlmSchema.Model>(
|
|
98
107
|
ctx: AutoBeContext<Model>,
|
|
99
|
-
|
|
100
|
-
|
|
108
|
+
entireFiles: Record<string, string>,
|
|
109
|
+
testFiles: AutoBeTestFile[],
|
|
101
110
|
life: number,
|
|
102
111
|
): Promise<AutoBeTestValidateEvent> {
|
|
103
112
|
// COMPILE TEST CODE
|
|
104
|
-
|
|
105
113
|
const result: IAutoBeTypeScriptCompilerResult =
|
|
106
114
|
await ctx.compiler.typescript.compile({
|
|
107
|
-
files
|
|
115
|
+
files: {
|
|
116
|
+
...entireFiles,
|
|
117
|
+
...testFiles
|
|
118
|
+
.map((el) => ({ [el.location]: el.content }))
|
|
119
|
+
.reduce((acc, cur) => Object.assign(acc, cur), {}),
|
|
120
|
+
},
|
|
108
121
|
});
|
|
122
|
+
|
|
109
123
|
if (result.type === "success") {
|
|
110
124
|
// SUCCESS
|
|
111
125
|
return {
|
|
112
126
|
type: "testValidate",
|
|
113
127
|
created_at: new Date().toISOString(),
|
|
114
|
-
files,
|
|
128
|
+
files: testFiles,
|
|
115
129
|
result,
|
|
116
130
|
step: ctx.state().interface?.step ?? 0,
|
|
117
131
|
};
|
|
@@ -122,7 +136,7 @@ async function step<Model extends ILlmSchema.Model>(
|
|
|
122
136
|
ctx.dispatch({
|
|
123
137
|
type: "testValidate",
|
|
124
138
|
created_at: new Date().toISOString(),
|
|
125
|
-
files,
|
|
139
|
+
files: testFiles,
|
|
126
140
|
result,
|
|
127
141
|
step: ctx.state().interface?.step ?? 0,
|
|
128
142
|
});
|
|
@@ -151,7 +165,7 @@ async function step<Model extends ILlmSchema.Model>(
|
|
|
151
165
|
return {
|
|
152
166
|
type: "testValidate",
|
|
153
167
|
created_at: new Date().toISOString(),
|
|
154
|
-
files,
|
|
168
|
+
files: testFiles,
|
|
155
169
|
result: {
|
|
156
170
|
...result,
|
|
157
171
|
type: "success",
|
|
@@ -164,7 +178,7 @@ async function step<Model extends ILlmSchema.Model>(
|
|
|
164
178
|
ctx.dispatch({
|
|
165
179
|
type: "testValidate",
|
|
166
180
|
created_at: new Date().toISOString(),
|
|
167
|
-
files,
|
|
181
|
+
files: testFiles,
|
|
168
182
|
result,
|
|
169
183
|
step: ctx.state().interface?.step ?? 0,
|
|
170
184
|
});
|
|
@@ -173,44 +187,66 @@ async function step<Model extends ILlmSchema.Model>(
|
|
|
173
187
|
return {
|
|
174
188
|
type: "testValidate",
|
|
175
189
|
created_at: new Date().toISOString(),
|
|
176
|
-
files,
|
|
190
|
+
files: testFiles,
|
|
177
191
|
result,
|
|
178
192
|
step: ctx.state().interface?.step ?? 0,
|
|
179
193
|
};
|
|
180
194
|
|
|
181
195
|
// VALIDATION FAILED
|
|
182
|
-
const
|
|
183
|
-
Object.entries(diagnostics).map(
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
196
|
+
const validatedFiles: AutoBeTestFile[] = await Promise.all(
|
|
197
|
+
Object.entries(diagnostics).map(
|
|
198
|
+
async ([filename, d]): Promise<AutoBeTestFile> => {
|
|
199
|
+
const file = testFiles.find((f) => f.location === filename);
|
|
200
|
+
const code: string = file?.content!;
|
|
201
|
+
const scenario = file?.scenario!;
|
|
202
|
+
|
|
203
|
+
const response: ICorrectTestFunctionProps = await process(
|
|
204
|
+
ctx,
|
|
205
|
+
d,
|
|
206
|
+
code,
|
|
207
|
+
scenario,
|
|
208
|
+
);
|
|
209
|
+
ctx.dispatch({
|
|
210
|
+
type: "testCorrect",
|
|
211
|
+
created_at: new Date().toISOString(),
|
|
212
|
+
files: { ...testFiles, [filename]: response.content },
|
|
213
|
+
result,
|
|
214
|
+
solution: response.solution,
|
|
215
|
+
think_without_compile_error: response.think_without_compile_error,
|
|
216
|
+
think_again_with_compile_error:
|
|
217
|
+
response.think_again_with_compile_error,
|
|
218
|
+
step: ctx.state().interface?.step ?? 0,
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
return {
|
|
222
|
+
location: filename,
|
|
223
|
+
content: response.content,
|
|
224
|
+
scenario: scenario,
|
|
225
|
+
};
|
|
226
|
+
},
|
|
227
|
+
),
|
|
207
228
|
);
|
|
208
229
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
230
|
+
return step(
|
|
231
|
+
ctx,
|
|
232
|
+
Object.entries(entireFiles)
|
|
233
|
+
.map(([filename, content]) => {
|
|
234
|
+
const overwritten = validatedFiles.find(
|
|
235
|
+
(el) => el.location === filename,
|
|
236
|
+
);
|
|
237
|
+
return overwritten
|
|
238
|
+
? { [overwritten.location]: overwritten.content }
|
|
239
|
+
: {
|
|
240
|
+
[filename]: content,
|
|
241
|
+
};
|
|
242
|
+
})
|
|
243
|
+
.reduce((acc, cur) => Object.assign(acc, cur), {}),
|
|
244
|
+
testFiles.map((f) => {
|
|
245
|
+
const validated = validatedFiles.find((v) => v.location === f.location);
|
|
246
|
+
return validated ? validated : f;
|
|
247
|
+
}),
|
|
248
|
+
life - 1,
|
|
249
|
+
);
|
|
214
250
|
}
|
|
215
251
|
|
|
216
252
|
/**
|
|
@@ -227,32 +263,28 @@ async function process<Model extends ILlmSchema.Model>(
|
|
|
227
263
|
ctx: AutoBeContext<Model>,
|
|
228
264
|
diagnostics: IAutoBeTypeScriptCompilerResult.IDiagnostic[],
|
|
229
265
|
code: string,
|
|
230
|
-
scenario:
|
|
266
|
+
scenario: AutoBeTestScenario,
|
|
231
267
|
): Promise<ICorrectTestFunctionProps> {
|
|
232
268
|
const pointer: IPointer<ICorrectTestFunctionProps | null> = {
|
|
233
269
|
value: null,
|
|
234
270
|
};
|
|
271
|
+
const artifacts: IAutoBeTestScenarioArtifacts = await compileTestScenario(
|
|
272
|
+
ctx,
|
|
273
|
+
scenario,
|
|
274
|
+
);
|
|
235
275
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
// const dtoFiles = Object.entries(ctx.state().interface?.files ?? {})
|
|
250
|
-
// .filter(([filename]) => {
|
|
251
|
-
// return filename.startsWith("src/api/structures/");
|
|
252
|
-
// })
|
|
253
|
-
// .reduce<Record<string, string>>((acc, [filename, content]) => {
|
|
254
|
-
// return Object.assign(acc, { [filename]: content });
|
|
255
|
-
// }, {});
|
|
276
|
+
const lines = code.split("\n").map((line, num, arr) => {
|
|
277
|
+
const start = arr
|
|
278
|
+
.slice(0, num)
|
|
279
|
+
.map((el) => el.length + 1)
|
|
280
|
+
.reduce((acc, cur) => acc + cur, 0);
|
|
281
|
+
return {
|
|
282
|
+
line: num + 1,
|
|
283
|
+
text: line,
|
|
284
|
+
start: start,
|
|
285
|
+
end: start + line.length + 1, // exclusive
|
|
286
|
+
};
|
|
287
|
+
});
|
|
256
288
|
|
|
257
289
|
const agentica = new MicroAgentica({
|
|
258
290
|
model: ctx.model,
|
|
@@ -260,7 +292,15 @@ async function process<Model extends ILlmSchema.Model>(
|
|
|
260
292
|
config: {
|
|
261
293
|
...(ctx.config ?? {}),
|
|
262
294
|
},
|
|
263
|
-
histories: transformTestCorrectHistories(
|
|
295
|
+
histories: transformTestCorrectHistories(
|
|
296
|
+
code,
|
|
297
|
+
artifacts,
|
|
298
|
+
diagnostics.map((diagnostic) =>
|
|
299
|
+
diagnostic.start === undefined || diagnostic.length === undefined
|
|
300
|
+
? ""
|
|
301
|
+
: formatDiagnostic(code, lines, diagnostic),
|
|
302
|
+
),
|
|
303
|
+
),
|
|
264
304
|
controllers: [
|
|
265
305
|
createApplication({
|
|
266
306
|
model: ctx.model,
|
|
@@ -276,38 +316,7 @@ async function process<Model extends ILlmSchema.Model>(
|
|
|
276
316
|
await randomBackoffRetry(async () => {
|
|
277
317
|
await agentica.conversate(
|
|
278
318
|
[
|
|
279
|
-
"
|
|
280
|
-
"",
|
|
281
|
-
"## Original Code",
|
|
282
|
-
"```typescript",
|
|
283
|
-
code,
|
|
284
|
-
"```",
|
|
285
|
-
"",
|
|
286
|
-
diagnostics.map((diagnostic) => {
|
|
287
|
-
if (diagnostic.start === undefined || diagnostic.length === undefined)
|
|
288
|
-
return "";
|
|
289
|
-
|
|
290
|
-
const checkDtoRegexp = `Cannot find module '@ORGANIZATION/template-api/lib/structures/IBbsArticleComment' or its corresponding type declarations.`;
|
|
291
|
-
const [group] = [
|
|
292
|
-
...checkDtoRegexp.matchAll(
|
|
293
|
-
/Cannot find module '(.*lib\/structures\/.*)'/g,
|
|
294
|
-
),
|
|
295
|
-
];
|
|
296
|
-
|
|
297
|
-
const [_, filename] = group ?? [];
|
|
298
|
-
|
|
299
|
-
return [
|
|
300
|
-
"## Error Information",
|
|
301
|
-
`- Position: Characters ${diagnostic.start} to ${diagnostic.start + diagnostic.length}`,
|
|
302
|
-
`- Error Message: ${diagnostic.messageText}`,
|
|
303
|
-
`- Problematic Code: \`${code.substring(diagnostic.start, diagnostic.start + diagnostic.length)}\``,
|
|
304
|
-
filename
|
|
305
|
-
? `The type files located under **/lib/structures are declared in '@ORGANIZATION/PROJECT-api/lib/structures'.\n` +
|
|
306
|
-
`Note: '@ORGANIZATION/PROJECT-api' must be written exactly as is and should not be replaced.\n`
|
|
307
|
-
: "",
|
|
308
|
-
].join("\n");
|
|
309
|
-
}),
|
|
310
|
-
"## Instructions",
|
|
319
|
+
"# Instructions",
|
|
311
320
|
"1. Focus on the specific error location and message",
|
|
312
321
|
"2. Provide the corrected TypeScript code",
|
|
313
322
|
"3. Ensure the fix resolves the compilation error",
|
|
@@ -317,9 +326,160 @@ async function process<Model extends ILlmSchema.Model>(
|
|
|
317
326
|
);
|
|
318
327
|
});
|
|
319
328
|
if (pointer.value === null) throw new Error("Failed to modify test code.");
|
|
329
|
+
|
|
330
|
+
const typeReferences: string[] = Array.from(
|
|
331
|
+
new Set(
|
|
332
|
+
Object.keys(artifacts.document.components.schemas).map(
|
|
333
|
+
(key) => key.split(".")[0]!,
|
|
334
|
+
),
|
|
335
|
+
),
|
|
336
|
+
);
|
|
337
|
+
|
|
338
|
+
pointer.value.content = pointer.value.content
|
|
339
|
+
.replace(/^[ \t]*import\b[\s\S]*?;[ \t]*$/gm, "")
|
|
340
|
+
.trim();
|
|
341
|
+
pointer.value.content = [
|
|
342
|
+
`import { TestValidator } from "@nestia/e2e";`,
|
|
343
|
+
`import typia, { tags } from "typia";`,
|
|
344
|
+
"",
|
|
345
|
+
`import api from "@ORGANIZATION/PROJECT-api";`,
|
|
346
|
+
...typeReferences.map(
|
|
347
|
+
(ref) =>
|
|
348
|
+
`import type { ${ref} } from "@ORGANIZATION/PROJECT-api/lib/structures/${ref}";`,
|
|
349
|
+
),
|
|
350
|
+
"",
|
|
351
|
+
pointer.value.content,
|
|
352
|
+
].join("\n");
|
|
353
|
+
|
|
354
|
+
pointer.value.content = pointer.value.content.replaceAll(
|
|
355
|
+
'string & Format<"uuid">',
|
|
356
|
+
'string & tags.Format<"uuid">',
|
|
357
|
+
);
|
|
358
|
+
|
|
320
359
|
return pointer.value;
|
|
321
360
|
}
|
|
322
361
|
|
|
362
|
+
function formatDiagnostic(
|
|
363
|
+
code: string,
|
|
364
|
+
lines: {
|
|
365
|
+
line: number; // line number
|
|
366
|
+
text: string;
|
|
367
|
+
start: number;
|
|
368
|
+
end: number; // exclusive
|
|
369
|
+
}[],
|
|
370
|
+
diagnostic: IAutoBeTypeScriptCompilerResult.IDiagnostic,
|
|
371
|
+
): string {
|
|
372
|
+
const { start, length, messageText } = diagnostic;
|
|
373
|
+
const message = messageText;
|
|
374
|
+
if (typeof start === "number" && typeof length === "number") {
|
|
375
|
+
const end = start + length;
|
|
376
|
+
const problematicCode = code.substring(start, end);
|
|
377
|
+
const errorLine = lines.find((line) => line.end > start) ?? null;
|
|
378
|
+
const lineText = errorLine?.text ?? "";
|
|
379
|
+
|
|
380
|
+
const hints = getHints(message, lineText);
|
|
381
|
+
|
|
382
|
+
function createAdjustedArray(n: number): number[] {
|
|
383
|
+
let start = n - 2;
|
|
384
|
+
|
|
385
|
+
// 시작 값이 음수라면, 0부터 시작해서 5개의 숫자 생성
|
|
386
|
+
if (start < 0) {
|
|
387
|
+
start = 0;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
return Array.from({ length: 5 }, (_, i) => start + i);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
const errorLines = createAdjustedArray(errorLine?.line ?? 0);
|
|
394
|
+
|
|
395
|
+
const context = errorLines
|
|
396
|
+
.map((num) => {
|
|
397
|
+
if (num === errorLine?.line) {
|
|
398
|
+
if (lines[num - 1]) {
|
|
399
|
+
return `${lines[num - 1]?.text} // <- ERROR LINE (line:${num})`;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
if (lines[num - 1]) {
|
|
403
|
+
return lines[num - 1]?.text;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
return null;
|
|
407
|
+
})
|
|
408
|
+
.filter((el) => el !== null);
|
|
409
|
+
|
|
410
|
+
return [
|
|
411
|
+
"## Error Information",
|
|
412
|
+
`- Position: Characters ${start} to ${end}`,
|
|
413
|
+
`- Error Message: ${message}`,
|
|
414
|
+
`- Error Lines: \n${
|
|
415
|
+
context.length
|
|
416
|
+
? [
|
|
417
|
+
"\t```ts", //
|
|
418
|
+
...context.map((el) => `\t${el}`),
|
|
419
|
+
"\t```",
|
|
420
|
+
].join("\n")
|
|
421
|
+
: "(none)"
|
|
422
|
+
}`,
|
|
423
|
+
`- Problematic Code: ${problematicCode.length > 0 ? `\`${problematicCode}\`` : "(none)"}`,
|
|
424
|
+
...hints.map((hint) => `- Hint: ${hint}`),
|
|
425
|
+
].join("\n");
|
|
426
|
+
}
|
|
427
|
+
return ["## Error Information", `- Error Message: ${message}`].join("\n");
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
function getHints(message: string, lineText: string): string[] {
|
|
431
|
+
const isTestValidator = lineText.includes("TestValidator");
|
|
432
|
+
const isTypia =
|
|
433
|
+
message === "Cannot find name 'Format'. Did you mean 'FormData'?";
|
|
434
|
+
const isJest = message === "Cannot find name 'expect'.";
|
|
435
|
+
const isCurrying =
|
|
436
|
+
isTestValidator && message === "Expected 1 arguments, but got 2";
|
|
437
|
+
const isAssignability =
|
|
438
|
+
/Argument of type '([^']+)' is not assignable to parameter of type '([^']+)'/.test(
|
|
439
|
+
message,
|
|
440
|
+
);
|
|
441
|
+
|
|
442
|
+
const hints: string[] = [];
|
|
443
|
+
|
|
444
|
+
if (isTypia) {
|
|
445
|
+
hints.push(
|
|
446
|
+
"If you want to use typia tags, use `tags.Format` instead of `Format`.",
|
|
447
|
+
);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
if (isJest) {
|
|
451
|
+
hints.push(
|
|
452
|
+
'Detected invalid `expect` usage. Use `TestValidator.equals("description")(expected)(actual)`.',
|
|
453
|
+
);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
if (isCurrying) {
|
|
457
|
+
hints.push(
|
|
458
|
+
"`TestValidator.equals` is a curried function and must be called in **three steps**: `title → expected → actual`.",
|
|
459
|
+
);
|
|
460
|
+
} else if (isTestValidator) {
|
|
461
|
+
hints.push(
|
|
462
|
+
"The second argument `expected` must be assignable from the type of `actual`. Consider swapping the order if you get a type error.",
|
|
463
|
+
);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
if (isAssignability && isTestValidator) {
|
|
467
|
+
const match = lineText
|
|
468
|
+
.trim()
|
|
469
|
+
.match(/TestValidator\.equals\("([^"]+)"\)\(([^)]+)\)\(([^)]+)\)/);
|
|
470
|
+
if (match) {
|
|
471
|
+
const [, title, expected, actual] = match;
|
|
472
|
+
if (actual.includes(expected)) {
|
|
473
|
+
hints.push(
|
|
474
|
+
`You can try rearranging the order like this: TestValidator.equals("${title}")(${actual})(${expected})`,
|
|
475
|
+
);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
return hints;
|
|
481
|
+
}
|
|
482
|
+
|
|
323
483
|
function createApplication<Model extends ILlmSchema.Model>(props: {
|
|
324
484
|
model: Model;
|
|
325
485
|
build: (next: ICorrectTestFunctionProps) => void;
|
|
@@ -3,8 +3,11 @@ import {
|
|
|
3
3
|
IAgenticaHistoryJson,
|
|
4
4
|
MicroAgentica,
|
|
5
5
|
} from "@agentica/core";
|
|
6
|
-
import {
|
|
7
|
-
|
|
6
|
+
import {
|
|
7
|
+
AutoBeOpenApi,
|
|
8
|
+
AutoBeTestScenario,
|
|
9
|
+
AutoBeTestScenarioEvent,
|
|
10
|
+
} from "@autobe/interface";
|
|
8
11
|
import { ILlmApplication, ILlmSchema, IValidation } from "@samchon/openapi";
|
|
9
12
|
import { IPointer } from "tstl";
|
|
10
13
|
import typia from "typia";
|
|
@@ -31,7 +34,7 @@ export async function orchestrateTestScenario<Model extends ILlmSchema.Model>(
|
|
|
31
34
|
let include: AutoBeOpenApi.IOperation[] = Array.from(operations);
|
|
32
35
|
|
|
33
36
|
do {
|
|
34
|
-
const matrix = divideArray({ array: include, capacity:
|
|
37
|
+
const matrix = divideArray({ array: include, capacity: 5 });
|
|
35
38
|
|
|
36
39
|
await Promise.all(
|
|
37
40
|
matrix.map(async (_include) => {
|
|
@@ -68,8 +71,8 @@ export async function orchestrateTestScenario<Model extends ILlmSchema.Model>(
|
|
|
68
71
|
endpoint: pg.endpoint,
|
|
69
72
|
draft: plan.draft,
|
|
70
73
|
functionName: plan.functionName,
|
|
71
|
-
dependencies: plan.
|
|
72
|
-
} satisfies
|
|
74
|
+
dependencies: plan.dependencies,
|
|
75
|
+
} satisfies AutoBeTestScenario;
|
|
73
76
|
});
|
|
74
77
|
}),
|
|
75
78
|
created_at: new Date().toISOString(),
|
|
@@ -120,7 +123,9 @@ const createHistoryProperties = (
|
|
|
120
123
|
operations: AutoBeOpenApi.IOperation[],
|
|
121
124
|
include: Pick<AutoBeOpenApi.IOperation, "method" | "path">[],
|
|
122
125
|
exclude: Pick<AutoBeOpenApi.IOperation, "method" | "path">[],
|
|
123
|
-
)
|
|
126
|
+
): Array<
|
|
127
|
+
IAgenticaHistoryJson.IAssistantMessage | IAgenticaHistoryJson.ISystemMessage
|
|
128
|
+
> => [
|
|
124
129
|
{
|
|
125
130
|
id: v4(),
|
|
126
131
|
created_at: new Date().toISOString(),
|
|
@@ -132,17 +137,29 @@ const createHistoryProperties = (
|
|
|
132
137
|
created_at: new Date().toISOString(),
|
|
133
138
|
type: "systemMessage",
|
|
134
139
|
text: [
|
|
140
|
+
"# Operations",
|
|
135
141
|
"Below are the full operations. Please refer to this.",
|
|
136
142
|
"Your role is to draft all test cases for each given Operation.",
|
|
137
143
|
"It is also permissible to write multiple test codes on a single endpoint.",
|
|
138
144
|
"However, rather than meaningless tests, business logic tests should be written and an E2E test situation should be assumed.",
|
|
139
145
|
"",
|
|
146
|
+
"Please carefully analyze each operation to identify all dependencies required for testing.",
|
|
147
|
+
"For example, if you want to test liking and then deleting a post,",
|
|
148
|
+
"you might think to test post creation, liking, and unlike operations.",
|
|
149
|
+
"However, even if not explicitly mentioned, user registration and login are essential prerequisites.",
|
|
150
|
+
"Pay close attention to IDs and related values in the API,",
|
|
151
|
+
"and ensure you identify all dependencies between endpoints.",
|
|
152
|
+
"",
|
|
140
153
|
"```json",
|
|
141
154
|
JSON.stringify(
|
|
142
155
|
operations.map((el) => ({
|
|
143
156
|
path: el.path,
|
|
144
157
|
method: el.method,
|
|
145
158
|
summary: el.summary,
|
|
159
|
+
description: el.description,
|
|
160
|
+
parameters: el.parameters,
|
|
161
|
+
requestBody: el.requestBody,
|
|
162
|
+
responseBody: el.responseBody,
|
|
146
163
|
})),
|
|
147
164
|
),
|
|
148
165
|
"```",
|