@akanjs/devkit 2.2.1 → 2.2.2-rc.0

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/aiEditor.ts CHANGED
@@ -19,11 +19,16 @@ import type { FileContent } from "./types";
19
19
 
20
20
  const MAX_ASK_TRY = 300;
21
21
 
22
- export const supportedLlmModels = [
23
- "deepseek-chat",
24
- "deepseek-reasoner",
25
- ] as const;
22
+ const deepSeekLlmModels = ["deepseek-chat", "deepseek-reasoner"] as const;
23
+
24
+ const openAiLlmModels = ["gpt-5.5", "gpt-4.1", "gpt-4.1-mini", "gpt-4o", "gpt-4o-mini"] as const;
25
+
26
+ export const supportedLlmModels = [...deepSeekLlmModels, ...openAiLlmModels] as const;
26
27
  export type SupportedLlmModel = (typeof supportedLlmModels)[number];
28
+ type OpenAiLlmModel = (typeof openAiLlmModels)[number];
29
+
30
+ const isOpenAiLlmModel = (model: SupportedLlmModel): model is OpenAiLlmModel =>
31
+ openAiLlmModels.includes(model as OpenAiLlmModel);
27
32
 
28
33
  interface EditOptions {
29
34
  onReasoning?: (reasoning: string) => void;
@@ -102,19 +107,31 @@ export class AiSession {
102
107
  await session.setLlmConfig({ model, apiKey });
103
108
  return session;
104
109
  }
105
- static #setChatModel(
110
+ static #setChatModel(model: SupportedLlmModel, apiKey: string, { temperature = 0 }: { temperature?: number } = {}) {
111
+ AiSession.#chat = AiSession.#createChatModel(model, apiKey, {
112
+ temperature,
113
+ streaming: true,
114
+ });
115
+ return AiSession;
116
+ }
117
+ static #createChatModel(
106
118
  model: SupportedLlmModel,
107
119
  apiKey: string,
108
- { temperature = 0 }: { temperature?: number } = {},
120
+ { temperature = 0, streaming = false }: { temperature?: number; streaming?: boolean } = {},
109
121
  ) {
110
- AiSession.#chat = new ChatDeepSeek({
122
+ if (isOpenAiLlmModel(model))
123
+ return new ChatOpenAI({
124
+ modelName: model,
125
+ temperature,
126
+ streaming,
127
+ openAIApiKey: apiKey,
128
+ });
129
+ return new ChatDeepSeek({
111
130
  modelName: model,
112
131
  temperature,
113
- streaming: true,
132
+ streaming,
114
133
  apiKey,
115
- // configuration: { baseURL: "https://api.deepseek.com/v1", apiKey },
116
134
  });
117
- return AiSession;
118
135
  }
