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