@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.
@@ -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.7, useExisting = true } = {}) {
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.7 } = {}) {
60
- this.#chat = new import_openai.ChatOpenAI({
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
- configuration: { baseURL: "https://api.deepseek.com/v1", apiKey }
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
- constructor(messageHistory = []) {
105
- this.messageHistory = messageHistory;
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() && chunk.content.length)
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
- const response = await this.ask(question, { onChunk });
148
- const isConfirmed = await (0, import_prompts.select)({
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 = {
@@ -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`);