@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.
@@ -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,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
- 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
  }
@@ -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() && chunk.content.length)
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
- const response = await this.ask(question, { onChunk });
146
- 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)({
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 = {
@@ -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
- const sysType = argMeta.type.toLowerCase();
209
- programCommand = programCommand.argument(
210
- `[${sysType}]`,
211
- `${sysType} in this workspace ${sysType}s/<${sysType}Name>`
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) => {