acmecode 1.0.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/.acmecode/config.json +6 -0
- package/README.md +124 -0
- package/dist/agent/index.js +161 -0
- package/dist/cli/bin/acmecode.js +3 -0
- package/dist/cli/package.json +25 -0
- package/dist/cli/src/index.d.ts +1 -0
- package/dist/cli/src/index.js +53 -0
- package/dist/config/index.js +92 -0
- package/dist/context/index.js +30 -0
- package/dist/core/src/agent/index.d.ts +52 -0
- package/dist/core/src/agent/index.js +476 -0
- package/dist/core/src/config/index.d.ts +83 -0
- package/dist/core/src/config/index.js +318 -0
- package/dist/core/src/context/index.d.ts +1 -0
- package/dist/core/src/context/index.js +30 -0
- package/dist/core/src/llm/provider.d.ts +27 -0
- package/dist/core/src/llm/provider.js +202 -0
- package/dist/core/src/llm/vision.d.ts +7 -0
- package/dist/core/src/llm/vision.js +37 -0
- package/dist/core/src/mcp/index.d.ts +10 -0
- package/dist/core/src/mcp/index.js +84 -0
- package/dist/core/src/prompt/anthropic.d.ts +1 -0
- package/dist/core/src/prompt/anthropic.js +32 -0
- package/dist/core/src/prompt/architect.d.ts +1 -0
- package/dist/core/src/prompt/architect.js +17 -0
- package/dist/core/src/prompt/autopilot.d.ts +1 -0
- package/dist/core/src/prompt/autopilot.js +18 -0
- package/dist/core/src/prompt/beast.d.ts +1 -0
- package/dist/core/src/prompt/beast.js +83 -0
- package/dist/core/src/prompt/gemini.d.ts +1 -0
- package/dist/core/src/prompt/gemini.js +45 -0
- package/dist/core/src/prompt/index.d.ts +18 -0
- package/dist/core/src/prompt/index.js +239 -0
- package/dist/core/src/prompt/zen.d.ts +1 -0
- package/dist/core/src/prompt/zen.js +13 -0
- package/dist/core/src/session/index.d.ts +18 -0
- package/dist/core/src/session/index.js +97 -0
- package/dist/core/src/skills/index.d.ts +6 -0
- package/dist/core/src/skills/index.js +72 -0
- package/dist/core/src/tools/batch.d.ts +2 -0
- package/dist/core/src/tools/batch.js +65 -0
- package/dist/core/src/tools/browser.d.ts +7 -0
- package/dist/core/src/tools/browser.js +86 -0
- package/dist/core/src/tools/edit.d.ts +11 -0
- package/dist/core/src/tools/edit.js +312 -0
- package/dist/core/src/tools/index.d.ts +13 -0
- package/dist/core/src/tools/index.js +980 -0
- package/dist/core/src/tools/lsp-client.d.ts +11 -0
- package/dist/core/src/tools/lsp-client.js +224 -0
- package/dist/index.js +41 -0
- package/dist/llm/provider.js +34 -0
- package/dist/mcp/index.js +84 -0
- package/dist/session/index.js +74 -0
- package/dist/skills/index.js +32 -0
- package/dist/tools/index.js +96 -0
- package/dist/tui/App.js +297 -0
- package/dist/tui/Spinner.js +16 -0
- package/dist/tui/TextInput.js +98 -0
- package/dist/tui/src/App.d.ts +11 -0
- package/dist/tui/src/App.js +1211 -0
- package/dist/tui/src/CatLogo.d.ts +10 -0
- package/dist/tui/src/CatLogo.js +99 -0
- package/dist/tui/src/OptionList.d.ts +15 -0
- package/dist/tui/src/OptionList.js +60 -0
- package/dist/tui/src/Spinner.d.ts +7 -0
- package/dist/tui/src/Spinner.js +18 -0
- package/dist/tui/src/TextInput.d.ts +28 -0
- package/dist/tui/src/TextInput.js +139 -0
- package/dist/tui/src/Tips.d.ts +2 -0
- package/dist/tui/src/Tips.js +62 -0
- package/dist/tui/src/Toast.d.ts +19 -0
- package/dist/tui/src/Toast.js +39 -0
- package/dist/tui/src/TodoItem.d.ts +7 -0
- package/dist/tui/src/TodoItem.js +21 -0
- package/dist/tui/src/i18n.d.ts +172 -0
- package/dist/tui/src/i18n.js +189 -0
- package/dist/tui/src/markdown.d.ts +6 -0
- package/dist/tui/src/markdown.js +356 -0
- package/dist/tui/src/theme.d.ts +31 -0
- package/dist/tui/src/theme.js +239 -0
- package/output.txt +0 -0
- package/package.json +44 -0
- package/packages/cli/package.json +25 -0
- package/packages/cli/src/index.ts +59 -0
- package/packages/cli/tsconfig.json +26 -0
- package/packages/core/package.json +39 -0
- package/packages/core/src/agent/index.ts +588 -0
- package/packages/core/src/config/index.ts +383 -0
- package/packages/core/src/context/index.ts +34 -0
- package/packages/core/src/llm/provider.ts +237 -0
- package/packages/core/src/llm/vision.ts +43 -0
- package/packages/core/src/mcp/index.ts +110 -0
- package/packages/core/src/prompt/anthropic.ts +32 -0
- package/packages/core/src/prompt/architect.ts +17 -0
- package/packages/core/src/prompt/autopilot.ts +18 -0
- package/packages/core/src/prompt/beast.ts +83 -0
- package/packages/core/src/prompt/gemini.ts +45 -0
- package/packages/core/src/prompt/index.ts +267 -0
- package/packages/core/src/prompt/zen.ts +13 -0
- package/packages/core/src/session/index.ts +129 -0
- package/packages/core/src/skills/index.ts +86 -0
- package/packages/core/src/tools/batch.ts +73 -0
- package/packages/core/src/tools/browser.ts +95 -0
- package/packages/core/src/tools/edit.ts +317 -0
- package/packages/core/src/tools/index.ts +1112 -0
- package/packages/core/src/tools/lsp-client.ts +303 -0
- package/packages/core/tsconfig.json +19 -0
- package/packages/tui/package.json +24 -0
- package/packages/tui/src/App.tsx +1702 -0
- package/packages/tui/src/CatLogo.tsx +134 -0
- package/packages/tui/src/OptionList.tsx +95 -0
- package/packages/tui/src/Spinner.tsx +28 -0
- package/packages/tui/src/TextInput.tsx +202 -0
- package/packages/tui/src/Tips.tsx +64 -0
- package/packages/tui/src/Toast.tsx +60 -0
- package/packages/tui/src/TodoItem.tsx +29 -0
- package/packages/tui/src/i18n.ts +203 -0
- package/packages/tui/src/markdown.ts +403 -0
- package/packages/tui/src/theme.ts +287 -0
- package/packages/tui/tsconfig.json +24 -0
- package/tsconfig.json +18 -0
- package/vscode-acmecode/.vscodeignore +11 -0
- package/vscode-acmecode/README.md +57 -0
- package/vscode-acmecode/esbuild.js +46 -0
- package/vscode-acmecode/images/button-dark.svg +5 -0
- package/vscode-acmecode/images/button-light.svg +5 -0
- package/vscode-acmecode/images/icon.png +1 -0
- package/vscode-acmecode/package-lock.json +490 -0
- package/vscode-acmecode/package.json +87 -0
- package/vscode-acmecode/src/extension.ts +128 -0
- package/vscode-acmecode/tsconfig.json +16 -0
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import { config } from "dotenv";
|
|
2
|
+
import { resolve, join } from "path";
|
|
3
|
+
import { homedir } from "os";
|
|
4
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
|
|
5
|
+
// Load environment variables from .env in project root if it exists
|
|
6
|
+
config({ quiet: true });
|
|
7
|
+
// Load environment variables from ~/.acmecode/.env if it exists
|
|
8
|
+
config({ path: resolve(homedir(), ".acmecode", ".env"), quiet: true });
|
|
9
|
+
const GLOBAL_CONFIG_DIR = resolve(homedir(), ".acmecode");
|
|
10
|
+
const GLOBAL_CONFIG_FILE = join(GLOBAL_CONFIG_DIR, "config.json");
|
|
11
|
+
function getProjectConfigDir() {
|
|
12
|
+
return resolve(process.cwd(), ".acmecode");
|
|
13
|
+
}
|
|
14
|
+
function getProjectConfigFile() {
|
|
15
|
+
return join(getProjectConfigDir(), "config.json");
|
|
16
|
+
}
|
|
17
|
+
function readJsonFile(filePath) {
|
|
18
|
+
try {
|
|
19
|
+
if (existsSync(filePath)) {
|
|
20
|
+
return JSON.parse(readFileSync(filePath, "utf-8"));
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
catch { }
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
function writeJsonFile(filePath, data) {
|
|
27
|
+
const dir = resolve(filePath, "..");
|
|
28
|
+
if (!existsSync(dir)) {
|
|
29
|
+
mkdirSync(dir, { recursive: true });
|
|
30
|
+
}
|
|
31
|
+
writeFileSync(filePath, JSON.stringify(data, null, 2), "utf-8");
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Load model config with priority: project .acmecode/config.json > global ~/.acmecode/config.json > defaults
|
|
35
|
+
*/
|
|
36
|
+
export function loadModelConfig() {
|
|
37
|
+
const projectConfig = readJsonFile(getProjectConfigFile());
|
|
38
|
+
const globalConfig = readJsonFile(GLOBAL_CONFIG_FILE);
|
|
39
|
+
// Resolve with priority: project > global > defaults
|
|
40
|
+
const provider = projectConfig?.provider || globalConfig?.provider || "extralink";
|
|
41
|
+
const model = projectConfig?.model || globalConfig?.model || "";
|
|
42
|
+
const visionProvider = projectConfig?.visionProvider || globalConfig?.visionProvider;
|
|
43
|
+
const visionModel = projectConfig?.visionModel || globalConfig?.visionModel;
|
|
44
|
+
return { provider, model, visionProvider, visionModel };
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Load language config with priority: project .acmecode/config.json > global ~/.acmecode/config.json > default "en"
|
|
48
|
+
*/
|
|
49
|
+
export function loadLangConfig() {
|
|
50
|
+
const projectConfig = readJsonFile(getProjectConfigFile());
|
|
51
|
+
if (projectConfig?.lang)
|
|
52
|
+
return projectConfig.lang;
|
|
53
|
+
const globalConfig = readJsonFile(GLOBAL_CONFIG_FILE);
|
|
54
|
+
if (globalConfig?.lang)
|
|
55
|
+
return globalConfig.lang;
|
|
56
|
+
return "en";
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Load theme config with priority: project .acmecode/config.json > global ~/.acmecode/config.json > default "dark"
|
|
60
|
+
*/
|
|
61
|
+
export function loadThemeConfig() {
|
|
62
|
+
const projectConfig = readJsonFile(getProjectConfigFile());
|
|
63
|
+
if (projectConfig?.theme)
|
|
64
|
+
return projectConfig.theme;
|
|
65
|
+
const globalConfig = readJsonFile(GLOBAL_CONFIG_FILE);
|
|
66
|
+
if (globalConfig?.theme)
|
|
67
|
+
return globalConfig.theme;
|
|
68
|
+
return "dark";
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Load reasoning level config with priority: project .acmecode/config.json > global ~/.acmecode/config.json > default "medium"
|
|
72
|
+
*/
|
|
73
|
+
export function loadReasoningLevel() {
|
|
74
|
+
const projectConfig = readJsonFile(getProjectConfigFile());
|
|
75
|
+
if (projectConfig?.reasoning)
|
|
76
|
+
return projectConfig.reasoning;
|
|
77
|
+
const globalConfig = readJsonFile(GLOBAL_CONFIG_FILE);
|
|
78
|
+
if (globalConfig?.reasoning)
|
|
79
|
+
return globalConfig.reasoning;
|
|
80
|
+
return "medium";
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Load agent mode config with priority: project .acmecode/config.json > default "agent"
|
|
84
|
+
*/
|
|
85
|
+
export function loadAgentModeConfig() {
|
|
86
|
+
const projectConfig = readJsonFile(getProjectConfigFile());
|
|
87
|
+
return {
|
|
88
|
+
mode: projectConfig?.agentMode || "agent",
|
|
89
|
+
planFile: projectConfig?.activePlanFile,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Save model config to the project's .acmecode/config.json
|
|
94
|
+
*/
|
|
95
|
+
export function saveProjectModelConfig(provider, model, visionProvider, visionModel) {
|
|
96
|
+
const filePath = getProjectConfigFile();
|
|
97
|
+
const existing = readJsonFile(filePath) || {};
|
|
98
|
+
existing.provider = provider;
|
|
99
|
+
existing.model = model;
|
|
100
|
+
if (visionProvider)
|
|
101
|
+
existing.visionProvider = visionProvider;
|
|
102
|
+
if (visionModel)
|
|
103
|
+
existing.visionModel = visionModel;
|
|
104
|
+
writeJsonFile(filePath, existing);
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Save agent mode config to the project's .acmecode/config.json
|
|
108
|
+
*/
|
|
109
|
+
export function saveAgentModeConfig(mode, planFile) {
|
|
110
|
+
const filePath = getProjectConfigFile();
|
|
111
|
+
const existing = readJsonFile(filePath) || {};
|
|
112
|
+
existing.agentMode = mode;
|
|
113
|
+
if (planFile !== undefined) {
|
|
114
|
+
existing.activePlanFile = planFile;
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
delete existing.activePlanFile;
|
|
118
|
+
}
|
|
119
|
+
writeJsonFile(filePath, existing);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Save model config to the global ~/.acmecode/config.json
|
|
123
|
+
*/
|
|
124
|
+
export function saveGlobalModelConfig(provider, model) {
|
|
125
|
+
const existing = readJsonFile(GLOBAL_CONFIG_FILE) || {};
|
|
126
|
+
existing.provider = provider;
|
|
127
|
+
existing.model = model;
|
|
128
|
+
writeJsonFile(GLOBAL_CONFIG_FILE, existing);
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Save language config to the global ~/.acmecode/config.json
|
|
132
|
+
*/
|
|
133
|
+
export function saveGlobalLangConfig(lang) {
|
|
134
|
+
const existing = readJsonFile(GLOBAL_CONFIG_FILE) || {};
|
|
135
|
+
existing.lang = lang;
|
|
136
|
+
writeJsonFile(GLOBAL_CONFIG_FILE, existing);
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Save theme config to the global ~/.acmecode/config.json
|
|
140
|
+
*/
|
|
141
|
+
export function saveGlobalThemeConfig(theme) {
|
|
142
|
+
const existing = readJsonFile(GLOBAL_CONFIG_FILE) || {};
|
|
143
|
+
existing.theme = theme;
|
|
144
|
+
writeJsonFile(GLOBAL_CONFIG_FILE, existing);
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Save reasoning level config to the global ~/.acmecode/config.json
|
|
148
|
+
*/
|
|
149
|
+
export function saveGlobalReasoningLevel(level) {
|
|
150
|
+
const existing = readJsonFile(GLOBAL_CONFIG_FILE) || {};
|
|
151
|
+
writeJsonFile(GLOBAL_CONFIG_FILE, existing);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Save provider-specific settings to ~/.acmecode/config.json
|
|
155
|
+
*/
|
|
156
|
+
export function saveProviderConfig(provider, config) {
|
|
157
|
+
const existing = readJsonFile(GLOBAL_CONFIG_FILE) || {};
|
|
158
|
+
if (!existing.providers)
|
|
159
|
+
existing.providers = {};
|
|
160
|
+
existing.providers[provider] = config;
|
|
161
|
+
writeJsonFile(GLOBAL_CONFIG_FILE, existing);
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Load all custom providers from global config
|
|
165
|
+
*/
|
|
166
|
+
export function loadCustomProviders() {
|
|
167
|
+
const globalConfig = readJsonFile(GLOBAL_CONFIG_FILE);
|
|
168
|
+
const providers = globalConfig?.providers || {};
|
|
169
|
+
const customs = {};
|
|
170
|
+
const officialIds = [
|
|
171
|
+
"extralink",
|
|
172
|
+
"openai",
|
|
173
|
+
"anthropic",
|
|
174
|
+
"google",
|
|
175
|
+
"xai",
|
|
176
|
+
"mistral",
|
|
177
|
+
"groq",
|
|
178
|
+
"deepinfra",
|
|
179
|
+
"openrouter",
|
|
180
|
+
];
|
|
181
|
+
for (const [id, cfg] of Object.entries(providers)) {
|
|
182
|
+
if (cfg.isCustom || !officialIds.includes(id)) {
|
|
183
|
+
customs[id] = cfg;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return customs;
|
|
187
|
+
}
|
|
188
|
+
export const getProviderKey = (provider) => {
|
|
189
|
+
// 1. Check saved config in ~/.acmecode/config.json
|
|
190
|
+
const globalConfig = readJsonFile(GLOBAL_CONFIG_FILE);
|
|
191
|
+
if (globalConfig?.providers?.[provider]?.apiKey) {
|
|
192
|
+
return globalConfig.providers[provider].apiKey;
|
|
193
|
+
}
|
|
194
|
+
// 2. Check environment variables
|
|
195
|
+
switch (provider) {
|
|
196
|
+
case "extralink":
|
|
197
|
+
return (process.env.EXTRALINK_API_KEY ||
|
|
198
|
+
"sk-7qwFHXqvDgqTMfswYhgnMDoAWNU2zneozvFgI9DaR5Cpx49w");
|
|
199
|
+
case "openai":
|
|
200
|
+
return (process.env.OPENAI_API_KEY ||
|
|
201
|
+
"sk-Plobc3VM4qzRkIUAakQjtj7hwHPedlSoU4haaPNWWNIESiya");
|
|
202
|
+
case "anthropic":
|
|
203
|
+
return process.env.ANTHROPIC_API_KEY;
|
|
204
|
+
case "google":
|
|
205
|
+
return process.env.GOOGLE_GENERATIVE_AI_API_KEY;
|
|
206
|
+
case "xai":
|
|
207
|
+
return process.env.XAI_API_KEY;
|
|
208
|
+
case "mistral":
|
|
209
|
+
return process.env.MISTRAL_API_KEY;
|
|
210
|
+
case "groq":
|
|
211
|
+
return process.env.GROQ_API_KEY;
|
|
212
|
+
case "deepinfra":
|
|
213
|
+
return process.env.DEEPINFRA_API_KEY;
|
|
214
|
+
case "openrouter":
|
|
215
|
+
return process.env.OPENROUTER_API_KEY;
|
|
216
|
+
default:
|
|
217
|
+
return undefined;
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
/**
|
|
221
|
+
* Normalizes a base URL by removing trailing slashes and ensuring consistency.
|
|
222
|
+
*/
|
|
223
|
+
export const normalizeBaseUrl = (url) => {
|
|
224
|
+
if (!url)
|
|
225
|
+
return undefined;
|
|
226
|
+
return url.replace(/\/+$/, "");
|
|
227
|
+
};
|
|
228
|
+
/**
|
|
229
|
+
* Intelligently adapts a base URL for a given protocol if version segments are missing.
|
|
230
|
+
*/
|
|
231
|
+
export const getAdaptedBaseUrl = (protocol, baseUrl) => {
|
|
232
|
+
if (!baseUrl)
|
|
233
|
+
return undefined;
|
|
234
|
+
const clean = baseUrl.replace(/\/+$/, "");
|
|
235
|
+
// Official or well-known protocols often expect version segments
|
|
236
|
+
if (protocol === "anthropic" && !clean.includes("/v1")) {
|
|
237
|
+
return `${clean}/v1`;
|
|
238
|
+
}
|
|
239
|
+
if (protocol === "google" && !clean.includes("/v1")) {
|
|
240
|
+
// google usually expects /v1 or /v1beta for the SDK
|
|
241
|
+
return `${clean}/v1beta`;
|
|
242
|
+
}
|
|
243
|
+
if (protocol === "openai" &&
|
|
244
|
+
!clean.includes("/v1") &&
|
|
245
|
+
!clean.includes("/web")) {
|
|
246
|
+
// Many OpenAI relays use /v1, but some use root. We'll leave root as is
|
|
247
|
+
// but normalize to clean for now.
|
|
248
|
+
}
|
|
249
|
+
return clean;
|
|
250
|
+
};
|
|
251
|
+
export const getProviderBaseUrl = (provider) => {
|
|
252
|
+
// 1. Check saved config in ~/.acmecode/config.json
|
|
253
|
+
const globalConfig = readJsonFile(GLOBAL_CONFIG_FILE);
|
|
254
|
+
const saved = globalConfig?.providers?.[provider];
|
|
255
|
+
if (saved?.baseUrl) {
|
|
256
|
+
return getAdaptedBaseUrl(saved.protocol || "openai", saved.baseUrl);
|
|
257
|
+
}
|
|
258
|
+
// 2. Check environment variables or official defaults
|
|
259
|
+
let url;
|
|
260
|
+
const protocol = getProviderProtocol(provider);
|
|
261
|
+
switch (provider) {
|
|
262
|
+
case "extralink":
|
|
263
|
+
url = process.env.EXTRALINK_BASE_URL || "https://apis.extralink.net/v1";
|
|
264
|
+
break;
|
|
265
|
+
case "openai":
|
|
266
|
+
url = process.env.OPENAI_BASE_URL || "https://api.openai.com/v1";
|
|
267
|
+
break;
|
|
268
|
+
case "anthropic":
|
|
269
|
+
url = process.env.ANTHROPIC_BASE_URL || "https://api.anthropic.com/v1";
|
|
270
|
+
break;
|
|
271
|
+
case "google":
|
|
272
|
+
url =
|
|
273
|
+
process.env.GOOGLE_GENERATIVE_AI_BASE_URL ||
|
|
274
|
+
"https://generativelanguage.googleapis.com/v1beta";
|
|
275
|
+
break;
|
|
276
|
+
case "xai":
|
|
277
|
+
url = process.env.XAI_BASE_URL || "https://api.x.ai/v1";
|
|
278
|
+
break;
|
|
279
|
+
case "mistral":
|
|
280
|
+
url = process.env.MISTRAL_BASE_URL || "https://api.mistral.ai/v1";
|
|
281
|
+
break;
|
|
282
|
+
case "groq":
|
|
283
|
+
url = process.env.GROQ_BASE_URL || "https://api.groq.com/openai/v1";
|
|
284
|
+
break;
|
|
285
|
+
case "deepinfra":
|
|
286
|
+
url =
|
|
287
|
+
process.env.DEEPINFRA_BASE_URL || "https://api.deepinfra.com/v1/openai";
|
|
288
|
+
break;
|
|
289
|
+
case "openrouter":
|
|
290
|
+
url = process.env.OPENROUTER_BASE_URL || "https://openrouter.ai/api/v1";
|
|
291
|
+
break;
|
|
292
|
+
}
|
|
293
|
+
return getAdaptedBaseUrl(protocol, url);
|
|
294
|
+
};
|
|
295
|
+
/**
|
|
296
|
+
* Get the protocol for a provider (e.g. 'openai', 'anthropic', 'google')
|
|
297
|
+
*/
|
|
298
|
+
export const getProviderProtocol = (provider) => {
|
|
299
|
+
const globalConfig = readJsonFile(GLOBAL_CONFIG_FILE);
|
|
300
|
+
if (globalConfig?.providers?.[provider]?.protocol) {
|
|
301
|
+
return globalConfig.providers[provider].protocol;
|
|
302
|
+
}
|
|
303
|
+
// Default to provider ID for official ones, or 'openai' as safe fallback
|
|
304
|
+
const official = [
|
|
305
|
+
"extralink",
|
|
306
|
+
"openai",
|
|
307
|
+
"anthropic",
|
|
308
|
+
"google",
|
|
309
|
+
"xai",
|
|
310
|
+
"mistral",
|
|
311
|
+
"groq",
|
|
312
|
+
"deepinfra",
|
|
313
|
+
"openrouter",
|
|
314
|
+
];
|
|
315
|
+
if (official.includes(provider))
|
|
316
|
+
return provider;
|
|
317
|
+
return "openai";
|
|
318
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function getProjectContext(): Promise<string>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import * as fs from 'fs/promises';
|
|
2
|
+
import { exec } from 'child_process';
|
|
3
|
+
import { promisify } from 'util';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
const execAsync = promisify(exec);
|
|
6
|
+
export async function getProjectContext() {
|
|
7
|
+
let context = '';
|
|
8
|
+
// 1. ACMECODE.md
|
|
9
|
+
try {
|
|
10
|
+
const acmecodePath = path.join(process.cwd(), 'ACMECODE.md');
|
|
11
|
+
const content = await fs.readFile(acmecodePath, 'utf8');
|
|
12
|
+
context += `\n\n[Project Instructions (ACMECODE.md)]\n${content}`;
|
|
13
|
+
}
|
|
14
|
+
catch (e) {
|
|
15
|
+
// Ignore if missing
|
|
16
|
+
}
|
|
17
|
+
// 2. Git context
|
|
18
|
+
try {
|
|
19
|
+
const { stdout: branch } = await execAsync('git rev-parse --abbrev-ref HEAD');
|
|
20
|
+
const { stdout: status } = await execAsync('git status -s');
|
|
21
|
+
context += `\n\n[Git Status (Branch: ${branch.trim()})]\n${status.trim() || 'Clean working directory'}`;
|
|
22
|
+
// Let's also get the last commit message for context
|
|
23
|
+
const { stdout: log } = await execAsync('git log -1 --oneline');
|
|
24
|
+
context += `\nLast Commit: ${log.trim()}`;
|
|
25
|
+
}
|
|
26
|
+
catch (e) {
|
|
27
|
+
// Ignore if not a git repo
|
|
28
|
+
}
|
|
29
|
+
return context;
|
|
30
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { LanguageModel } from "ai";
|
|
2
|
+
export type ProviderType = "extralink" | "openai" | "anthropic" | "google" | "xai" | "mistral" | "groq" | "deepinfra" | "openrouter";
|
|
3
|
+
export interface ProviderInfo {
|
|
4
|
+
id: ProviderType;
|
|
5
|
+
name: string;
|
|
6
|
+
envKey: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function getProviders(): ProviderInfo[];
|
|
9
|
+
/**
|
|
10
|
+
* Create a language model instance for the given provider and model name.
|
|
11
|
+
* Reads API key and optional base URL from the project/env configuration.
|
|
12
|
+
*/
|
|
13
|
+
export declare function getModel(provider: ProviderType, modelName: string): LanguageModel;
|
|
14
|
+
/**
|
|
15
|
+
* List all providers that have API keys configured.
|
|
16
|
+
*/
|
|
17
|
+
export declare function getAvailableProviders(): ProviderInfo[];
|
|
18
|
+
export declare function fetchModelsDevCache(): Promise<void>;
|
|
19
|
+
/**
|
|
20
|
+
* Get the known context window size for a model.
|
|
21
|
+
*/
|
|
22
|
+
export declare function getContextWindow(modelName: string): number;
|
|
23
|
+
/**
|
|
24
|
+
* Heuristic to estimate token count from string content.
|
|
25
|
+
* Average 3.5 characters per token for mixed English/Chinese.
|
|
26
|
+
*/
|
|
27
|
+
export declare function estimateTokens(text: string): number;
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { createOpenAI } from "@ai-sdk/openai";
|
|
2
|
+
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
3
|
+
import { createGoogleGenerativeAI } from "@ai-sdk/google";
|
|
4
|
+
import { createXai } from "@ai-sdk/xai";
|
|
5
|
+
import { createMistral } from "@ai-sdk/mistral";
|
|
6
|
+
import { createGroq } from "@ai-sdk/groq";
|
|
7
|
+
import { createDeepInfra } from "@ai-sdk/deepinfra";
|
|
8
|
+
import { createOpenRouter } from "@openrouter/ai-sdk-provider";
|
|
9
|
+
import { getProviderKey, getProviderBaseUrl, loadCustomProviders, getProviderProtocol, } from "../config/index.js";
|
|
10
|
+
const OFFICIAL_PROVIDERS = [
|
|
11
|
+
{
|
|
12
|
+
id: "extralink",
|
|
13
|
+
name: "Extralink",
|
|
14
|
+
envKey: "EXTRALINK_API_KEY",
|
|
15
|
+
},
|
|
16
|
+
{ id: "openai", name: "OpenAI", envKey: "OPENAI_API_KEY" },
|
|
17
|
+
{ id: "anthropic", name: "Anthropic", envKey: "ANTHROPIC_API_KEY" },
|
|
18
|
+
{ id: "google", name: "Google", envKey: "GOOGLE_GENERATIVE_AI_API_KEY" },
|
|
19
|
+
{ id: "xai", name: "xAI (Grok)", envKey: "XAI_API_KEY" },
|
|
20
|
+
{ id: "mistral", name: "Mistral", envKey: "MISTRAL_API_KEY" },
|
|
21
|
+
{ id: "groq", name: "Groq", envKey: "GROQ_API_KEY" },
|
|
22
|
+
{ id: "deepinfra", name: "DeepInfra", envKey: "DEEPINFRA_API_KEY" },
|
|
23
|
+
{ id: "openrouter", name: "OpenRouter", envKey: "OPENROUTER_API_KEY" },
|
|
24
|
+
];
|
|
25
|
+
export function getProviders() {
|
|
26
|
+
const customs = loadCustomProviders();
|
|
27
|
+
const customList = Object.keys(customs).map((id) => ({
|
|
28
|
+
id: id,
|
|
29
|
+
name: id,
|
|
30
|
+
envKey: "CUSTOM",
|
|
31
|
+
}));
|
|
32
|
+
// Filter out custom providers that duplicate official ones
|
|
33
|
+
const officialIds = new Set(OFFICIAL_PROVIDERS.map((p) => p.id));
|
|
34
|
+
const filteredCustoms = customList.filter((c) => !officialIds.has(c.id));
|
|
35
|
+
return [...OFFICIAL_PROVIDERS, ...filteredCustoms];
|
|
36
|
+
}
|
|
37
|
+
const PROVIDER_REGISTRY = {
|
|
38
|
+
extralink: (apiKey, baseURL) => {
|
|
39
|
+
const sdk = createOpenAI({ apiKey, baseURL });
|
|
40
|
+
return (model) => sdk.chat(model);
|
|
41
|
+
},
|
|
42
|
+
openai: (apiKey, baseURL) => {
|
|
43
|
+
const sdk = createOpenAI({ apiKey, baseURL });
|
|
44
|
+
return (model) => sdk.chat(model);
|
|
45
|
+
},
|
|
46
|
+
anthropic: (apiKey, baseURL) => {
|
|
47
|
+
const sdk = createAnthropic({ apiKey, baseURL });
|
|
48
|
+
return (model) => sdk(model);
|
|
49
|
+
},
|
|
50
|
+
google: (apiKey, baseURL) => {
|
|
51
|
+
const sdk = createGoogleGenerativeAI({ apiKey, baseURL });
|
|
52
|
+
return (model) => sdk(model);
|
|
53
|
+
},
|
|
54
|
+
xai: (apiKey, baseURL) => {
|
|
55
|
+
const sdk = createXai({ apiKey, baseURL });
|
|
56
|
+
return (model) => sdk(model);
|
|
57
|
+
},
|
|
58
|
+
mistral: (apiKey, baseURL) => {
|
|
59
|
+
const sdk = createMistral({ apiKey, baseURL });
|
|
60
|
+
return (model) => sdk(model);
|
|
61
|
+
},
|
|
62
|
+
groq: (apiKey, baseURL) => {
|
|
63
|
+
const sdk = createGroq({ apiKey, baseURL });
|
|
64
|
+
return (model) => sdk(model);
|
|
65
|
+
},
|
|
66
|
+
deepinfra: (apiKey, baseURL) => {
|
|
67
|
+
const sdk = createDeepInfra({ apiKey, baseURL });
|
|
68
|
+
return (model) => sdk(model);
|
|
69
|
+
},
|
|
70
|
+
openrouter: (apiKey, baseURL) => {
|
|
71
|
+
const sdk = createOpenRouter({ apiKey, baseURL });
|
|
72
|
+
return (model) => sdk.chat(model);
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* Create a language model instance for the given provider and model name.
|
|
77
|
+
* Reads API key and optional base URL from the project/env configuration.
|
|
78
|
+
*/
|
|
79
|
+
export function getModel(provider, modelName) {
|
|
80
|
+
const protocol = getProviderProtocol(provider);
|
|
81
|
+
const factory = PROVIDER_REGISTRY[protocol];
|
|
82
|
+
const apiKey = getProviderKey(provider);
|
|
83
|
+
if (!apiKey) {
|
|
84
|
+
const info = getProviders().find((p) => p.id === provider);
|
|
85
|
+
throw new Error(`${info?.envKey || provider.toUpperCase() + "_API_KEY"} is not set.`);
|
|
86
|
+
}
|
|
87
|
+
const baseURL = getProviderBaseUrl(provider);
|
|
88
|
+
if (!factory) {
|
|
89
|
+
// Fallback for custom providers if protocol is unknown: assume OpenAI-compatible
|
|
90
|
+
const sdk = createOpenAI({ apiKey, baseURL });
|
|
91
|
+
return sdk.chat(modelName);
|
|
92
|
+
}
|
|
93
|
+
const createModel = factory(apiKey, baseURL);
|
|
94
|
+
return createModel(modelName);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* List all providers that have API keys configured.
|
|
98
|
+
*/
|
|
99
|
+
export function getAvailableProviders() {
|
|
100
|
+
return getProviders().filter((p) => {
|
|
101
|
+
try {
|
|
102
|
+
return !!getProviderKey(p.id);
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
// ── Dynamic Context Size Caching ──
|
|
110
|
+
let modelsDevCache = null;
|
|
111
|
+
export async function fetchModelsDevCache() {
|
|
112
|
+
try {
|
|
113
|
+
const res = await fetch("https://models.dev/api.json");
|
|
114
|
+
if (!res.ok)
|
|
115
|
+
return;
|
|
116
|
+
const data = await res.json();
|
|
117
|
+
const cache = {};
|
|
118
|
+
for (const provider of Object.values(data)) {
|
|
119
|
+
const parsedProvider = provider;
|
|
120
|
+
if (parsedProvider?.models) {
|
|
121
|
+
for (const [modId, modData] of Object.entries(parsedProvider.models)) {
|
|
122
|
+
if (modData?.limit?.context) {
|
|
123
|
+
cache[modId] = modData.limit.context;
|
|
124
|
+
cache[modId.toLowerCase()] = modData.limit.context;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
modelsDevCache = cache;
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
// silently ignore fetch errors
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Get the known context window size for a model.
|
|
137
|
+
*/
|
|
138
|
+
export function getContextWindow(modelName) {
|
|
139
|
+
if (modelsDevCache) {
|
|
140
|
+
if (modelsDevCache[modelName])
|
|
141
|
+
return modelsDevCache[modelName];
|
|
142
|
+
if (modelsDevCache[modelName.toLowerCase()])
|
|
143
|
+
return modelsDevCache[modelName.toLowerCase()];
|
|
144
|
+
// Fuzzy match
|
|
145
|
+
for (const [key, val] of Object.entries(modelsDevCache)) {
|
|
146
|
+
if (modelName.toLowerCase().includes(key) ||
|
|
147
|
+
key.includes(modelName.toLowerCase())) {
|
|
148
|
+
return val;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
const lower = modelName.toLowerCase();
|
|
153
|
+
// ── User provided specifics from models.dev ──
|
|
154
|
+
if (lower.includes("kimi-k2.5"))
|
|
155
|
+
return 262144;
|
|
156
|
+
if (lower.includes("llama-3.3"))
|
|
157
|
+
return 131072;
|
|
158
|
+
if (lower.includes("phi-4"))
|
|
159
|
+
return 32000;
|
|
160
|
+
if (lower.includes("gpt-oss"))
|
|
161
|
+
return 65536;
|
|
162
|
+
if (lower.includes("magistral"))
|
|
163
|
+
return 131072;
|
|
164
|
+
if (lower.includes("devstral"))
|
|
165
|
+
return 32768;
|
|
166
|
+
if (lower.includes("voxtral"))
|
|
167
|
+
return 32000;
|
|
168
|
+
if (lower.includes("whisper"))
|
|
169
|
+
return 448;
|
|
170
|
+
if (lower.includes("e5-large"))
|
|
171
|
+
return 512;
|
|
172
|
+
// ── Standard defaults ──
|
|
173
|
+
if (lower.includes("o1") || lower.includes("o3") || lower.includes("o4"))
|
|
174
|
+
return 128000;
|
|
175
|
+
if (lower.includes("claude-3-5"))
|
|
176
|
+
return 200000;
|
|
177
|
+
if (lower.includes("claude-3"))
|
|
178
|
+
return 200000;
|
|
179
|
+
if (lower.includes("gemini-1.5-pro"))
|
|
180
|
+
return 2000000;
|
|
181
|
+
if (lower.includes("gemini-1.5-flash"))
|
|
182
|
+
return 1000000;
|
|
183
|
+
if (lower.includes("gpt-4o"))
|
|
184
|
+
return 128000;
|
|
185
|
+
if (lower.includes("gpt-4"))
|
|
186
|
+
return 128000;
|
|
187
|
+
if (lower.includes("deepseek"))
|
|
188
|
+
return 64000;
|
|
189
|
+
if (lower.includes("qwen"))
|
|
190
|
+
return 32000;
|
|
191
|
+
return 32000; // conservative default
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Heuristic to estimate token count from string content.
|
|
195
|
+
* Average 3.5 characters per token for mixed English/Chinese.
|
|
196
|
+
*/
|
|
197
|
+
export function estimateTokens(text) {
|
|
198
|
+
if (!text)
|
|
199
|
+
return 0;
|
|
200
|
+
// Basic heuristic: 1 token ≈ 3.0 characters (safer for mixed Code/Chinese which are token heavy)
|
|
201
|
+
return Math.ceil(text.length / 3.0);
|
|
202
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { ModelConfig } from '../config/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Analyzes an image using a specialized vision model and returns a textual description.
|
|
4
|
+
* This allows non-vision primary models to "see" via delegation.
|
|
5
|
+
*/
|
|
6
|
+
export declare function analyzeImage(imageData: string, // base64
|
|
7
|
+
config: ModelConfig): Promise<string>;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { generateText } from 'ai';
|
|
2
|
+
import { getModel } from './provider.js';
|
|
3
|
+
/**
|
|
4
|
+
* Analyzes an image using a specialized vision model and returns a textual description.
|
|
5
|
+
* This allows non-vision primary models to "see" via delegation.
|
|
6
|
+
*/
|
|
7
|
+
export async function analyzeImage(imageData, // base64
|
|
8
|
+
config) {
|
|
9
|
+
if (!config.visionProvider || !config.visionModel) {
|
|
10
|
+
return "Error: Vision model not configured. Please set visionProvider and visionModel in config.";
|
|
11
|
+
}
|
|
12
|
+
const model = getModel(config.visionProvider, config.visionModel);
|
|
13
|
+
try {
|
|
14
|
+
const { text } = await generateText({
|
|
15
|
+
model,
|
|
16
|
+
abortSignal: AbortSignal.timeout(60000), // 60 seconds timeout
|
|
17
|
+
maxRetries: 3,
|
|
18
|
+
messages: [
|
|
19
|
+
{
|
|
20
|
+
role: 'user',
|
|
21
|
+
content: [
|
|
22
|
+
{ type: 'text', text: "Please describe this screenshot in detail. Focus on the layout, visible text, interactive elements (buttons, inputs), and any apparent errors or status messages. This description will be used by another AI to understand the state of the web application." },
|
|
23
|
+
{ type: 'image', image: imageData }
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
});
|
|
28
|
+
return `[Vision Model Analysis (${config.visionModel})]:\n${text}`;
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
let msg = err.message;
|
|
32
|
+
if (err.name === 'TimeoutError' || err.message.includes('timeout')) {
|
|
33
|
+
msg = `Analysis timed out after 60s. The image might be too complex or the provider is slow.`;
|
|
34
|
+
}
|
|
35
|
+
return `Error during vision analysis: ${msg}`;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface McpServerConfig {
|
|
2
|
+
command: string;
|
|
3
|
+
args?: string[];
|
|
4
|
+
env?: Record<string, string>;
|
|
5
|
+
}
|
|
6
|
+
export interface McpConfig {
|
|
7
|
+
mcpServers: Record<string, McpServerConfig>;
|
|
8
|
+
}
|
|
9
|
+
export declare function loadMcpConfig(): Promise<McpConfig | null>;
|
|
10
|
+
export declare function getMcpTools(): Promise<Record<string, any>>;
|