@akanjs/devkit 0.0.142 → 0.0.144
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/cjs/src/aiEditor.js +151 -13
- package/cjs/src/builder.js +1 -2
- package/cjs/src/commandDecorators/argMeta.js +4 -1
- package/cjs/src/commandDecorators/command.js +44 -6
- package/cjs/src/executors.js +165 -60
- package/cjs/src/guideline.js +15 -0
- package/cjs/src/index.js +5 -1
- package/cjs/src/linter.js +238 -0
- package/cjs/src/prompter.js +78 -0
- package/cjs/src/typeChecker.js +203 -0
- package/cjs/src/uploadRelease.js +59 -33
- package/esm/src/aiEditor.js +157 -14
- package/esm/src/builder.js +1 -2
- package/esm/src/commandDecorators/argMeta.js +3 -1
- package/esm/src/commandDecorators/command.js +45 -7
- package/esm/src/executors.js +165 -61
- package/esm/src/guideline.js +0 -0
- package/esm/src/index.js +2 -0
- package/esm/src/linter.js +205 -0
- package/esm/src/prompter.js +45 -0
- package/esm/src/typeChecker.js +170 -0
- package/esm/src/uploadRelease.js +59 -33
- package/package.json +3 -1
- package/src/aiEditor.d.ts +23 -4
- package/src/commandDecorators/argMeta.d.ts +6 -2
- package/src/executors.d.ts +74 -23
- package/src/guideline.d.ts +19 -0
- package/src/index.d.ts +2 -0
- package/src/linter.d.ts +109 -0
- package/src/prompter.d.ts +13 -0
- package/src/typeChecker.d.ts +49 -0
- package/src/types.d.ts +4 -0
- package/src/uploadRelease.d.ts +1 -1
package/esm/src/aiEditor.js
CHANGED
|
@@ -1,15 +1,23 @@
|
|
|
1
1
|
import { Logger } from "@akanjs/common";
|
|
2
2
|
import { input, select } from "@inquirer/prompts";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
AIMessage,
|
|
5
|
+
HumanMessage,
|
|
6
|
+
mapChatMessagesToStoredMessages,
|
|
7
|
+
mapStoredMessagesToChatMessages
|
|
8
|
+
} from "@langchain/core/messages";
|
|
9
|
+
import { ChatDeepSeek } from "@langchain/deepseek";
|
|
4
10
|
import { ChatOpenAI } from "@langchain/openai";
|
|
5
11
|
import chalk from "chalk";
|
|
12
|
+
import fs from "fs";
|
|
6
13
|
import { getAkanGlobalConfig, setAkanGlobalConfig } from "./auth";
|
|
7
14
|
import { Spinner } from "./spinner";
|
|
8
15
|
const MAX_ASK_TRY = 300;
|
|
9
16
|
const supportedLlmModels = ["deepseek-chat", "deepseek-reasoner"];
|
|
10
17
|
class AiSession {
|
|
18
|
+
static #cacheDir = "node_modules/.cache/akan/aiSession";
|
|
11
19
|
static #chat = null;
|
|
12
|
-
static async init({ temperature = 0
|
|
20
|
+
static async init({ temperature = 0, useExisting = true } = {}) {
|
|
13
21
|
if (useExisting) {
|
|
14
22
|
const llmConfig2 = this.getLlmConfig();
|
|
15
23
|
if (llmConfig2) {
|
|
@@ -17,18 +25,20 @@ class AiSession {
|
|
|
17
25
|
Logger.rawLog(chalk.dim(`\u{1F916}akan editor uses existing LLM config (${llmConfig2.model})`));
|
|
18
26
|
return this;
|
|
19
27
|
}
|
|
20
|
-
}
|
|
28
|
+
} else
|
|
29
|
+
Logger.rawLog(chalk.yellow("\u{1F916}akan-editor is not initialized. LLM configuration should be set first."));
|
|
21
30
|
const llmConfig = await this.#requestLlmConfig();
|
|
22
31
|
const { model, apiKey } = llmConfig;
|
|
23
32
|
await this.#validateApiKey(model, apiKey);
|
|
24
33
|
return this.#setChatModel(model, apiKey, { temperature }).setLlmConfig({ model, apiKey });
|
|
25
34
|
}
|
|
26
|
-
static #setChatModel(model, apiKey, { temperature = 0
|
|
27
|
-
this.#chat = new
|
|
35
|
+
static #setChatModel(model, apiKey, { temperature = 0 } = {}) {
|
|
36
|
+
this.#chat = new ChatDeepSeek({
|
|
28
37
|
modelName: model,
|
|
29
38
|
temperature,
|
|
30
39
|
streaming: true,
|
|
31
|
-
|
|
40
|
+
apiKey
|
|
41
|
+
// configuration: { baseURL: "https://api.deepseek.com/v1", apiKey },
|
|
32
42
|
});
|
|
33
43
|
return this;
|
|
34
44
|
}
|
|
@@ -67,11 +77,38 @@ class AiSession {
|
|
|
67
77
|
throw error;
|
|
68
78
|
}
|
|
69
79
|
}
|
|
80
|
+
static clearCache(workspaceRoot) {
|
|
81
|
+
const cacheDir = `${workspaceRoot}/${this.#cacheDir}`;
|
|
82
|
+
fs.rmSync(cacheDir, { recursive: true, force: true });
|
|
83
|
+
}
|
|
70
84
|
messageHistory = [];
|
|
71
|
-
|
|
72
|
-
|
|
85
|
+
sessionKey;
|
|
86
|
+
isCacheLoaded;
|
|
87
|
+
workspace;
|
|
88
|
+
constructor(type, { workspace, cacheKey, isContinued }) {
|
|
89
|
+
this.workspace = workspace;
|
|
90
|
+
this.sessionKey = `${type}${cacheKey ? `-${cacheKey}` : ""}`;
|
|
91
|
+
if (isContinued)
|
|
92
|
+
this.#loadCache();
|
|
93
|
+
}
|
|
94
|
+
#loadCache() {
|
|
95
|
+
const cacheFile = `${AiSession.#cacheDir}/${this.sessionKey}.json`;
|
|
96
|
+
const isCacheExists = this.workspace.exists(cacheFile);
|
|
97
|
+
if (isCacheExists)
|
|
98
|
+
this.messageHistory = mapStoredMessagesToChatMessages(this.workspace.readJson(cacheFile));
|
|
99
|
+
else
|
|
100
|
+
this.messageHistory = [];
|
|
101
|
+
this.isCacheLoaded = isCacheExists;
|
|
102
|
+
return isCacheExists;
|
|
103
|
+
}
|
|
104
|
+
#saveCache() {
|
|
105
|
+
const cacheFilePath = `${AiSession.#cacheDir}/${this.sessionKey}.json`;
|
|
106
|
+
this.workspace.writeJson(cacheFilePath, mapChatMessagesToStoredMessages(this.messageHistory));
|
|
73
107
|
}
|
|
74
108
|
async ask(question, {
|
|
109
|
+
onReasoning = (reasoning) => {
|
|
110
|
+
Logger.raw(chalk.dim(reasoning));
|
|
111
|
+
},
|
|
75
112
|
onChunk = (chunk) => {
|
|
76
113
|
Logger.raw(chunk);
|
|
77
114
|
}
|
|
@@ -87,10 +124,21 @@ class AiSession {
|
|
|
87
124
|
const humanMessage = new HumanMessage(question);
|
|
88
125
|
this.messageHistory.push(humanMessage);
|
|
89
126
|
const stream = await AiSession.#chat.stream(this.messageHistory);
|
|
90
|
-
let fullResponse = "", tokenIdx = 0;
|
|
127
|
+
let reasoningResponse = "", fullResponse = "", tokenIdx = 0;
|
|
91
128
|
for await (const chunk of stream) {
|
|
92
|
-
if (loader.isSpinning()
|
|
129
|
+
if (loader.isSpinning())
|
|
93
130
|
loader.succeed(`${AiSession.#chat.model} responded`);
|
|
131
|
+
if (!fullResponse.length) {
|
|
132
|
+
const reasoningContent = chunk.additional_kwargs.reasoning_content ?? "";
|
|
133
|
+
if (reasoningContent.length) {
|
|
134
|
+
reasoningResponse += reasoningContent;
|
|
135
|
+
onReasoning(reasoningContent);
|
|
136
|
+
continue;
|
|
137
|
+
} else if (chunk.content.length) {
|
|
138
|
+
reasoningResponse += "\n";
|
|
139
|
+
onReasoning(reasoningResponse);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
94
142
|
const content = chunk.content;
|
|
95
143
|
if (typeof content === "string") {
|
|
96
144
|
fullResponse += content;
|
|
@@ -107,18 +155,25 @@ class AiSession {
|
|
|
107
155
|
throw new Error("Failed to stream response");
|
|
108
156
|
}
|
|
109
157
|
}
|
|
110
|
-
async edit(question, { onChunk, maxTry = MAX_ASK_TRY } = {}) {
|
|
158
|
+
async edit(question, { onChunk, onReasoning, maxTry = MAX_ASK_TRY, validate, approve } = {}) {
|
|
111
159
|
for (let tryCount = 0; tryCount < maxTry; tryCount++) {
|
|
112
|
-
|
|
113
|
-
|
|
160
|
+
let response = await this.ask(question, { onChunk, onReasoning });
|
|
161
|
+
if (validate?.length && tryCount === 0) {
|
|
162
|
+
const validateQuestion = `Double check if the response meets the requirements and conditions, and follow the instructions. If not, rewrite it.
|
|
163
|
+
${validate.map((v) => `- ${v}`).join("\n")}`;
|
|
164
|
+
response = await this.ask(validateQuestion, { onChunk, onReasoning });
|
|
165
|
+
}
|
|
166
|
+
const isConfirmed = approve ? true : await select({
|
|
114
167
|
message: "Do you want to edit the response?",
|
|
115
168
|
choices: [
|
|
116
169
|
{ name: "\u2705 Yes, confirm and apply this result", value: true },
|
|
117
170
|
{ name: "\u{1F504} No, I want to edit it more", value: false }
|
|
118
171
|
]
|
|
119
172
|
});
|
|
120
|
-
if (isConfirmed)
|
|
173
|
+
if (isConfirmed) {
|
|
174
|
+
this.#saveCache();
|
|
121
175
|
return response.content;
|
|
176
|
+
}
|
|
122
177
|
question = await input({ message: "What do you want to change?" });
|
|
123
178
|
tryCount++;
|
|
124
179
|
}
|
|
@@ -129,9 +184,97 @@ class AiSession {
|
|
|
129
184
|
return this.#getTypescriptCode(content);
|
|
130
185
|
}
|
|
131
186
|
#getTypescriptCode(content) {
|
|
187
|
+
//! will be deprecated
|
|
132
188
|
const code = /```(typescript|tsx)([\s\S]*?)```/.exec(content);
|
|
133
189
|
return code ? code[2] : content;
|
|
134
190
|
}
|
|
191
|
+
addToolMessgaes(messages) {
|
|
192
|
+
const toolMessages = messages.map((message) => new HumanMessage(message.content));
|
|
193
|
+
this.messageHistory.push(...toolMessages);
|
|
194
|
+
return this;
|
|
195
|
+
}
|
|
196
|
+
async writeTypescripts(question, executor, options = {}) {
|
|
197
|
+
const content = await this.edit(question, options);
|
|
198
|
+
const writes = this.#getTypescriptCodes(content);
|
|
199
|
+
for (const write of writes)
|
|
200
|
+
executor.writeFile(write.filePath, write.content);
|
|
201
|
+
return await this.#tryFixTypescripts(writes, executor, options);
|
|
202
|
+
}
|
|
203
|
+
async #editTypescripts(question, options = {}) {
|
|
204
|
+
const content = await this.edit(question, options);
|
|
205
|
+
return this.#getTypescriptCodes(content);
|
|
206
|
+
}
|
|
207
|
+
async #tryFixTypescripts(writes, executor, options = {}) {
|
|
208
|
+
const MAX_EDIT_TRY = 5;
|
|
209
|
+
for (let tryCount = 0; tryCount < MAX_EDIT_TRY; tryCount++) {
|
|
210
|
+
const loader = new Spinner(`Type checking and linting...`, { prefix: `\u{1F916}akan-editor` }).start();
|
|
211
|
+
const fileChecks = await Promise.all(
|
|
212
|
+
writes.map(async ({ filePath }) => {
|
|
213
|
+
const typeCheckResult = executor.typeCheck(filePath);
|
|
214
|
+
const lintResult = await executor.lint(filePath);
|
|
215
|
+
const needFix2 = !!typeCheckResult.errors.length || !!lintResult.errors.length;
|
|
216
|
+
return { filePath, typeCheckResult, lintResult, needFix: needFix2 };
|
|
217
|
+
})
|
|
218
|
+
);
|
|
219
|
+
const needFix = fileChecks.some((fileCheck) => fileCheck.needFix);
|
|
220
|
+
if (needFix) {
|
|
221
|
+
loader.fail("Type checking and linting has some errors, try to fix them");
|
|
222
|
+
fileChecks.forEach((fileCheck) => {
|
|
223
|
+
Logger.rawLog(
|
|
224
|
+
`TypeCheck Result
|
|
225
|
+
${fileCheck.typeCheckResult.message}
|
|
226
|
+
Lint Result
|
|
227
|
+
${fileCheck.lintResult.message}`
|
|
228
|
+
);
|
|
229
|
+
this.addToolMessgaes([
|
|
230
|
+
{ type: "typescript", content: fileCheck.typeCheckResult.message },
|
|
231
|
+
{ type: "eslint", content: fileCheck.lintResult.message }
|
|
232
|
+
]);
|
|
233
|
+
});
|
|
234
|
+
writes = await this.#editTypescripts("Fix the typescript and eslint errors", {
|
|
235
|
+
...options,
|
|
236
|
+
validate: void 0,
|
|
237
|
+
approve: true
|
|
238
|
+
});
|
|
239
|
+
for (const write of writes)
|
|
240
|
+
executor.writeFile(write.filePath, write.content);
|
|
241
|
+
} else {
|
|
242
|
+
loader.succeed("Type checking and linting has no errors");
|
|
243
|
+
return writes;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
throw new Error("Failed to create scalar");
|
|
247
|
+
}
|
|
248
|
+
#getTypescriptCodes(text) {
|
|
249
|
+
const codes = text.match(/```typescript([\s\S]*?)```/g);
|
|
250
|
+
if (!codes)
|
|
251
|
+
return [];
|
|
252
|
+
const result = codes.map((code) => {
|
|
253
|
+
const content = /```(typescript|tsx)([\s\S]*?)```/.exec(code)?.[2];
|
|
254
|
+
if (!content)
|
|
255
|
+
return null;
|
|
256
|
+
const filePath = /\/\/ File: (.*?)(?:\n|$)/.exec(content)?.[1]?.trim();
|
|
257
|
+
if (!filePath)
|
|
258
|
+
return null;
|
|
259
|
+
const contentWithoutFilepath = content.replace(`// File: ${filePath}
|
|
260
|
+
`, "").trim();
|
|
261
|
+
return { filePath, content: contentWithoutFilepath };
|
|
262
|
+
});
|
|
263
|
+
return result.filter((code) => code !== null);
|
|
264
|
+
}
|
|
265
|
+
async editMarkdown(request, options = {}) {
|
|
266
|
+
const content = await this.edit(request, options);
|
|
267
|
+
return this.#getMarkdownContent(content);
|
|
268
|
+
}
|
|
269
|
+
#getMarkdownContent(text) {
|
|
270
|
+
const searchText = "```markdown";
|
|
271
|
+
const firstIndex = text.indexOf("```markdown");
|
|
272
|
+
const lastIndex = text.lastIndexOf("```");
|
|
273
|
+
if (firstIndex === -1)
|
|
274
|
+
return text;
|
|
275
|
+
else
|
|
276
|
+
return text.slice(firstIndex + searchText.length, lastIndex).trim();
|
|
277
|
+
}
|
|
135
278
|
}
|
|
136
279
|
export {
|
|
137
280
|
AiSession,
|
package/esm/src/builder.js
CHANGED
|
@@ -17,7 +17,6 @@ class Builder {
|
|
|
17
17
|
return {
|
|
18
18
|
entryPoints: [
|
|
19
19
|
...bundle ? [`${this.#executor.cwdPath}/index.ts`] : [`${this.#executor.cwdPath}/**/*.ts`, `${this.#executor.cwdPath}/**/*.tsx`],
|
|
20
|
-
`${this.#executor.cwdPath}/**/*.template`,
|
|
21
20
|
...additionalEntryPoints
|
|
22
21
|
],
|
|
23
22
|
bundle,
|
|
@@ -27,7 +26,7 @@ class Builder {
|
|
|
27
26
|
format,
|
|
28
27
|
outdir: `${this.#distExecutor.cwdPath}/${format}`,
|
|
29
28
|
logLevel: "error",
|
|
30
|
-
loader: { ".template": "copy" }
|
|
29
|
+
loader: { ".template": "copy", ".md": "copy" }
|
|
31
30
|
};
|
|
32
31
|
}
|
|
33
32
|
#getAssetBuildOptions() {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import "reflect-metadata";
|
|
2
2
|
const argTypes = ["Argument", "Option"];
|
|
3
|
-
const internalArgTypes = ["Workspace", "App", "Lib", "Sys", "Pkg", "Exec"];
|
|
3
|
+
const internalArgTypes = ["Workspace", "App", "Lib", "Sys", "Pkg", "Module", "Exec"];
|
|
4
4
|
const getArgMetas = (command, key) => {
|
|
5
5
|
const allArgMetas = getArgMetasOnPrototype(command.prototype, key);
|
|
6
6
|
const argMetas = allArgMetas.filter((argMeta) => argMeta.type === "Option");
|
|
@@ -36,12 +36,14 @@ const Lib = createArgMetaDecorator("Lib");
|
|
|
36
36
|
const Sys = createArgMetaDecorator("Sys");
|
|
37
37
|
const Exec = createArgMetaDecorator("Exec");
|
|
38
38
|
const Pkg = createArgMetaDecorator("Pkg");
|
|
39
|
+
const Module = createArgMetaDecorator("Module");
|
|
39
40
|
const Workspace = createArgMetaDecorator("Workspace");
|
|
40
41
|
export {
|
|
41
42
|
App,
|
|
42
43
|
Argument,
|
|
43
44
|
Exec,
|
|
44
45
|
Lib,
|
|
46
|
+
Module,
|
|
45
47
|
Option,
|
|
46
48
|
Pkg,
|
|
47
49
|
Sys,
|
|
@@ -3,7 +3,7 @@ import { confirm, input, select } from "@inquirer/prompts";
|
|
|
3
3
|
import chalk from "chalk";
|
|
4
4
|
import { program } from "commander";
|
|
5
5
|
import fs from "fs";
|
|
6
|
-
import { AppExecutor, Executor, LibExecutor, PkgExecutor, WorkspaceExecutor } from "../executors";
|
|
6
|
+
import { AppExecutor, Executor, LibExecutor, ModuleExecutor, PkgExecutor, WorkspaceExecutor } from "../executors";
|
|
7
7
|
import { getArgMetas } from "./argMeta";
|
|
8
8
|
import { getTargetMetas } from "./targetMeta";
|
|
9
9
|
const camelToKebabCase = (str) => str.replace(/([A-Z])/g, "-$1").toLowerCase();
|
|
@@ -142,12 +142,43 @@ const getInternalArgumentValue = async (argMeta, value, workspace) => {
|
|
|
142
142
|
return PkgExecutor.from(workspace, value);
|
|
143
143
|
const pkgName = await select({ message: `Select the ${sysType} name`, choices: pkgs });
|
|
144
144
|
return PkgExecutor.from(workspace, pkgName);
|
|
145
|
+
} else if (sysType === "module") {
|
|
146
|
+
if (value) {
|
|
147
|
+
const [sysName, moduleName2] = value.split(":");
|
|
148
|
+
if (appNames.includes(sysName)) {
|
|
149
|
+
const app = AppExecutor.from(workspace, sysName);
|
|
150
|
+
const modules2 = await app.getModules();
|
|
151
|
+
if (modules2.includes(moduleName2))
|
|
152
|
+
return ModuleExecutor.from(app, moduleName2);
|
|
153
|
+
else
|
|
154
|
+
throw new Error(`Invalid module name: ${moduleName2}`);
|
|
155
|
+
} else if (libNames.includes(sysName)) {
|
|
156
|
+
const lib = LibExecutor.from(workspace, sysName);
|
|
157
|
+
const modules2 = await lib.getModules();
|
|
158
|
+
if (modules2.includes(moduleName2))
|
|
159
|
+
return ModuleExecutor.from(lib, moduleName2);
|
|
160
|
+
} else
|
|
161
|
+
throw new Error(`Invalid system name: ${sysName}`);
|
|
162
|
+
}
|
|
163
|
+
const { type, name } = await select({
|
|
164
|
+
message: `select the App or Lib name`,
|
|
165
|
+
choices: [
|
|
166
|
+
...appNames.map((name2) => ({ name: name2, value: { type: "app", name: name2 } })),
|
|
167
|
+
...libNames.map((name2) => ({ name: name2, value: { type: "lib", name: name2 } }))
|
|
168
|
+
]
|
|
169
|
+
});
|
|
170
|
+
const executor = type === "app" ? AppExecutor.from(workspace, name) : LibExecutor.from(workspace, name);
|
|
171
|
+
const modules = await executor.getModules();
|
|
172
|
+
const moduleName = await select({
|
|
173
|
+
message: `Select the module name`,
|
|
174
|
+
choices: modules.map((name2) => ({ name: `${executor.name}:${name2}`, value: name2 }))
|
|
175
|
+
});
|
|
176
|
+
return ModuleExecutor.from(executor, moduleName);
|
|
145
177
|
} else
|
|
146
178
|
throw new Error(`Invalid system type: ${argMeta.type}`);
|
|
147
179
|
};
|
|
148
180
|
const runCommands = async (...commands) => {
|
|
149
181
|
process.on("unhandledRejection", (error) => {
|
|
150
|
-
console.error(chalk.red("[fatal]"), error);
|
|
151
182
|
process.exit(1);
|
|
152
183
|
});
|
|
153
184
|
const hasPackageJson = fs.existsSync(`${__dirname}/../package.json`);
|
|
@@ -173,11 +204,18 @@ const runCommands = async (...commands) => {
|
|
|
173
204
|
programCommand = handleArgument(programCommand, argMeta);
|
|
174
205
|
else if (argMeta.type === "Workspace")
|
|
175
206
|
continue;
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
207
|
+
else if (argMeta.type === "Module") {
|
|
208
|
+
programCommand = programCommand.argument(
|
|
209
|
+
`[sys-name:module-name]`,
|
|
210
|
+
`${argMeta.type} in this workspace (apps|libs)/<sys-name>/lib/<module-name>`
|
|
211
|
+
);
|
|
212
|
+
} else {
|
|
213
|
+
const sysType = argMeta.type.toLowerCase();
|
|
214
|
+
programCommand = programCommand.argument(
|
|
215
|
+
`[${sysType}]`,
|
|
216
|
+
`${sysType} in this workspace ${sysType}s/<${sysType}Name>`
|
|
217
|
+
);
|
|
218
|
+
}
|
|
181
219
|
}
|
|
182
220
|
programCommand = programCommand.option(`-v, --verbose [boolean]`, `verbose output`);
|
|
183
221
|
programCommand.action(async (...args) => {
|