@akanjs/devkit 1.0.20 → 2.1.0-rc.1
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/README.ko.md +65 -0
- package/README.md +62 -6
- package/aiEditor.ts +304 -0
- package/akanApp/akanApp.host.ts +393 -0
- package/akanApp/index.ts +1 -0
- package/akanConfig/akanConfig.test.ts +236 -0
- package/akanConfig/akanConfig.ts +384 -0
- package/akanConfig/index.ts +2 -0
- package/akanConfig/types.ts +23 -0
- package/applicationBuildReporter.ts +69 -0
- package/applicationBuildRunner.ts +302 -0
- package/applicationReleasePackager.ts +206 -0
- package/artifact/implicitRootLayout.ts +155 -0
- package/artifact/index.ts +1 -0
- package/artifact/routeSeedIndex.test.ts +98 -0
- package/artifact/routeSeedIndex.ts +130 -0
- package/auth.ts +41 -0
- package/builder.ts +164 -0
- package/capacitor.base.config.ts +88 -0
- package/capacitorApp.ts +440 -0
- package/commandDecorators/argMeta.ts +102 -0
- package/commandDecorators/command.ts +351 -0
- package/commandDecorators/commandBuilder.ts +224 -0
- package/commandDecorators/commandDecorators.test.ts +212 -0
- package/commandDecorators/commandMeta.ts +7 -0
- package/commandDecorators/dependencyBuilder.ts +100 -0
- package/{esm/src/commandDecorators/helpFormatter.js → commandDecorators/helpFormatter.ts} +100 -47
- package/{esm/src/commandDecorators/index.js → commandDecorators/index.ts} +4 -2
- package/commandDecorators/targetMeta.ts +31 -0
- package/commandDecorators/types.ts +10 -0
- package/constants.ts +25 -0
- package/createTunnel.ts +36 -0
- package/dependencyScanner.ts +357 -0
- package/devkitUtils.test.ts +259 -0
- package/executors.test.ts +315 -0
- package/executors.ts +1390 -0
- package/{esm/src/extractDeps.js → extractDeps.ts} +26 -20
- package/{esm/src/fileEditor.js → fileEditor.ts} +51 -32
- package/fileSys.ts +39 -0
- package/frontendBuild/allRoutesBuilder.ts +103 -0
- package/frontendBuild/buildRouteClient.test.ts +190 -0
- package/frontendBuild/clientBuildTypes.ts +114 -0
- package/frontendBuild/clientEntriesBundler.ts +303 -0
- package/frontendBuild/clientEntryDiscovery.ts +199 -0
- package/frontendBuild/csrArtifactBuilder.ts +237 -0
- package/frontendBuild/cssCompiler.ts +286 -0
- package/frontendBuild/cssImportResolver.ts +116 -0
- package/frontendBuild/fontOptimizer.ts +427 -0
- package/frontendBuild/frontendBuild.test.ts +204 -0
- package/frontendBuild/hmrChangeClassifier.ts +28 -0
- package/frontendBuild/hmrWatcher.ts +102 -0
- package/frontendBuild/index.ts +18 -0
- package/frontendBuild/pagesBundleBuilder.ts +137 -0
- package/frontendBuild/pagesEntrySourceGenerator.ts +37 -0
- package/frontendBuild/precompressArtifacts.ts +59 -0
- package/frontendBuild/routeClientBuilder.ts +290 -0
- package/frontendBuild/routesManifestArtifactSerializer.ts +62 -0
- package/frontendBuild/ssrBaseArtifactBuilder.ts +139 -0
- package/frontendBuild/vendorSpecifiers.ts +16 -0
- package/frontendBuild/watchRootResolver.ts +28 -0
- package/getCredentials.ts +19 -0
- package/getDirname.ts +3 -0
- package/getModelFileData.ts +59 -0
- package/getRelatedCnsts.ts +313 -0
- package/guideline.ts +19 -0
- package/incrementalBuilder/incrementalBuilder.host.test.ts +51 -0
- package/incrementalBuilder/incrementalBuilder.host.ts +152 -0
- package/incrementalBuilder/incrementalBuilder.proc.ts +331 -0
- package/incrementalBuilder/index.ts +1 -0
- package/{esm/src/index.js → index.ts} +28 -15
- package/lint/no-deep-internal-import.grit +25 -0
- package/lint/no-import-client-functions.grit +32 -0
- package/lint/no-import-external-library.grit +21 -0
- package/lint/no-js-private-class-method.grit +42 -0
- package/lint/no-use-client-in-server.grit +7 -0
- package/lint/non-scalar-props-restricted.grit +13 -0
- package/linter.ts +271 -0
- package/mobile/index.ts +1 -0
- package/mobile/mobileTarget.test.ts +53 -0
- package/mobile/mobileTarget.ts +88 -0
- package/package.json +48 -31
- package/prompter.ts +72 -0
- package/scanInfo.ts +606 -0
- package/selectModel.ts +11 -0
- package/{esm/src/spinner.js → spinner.ts} +22 -28
- package/{esm/src/capacitorApp.js → src/capacitorApp.ts} +82 -81
- package/sshTunnel.ts +152 -0
- package/{esm/src/streamAi.js → streamAi.ts} +18 -12
- package/transforms/barrelAnalyzer.ts +278 -0
- package/transforms/barrelImportsPlugin.ts +504 -0
- package/transforms/externalizeFrameworkPlugin.ts +185 -0
- package/transforms/index.ts +5 -0
- package/transforms/rscUseClientTransform.ts +59 -0
- package/transforms/transforms.test.ts +208 -0
- package/transforms/useClientBundlePlugin.ts +47 -0
- package/tsconfig.json +37 -0
- package/typeChecker.ts +264 -0
- package/types.ts +44 -0
- package/ui/MultiScrollList.tsx +242 -0
- package/ui/ScrollList.tsx +107 -0
- package/ui/index.ts +2 -0
- package/{esm/src/uploadRelease.js → uploadRelease.ts} +50 -34
- package/{esm/src/useStdoutDimensions.js → useStdoutDimensions.ts} +5 -5
- package/cjs/index.js +0 -21
- package/cjs/src/aiEditor.js +0 -311
- package/cjs/src/auth.js +0 -72
- package/cjs/src/builder.js +0 -114
- package/cjs/src/capacitorApp.js +0 -313
- package/cjs/src/commandDecorators/argMeta.js +0 -88
- package/cjs/src/commandDecorators/command.js +0 -324
- package/cjs/src/commandDecorators/commandMeta.js +0 -30
- package/cjs/src/commandDecorators/helpFormatter.js +0 -211
- package/cjs/src/commandDecorators/index.js +0 -31
- package/cjs/src/commandDecorators/targetMeta.js +0 -57
- package/cjs/src/commandDecorators/types.js +0 -15
- package/cjs/src/constants.js +0 -46
- package/cjs/src/createTunnel.js +0 -49
- package/cjs/src/dependencyScanner.js +0 -220
- package/cjs/src/executors.js +0 -964
- package/cjs/src/extractDeps.js +0 -103
- package/cjs/src/fileEditor.js +0 -120
- package/cjs/src/getCredentials.js +0 -44
- package/cjs/src/getDirname.js +0 -38
- package/cjs/src/getModelFileData.js +0 -66
- package/cjs/src/getRelatedCnsts.js +0 -260
- package/cjs/src/guideline.js +0 -15
- package/cjs/src/index.js +0 -65
- package/cjs/src/linter.js +0 -238
- package/cjs/src/prompter.js +0 -85
- package/cjs/src/scanInfo.js +0 -491
- package/cjs/src/selectModel.js +0 -46
- package/cjs/src/spinner.js +0 -93
- package/cjs/src/streamAi.js +0 -62
- package/cjs/src/typeChecker.js +0 -207
- package/cjs/src/types.js +0 -15
- package/cjs/src/uploadRelease.js +0 -112
- package/cjs/src/useStdoutDimensions.js +0 -43
- package/esm/index.js +0 -1
- package/esm/src/aiEditor.js +0 -282
- package/esm/src/auth.js +0 -42
- package/esm/src/builder.js +0 -81
- package/esm/src/commandDecorators/argMeta.js +0 -54
- package/esm/src/commandDecorators/command.js +0 -290
- package/esm/src/commandDecorators/commandMeta.js +0 -7
- package/esm/src/commandDecorators/targetMeta.js +0 -33
- package/esm/src/commandDecorators/types.js +0 -0
- package/esm/src/constants.js +0 -17
- package/esm/src/createTunnel.js +0 -26
- package/esm/src/dependencyScanner.js +0 -187
- package/esm/src/executors.js +0 -928
- package/esm/src/getCredentials.js +0 -11
- package/esm/src/getDirname.js +0 -5
- package/esm/src/getModelFileData.js +0 -33
- package/esm/src/getRelatedCnsts.js +0 -221
- package/esm/src/guideline.js +0 -0
- package/esm/src/linter.js +0 -205
- package/esm/src/prompter.js +0 -51
- package/esm/src/scanInfo.js +0 -455
- package/esm/src/selectModel.js +0 -13
- package/esm/src/typeChecker.js +0 -174
- package/esm/src/types.js +0 -0
- package/index.d.ts +0 -1
- package/src/aiEditor.d.ts +0 -50
- package/src/auth.d.ts +0 -9
- package/src/builder.d.ts +0 -18
- package/src/capacitorApp.d.ts +0 -39
- package/src/commandDecorators/argMeta.d.ts +0 -67
- package/src/commandDecorators/command.d.ts +0 -2
- package/src/commandDecorators/commandMeta.d.ts +0 -2
- package/src/commandDecorators/helpFormatter.d.ts +0 -3
- package/src/commandDecorators/index.d.ts +0 -6
- package/src/commandDecorators/targetMeta.d.ts +0 -19
- package/src/commandDecorators/types.d.ts +0 -1
- package/src/constants.d.ts +0 -26
- package/src/createTunnel.d.ts +0 -8
- package/src/dependencyScanner.d.ts +0 -23
- package/src/executors.d.ts +0 -296
- package/src/extractDeps.d.ts +0 -7
- package/src/fileEditor.d.ts +0 -16
- package/src/getCredentials.d.ts +0 -12
- package/src/getDirname.d.ts +0 -1
- package/src/getModelFileData.d.ts +0 -16
- package/src/getRelatedCnsts.d.ts +0 -53
- package/src/guideline.d.ts +0 -19
- package/src/index.d.ts +0 -23
- package/src/linter.d.ts +0 -109
- package/src/prompter.d.ts +0 -14
- package/src/scanInfo.d.ts +0 -82
- package/src/selectModel.d.ts +0 -1
- package/src/spinner.d.ts +0 -20
- package/src/streamAi.d.ts +0 -6
- package/src/typeChecker.d.ts +0 -52
- package/src/types.d.ts +0 -31
- package/src/uploadRelease.d.ts +0 -10
- package/src/useStdoutDimensions.d.ts +0 -1
package/README.ko.md
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# @akanjs/devkit
|
|
2
|
+
|
|
3
|
+
[문서](https://akanjs.com/docs) | [npm](https://www.npmjs.com/package/@akanjs/devkit) | [런타임](https://www.npmjs.com/package/akanjs)
|
|
4
|
+
|
|
5
|
+
Akan.js를 위한 development tooling primitive입니다.
|
|
6
|
+
|
|
7
|
+
`@akanjs/devkit`은 Akan CLI와 프레임워크 수준 tooling에서 사용하는 build runner, workspace
|
|
8
|
+
executor, config loader, dependency scanner, frontend artifact builder, command decorator, prompt,
|
|
9
|
+
release helper를 담고 있습니다. 애플리케이션 런타임 코드가 아니라 tooling과 package author를 위한
|
|
10
|
+
패키지입니다.
|
|
11
|
+
|
|
12
|
+
## 설치
|
|
13
|
+
|
|
14
|
+
대부분의 사용자는 devkit 대신 CLI를 설치하면 됩니다.
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
bun install -g @akanjs/cli@latest
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Akan-aware tooling을 직접 만들 때만 `@akanjs/devkit`을 설치하세요.
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
bun add -d @akanjs/devkit
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## 사용 예시
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
import { ApplicationBuildRunner, WorkspaceExecutor } from "@akanjs/devkit";
|
|
30
|
+
|
|
31
|
+
const workspace = WorkspaceExecutor.fromRoot();
|
|
32
|
+
const app = await workspace.getApp("my-app");
|
|
33
|
+
const runner = new ApplicationBuildRunner(app);
|
|
34
|
+
|
|
35
|
+
await runner.build();
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## 제공하는 것
|
|
39
|
+
|
|
40
|
+
- Workspace, app, library, package, module executor.
|
|
41
|
+
- `akan.config.ts` 로딩과 정규화.
|
|
42
|
+
- Application build, typecheck, SSR, CSR, release runner.
|
|
43
|
+
- Dependency scanning과 package metadata 생성 helper.
|
|
44
|
+
- Frontend build transform과 RSC/SSR artifact builder.
|
|
45
|
+
- `@akanjs/cli`가 사용하는 command/script decorator.
|
|
46
|
+
- AI prompt, guideline, code-generation 지원 utility.
|
|
47
|
+
- Capacitor와 mobile release helper.
|
|
48
|
+
|
|
49
|
+
## 패키지 경계
|
|
50
|
+
|
|
51
|
+
- 런타임 코드는 `akanjs`에서 import해야 합니다. `AppConfig`, `LibConfig`, `AppInfo`, `LibInfo` 같은
|
|
52
|
+
공유 config 타입도 `akanjs`에서 가져옵니다.
|
|
53
|
+
- CLI 사용자는 `@akanjs/cli`를 설치하면 됩니다. published CLI는 이 devkit을 내부에 번들링합니다.
|
|
54
|
+
- Tooling author는 Akan workspace introspection이나 build API가 필요할 때 `@akanjs/devkit`을 직접
|
|
55
|
+
import할 수 있습니다.
|
|
56
|
+
|
|
57
|
+
## 요구사항
|
|
58
|
+
|
|
59
|
+
- [Bun](https://bun.sh) `>=1.3.13`
|
|
60
|
+
- TypeScript
|
|
61
|
+
- Optional peer는 Capacitor integration처럼 해당 기능을 사용할 때만 필요합니다.
|
|
62
|
+
|
|
63
|
+
## 라이선스
|
|
64
|
+
|
|
65
|
+
MIT
|
package/README.md
CHANGED
|
@@ -1,7 +1,63 @@
|
|
|
1
|
-
|
|
2
|
-
>
|
|
3
|
-
> This package is under maintenance for existing users, but we recommend using Akanjs v2 for new projects.
|
|
4
|
-
>
|
|
5
|
-
> For the unified and latest version package, please visit [akanjs](https://www.npmjs.com/package/akanjs).
|
|
1
|
+
# @akanjs/devkit
|
|
6
2
|
|
|
7
|
-
|
|
3
|
+
[Docs](https://akanjs.com/docs) | [npm](https://www.npmjs.com/package/@akanjs/devkit) | [Runtime](https://www.npmjs.com/package/akanjs)
|
|
4
|
+
|
|
5
|
+
Development tooling primitives for Akan.js.
|
|
6
|
+
|
|
7
|
+
`@akanjs/devkit` contains the build runners, workspace executors, config loaders, dependency scanners,
|
|
8
|
+
frontend artifact builders, command decorators, prompts, and release helpers used by the Akan CLI and by
|
|
9
|
+
framework-level tooling. It is intended for tools and package authors, not for application runtime code.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
Most users should install the CLI instead:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
bun install -g @akanjs/cli@latest
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Install `@akanjs/devkit` directly only when building Akan-aware tooling:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
bun add -d @akanjs/devkit
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
import { ApplicationBuildRunner, WorkspaceExecutor } from "@akanjs/devkit";
|
|
29
|
+
|
|
30
|
+
const workspace = WorkspaceExecutor.fromRoot();
|
|
31
|
+
const app = await workspace.getApp("my-app");
|
|
32
|
+
const runner = new ApplicationBuildRunner(app);
|
|
33
|
+
|
|
34
|
+
await runner.build();
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## What It Provides
|
|
38
|
+
|
|
39
|
+
- Workspace, app, library, package, and module executors.
|
|
40
|
+
- `akan.config.ts` loading and normalization.
|
|
41
|
+
- Application build, typecheck, SSR, CSR, and release runners.
|
|
42
|
+
- Dependency scanning and package metadata generation helpers.
|
|
43
|
+
- Frontend build transforms and RSC/SSR artifact builders.
|
|
44
|
+
- Command/script decorators used by `@akanjs/cli`.
|
|
45
|
+
- AI prompt, guideline, and code-generation support utilities.
|
|
46
|
+
- Capacitor and mobile release helpers.
|
|
47
|
+
|
|
48
|
+
## Package Boundary
|
|
49
|
+
|
|
50
|
+
- Runtime code should import from `akanjs`, including shared config types such as `AppConfig`, `LibConfig`,
|
|
51
|
+
`AppInfo`, and `LibInfo`.
|
|
52
|
+
- CLI users should install `@akanjs/cli`; the published CLI bundles this devkit internally.
|
|
53
|
+
- Tooling authors can import `@akanjs/devkit` directly when they need Akan workspace introspection or build APIs.
|
|
54
|
+
|
|
55
|
+
## Requirements
|
|
56
|
+
|
|
57
|
+
- [Bun](https://bun.sh) `>=1.3.13`
|
|
58
|
+
- TypeScript
|
|
59
|
+
- Optional peers are only needed for the features that use them, such as Capacitor integration.
|
|
60
|
+
|
|
61
|
+
## License
|
|
62
|
+
|
|
63
|
+
MIT
|
package/aiEditor.ts
ADDED
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import { input, select } from "@inquirer/prompts";
|
|
2
|
+
import {
|
|
3
|
+
AIMessage,
|
|
4
|
+
type BaseMessage,
|
|
5
|
+
HumanMessage,
|
|
6
|
+
mapChatMessagesToStoredMessages,
|
|
7
|
+
mapStoredMessagesToChatMessages,
|
|
8
|
+
type StoredMessage,
|
|
9
|
+
} from "@langchain/core/messages";
|
|
10
|
+
import { ChatDeepSeek } from "@langchain/deepseek";
|
|
11
|
+
import { ChatOpenAI } from "@langchain/openai";
|
|
12
|
+
import { Logger } from "akanjs/common";
|
|
13
|
+
import chalk from "chalk";
|
|
14
|
+
|
|
15
|
+
import { getAkanGlobalConfig, setAkanGlobalConfig } from "./auth";
|
|
16
|
+
import type { Executor, WorkspaceExecutor } from "./executors";
|
|
17
|
+
import { Spinner } from "./spinner";
|
|
18
|
+
import type { FileContent } from "./types";
|
|
19
|
+
|
|
20
|
+
const MAX_ASK_TRY = 300;
|
|
21
|
+
|
|
22
|
+
export const supportedLlmModels = ["deepseek-chat", "deepseek-reasoner"] as const;
|
|
23
|
+
export type SupportedLlmModel = (typeof supportedLlmModels)[number];
|
|
24
|
+
|
|
25
|
+
interface EditOptions {
|
|
26
|
+
onReasoning?: (reasoning: string) => void;
|
|
27
|
+
onChunk?: (chunk: string) => void;
|
|
28
|
+
maxTry?: number;
|
|
29
|
+
validate?: string[];
|
|
30
|
+
approve?: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export class AiSession {
|
|
34
|
+
static #cacheDir = "node_modules/.cache/akan/aiSession";
|
|
35
|
+
static #chat: ChatDeepSeek | ChatOpenAI | null = null;
|
|
36
|
+
static async init({ temperature = 0, useExisting = true }: { temperature?: number; useExisting?: boolean } = {}) {
|
|
37
|
+
if (useExisting) {
|
|
38
|
+
const llmConfig = await AiSession.getLlmConfig();
|
|
39
|
+
if (llmConfig) {
|
|
40
|
+
AiSession.#setChatModel(llmConfig.model, llmConfig.apiKey);
|
|
41
|
+
Logger.rawLog(chalk.dim(`🤖akan editor uses existing LLM config (${llmConfig.model})`));
|
|
42
|
+
return AiSession;
|
|
43
|
+
}
|
|
44
|
+
} else Logger.rawLog(chalk.yellow("🤖akan-editor is not initialized. LLM configuration should be set first."));
|
|
45
|
+
|
|
46
|
+
const llmConfig = await AiSession.#requestLlmConfig();
|
|
47
|
+
const { model, apiKey } = llmConfig;
|
|
48
|
+
|
|
49
|
+
await AiSession.#validateApiKey(model, apiKey);
|
|
50
|
+
const session = AiSession.#setChatModel(model, apiKey, { temperature });
|
|
51
|
+
await session.setLlmConfig({ model, apiKey });
|
|
52
|
+
return session;
|
|
53
|
+
}
|
|
54
|
+
static #setChatModel(model: SupportedLlmModel, apiKey: string, { temperature = 0 }: { temperature?: number } = {}) {
|
|
55
|
+
AiSession.#chat = new ChatDeepSeek({
|
|
56
|
+
modelName: model,
|
|
57
|
+
temperature,
|
|
58
|
+
streaming: true,
|
|
59
|
+
apiKey,
|
|
60
|
+
// configuration: { baseURL: "https://api.deepseek.com/v1", apiKey },
|
|
61
|
+
});
|
|
62
|
+
return AiSession;
|
|
63
|
+
}
|
|
64
|
+
static async getLlmConfig() {
|
|
65
|
+
const akanConfig = await getAkanGlobalConfig();
|
|
66
|
+
return akanConfig.llm ?? null;
|
|
67
|
+
}
|
|
68
|
+
static async setLlmConfig(llmConfig: { model: SupportedLlmModel; apiKey: string } | null) {
|
|
69
|
+
const akanConfig = await getAkanGlobalConfig();
|
|
70
|
+
akanConfig.llm = llmConfig;
|
|
71
|
+
await setAkanGlobalConfig(akanConfig);
|
|
72
|
+
return AiSession;
|
|
73
|
+
}
|
|
74
|
+
static async #requestLlmConfig() {
|
|
75
|
+
const model = await select<SupportedLlmModel>({ message: "Select a LLM model", choices: supportedLlmModels });
|
|
76
|
+
const apiKey = await input({ message: "Enter your API key" });
|
|
77
|
+
return { model, apiKey };
|
|
78
|
+
}
|
|
79
|
+
static async #validateApiKey(modelName: SupportedLlmModel, apiKey: string) {
|
|
80
|
+
const spinner = new Spinner("Validating LLM API key...", { prefix: `🤖akan-editor` }).start();
|
|
81
|
+
const chat = new ChatOpenAI({
|
|
82
|
+
modelName,
|
|
83
|
+
temperature: 0,
|
|
84
|
+
configuration: { baseURL: "https://api.deepseek.com/v1", apiKey },
|
|
85
|
+
});
|
|
86
|
+
try {
|
|
87
|
+
await chat.invoke("Hi, and just say 'ok'");
|
|
88
|
+
spinner.succeed("LLM API key is valid");
|
|
89
|
+
return true;
|
|
90
|
+
} catch (error) {
|
|
91
|
+
spinner.fail(
|
|
92
|
+
chalk.red(
|
|
93
|
+
`LLM API key is invalid. Please check your API key and try again. You can set it again by running "akan set-llm" or reset by running "akan reset-llm"`,
|
|
94
|
+
),
|
|
95
|
+
);
|
|
96
|
+
throw error;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
static async clearCache(workspaceRoot: string) {
|
|
100
|
+
const cacheDir = `${workspaceRoot}/${AiSession.#cacheDir}`;
|
|
101
|
+
await Bun.$`rm -rf ${cacheDir}`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
messageHistory: BaseMessage[] = [];
|
|
105
|
+
readonly sessionKey: string;
|
|
106
|
+
isCacheLoaded: boolean = false;
|
|
107
|
+
workspace: WorkspaceExecutor;
|
|
108
|
+
constructor(
|
|
109
|
+
type: string,
|
|
110
|
+
{ workspace, cacheKey, isContinued }: { workspace: WorkspaceExecutor; cacheKey?: string; isContinued?: boolean },
|
|
111
|
+
) {
|
|
112
|
+
this.workspace = workspace;
|
|
113
|
+
this.sessionKey = `${type}${cacheKey ? `-${cacheKey}` : ""}`;
|
|
114
|
+
if (isContinued) this.#cacheLoadPromise = this.#loadCache();
|
|
115
|
+
}
|
|
116
|
+
#cacheLoadPromise: Promise<void> | null = null;
|
|
117
|
+
async #loadCache() {
|
|
118
|
+
const cacheFile = `${AiSession.#cacheDir}/${this.sessionKey}.json`;
|
|
119
|
+
const isCacheExists = await this.workspace.exists(cacheFile);
|
|
120
|
+
if (isCacheExists)
|
|
121
|
+
this.messageHistory = mapStoredMessagesToChatMessages(
|
|
122
|
+
(await this.workspace.readJson(cacheFile)) as StoredMessage[],
|
|
123
|
+
);
|
|
124
|
+
else this.messageHistory = [];
|
|
125
|
+
this.isCacheLoaded = isCacheExists;
|
|
126
|
+
}
|
|
127
|
+
async #saveCache() {
|
|
128
|
+
const cacheFilePath = `${AiSession.#cacheDir}/${this.sessionKey}.json`;
|
|
129
|
+
await this.workspace.writeJson(cacheFilePath, mapChatMessagesToStoredMessages(this.messageHistory));
|
|
130
|
+
}
|
|
131
|
+
async ask(
|
|
132
|
+
question: string,
|
|
133
|
+
{
|
|
134
|
+
onReasoning = (reasoning: string) => {
|
|
135
|
+
Logger.raw(chalk.dim(reasoning));
|
|
136
|
+
},
|
|
137
|
+
onChunk = (chunk: string) => {
|
|
138
|
+
Logger.raw(chunk);
|
|
139
|
+
},
|
|
140
|
+
}: EditOptions = {},
|
|
141
|
+
): Promise<{ content: string; messageHistory: BaseMessage[] }> {
|
|
142
|
+
if (!AiSession.#chat) await AiSession.init();
|
|
143
|
+
if (this.#cacheLoadPromise) await this.#cacheLoadPromise;
|
|
144
|
+
|
|
145
|
+
if (!AiSession.#chat) throw new Error("Failed to initialize the AI session");
|
|
146
|
+
const loader = new Spinner(`${AiSession.#chat.model} is thinking...`, {
|
|
147
|
+
prefix: `🤖akan-editor`,
|
|
148
|
+
}).start();
|
|
149
|
+
try {
|
|
150
|
+
const humanMessage = new HumanMessage(question);
|
|
151
|
+
this.messageHistory.push(humanMessage);
|
|
152
|
+
const stream = await AiSession.#chat.stream(this.messageHistory);
|
|
153
|
+
let reasoningResponse = "",
|
|
154
|
+
fullResponse = "",
|
|
155
|
+
tokenIdx = 0;
|
|
156
|
+
for await (const chunk of stream) {
|
|
157
|
+
if (loader.isSpinning()) loader.succeed(`${AiSession.#chat.model} responded`);
|
|
158
|
+
|
|
159
|
+
if (!fullResponse.length) {
|
|
160
|
+
const reasoningContent = (chunk.additional_kwargs as { reasoning_content?: string }).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
|
+
}
|
|
170
|
+
|
|
171
|
+
const content = chunk.content;
|
|
172
|
+
if (typeof content === "string") {
|
|
173
|
+
fullResponse += content;
|
|
174
|
+
onChunk(content); // Send individual chunks to callback
|
|
175
|
+
}
|
|
176
|
+
tokenIdx++;
|
|
177
|
+
}
|
|
178
|
+
fullResponse += "\n";
|
|
179
|
+
onChunk("\n");
|
|
180
|
+
this.messageHistory.push(new AIMessage(fullResponse));
|
|
181
|
+
return { content: fullResponse, messageHistory: this.messageHistory };
|
|
182
|
+
} catch (error) {
|
|
183
|
+
loader.fail(`${AiSession.#chat.model} failed to respond`);
|
|
184
|
+
throw new Error("Failed to stream response");
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
async edit(question: string, { onChunk, onReasoning, maxTry = MAX_ASK_TRY, validate, approve }: EditOptions = {}) {
|
|
188
|
+
for (let tryCount = 0; tryCount < maxTry; tryCount++) {
|
|
189
|
+
let response = await this.ask(question, { onChunk, onReasoning });
|
|
190
|
+
if (validate?.length && tryCount === 0) {
|
|
191
|
+
const validateQuestion = `Double check if the response meets the requirements and conditions, and follow the instructions. If not, rewrite it.
|
|
192
|
+
${validate.map((v) => `- ${v}`).join("\n")}`;
|
|
193
|
+
response = await this.ask(validateQuestion, { onChunk, onReasoning });
|
|
194
|
+
}
|
|
195
|
+
const isConfirmed = approve
|
|
196
|
+
? true
|
|
197
|
+
: await select({
|
|
198
|
+
message: "Do you want to edit the response?",
|
|
199
|
+
choices: [
|
|
200
|
+
{ name: "✅ Yes, confirm and apply this result", value: true },
|
|
201
|
+
{ name: "🔄 No, I want to edit it more", value: false },
|
|
202
|
+
],
|
|
203
|
+
});
|
|
204
|
+
if (isConfirmed) {
|
|
205
|
+
await this.#saveCache();
|
|
206
|
+
return response.content;
|
|
207
|
+
}
|
|
208
|
+
question = await input({ message: "What do you want to change?" });
|
|
209
|
+
tryCount++;
|
|
210
|
+
}
|
|
211
|
+
throw new Error("Failed to edit");
|
|
212
|
+
}
|
|
213
|
+
async editTypescript(question: string, options: EditOptions = {}) {
|
|
214
|
+
const content = await this.edit(question, options);
|
|
215
|
+
return this.#getTypescriptCode(content);
|
|
216
|
+
}
|
|
217
|
+
#getTypescriptCode(content: string) {
|
|
218
|
+
//! will be deprecated
|
|
219
|
+
const code = /```(typescript|tsx)([\s\S]*?)```/.exec(content);
|
|
220
|
+
// 2번째로 해야 반환되는데 모르겟음 아무튼 일단 이렇게 함.
|
|
221
|
+
|
|
222
|
+
return code?.[2] ?? content;
|
|
223
|
+
// return code ? code[1] : content;
|
|
224
|
+
}
|
|
225
|
+
addToolMessgaes(messages: { type: string; content: string }[]) {
|
|
226
|
+
// const toolMessages = messages.map(
|
|
227
|
+
// (message) => new ToolMessage({ content: message.content, tool_call_id: message.type })
|
|
228
|
+
// );
|
|
229
|
+
const toolMessages = messages.map((message) => new HumanMessage(message.content));
|
|
230
|
+
this.messageHistory.push(...toolMessages);
|
|
231
|
+
return this;
|
|
232
|
+
}
|
|
233
|
+
async writeTypescripts(question: string, executor: Executor, options: EditOptions = {}) {
|
|
234
|
+
const content = await this.edit(question, options);
|
|
235
|
+
const writes = this.#getTypescriptCodes(content);
|
|
236
|
+
for (const write of writes) await executor.writeFile(write.filePath, write.content);
|
|
237
|
+
return await this.#tryFixTypescripts(writes, executor, options);
|
|
238
|
+
}
|
|
239
|
+
async #editTypescripts(question: string, options: EditOptions = {}) {
|
|
240
|
+
const content = await this.edit(question, options);
|
|
241
|
+
return this.#getTypescriptCodes(content);
|
|
242
|
+
}
|
|
243
|
+
async #tryFixTypescripts(writes: FileContent[], executor: Executor, options: EditOptions = {}) {
|
|
244
|
+
const MAX_EDIT_TRY = 5;
|
|
245
|
+
for (let tryCount = 0; tryCount < MAX_EDIT_TRY; tryCount++) {
|
|
246
|
+
const loader = new Spinner(`Type checking and linting...`, { prefix: `🤖akan-editor` }).start();
|
|
247
|
+
const fileChecks = await Promise.all(
|
|
248
|
+
writes.map(async ({ filePath }) => {
|
|
249
|
+
const typeCheckResult = executor.typeCheck(filePath);
|
|
250
|
+
const lintResult = await executor.lint(filePath);
|
|
251
|
+
const needFix = !!typeCheckResult.fileErrors.length || !!lintResult.errors.length;
|
|
252
|
+
return { filePath, typeCheckResult, lintResult, needFix };
|
|
253
|
+
}),
|
|
254
|
+
);
|
|
255
|
+
const needFix = fileChecks.some((fileCheck) => fileCheck.needFix);
|
|
256
|
+
if (needFix) {
|
|
257
|
+
loader.fail("Type checking and linting has some errors, try to fix them");
|
|
258
|
+
fileChecks.forEach((fileCheck) => {
|
|
259
|
+
Logger.rawLog(
|
|
260
|
+
`TypeCheck Result \n${fileCheck.typeCheckResult.message}\nLint Result \n${fileCheck.lintResult.message}`,
|
|
261
|
+
);
|
|
262
|
+
this.addToolMessgaes([
|
|
263
|
+
{ type: "typescript", content: fileCheck.typeCheckResult.message },
|
|
264
|
+
{ type: "eslint", content: fileCheck.lintResult.message },
|
|
265
|
+
]);
|
|
266
|
+
});
|
|
267
|
+
writes = await this.#editTypescripts("Fix the typescript and eslint errors", {
|
|
268
|
+
...options,
|
|
269
|
+
validate: undefined,
|
|
270
|
+
approve: true,
|
|
271
|
+
});
|
|
272
|
+
for (const write of writes) await executor.writeFile(write.filePath, write.content);
|
|
273
|
+
} else {
|
|
274
|
+
loader.succeed("Type checking and linting has no errors");
|
|
275
|
+
return writes;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
throw new Error("Failed to create scalar");
|
|
279
|
+
}
|
|
280
|
+
#getTypescriptCodes(text: string): FileContent[] {
|
|
281
|
+
const codes = text.match(/```(typescript|tsx)([\s\S]*?)```/g);
|
|
282
|
+
if (!codes) return [];
|
|
283
|
+
const result = codes.map((code) => {
|
|
284
|
+
const content = /```(typescript|tsx)([\s\S]*?)```/.exec(code)?.[2];
|
|
285
|
+
if (!content) return null;
|
|
286
|
+
const filePath = /\/\/ File: (.*?)(?:\n|$)/.exec(content)?.[1]?.trim();
|
|
287
|
+
if (!filePath) return null;
|
|
288
|
+
const contentWithoutFilepath = content.replace(`// File: ${filePath}\n`, "").trim();
|
|
289
|
+
return { filePath, content: contentWithoutFilepath };
|
|
290
|
+
});
|
|
291
|
+
return result.filter((code) => code !== null) as FileContent[];
|
|
292
|
+
}
|
|
293
|
+
async editMarkdown(request: string, options: EditOptions = {}) {
|
|
294
|
+
const content = await this.edit(request, options);
|
|
295
|
+
return this.#getMarkdownContent(content);
|
|
296
|
+
}
|
|
297
|
+
#getMarkdownContent(text: string) {
|
|
298
|
+
const searchText = "```markdown";
|
|
299
|
+
const firstIndex = text.indexOf("```markdown");
|
|
300
|
+
const lastIndex = text.lastIndexOf("```");
|
|
301
|
+
if (firstIndex === -1) return text;
|
|
302
|
+
else return text.slice(firstIndex + searchText.length, lastIndex).trim();
|
|
303
|
+
}
|
|
304
|
+
}
|