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.
Files changed (131) hide show
  1. package/.acmecode/config.json +6 -0
  2. package/README.md +124 -0
  3. package/dist/agent/index.js +161 -0
  4. package/dist/cli/bin/acmecode.js +3 -0
  5. package/dist/cli/package.json +25 -0
  6. package/dist/cli/src/index.d.ts +1 -0
  7. package/dist/cli/src/index.js +53 -0
  8. package/dist/config/index.js +92 -0
  9. package/dist/context/index.js +30 -0
  10. package/dist/core/src/agent/index.d.ts +52 -0
  11. package/dist/core/src/agent/index.js +476 -0
  12. package/dist/core/src/config/index.d.ts +83 -0
  13. package/dist/core/src/config/index.js +318 -0
  14. package/dist/core/src/context/index.d.ts +1 -0
  15. package/dist/core/src/context/index.js +30 -0
  16. package/dist/core/src/llm/provider.d.ts +27 -0
  17. package/dist/core/src/llm/provider.js +202 -0
  18. package/dist/core/src/llm/vision.d.ts +7 -0
  19. package/dist/core/src/llm/vision.js +37 -0
  20. package/dist/core/src/mcp/index.d.ts +10 -0
  21. package/dist/core/src/mcp/index.js +84 -0
  22. package/dist/core/src/prompt/anthropic.d.ts +1 -0
  23. package/dist/core/src/prompt/anthropic.js +32 -0
  24. package/dist/core/src/prompt/architect.d.ts +1 -0
  25. package/dist/core/src/prompt/architect.js +17 -0
  26. package/dist/core/src/prompt/autopilot.d.ts +1 -0
  27. package/dist/core/src/prompt/autopilot.js +18 -0
  28. package/dist/core/src/prompt/beast.d.ts +1 -0
  29. package/dist/core/src/prompt/beast.js +83 -0
  30. package/dist/core/src/prompt/gemini.d.ts +1 -0
  31. package/dist/core/src/prompt/gemini.js +45 -0
  32. package/dist/core/src/prompt/index.d.ts +18 -0
  33. package/dist/core/src/prompt/index.js +239 -0
  34. package/dist/core/src/prompt/zen.d.ts +1 -0
  35. package/dist/core/src/prompt/zen.js +13 -0
  36. package/dist/core/src/session/index.d.ts +18 -0
  37. package/dist/core/src/session/index.js +97 -0
  38. package/dist/core/src/skills/index.d.ts +6 -0
  39. package/dist/core/src/skills/index.js +72 -0
  40. package/dist/core/src/tools/batch.d.ts +2 -0
  41. package/dist/core/src/tools/batch.js +65 -0
  42. package/dist/core/src/tools/browser.d.ts +7 -0
  43. package/dist/core/src/tools/browser.js +86 -0
  44. package/dist/core/src/tools/edit.d.ts +11 -0
  45. package/dist/core/src/tools/edit.js +312 -0
  46. package/dist/core/src/tools/index.d.ts +13 -0
  47. package/dist/core/src/tools/index.js +980 -0
  48. package/dist/core/src/tools/lsp-client.d.ts +11 -0
  49. package/dist/core/src/tools/lsp-client.js +224 -0
  50. package/dist/index.js +41 -0
  51. package/dist/llm/provider.js +34 -0
  52. package/dist/mcp/index.js +84 -0
  53. package/dist/session/index.js +74 -0
  54. package/dist/skills/index.js +32 -0
  55. package/dist/tools/index.js +96 -0
  56. package/dist/tui/App.js +297 -0
  57. package/dist/tui/Spinner.js +16 -0
  58. package/dist/tui/TextInput.js +98 -0
  59. package/dist/tui/src/App.d.ts +11 -0
  60. package/dist/tui/src/App.js +1211 -0
  61. package/dist/tui/src/CatLogo.d.ts +10 -0
  62. package/dist/tui/src/CatLogo.js +99 -0
  63. package/dist/tui/src/OptionList.d.ts +15 -0
  64. package/dist/tui/src/OptionList.js +60 -0
  65. package/dist/tui/src/Spinner.d.ts +7 -0
  66. package/dist/tui/src/Spinner.js +18 -0
  67. package/dist/tui/src/TextInput.d.ts +28 -0
  68. package/dist/tui/src/TextInput.js +139 -0
  69. package/dist/tui/src/Tips.d.ts +2 -0
  70. package/dist/tui/src/Tips.js +62 -0
  71. package/dist/tui/src/Toast.d.ts +19 -0
  72. package/dist/tui/src/Toast.js +39 -0
  73. package/dist/tui/src/TodoItem.d.ts +7 -0
  74. package/dist/tui/src/TodoItem.js +21 -0
  75. package/dist/tui/src/i18n.d.ts +172 -0
  76. package/dist/tui/src/i18n.js +189 -0
  77. package/dist/tui/src/markdown.d.ts +6 -0
  78. package/dist/tui/src/markdown.js +356 -0
  79. package/dist/tui/src/theme.d.ts +31 -0
  80. package/dist/tui/src/theme.js +239 -0
  81. package/output.txt +0 -0
  82. package/package.json +44 -0
  83. package/packages/cli/package.json +25 -0
  84. package/packages/cli/src/index.ts +59 -0
  85. package/packages/cli/tsconfig.json +26 -0
  86. package/packages/core/package.json +39 -0
  87. package/packages/core/src/agent/index.ts +588 -0
  88. package/packages/core/src/config/index.ts +383 -0
  89. package/packages/core/src/context/index.ts +34 -0
  90. package/packages/core/src/llm/provider.ts +237 -0
  91. package/packages/core/src/llm/vision.ts +43 -0
  92. package/packages/core/src/mcp/index.ts +110 -0
  93. package/packages/core/src/prompt/anthropic.ts +32 -0
  94. package/packages/core/src/prompt/architect.ts +17 -0
  95. package/packages/core/src/prompt/autopilot.ts +18 -0
  96. package/packages/core/src/prompt/beast.ts +83 -0
  97. package/packages/core/src/prompt/gemini.ts +45 -0
  98. package/packages/core/src/prompt/index.ts +267 -0
  99. package/packages/core/src/prompt/zen.ts +13 -0
  100. package/packages/core/src/session/index.ts +129 -0
  101. package/packages/core/src/skills/index.ts +86 -0
  102. package/packages/core/src/tools/batch.ts +73 -0
  103. package/packages/core/src/tools/browser.ts +95 -0
  104. package/packages/core/src/tools/edit.ts +317 -0
  105. package/packages/core/src/tools/index.ts +1112 -0
  106. package/packages/core/src/tools/lsp-client.ts +303 -0
  107. package/packages/core/tsconfig.json +19 -0
  108. package/packages/tui/package.json +24 -0
  109. package/packages/tui/src/App.tsx +1702 -0
  110. package/packages/tui/src/CatLogo.tsx +134 -0
  111. package/packages/tui/src/OptionList.tsx +95 -0
  112. package/packages/tui/src/Spinner.tsx +28 -0
  113. package/packages/tui/src/TextInput.tsx +202 -0
  114. package/packages/tui/src/Tips.tsx +64 -0
  115. package/packages/tui/src/Toast.tsx +60 -0
  116. package/packages/tui/src/TodoItem.tsx +29 -0
  117. package/packages/tui/src/i18n.ts +203 -0
  118. package/packages/tui/src/markdown.ts +403 -0
  119. package/packages/tui/src/theme.ts +287 -0
  120. package/packages/tui/tsconfig.json +24 -0
  121. package/tsconfig.json +18 -0
  122. package/vscode-acmecode/.vscodeignore +11 -0
  123. package/vscode-acmecode/README.md +57 -0
  124. package/vscode-acmecode/esbuild.js +46 -0
  125. package/vscode-acmecode/images/button-dark.svg +5 -0
  126. package/vscode-acmecode/images/button-light.svg +5 -0
  127. package/vscode-acmecode/images/icon.png +1 -0
  128. package/vscode-acmecode/package-lock.json +490 -0
  129. package/vscode-acmecode/package.json +87 -0
  130. package/vscode-acmecode/src/extension.ts +128 -0
  131. 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>>;