@akanjs/devkit 0.0.143 → 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 +152 -16
- package/cjs/src/builder.js +1 -2
- package/cjs/src/commandDecorators/command.js +0 -1
- package/cjs/src/executors.js +151 -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 +158 -17
- package/esm/src/builder.js +1 -2
- package/esm/src/commandDecorators/command.js +0 -1
- package/esm/src/executors.js +152 -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/executors.d.ts +65 -25
- 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,19 +105,44 @@ 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
|
}
|
|
111
143
|
} = {}) {
|
|
112
|
-
if (!AiSession.#chat)
|
|
113
|
-
import_common.Logger.rawLog(import_chalk.default.yellow("\u{1F916}akan-editor is not initialized. LLM configuration should be set first."));
|
|
144
|
+
if (!AiSession.#chat)
|
|
114
145
|
await AiSession.init();
|
|
115
|
-
}
|
|
116
146
|
if (!AiSession.#chat)
|
|
117
147
|
throw new Error("Failed to initialize the AI session");
|
|
118
148
|
const loader = new import_spinner.Spinner(`${AiSession.#chat.model} is thinking...`, {
|
|
@@ -122,10 +152,21 @@ class AiSession {
|
|
|
122
152
|
const humanMessage = new import_messages.HumanMessage(question);
|
|
123
153
|
this.messageHistory.push(humanMessage);
|
|
124
154
|
const stream = await AiSession.#chat.stream(this.messageHistory);
|
|
125
|
-
let fullResponse = "", tokenIdx = 0;
|
|
155
|
+
let reasoningResponse = "", fullResponse = "", tokenIdx = 0;
|
|
126
156
|
for await (const chunk of stream) {
|
|
127
|
-
if (loader.isSpinning()
|
|
157
|
+
if (loader.isSpinning())
|
|
128
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
|
+
}
|
|
129
170
|
const content = chunk.content;
|
|
130
171
|
if (typeof content === "string") {
|
|
131
172
|
fullResponse += content;
|
|
@@ -142,18 +183,25 @@ class AiSession {
|
|
|
142
183
|
throw new Error("Failed to stream response");
|
|
143
184
|
}
|
|
144
185
|
}
|
|
145
|
-
async edit(question, { onChunk, maxTry = MAX_ASK_TRY } = {}) {
|
|
186
|
+
async edit(question, { onChunk, onReasoning, maxTry = MAX_ASK_TRY, validate, approve } = {}) {
|
|
146
187
|
for (let tryCount = 0; tryCount < maxTry; tryCount++) {
|
|
147
|
-
|
|
148
|
-
|
|
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)({
|
|
149
195
|
message: "Do you want to edit the response?",
|
|
150
196
|
choices: [
|
|
151
197
|
{ name: "\u2705 Yes, confirm and apply this result", value: true },
|
|
152
198
|
{ name: "\u{1F504} No, I want to edit it more", value: false }
|
|
153
199
|
]
|
|
154
200
|
});
|
|
155
|
-
if (isConfirmed)
|
|
201
|
+
if (isConfirmed) {
|
|
202
|
+
this.#saveCache();
|
|
156
203
|
return response.content;
|
|
204
|
+
}
|
|
157
205
|
question = await (0, import_prompts.input)({ message: "What do you want to change?" });
|
|
158
206
|
tryCount++;
|
|
159
207
|
}
|
|
@@ -164,9 +212,97 @@ class AiSession {
|
|
|
164
212
|
return this.#getTypescriptCode(content);
|
|
165
213
|
}
|
|
166
214
|
#getTypescriptCode(content) {
|
|
215
|
+
//! will be deprecated
|
|
167
216
|
const code = /```(typescript|tsx)([\s\S]*?)```/.exec(content);
|
|
168
217
|
return code ? code[2] : content;
|
|
169
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
|
+
}
|
|
170
306
|
}
|
|
171
307
|
// Annotate the CommonJS export names for ESM import in node:
|
|
172
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() {
|
|
@@ -211,7 +211,6 @@ const getInternalArgumentValue = async (argMeta, value, workspace) => {
|
|
|
211
211
|
};
|
|
212
212
|
const runCommands = async (...commands) => {
|
|
213
213
|
process.on("unhandledRejection", (error) => {
|
|
214
|
-
console.error(import_chalk.default.red("[fatal]"), error);
|
|
215
214
|
process.exit(1);
|
|
216
215
|
});
|
|
217
216
|
const hasPackageJson = import_fs.default.existsSync(`${__dirname}/../package.json`);
|