ashtar 1.0.10
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.md +146 -0
- package/cli/guard.cjs +144 -0
- package/desktop/dist/daemon.cjs +20696 -0
- package/desktop/dist/overlay.html +283 -0
- package/desktop/dist/preload.cjs +7 -0
- package/desktop/resources/.gitkeep +1 -0
- package/desktop/resources/capture_testpad.ps1 +125 -0
- package/desktop/resources/find_testpad.ps1 +37 -0
- package/desktop/resources/inject_dll.ps1 +43 -0
- package/desktop/resources/unprotect.dll +0 -0
- package/dist/cli/index.js +657 -0
- package/dist/shared/index.cjs +553 -0
- package/dist/shared/index.d.cts +47 -0
- package/dist/shared/index.d.ts +47 -0
- package/dist/shared/index.js +518 -0
- package/docs/deployment.md +27 -0
- package/package.json +80 -0
- package/shared/keys.json +6 -0
|
@@ -0,0 +1,553 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
function loadDotenvCJS() {
|
|
3
|
+
try {
|
|
4
|
+
const fs = require("fs");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
const seeds = [];
|
|
7
|
+
if (typeof __dirname !== "undefined") {
|
|
8
|
+
seeds.push(__dirname);
|
|
9
|
+
}
|
|
10
|
+
seeds.push(process.cwd());
|
|
11
|
+
const candidates = new Set();
|
|
12
|
+
for (const seed of seeds) {
|
|
13
|
+
candidates.add(path.resolve(seed, ".env"));
|
|
14
|
+
candidates.add(path.resolve(seed, "..", ".env"));
|
|
15
|
+
candidates.add(path.resolve(seed, "..", "..", ".env"));
|
|
16
|
+
candidates.add(path.resolve(seed, "..", "..", "..", ".env"));
|
|
17
|
+
}
|
|
18
|
+
for (const cand of candidates) {
|
|
19
|
+
if (fs.existsSync(cand)) {
|
|
20
|
+
const dotenv = require("dotenv");
|
|
21
|
+
dotenv.config({ path: cand });
|
|
22
|
+
break;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
} catch (err) {}
|
|
26
|
+
}
|
|
27
|
+
loadDotenvCJS();
|
|
28
|
+
|
|
29
|
+
function isGeminiKey(key) {
|
|
30
|
+
if (!key) return false;
|
|
31
|
+
return key.startsWith("AIzaSy") || key.startsWith("AAQ.");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
var __create = Object.create;
|
|
35
|
+
var __defProp = Object.defineProperty;
|
|
36
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
37
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
38
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
39
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
40
|
+
var __export = (target, all) => {
|
|
41
|
+
for (var name in all)
|
|
42
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
43
|
+
};
|
|
44
|
+
var __copyProps = (to, from, except, desc) => {
|
|
45
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
46
|
+
for (let key of __getOwnPropNames(from))
|
|
47
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
48
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
49
|
+
}
|
|
50
|
+
return to;
|
|
51
|
+
};
|
|
52
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
53
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
54
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
55
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
56
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
57
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
58
|
+
mod
|
|
59
|
+
));
|
|
60
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
61
|
+
|
|
62
|
+
// shared/index.ts
|
|
63
|
+
var index_exports = {};
|
|
64
|
+
__export(index_exports, {
|
|
65
|
+
DEFAULT_MODEL: () => DEFAULT_MODEL,
|
|
66
|
+
askGroq: () => askGroq,
|
|
67
|
+
askWithAutoRotate: () => askWithAutoRotate,
|
|
68
|
+
decryptPoolKey: () => decryptPoolKey,
|
|
69
|
+
decryptValue: () => decryptValue,
|
|
70
|
+
encryptPoolKey: () => encryptPoolKey,
|
|
71
|
+
encryptValue: () => encryptValue,
|
|
72
|
+
loadPool: () => loadPool,
|
|
73
|
+
loadUserConfig: () => loadUserConfig,
|
|
74
|
+
maskKey: () => maskKey,
|
|
75
|
+
patchUserConfig: () => patchUserConfig,
|
|
76
|
+
resolveCurrentKey: () => resolveCurrentKey,
|
|
77
|
+
rotateKey: () => rotateKey,
|
|
78
|
+
saveUserConfig: () => saveUserConfig
|
|
79
|
+
});
|
|
80
|
+
module.exports = __toCommonJS(index_exports);
|
|
81
|
+
|
|
82
|
+
// shared/crypto.ts
|
|
83
|
+
var import_node_crypto = require("crypto");
|
|
84
|
+
var ALGO = "aes-256-gcm";
|
|
85
|
+
function machineScopedSecret() {
|
|
86
|
+
const basis = `${process.platform}|${process.arch}|${process.env.USERNAME ?? process.env.USER ?? "user"}`;
|
|
87
|
+
return (0, import_node_crypto.createHash)("sha256").update(basis).digest();
|
|
88
|
+
}
|
|
89
|
+
function poolSecret() {
|
|
90
|
+
const parts = [112, 97, 103, 101, 97, 105, 45, 112, 111, 111, 108, 45, 118, 50];
|
|
91
|
+
return (0, import_node_crypto.createHash)("sha256").update(Buffer.from(parts)).digest();
|
|
92
|
+
}
|
|
93
|
+
function encryptValue(raw) {
|
|
94
|
+
const iv = (0, import_node_crypto.randomBytes)(12);
|
|
95
|
+
const key = machineScopedSecret();
|
|
96
|
+
const cipher = (0, import_node_crypto.createCipheriv)(ALGO, key, iv);
|
|
97
|
+
const encrypted = Buffer.concat([cipher.update(raw, "utf8"), cipher.final()]);
|
|
98
|
+
const tag = cipher.getAuthTag();
|
|
99
|
+
return Buffer.concat([iv, tag, encrypted]).toString("base64");
|
|
100
|
+
}
|
|
101
|
+
function decryptValue(payload) {
|
|
102
|
+
const data = Buffer.from(payload, "base64");
|
|
103
|
+
const iv = data.subarray(0, 12);
|
|
104
|
+
const tag = data.subarray(12, 28);
|
|
105
|
+
const encrypted = data.subarray(28);
|
|
106
|
+
const key = machineScopedSecret();
|
|
107
|
+
const decipher = (0, import_node_crypto.createDecipheriv)(ALGO, key, iv);
|
|
108
|
+
decipher.setAuthTag(tag);
|
|
109
|
+
const result = Buffer.concat([decipher.update(encrypted), decipher.final()]);
|
|
110
|
+
return result.toString("utf8");
|
|
111
|
+
}
|
|
112
|
+
function encryptPoolKey(raw) {
|
|
113
|
+
const iv = (0, import_node_crypto.randomBytes)(12);
|
|
114
|
+
const key = poolSecret();
|
|
115
|
+
const cipher = (0, import_node_crypto.createCipheriv)(ALGO, key, iv);
|
|
116
|
+
const encrypted = Buffer.concat([cipher.update(raw, "utf8"), cipher.final()]);
|
|
117
|
+
const tag = cipher.getAuthTag();
|
|
118
|
+
return Buffer.concat([iv, tag, encrypted]).toString("base64");
|
|
119
|
+
}
|
|
120
|
+
function decryptPoolKey(payload) {
|
|
121
|
+
const data = Buffer.from(payload, "base64");
|
|
122
|
+
const iv = data.subarray(0, 12);
|
|
123
|
+
const tag = data.subarray(12, 28);
|
|
124
|
+
const encrypted = data.subarray(28);
|
|
125
|
+
const key = poolSecret();
|
|
126
|
+
const decipher = (0, import_node_crypto.createDecipheriv)(ALGO, key, iv);
|
|
127
|
+
decipher.setAuthTag(tag);
|
|
128
|
+
const result = Buffer.concat([decipher.update(encrypted), decipher.final()]);
|
|
129
|
+
return result.toString("utf8");
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// shared/storage.ts
|
|
133
|
+
var import_conf = __toESM(require("conf"), 1);
|
|
134
|
+
var conf;
|
|
135
|
+
function getConf() {
|
|
136
|
+
if (!conf) {
|
|
137
|
+
conf = new import_conf.default({
|
|
138
|
+
projectName: "Idlidosa",
|
|
139
|
+
defaults: {
|
|
140
|
+
config: {
|
|
141
|
+
model: "llama-3.3-70b-versatile",
|
|
142
|
+
autoSummarizeOnLoad: false,
|
|
143
|
+
theme: "dark",
|
|
144
|
+
stealth: true
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
return conf;
|
|
150
|
+
}
|
|
151
|
+
function saveUserConfig(config) {
|
|
152
|
+
const toSave = { ...config };
|
|
153
|
+
if (toSave.apiKey) {
|
|
154
|
+
toSave.apiKey = encryptValue(toSave.apiKey);
|
|
155
|
+
}
|
|
156
|
+
getConf().set("config", toSave);
|
|
157
|
+
}
|
|
158
|
+
function loadUserConfig() {
|
|
159
|
+
const config = getConf().get("config");
|
|
160
|
+
if (config?.apiKey) {
|
|
161
|
+
try {
|
|
162
|
+
return { ...config, apiKey: decryptValue(config.apiKey) };
|
|
163
|
+
} catch {
|
|
164
|
+
return { ...config, apiKey: void 0 };
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return config;
|
|
168
|
+
}
|
|
169
|
+
function patchUserConfig(patch) {
|
|
170
|
+
const current = loadUserConfig();
|
|
171
|
+
const merged = { ...current, ...patch };
|
|
172
|
+
saveUserConfig(merged);
|
|
173
|
+
return merged;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// shared/groq.ts
|
|
177
|
+
var import_groq_sdk = __toESM(require("groq-sdk"), 1);
|
|
178
|
+
|
|
179
|
+
// shared/keyPool.ts
|
|
180
|
+
var import_node_crypto2 = require("crypto");
|
|
181
|
+
var import_node_fs = require("fs");
|
|
182
|
+
var import_node_path = require("path");
|
|
183
|
+
var import_node_machine_id = __toESM(require("node-machine-id"), 1);
|
|
184
|
+
var machineIdSync = import_node_machine_id.default.machineIdSync ?? import_node_machine_id.default.default?.machineIdSync ?? (() => {
|
|
185
|
+
throw new Error("node-machine-id is unavailable");
|
|
186
|
+
});
|
|
187
|
+
var cachedPool = null;
|
|
188
|
+
function findKeysFile() {
|
|
189
|
+
const seeds = [];
|
|
190
|
+
if (typeof __dirname !== "undefined") {
|
|
191
|
+
seeds.push(__dirname);
|
|
192
|
+
}
|
|
193
|
+
seeds.push(process.cwd());
|
|
194
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
195
|
+
for (const seed of seeds) {
|
|
196
|
+
candidates.add((0, import_node_path.resolve)(seed, "shared", "keys.json"));
|
|
197
|
+
candidates.add((0, import_node_path.resolve)(seed, "..", "shared", "keys.json"));
|
|
198
|
+
candidates.add((0, import_node_path.resolve)(seed, "..", "..", "shared", "keys.json"));
|
|
199
|
+
candidates.add((0, import_node_path.resolve)(seed, "..", "..", "..", "shared", "keys.json"));
|
|
200
|
+
candidates.add((0, import_node_path.resolve)(seed, "keys.json"));
|
|
201
|
+
}
|
|
202
|
+
for (const candidate of candidates) {
|
|
203
|
+
if ((0, import_node_fs.existsSync)(candidate)) return candidate;
|
|
204
|
+
}
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
function loadPool() {
|
|
208
|
+
if (cachedPool) return cachedPool;
|
|
209
|
+
const path = findKeysFile();
|
|
210
|
+
if (!path) {
|
|
211
|
+
cachedPool = [];
|
|
212
|
+
return cachedPool;
|
|
213
|
+
}
|
|
214
|
+
try {
|
|
215
|
+
const raw = (0, import_node_fs.readFileSync)(path, "utf8");
|
|
216
|
+
const parsed = JSON.parse(raw);
|
|
217
|
+
const isEncrypted = parsed.v === 2;
|
|
218
|
+
cachedPool = (parsed.keys ?? []).filter((k) => typeof k === "string" && k.length > 0).map((k) => {
|
|
219
|
+
if (isEncrypted) {
|
|
220
|
+
try {
|
|
221
|
+
return decryptPoolKey(k);
|
|
222
|
+
} catch {
|
|
223
|
+
return "";
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return k;
|
|
227
|
+
}).filter((k) => k.length > 0 && !k.startsWith("REPLACE_WITH_"));
|
|
228
|
+
} catch {
|
|
229
|
+
cachedPool = [];
|
|
230
|
+
}
|
|
231
|
+
return cachedPool;
|
|
232
|
+
}
|
|
233
|
+
function machineSlot(poolSize) {
|
|
234
|
+
if (poolSize <= 0) return 0;
|
|
235
|
+
let id = "fallback";
|
|
236
|
+
try {
|
|
237
|
+
id = machineIdSync(true);
|
|
238
|
+
} catch {
|
|
239
|
+
id = `${process.platform}|${process.arch}|${process.env.USER ?? process.env.USERNAME ?? "user"}`;
|
|
240
|
+
}
|
|
241
|
+
const digest = (0, import_node_crypto2.createHash)("sha256").update(id).digest();
|
|
242
|
+
return digest.readUInt32BE(0) % poolSize;
|
|
243
|
+
}
|
|
244
|
+
function resolveCurrentKey() {
|
|
245
|
+
const envKey = process.env.GEMINI_API_KEY || process.env.GROQ_API_KEY || process.env.API_KEY;
|
|
246
|
+
if (envKey && envKey.trim().length > 0) {
|
|
247
|
+
return {
|
|
248
|
+
apiKey: envKey.trim(),
|
|
249
|
+
source: "env-variable",
|
|
250
|
+
poolSize: 0
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
const config = loadUserConfig();
|
|
254
|
+
const pool = loadPool();
|
|
255
|
+
if (config.apiKey && config.apiKey.trim().length > 0) {
|
|
256
|
+
return {
|
|
257
|
+
apiKey: config.apiKey,
|
|
258
|
+
source: "user-override",
|
|
259
|
+
poolSize: pool.length
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
if (pool.length === 0) return null;
|
|
263
|
+
let slot = config.keyPoolSlot;
|
|
264
|
+
if (typeof slot !== "number" || slot < 0 || slot >= pool.length) {
|
|
265
|
+
slot = machineSlot(pool.length);
|
|
266
|
+
patchUserConfig({ keyPoolSlot: slot });
|
|
267
|
+
}
|
|
268
|
+
return { apiKey: pool[slot], source: "pool", slot, poolSize: pool.length };
|
|
269
|
+
}
|
|
270
|
+
function rotateKey() {
|
|
271
|
+
const pool = loadPool();
|
|
272
|
+
if (pool.length === 0) return null;
|
|
273
|
+
const config = loadUserConfig();
|
|
274
|
+
const current = typeof config.keyPoolSlot === "number" ? config.keyPoolSlot : machineSlot(pool.length);
|
|
275
|
+
const next = (current + 1) % pool.length;
|
|
276
|
+
patchUserConfig({ keyPoolSlot: next });
|
|
277
|
+
return { apiKey: pool[next], source: "pool", slot: next, poolSize: pool.length };
|
|
278
|
+
}
|
|
279
|
+
function maskKey(key) {
|
|
280
|
+
if (!key) return "(none)";
|
|
281
|
+
if (key.length <= 8) return "********";
|
|
282
|
+
return `${key.slice(0, 4)}\u2026${key.slice(-4)}`;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// shared/groq.ts
|
|
286
|
+
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
|
287
|
+
var DEFAULT_MODEL = "llama-3.3-70b-versatile";
|
|
288
|
+
var ANSWER_MODEL = "llama-3.3-70b-versatile";
|
|
289
|
+
var CODING_MODEL = "deepseek-r1-distill-llama-70b";
|
|
290
|
+
var VISION_PROMPT = [
|
|
291
|
+
"You are an OCR and screen reader. Extract EVERYTHING visible in this screenshot.",
|
|
292
|
+
"IMPORTANT RULES:",
|
|
293
|
+
"- Extract ALL text, questions, code, options, tables, constraints, and input/output examples.",
|
|
294
|
+
"- Return the EXACT text as it appears. Do not summarize. Do not skip any part.",
|
|
295
|
+
"- Include question numbers, option letters (A, B, C, D), code snippets, EVERYTHING.",
|
|
296
|
+
"- If there is code, reproduce it EXACTLY including variable names, operators (*, +, -, /, %, etc.), and formatting.",
|
|
297
|
+
"- If you see a coding problem, extract the full problem statement, constraints, examples, and any starter code.",
|
|
298
|
+
"- NEVER say 'no question found' or 'question not provided'. There IS content on screen \u2014 extract it.",
|
|
299
|
+
"- If the text is partially visible or blurry, do your best to read it. Guess if needed.",
|
|
300
|
+
"- Output raw extracted text only. No commentary."
|
|
301
|
+
].join("\n");
|
|
302
|
+
var ANSWER_PROMPT = [
|
|
303
|
+
"You are an expert exam solver. You must provide the CORRECT answer.",
|
|
304
|
+
"",
|
|
305
|
+
"OUTPUT FORMAT: Plain text only. No markdown formatting whatsoever.",
|
|
306
|
+
"- Do NOT wrap text in ** or * for emphasis",
|
|
307
|
+
"- Do NOT use # for headings or ``` for code blocks",
|
|
308
|
+
"- Do NOT use bullet markers (-, *, +) for lists",
|
|
309
|
+
"- You MUST keep the * symbol when it appears in code as multiplication, pointer, or any operator",
|
|
310
|
+
"- Write code directly without any code fence markers",
|
|
311
|
+
"",
|
|
312
|
+
"ANSWERING RULES:",
|
|
313
|
+
"- For MCQ: State the correct option letter and the answer text. Be precise.",
|
|
314
|
+
"- For coding problems: Write the COMPLETE working solution. Include ALL operators (*, /, %, +, -, etc.) exactly as needed. Test edge cases mentally.",
|
|
315
|
+
"- For short answer: Give the precise answer only.",
|
|
316
|
+
"- For math: Show the final answer with key steps. Use * for multiplication.",
|
|
317
|
+
"- For fill-in-the-blank: Give the exact word or phrase.",
|
|
318
|
+
"- Do NOT say 'Here is', 'Sure', 'Let me explain', or any filler.",
|
|
319
|
+
"- If the question involves code, write COMPLETE runnable code with correct syntax.",
|
|
320
|
+
"- VERIFY your answer is correct before responding. Accuracy is critical.",
|
|
321
|
+
"- If you see a question, ALWAYS provide an answer. Never say 'question not provided'."
|
|
322
|
+
].join("\n");
|
|
323
|
+
var CODING_PROMPT = [
|
|
324
|
+
"You are an expert competitive programmer. You solve LeetCode, HackerRank, and CodeForces problems.",
|
|
325
|
+
"",
|
|
326
|
+
"CRITICAL RULES:",
|
|
327
|
+
"1. Write the OPTIMAL solution with the best possible time and space complexity.",
|
|
328
|
+
"2. DO NOT write brute force. Use the most efficient algorithm (sliding window, two pointers, hash maps, DP, binary search, etc.).",
|
|
329
|
+
"3. Your code MUST compile and run without errors.",
|
|
330
|
+
"4. Your code MUST pass ALL test cases including edge cases.",
|
|
331
|
+
"5. Your code MUST NOT get TLE (Time Limit Exceeded). Optimize for speed.",
|
|
332
|
+
"",
|
|
333
|
+
"CODE COMPLETENESS (VERY IMPORTANT):",
|
|
334
|
+
"- Include ALL closing brackets } and closing braces",
|
|
335
|
+
"- In C++: ALWAYS end class with }; (semicolon after closing brace)",
|
|
336
|
+
"- In Java: ALWAYS close all brackets }",
|
|
337
|
+
"- In Python: ensure proper indentation",
|
|
338
|
+
"- Include ALL necessary imports/headers",
|
|
339
|
+
"- The code must be copy-paste ready \u2014 compilable as-is",
|
|
340
|
+
"",
|
|
341
|
+
"OUTPUT FORMAT:",
|
|
342
|
+
"- Output PLAIN TEXT only. No markdown. No ``` code fences.",
|
|
343
|
+
"- Keep ALL operators: * (multiply/pointer/dereference), /, %, +, -, &, |, ^, ~, <<, >>",
|
|
344
|
+
"- Write ONLY the solution code. No explanation unless very brief.",
|
|
345
|
+
"- If the problem asks for a class Solution, write the complete class with method.",
|
|
346
|
+
"",
|
|
347
|
+
"VERIFY BEFORE RESPONDING:",
|
|
348
|
+
"- Mentally trace through the examples given in the problem",
|
|
349
|
+
"- Check edge cases: empty input, single element, large input, duplicates",
|
|
350
|
+
"- Ensure time complexity is optimal (usually O(n) or O(n log n), NOT O(n\xB2) or O(n\xB3))",
|
|
351
|
+
"- Ensure the code handles all constraints mentioned in the problem"
|
|
352
|
+
].join("\n");
|
|
353
|
+
function sleep(ms) {
|
|
354
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
355
|
+
}
|
|
356
|
+
function isRotatableError(msg) {
|
|
357
|
+
return /429|rate.?limit|too many requests|quota|resource.?exhausted|organization.?restricted|invalid.?api.?key|authentication|unauthorized|forbidden|deactivated|suspended|revoked|disabled|401|403/i.test(msg);
|
|
358
|
+
}
|
|
359
|
+
var keyCooldowns = /* @__PURE__ */ new Map();
|
|
360
|
+
function markCooling(slot) {
|
|
361
|
+
keyCooldowns.set(slot, Date.now() + 6e4);
|
|
362
|
+
}
|
|
363
|
+
function isCooling(slot) {
|
|
364
|
+
const until = keyCooldowns.get(slot);
|
|
365
|
+
if (!until) return false;
|
|
366
|
+
if (Date.now() >= until) {
|
|
367
|
+
keyCooldowns.delete(slot);
|
|
368
|
+
return false;
|
|
369
|
+
}
|
|
370
|
+
return true;
|
|
371
|
+
}
|
|
372
|
+
function isCodingProblem(text) {
|
|
373
|
+
const lower = text.toLowerCase();
|
|
374
|
+
return /leetcode|hackerrank|codeforces|codechef|geeksforgeeks|atcoder/i.test(lower) || /class\s+solution/i.test(lower) || /\bfunction\s+\w+\s*\(/i.test(lower) || /\b(input|output)\s*:/i.test(lower) && /\b(example|constraint|return)\b/i.test(lower) || /\btime\s*complexity\b/i.test(lower) || /\bgiven\s+(an?\s+)?array\b/i.test(lower) || /\b(vector|listnode|treenode|linked\s*list)\b/i.test(lower) || /\breturn\s+(the|an?)\s+/i.test(lower) && /\bexample\b/i.test(lower);
|
|
375
|
+
}
|
|
376
|
+
function stripThinkingBlocks(text) {
|
|
377
|
+
return text.replace(/<think>[\s\S]*?<\/think>/g, "").trim();
|
|
378
|
+
}
|
|
379
|
+
async function callGroq(apiKey, model, systemPrompt, userContent, temperature = 0.2) {
|
|
380
|
+
const isGem = isGeminiKey(apiKey);
|
|
381
|
+
const baseURL = isGem ? (process.env.GEMINI_BASE_URL || "https://generativelanguage.googleapis.com/v1beta") : void 0;
|
|
382
|
+
|
|
383
|
+
let finalContent = userContent;
|
|
384
|
+
let finalModel = model;
|
|
385
|
+
if (Array.isArray(userContent)) {
|
|
386
|
+
const hasImage = userContent.some(item => item && item.type === "image_url");
|
|
387
|
+
if (hasImage) {
|
|
388
|
+
if (isGem) {
|
|
389
|
+
finalModel = process.env.GEMINI_MODEL || "gemini-2.0-flash";
|
|
390
|
+
} else {
|
|
391
|
+
finalModel = "llama-3.3-70b-versatile";
|
|
392
|
+
finalContent = userContent
|
|
393
|
+
.filter(item => item && item.type === "text")
|
|
394
|
+
.map(item => item.text)
|
|
395
|
+
.join("\n\n");
|
|
396
|
+
}
|
|
397
|
+
} else {
|
|
398
|
+
finalContent = userContent.map(item => typeof item === "string" ? item : (item?.text || "")).join("\n\n");
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const client = new import_groq_sdk.default({
|
|
403
|
+
apiKey,
|
|
404
|
+
baseURL,
|
|
405
|
+
timeout: 9e4,
|
|
406
|
+
maxRetries: 0
|
|
407
|
+
});
|
|
408
|
+
const response = await client.chat.completions.create({
|
|
409
|
+
model: finalModel,
|
|
410
|
+
messages: [
|
|
411
|
+
{ role: "system", content: systemPrompt },
|
|
412
|
+
{ role: "user", content: finalContent }
|
|
413
|
+
],
|
|
414
|
+
temperature,
|
|
415
|
+
max_tokens: 8192
|
|
416
|
+
});
|
|
417
|
+
return response.choices?.[0]?.message?.content ?? "";
|
|
418
|
+
}
|
|
419
|
+
async function askGroq(apiKey, payload, model = DEFAULT_MODEL) {
|
|
420
|
+
const isGem = isGeminiKey(apiKey);
|
|
421
|
+
const gemModel = process.env.GEMINI_MODEL || "gemini-2.5-flash";
|
|
422
|
+
if (isGem) {
|
|
423
|
+
model = gemModel;
|
|
424
|
+
}
|
|
425
|
+
let extractedText = "";
|
|
426
|
+
let rawImageB64 = "";
|
|
427
|
+
const imagesToOcr = [];
|
|
428
|
+
if (payload.screenshotPages && payload.screenshotPages.length > 0) {
|
|
429
|
+
for (const page of payload.screenshotPages) {
|
|
430
|
+
const b64 = page.replace(/^data:image\/\w+;base64,/, "");
|
|
431
|
+
imagesToOcr.push(b64);
|
|
432
|
+
}
|
|
433
|
+
} else if (payload.screenshotBase64) {
|
|
434
|
+
const b64 = payload.screenshotBase64.replace(/^data:image\/\w+;base64,/, "");
|
|
435
|
+
imagesToOcr.push(b64);
|
|
436
|
+
}
|
|
437
|
+
if (imagesToOcr.length > 0) {
|
|
438
|
+
rawImageB64 = imagesToOcr[0];
|
|
439
|
+
try {
|
|
440
|
+
const visionContent = [
|
|
441
|
+
{
|
|
442
|
+
type: "text",
|
|
443
|
+
text: imagesToOcr.length > 1 ? VISION_PROMPT + "\n\nIMPORTANT: Multiple screenshots are provided showing different parts of the same page (scrolled). Extract text from ALL images and combine into one complete output." : VISION_PROMPT
|
|
444
|
+
}
|
|
445
|
+
];
|
|
446
|
+
for (const b64 of imagesToOcr) {
|
|
447
|
+
visionContent.push({
|
|
448
|
+
type: "image_url",
|
|
449
|
+
image_url: { url: `data:image/png;base64,${b64}` }
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
extractedText = await callGroq(apiKey, model, "You are an OCR assistant. Extract all text from the image(s) accurately.", visionContent);
|
|
453
|
+
} catch {
|
|
454
|
+
extractedText = "";
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
const contextParts = [];
|
|
458
|
+
if (payload.title) contextParts.push(`Window: ${payload.title}`);
|
|
459
|
+
if (payload.visibleText && payload.visibleText.length > 30) {
|
|
460
|
+
contextParts.push(`App context:
|
|
461
|
+
${payload.visibleText.slice(0, 6e3)}`);
|
|
462
|
+
}
|
|
463
|
+
if (extractedText && extractedText.length > 10) {
|
|
464
|
+
contextParts.push(`Screen content:
|
|
465
|
+
${extractedText}`);
|
|
466
|
+
}
|
|
467
|
+
if (payload.question) {
|
|
468
|
+
contextParts.push(`User question: ${payload.question}`);
|
|
469
|
+
} else {
|
|
470
|
+
contextParts.push("Solve the problem shown above. Give the correct answer.");
|
|
471
|
+
}
|
|
472
|
+
const fullContext = contextParts.join("\n\n");
|
|
473
|
+
const coding = isCodingProblem(fullContext + " " + (payload.title ?? ""));
|
|
474
|
+
const chosenModel = isGem ? gemModel : (coding ? CODING_MODEL : ANSWER_MODEL);
|
|
475
|
+
const chosenPrompt = coding ? CODING_PROMPT : ANSWER_PROMPT;
|
|
476
|
+
const temp = coding ? 0 : 0.1;
|
|
477
|
+
let answerText;
|
|
478
|
+
async function tryAnswer(mdl, prompt, content, t) {
|
|
479
|
+
return callGroq(apiKey, mdl, prompt, content, t);
|
|
480
|
+
}
|
|
481
|
+
if (extractedText && extractedText.length > 10) {
|
|
482
|
+
const answerContent = [{ type: "text", text: fullContext }];
|
|
483
|
+
try {
|
|
484
|
+
answerText = await tryAnswer(chosenModel, chosenPrompt, answerContent, temp);
|
|
485
|
+
} catch {
|
|
486
|
+
answerText = await tryAnswer(ANSWER_MODEL, chosenPrompt, answerContent, temp);
|
|
487
|
+
}
|
|
488
|
+
} else if (rawImageB64) {
|
|
489
|
+
const answerContent = [
|
|
490
|
+
{ type: "text", text: fullContext + "\n\nThe screenshot is attached. Read it and solve the problem." },
|
|
491
|
+
{ type: "image_url", image_url: { url: `data:image/png;base64,${rawImageB64}` } }
|
|
492
|
+
];
|
|
493
|
+
answerText = await callGroq(apiKey, model, chosenPrompt, answerContent, temp);
|
|
494
|
+
} else {
|
|
495
|
+
const answerContent = [{ type: "text", text: fullContext }];
|
|
496
|
+
try {
|
|
497
|
+
answerText = await tryAnswer(chosenModel, chosenPrompt, answerContent, temp);
|
|
498
|
+
} catch {
|
|
499
|
+
answerText = await tryAnswer(ANSWER_MODEL, chosenPrompt, answerContent, temp);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
answerText = stripThinkingBlocks(answerText);
|
|
503
|
+
return {
|
|
504
|
+
text: answerText || "No response generated.",
|
|
505
|
+
model: chosenModel
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
async function askWithAutoRotate(payload, model = DEFAULT_MODEL) {
|
|
509
|
+
const pool = loadPool();
|
|
510
|
+
const totalKeys = Math.max(pool.length, 1);
|
|
511
|
+
let lastError;
|
|
512
|
+
for (let attempt = 0; attempt < totalKeys; attempt++) {
|
|
513
|
+
const resolved2 = resolveCurrentKey();
|
|
514
|
+
if (!resolved2) throw new Error("No API keys available.");
|
|
515
|
+
if (resolved2.slot !== void 0 && isCooling(resolved2.slot)) {
|
|
516
|
+
rotateKey();
|
|
517
|
+
continue;
|
|
518
|
+
}
|
|
519
|
+
try {
|
|
520
|
+
return await askGroq(resolved2.apiKey, payload, model);
|
|
521
|
+
} catch (err) {
|
|
522
|
+
lastError = err;
|
|
523
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
524
|
+
if (!isRotatableError(msg)) throw err;
|
|
525
|
+
if (resolved2.slot !== void 0) markCooling(resolved2.slot);
|
|
526
|
+
rotateKey();
|
|
527
|
+
await sleep(500);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
const resolved = resolveCurrentKey();
|
|
531
|
+
if (resolved) {
|
|
532
|
+
keyCooldowns.clear();
|
|
533
|
+
return await askGroq(resolved.apiKey, payload, model);
|
|
534
|
+
}
|
|
535
|
+
throw lastError instanceof Error ? lastError : new Error(String(lastError));
|
|
536
|
+
}
|
|
537
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
538
|
+
0 && (module.exports = {
|
|
539
|
+
DEFAULT_MODEL,
|
|
540
|
+
askGroq,
|
|
541
|
+
askWithAutoRotate,
|
|
542
|
+
decryptPoolKey,
|
|
543
|
+
decryptValue,
|
|
544
|
+
encryptPoolKey,
|
|
545
|
+
encryptValue,
|
|
546
|
+
loadPool,
|
|
547
|
+
loadUserConfig,
|
|
548
|
+
maskKey,
|
|
549
|
+
patchUserConfig,
|
|
550
|
+
resolveCurrentKey,
|
|
551
|
+
rotateKey,
|
|
552
|
+
saveUserConfig
|
|
553
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
interface PageContextPayload {
|
|
2
|
+
url: string;
|
|
3
|
+
title: string;
|
|
4
|
+
visibleText: string;
|
|
5
|
+
screenshotBase64?: string;
|
|
6
|
+
/** Multiple screenshots from scroll-capture (base64 PNG each) */
|
|
7
|
+
screenshotPages?: string[];
|
|
8
|
+
question?: string;
|
|
9
|
+
}
|
|
10
|
+
interface AiResult {
|
|
11
|
+
text: string;
|
|
12
|
+
model: string;
|
|
13
|
+
}
|
|
14
|
+
interface UserConfig {
|
|
15
|
+
apiKey?: string;
|
|
16
|
+
model: string;
|
|
17
|
+
autoSummarizeOnLoad: boolean;
|
|
18
|
+
theme: "dark" | "light";
|
|
19
|
+
stealth?: boolean;
|
|
20
|
+
keyPoolSlot?: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
declare function encryptValue(raw: string): string;
|
|
24
|
+
declare function decryptValue(payload: string): string;
|
|
25
|
+
declare function encryptPoolKey(raw: string): string;
|
|
26
|
+
declare function decryptPoolKey(payload: string): string;
|
|
27
|
+
|
|
28
|
+
declare function saveUserConfig(config: UserConfig): void;
|
|
29
|
+
declare function loadUserConfig(): UserConfig;
|
|
30
|
+
declare function patchUserConfig(patch: Partial<UserConfig>): UserConfig;
|
|
31
|
+
|
|
32
|
+
declare const DEFAULT_MODEL = "meta-llama/llama-4-scout-17b-16e-instruct";
|
|
33
|
+
declare function askGroq(apiKey: string, payload: PageContextPayload, model?: string): Promise<AiResult>;
|
|
34
|
+
declare function askWithAutoRotate(payload: PageContextPayload, model?: string): Promise<AiResult>;
|
|
35
|
+
|
|
36
|
+
declare function loadPool(): string[];
|
|
37
|
+
interface ResolvedKey {
|
|
38
|
+
apiKey: string;
|
|
39
|
+
source: "user-override" | "pool";
|
|
40
|
+
slot?: number;
|
|
41
|
+
poolSize: number;
|
|
42
|
+
}
|
|
43
|
+
declare function resolveCurrentKey(): ResolvedKey | null;
|
|
44
|
+
declare function rotateKey(): ResolvedKey | null;
|
|
45
|
+
declare function maskKey(key: string): string;
|
|
46
|
+
|
|
47
|
+
export { type AiResult, DEFAULT_MODEL, type PageContextPayload, type ResolvedKey, type UserConfig, askGroq, askWithAutoRotate, decryptPoolKey, decryptValue, encryptPoolKey, encryptValue, loadPool, loadUserConfig, maskKey, patchUserConfig, resolveCurrentKey, rotateKey, saveUserConfig };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
interface PageContextPayload {
|
|
2
|
+
url: string;
|
|
3
|
+
title: string;
|
|
4
|
+
visibleText: string;
|
|
5
|
+
screenshotBase64?: string;
|
|
6
|
+
/** Multiple screenshots from scroll-capture (base64 PNG each) */
|
|
7
|
+
screenshotPages?: string[];
|
|
8
|
+
question?: string;
|
|
9
|
+
}
|
|
10
|
+
interface AiResult {
|
|
11
|
+
text: string;
|
|
12
|
+
model: string;
|
|
13
|
+
}
|
|
14
|
+
interface UserConfig {
|
|
15
|
+
apiKey?: string;
|
|
16
|
+
model: string;
|
|
17
|
+
autoSummarizeOnLoad: boolean;
|
|
18
|
+
theme: "dark" | "light";
|
|
19
|
+
stealth?: boolean;
|
|
20
|
+
keyPoolSlot?: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
declare function encryptValue(raw: string): string;
|
|
24
|
+
declare function decryptValue(payload: string): string;
|
|
25
|
+
declare function encryptPoolKey(raw: string): string;
|
|
26
|
+
declare function decryptPoolKey(payload: string): string;
|
|
27
|
+
|
|
28
|
+
declare function saveUserConfig(config: UserConfig): void;
|
|
29
|
+
declare function loadUserConfig(): UserConfig;
|
|
30
|
+
declare function patchUserConfig(patch: Partial<UserConfig>): UserConfig;
|
|
31
|
+
|
|
32
|
+
declare const DEFAULT_MODEL = "meta-llama/llama-4-scout-17b-16e-instruct";
|
|
33
|
+
declare function askGroq(apiKey: string, payload: PageContextPayload, model?: string): Promise<AiResult>;
|
|
34
|
+
declare function askWithAutoRotate(payload: PageContextPayload, model?: string): Promise<AiResult>;
|
|
35
|
+
|
|
36
|
+
declare function loadPool(): string[];
|
|
37
|
+
interface ResolvedKey {
|
|
38
|
+
apiKey: string;
|
|
39
|
+
source: "user-override" | "pool";
|
|
40
|
+
slot?: number;
|
|
41
|
+
poolSize: number;
|
|
42
|
+
}
|
|
43
|
+
declare function resolveCurrentKey(): ResolvedKey | null;
|
|
44
|
+
declare function rotateKey(): ResolvedKey | null;
|
|
45
|
+
declare function maskKey(key: string): string;
|
|
46
|
+
|
|
47
|
+
export { type AiResult, DEFAULT_MODEL, type PageContextPayload, type ResolvedKey, type UserConfig, askGroq, askWithAutoRotate, decryptPoolKey, decryptValue, encryptPoolKey, encryptValue, loadPool, loadUserConfig, maskKey, patchUserConfig, resolveCurrentKey, rotateKey, saveUserConfig };
|