119
136
  static async getLlmConfig() {
120
137
  return await GlobalConfig.getLlmConfig();
@@ -137,11 +154,7 @@ export class AiSession {
137
154
  const spinner = new Spinner("Validating LLM API key...", {
138
155
  prefix: `🤖akan-editor`,
139
156
  }).start();
140
- const chat = new ChatOpenAI({
141
- modelName,
142
- temperature: 0,
143
- configuration: { baseURL: "https://api.deepseek.com/v1", apiKey },
144
- });
157
+ const chat = AiSession.#createChatModel(modelName, apiKey);
145
158
  try {
146
159
  await chat.invoke("Hi, and just say 'ok'");
147
160
  spinner.succeed("LLM API key is valid");
package/cloud/cloudApi.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { Workspace } from "../commandDecorators";
2
- import { type AccessToken, type AccessTokenDto, akanCloudHost, type HostConfig } from "./constants";
2
+ import type { AccessToken, AccessTokenDto, HostConfig } from "./constants";
3
3
  import { GlobalConfig } from "./globalConfig";
4
4
 
5
5
  class HttpClient {
@@ -17,7 +17,7 @@ class HttpClient {
17
17
  ...headers,
18
18
  },
19
19
  });
20
- return response.json();
20
+ return await response.json();
21
21
  }
22
22
  async getFile(url: string, localPath: string, headers?: Record<string, string>): Promise<void> {
23
23
  const response = await fetch(`${this.baseUrl}${url}`, {
@@ -35,7 +35,7 @@ class HttpClient {
35
35
  ? { ...this.headers, ...headers }
36
36
  : { "Content-Type": "application/json", ...this.headers, ...headers },
37
37
  });
38
- return response.json();
38
+ return await response.json();
39
39
  }
40
40
  setHeaders(headers: Record<string, string>) {
41
41
  Object.assign(this.headers, headers);
@@ -47,6 +47,8 @@ export class CloudApi {
47
47
  readonly #api: HttpClient;
48
48
  #accessToken: AccessToken | null = null;
49
49
  #workspace: Workspace;
50
+ host: string;
51
+ url: string;
50
52
 
51
53
  static async fromHost(workspace: Workspace, host?: string) {
52
54
  const hostConfig = await GlobalConfig.getHostConfig(host);
@@ -54,9 +56,10 @@ export class CloudApi {
54
56
  }
55
57
  constructor(workspace: Workspace, hostConfig: HostConfig) {
56
58
  this.#workspace = workspace;
57
- const host = akanCloudHost;
58
- this.#api = new HttpClient(`${host}/api`);
59
59
  this.#accessToken = hostConfig.auth?.accessToken ?? null;
60
+ this.host = hostConfig.host;
61
+ this.url = `${this.host}/api`;
62
+ this.#api = new HttpClient(this.url);
60
63
  if (this.#accessToken && !GlobalConfig.needRefreshToken(this.#accessToken))
61
64
  this.#api.setHeaders({
62
65
  Authorization: `Bearer ${this.#accessToken.jwt}`,
@@ -64,6 +67,7 @@ export class CloudApi {
64
67
  }
65
68
 
66
69
  async uploadEnv(devProjectId: string, file: File): Promise<boolean> {
70
+ await this.#ensureAccessTokenLive();
67
71
  const formData = new FormData();
68
72
  formData.append("devProjectId", devProjectId);
69
73
  formData.append("file", file);
@@ -71,16 +75,13 @@ export class CloudApi {
71
75
  return data;
72
76
  }
73
77
  async downloadEnv(devProjectId: string): Promise<unknown> {
78
+ await this.#ensureAccessTokenLive();
74
79
  const localPath = `${this.#workspace.workspaceRoot}/local/env.tar`;
75
80
  await this.#api.getFile(`/downloadEnv/${devProjectId}`, localPath);
76
81
  return localPath;
77
82
  }
78
83
  async getRemoteAuthToken(remoteId: string): Promise<AccessToken | null> {
79
84
  try {
80
- if (this.#accessToken) {
81
- if (GlobalConfig.needRefreshToken(this.#accessToken)) return await this.#refreshAuthToken();
82
- else return await this.#refreshAuthToken();
83
- }
84
85
  const accessToken = await this.#api.get<AccessTokenDto>(`/getRemoteAuthToken/${remoteId}`);
85
86
  this.#accessToken = GlobalConfig.toAccessToken(accessToken);
86
87
  this.#api.setHeaders({
@@ -91,13 +92,20 @@ export class CloudApi {
91
92
  return null;
92
93
  }
93
94
  }
94
- async #refreshAuthToken(): Promise<AccessToken> {
95
+ async #ensureAccessTokenLive({
96
+ allowUnauthorized = false,
97
+ }: {
98
+ allowUnauthorized?: boolean;
99
+ } = {}): Promise<AccessToken> {
100
+ if (!this.#accessToken) throw new Error("No access token");
101
+ const needRefresh = GlobalConfig.needRefreshToken(this.#accessToken);
102
+ if (!needRefresh) return this.#accessToken;
95
103
  const refreshToken = this.#accessToken?.refreshToken;
96
104
  if (!refreshToken) throw new Error("No refresh token");
97
105
  return await this.refreshAuthToken(refreshToken);
98
106
  }
99
107
  async refreshAuthToken(refreshToken: string): Promise<AccessToken> {
100
- const response = await this.#api.post<AccessTokenDto>(`/refreshRemoteAuthToken`, { refreshToken });
108
+ const response = await this.#api.post<AccessTokenDto>(`/refreshAuthToken`, { refreshToken });
101
109
  this.#accessToken = GlobalConfig.toAccessToken(response);
102
110
  this.#api.setHeaders({ Authorization: `Bearer ${this.#accessToken.jwt}` });
103
111
  return this.#accessToken;
@@ -1,24 +1,25 @@
1
1
  import type { Dayjs } from "dayjs";
2
+ import { GlobalConfig } from "..";
2
3
  import type { SupportedLlmModel } from "../aiEditor";
3
4
 
4
5
  export const basePath = `${Bun.env.HOME ?? Bun.env.USERPROFILE}/.akan`;
5
6
  export const configPath = `${basePath}/config.json`;
6
- export const akanCloudHost = process.env.USE_AKANJS_PKGS === "true" ? "http://localhost" : "https://cloud.akanjs.com";
7
- export const akanCloudUrl = `${akanCloudHost}${process.env.USE_AKANJS_PKGS === "true" ? ":8282" : ""}/api`;
8
7
 
9
8
  export interface HostConfig {
9
+ host: string;
10
10
  auth?: {
11
11
  accessToken?: AccessToken;
12
12
  self?: { id: string; nickname: string };
13
13
  };
14
14
  }
15
15
  export interface HostConfigDto {
16
+ host: string;
16
17
  auth?: {
17
18
  accessToken?: AccessTokenDto;
18
19
  self?: { id: string; nickname: string };
19
20
  };
20
21
  }
21
- export const defaultHostConfig: HostConfig = {};
22
+ export const getDefaultHostConfig = (host = GlobalConfig.akanCloudHost): HostConfig => ({ host });
22
23
  export interface RemoteEnvServerConfig {
23
24
  host: string;
24
25
  username?: string;
@@ -5,17 +5,20 @@ import {
5
5
  type AccessToken,
6
6
  type AccessTokenDto,
7
7
  type AkanGlobalConfig,
8
- akanCloudHost,
9
8
  basePath,
10
9
  configPath,
11
10
  defaultAkanGlobalConfig,
12
- defaultHostConfig,
11
+ getDefaultHostConfig,
13
12
  type HostConfig,
14
13
  type HostConfigDto,
15
14
  type RemoteEnvServerConfig,
16
15
  } from "./constants";
17
16
 
18
17
  export class GlobalConfig {
18
+ static akanCloudHost =
19
+ process.env.USE_AKANJS_PKGS === "true"
20
+ ? `http://localhost:${process.env.CLOUD_HOST_PORT ?? 8283}`
21
+ : "https://cloud.akanjs.com";
19
22
  static async #getAkanGlobalConfig(): Promise<AkanGlobalConfig> {
20
23
  const exists = await FileSys.fileExists(configPath);
21
24
  const akanConfig = exists ? await FileSys.readJson<Partial<AkanGlobalConfig>>(configPath) : {};
@@ -30,13 +33,13 @@ export class GlobalConfig {
30
33
  await mkdir(basePath, { recursive: true });
31
34
  await Bun.write(configPath, JSON.stringify(akanConfig, null, 2));
32
35
  }
33
- static async getHostConfig(host = akanCloudHost): Promise<HostConfig> {
36
+ static async getHostConfig(host = GlobalConfig.akanCloudHost): Promise<HostConfig> {
34
37
  const akanConfig = await GlobalConfig.#getAkanGlobalConfig();
35
- return GlobalConfig.toHostConfig(akanConfig.cloudHost[host] ?? defaultHostConfig);
38
+ return GlobalConfig.toHostConfig(akanConfig.cloudHost[host] ?? getDefaultHostConfig(host));
36
39
  }
37
- static async setHostConfig(host = akanCloudHost, config: HostConfig = {}) {
40
+ static async setHostConfig(config: HostConfig = getDefaultHostConfig()) {
38
41
  const akanConfig = await GlobalConfig.#getAkanGlobalConfig();
39
- akanConfig.cloudHost[host] = GlobalConfig.toHostConfigDto(config);
42
+ akanConfig.cloudHost[config.host] = GlobalConfig.toHostConfigDto(config);
40
43
  await GlobalConfig.#setAkanGlobalConfig(akanConfig);
41
44
  }
42
45
  static async getLlmConfig(): Promise<AkanGlobalConfig["llm"]> {
@@ -88,6 +91,7 @@ export class GlobalConfig {
88
91
  }
89
92
  static toHostConfigDto(hostConfig: HostConfig): HostConfigDto {
90
93
  return {
94
+ host: hostConfig.host,
91
95
  auth: {
92
96
  accessToken: hostConfig.auth?.accessToken
93
97
  ? GlobalConfig.toAccessTokenDto(hostConfig.auth.accessToken)
@@ -98,6 +102,7 @@ export class GlobalConfig {
98
102
  }
99
103
  static toHostConfig(hostConfigDto: HostConfigDto): HostConfig {
100
104
  return {
105
+ host: hostConfigDto.host,
101
106
  auth: {
102
107
  accessToken: hostConfigDto.auth?.accessToken
103
108
  ? GlobalConfig.toAccessToken(hostConfigDto.auth.accessToken)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akanjs/devkit",
3
- "version": "2.2.1",
3
+ "version": "2.2.2-rc.0",
4
4
  "sourceType": "module",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -32,7 +32,7 @@
32
32
  "@langchain/openai": "^1.4.6",
33
33
  "@tailwindcss/node": "^4.3.0",
34
34
  "@trapezedev/project": "^7.1.4",
35
- "akanjs": "2.2.1",
35
+ "akanjs": "2.2.2-rc.0",
36
36
  "chalk": "^5.6.2",
37
37
  "commander": "^14.0.3",
38
38
  "daisyui": "^5.5.20",