yiyan-browser-agent 1.0.1 → 1.0.2
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/LICENSE +1 -1
- package/README.md +265 -94
- package/package.json +28 -30
- package/src/agent.js +257 -0
- package/src/browser.js +624 -0
- package/src/calibrate.js +178 -0
- package/src/config.js +68 -0
- package/src/index.js +218 -0
- package/src/logger.js +118 -0
- package/src/parser.js +272 -0
- package/src/postinstall.js +47 -0
- package/src/prompt.js +188 -0
- package/src/tools.js +547 -0
- package/dist/cli.d.ts +0 -26
- package/dist/cli.js +0 -1048
- package/dist/cli.js.map +0 -1
- package/dist/index.d.ts +0 -37
- package/dist/index.js +0 -874
- package/dist/index.js.map +0 -1
- package/dist/types-BhQ78DYf.d.ts +0 -39
package/dist/cli.js
DELETED
|
@@ -1,1048 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
3
|
-
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
4
|
-
}) : x)(function(x) {
|
|
5
|
-
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
6
|
-
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
7
|
-
});
|
|
8
|
-
|
|
9
|
-
// src/agent.ts
|
|
10
|
-
import path3 from "path";
|
|
11
|
-
import os2 from "os";
|
|
12
|
-
|
|
13
|
-
// src/types.ts
|
|
14
|
-
var YiyanAgentError = class extends Error {
|
|
15
|
-
constructor(type, message) {
|
|
16
|
-
super(message);
|
|
17
|
-
this.type = type;
|
|
18
|
-
this.name = "YiyanAgentError";
|
|
19
|
-
}
|
|
20
|
-
type;
|
|
21
|
-
};
|
|
22
|
-
var DEFAULT_OPTIONS = {
|
|
23
|
-
timeout: 12e4,
|
|
24
|
-
retryCount: 3,
|
|
25
|
-
profileDir: "",
|
|
26
|
-
chromePath: ""
|
|
27
|
-
};
|
|
28
|
-
var YIYAN_CHAT_URL = "https://yiyan.baidu.com/";
|
|
29
|
-
|
|
30
|
-
// src/profile.ts
|
|
31
|
-
import path from "path";
|
|
32
|
-
import fs from "fs";
|
|
33
|
-
import fsp from "fs/promises";
|
|
34
|
-
function getChromeExecutablePath(platform) {
|
|
35
|
-
switch (platform) {
|
|
36
|
-
case "win32":
|
|
37
|
-
const programFiles = process.env.PROGRAMFILES || "C:\\Program Files";
|
|
38
|
-
return path.join(programFiles, "Google", "Chrome", "Application", "chrome.exe");
|
|
39
|
-
case "darwin":
|
|
40
|
-
return "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome";
|
|
41
|
-
case "linux":
|
|
42
|
-
return "/usr/bin/google-chrome";
|
|
43
|
-
default:
|
|
44
|
-
return null;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
var PROFILE_TARGET_NAME = "chrome-profile";
|
|
48
|
-
async function clearCopiedProfile(profileDir) {
|
|
49
|
-
const targetPath = path.join(profileDir, PROFILE_TARGET_NAME);
|
|
50
|
-
if (fs.existsSync(targetPath)) {
|
|
51
|
-
await fsp.rm(targetPath, { recursive: true, force: true });
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
function profileExists(profileDir) {
|
|
55
|
-
const targetPath = path.join(profileDir, PROFILE_TARGET_NAME);
|
|
56
|
-
return fs.existsSync(targetPath);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// src/browser.ts
|
|
60
|
-
import { chromium } from "playwright-core";
|
|
61
|
-
import http from "http";
|
|
62
|
-
import { spawn, execSync } from "child_process";
|
|
63
|
-
import path2 from "path";
|
|
64
|
-
import os from "os";
|
|
65
|
-
import fs2 from "fs";
|
|
66
|
-
var CDP_PORT = 19222;
|
|
67
|
-
var DEBUG_DIR = path2.join(os.homedir(), ".yiyan-browser-agent", "debug");
|
|
68
|
-
function log(verbose, msg) {
|
|
69
|
-
if (verbose) {
|
|
70
|
-
process.stderr.write(`[yiyan-agent] ${msg}
|
|
71
|
-
`);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
function ensureDebugDir() {
|
|
75
|
-
if (!fs2.existsSync(DEBUG_DIR)) {
|
|
76
|
-
fs2.mkdirSync(DEBUG_DIR, { recursive: true });
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
function killProcessOnPort(port) {
|
|
80
|
-
try {
|
|
81
|
-
if (process.platform === "win32") {
|
|
82
|
-
const output = execSync(
|
|
83
|
-
`netstat -ano | findstr :${port} | findstr LISTENING`,
|
|
84
|
-
{ encoding: "utf-8", timeout: 5e3 }
|
|
85
|
-
).trim();
|
|
86
|
-
if (output) {
|
|
87
|
-
const lines = output.split("\n").filter((l) => l.trim());
|
|
88
|
-
const pids = /* @__PURE__ */ new Set();
|
|
89
|
-
for (const line of lines) {
|
|
90
|
-
const parts = line.trim().split(/\s+/);
|
|
91
|
-
const pid = parts[parts.length - 1];
|
|
92
|
-
if (pid && /^\d+$/.test(pid)) pids.add(pid);
|
|
93
|
-
}
|
|
94
|
-
for (const pid of pids) {
|
|
95
|
-
try {
|
|
96
|
-
execSync(`taskkill /F /T /PID ${pid}`, { timeout: 5e3 });
|
|
97
|
-
} catch {
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
} else {
|
|
102
|
-
const output = execSync(
|
|
103
|
-
`lsof -ti :${port}`,
|
|
104
|
-
{ encoding: "utf-8", timeout: 5e3 }
|
|
105
|
-
).trim();
|
|
106
|
-
if (output) {
|
|
107
|
-
const pids = output.split("\n").filter((l) => l.trim());
|
|
108
|
-
for (const pid of pids) {
|
|
109
|
-
try {
|
|
110
|
-
process.kill(parseInt(pid, 10), "SIGKILL");
|
|
111
|
-
} catch {
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
} catch {
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
function killProcessTree(pid) {
|
|
120
|
-
try {
|
|
121
|
-
if (process.platform === "win32") {
|
|
122
|
-
execSync(`taskkill /F /T /PID ${pid}`, { timeout: 5e3 });
|
|
123
|
-
} else {
|
|
124
|
-
try {
|
|
125
|
-
process.kill(-pid, "SIGKILL");
|
|
126
|
-
} catch {
|
|
127
|
-
process.kill(pid, "SIGKILL");
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
} catch {
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
function waitForCDP(port, timeout = 15e3) {
|
|
134
|
-
return new Promise((resolve, reject) => {
|
|
135
|
-
const start = Date.now();
|
|
136
|
-
const check = () => {
|
|
137
|
-
http.get(`http://localhost:${port}/json/version`, (res) => {
|
|
138
|
-
let data = "";
|
|
139
|
-
res.on("data", (chunk) => data += chunk);
|
|
140
|
-
res.on("end", () => {
|
|
141
|
-
try {
|
|
142
|
-
resolve(JSON.parse(data));
|
|
143
|
-
} catch (e) {
|
|
144
|
-
reject(e);
|
|
145
|
-
}
|
|
146
|
-
});
|
|
147
|
-
}).on("error", () => {
|
|
148
|
-
if (Date.now() - start > timeout) reject(new Error("CDP connection timeout"));
|
|
149
|
-
else setTimeout(check, 500);
|
|
150
|
-
});
|
|
151
|
-
};
|
|
152
|
-
check();
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
async function launchBrowser(options) {
|
|
156
|
-
const { chromePath, profilePath, headless, timeout = 3e4, verbose = false } = options;
|
|
157
|
-
try {
|
|
158
|
-
log(verbose, "\u6E05\u7406\u6B8B\u7559 Chrome \u8FDB\u7A0B...");
|
|
159
|
-
killProcessOnPort(CDP_PORT);
|
|
160
|
-
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
161
|
-
const chromeArgs = [
|
|
162
|
-
`--remote-debugging-port=${CDP_PORT}`,
|
|
163
|
-
`--user-data-dir=${profilePath}`,
|
|
164
|
-
"--no-first-run",
|
|
165
|
-
"--no-default-browser-check",
|
|
166
|
-
"--disable-blink-features=AutomationControlled"
|
|
167
|
-
];
|
|
168
|
-
if (headless) {
|
|
169
|
-
chromeArgs.push("--headless=new");
|
|
170
|
-
}
|
|
171
|
-
log(verbose, `\u542F\u52A8 Chrome: ${chromePath} ${headless ? "(headless)" : "(headed)"}`);
|
|
172
|
-
const chromeProcess = spawn(chromePath, chromeArgs, {
|
|
173
|
-
detached: true,
|
|
174
|
-
stdio: "ignore"
|
|
175
|
-
});
|
|
176
|
-
chromeProcess.unref();
|
|
177
|
-
const spawnError = new Promise((_, reject) => {
|
|
178
|
-
chromeProcess.on("error", (err) => {
|
|
179
|
-
reject(new YiyanAgentError("BROWSER_LAUNCH", `Failed to spawn Chrome: ${err.message}`));
|
|
180
|
-
});
|
|
181
|
-
});
|
|
182
|
-
log(verbose, "\u7B49\u5F85 CDP \u7AEF\u53E3\u5C31\u7EEA...");
|
|
183
|
-
await Promise.race([
|
|
184
|
-
waitForCDP(CDP_PORT, timeout),
|
|
185
|
-
spawnError
|
|
186
|
-
]);
|
|
187
|
-
log(verbose, "\u901A\u8FC7 CDP \u8FDE\u63A5\u6D4F\u89C8\u5668...");
|
|
188
|
-
const cdpBrowser = await chromium.connectOverCDP(`http://localhost:${CDP_PORT}`);
|
|
189
|
-
const contexts = cdpBrowser.contexts();
|
|
190
|
-
const context = contexts.length > 0 ? contexts[0] : await cdpBrowser.newContext();
|
|
191
|
-
context.setDefaultTimeout(timeout);
|
|
192
|
-
const pages = context.pages();
|
|
193
|
-
const page = pages.length > 0 ? pages[0] : await context.newPage();
|
|
194
|
-
log(verbose, "\u6D4F\u89C8\u5668\u542F\u52A8\u5B8C\u6210");
|
|
195
|
-
return { browser: context, cdpBrowser, page, chromeProcess };
|
|
196
|
-
} catch (error) {
|
|
197
|
-
killProcessOnPort(CDP_PORT);
|
|
198
|
-
throw new YiyanAgentError(
|
|
199
|
-
"BROWSER_LAUNCH",
|
|
200
|
-
`Failed to launch browser: ${error instanceof Error ? error.message : String(error)}`
|
|
201
|
-
);
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
async function closeBrowser(browser, chromeProcess, cdpBrowser, verbose) {
|
|
205
|
-
log(!!verbose, "\u5173\u95ED\u6D4F\u89C8\u5668...");
|
|
206
|
-
if (cdpBrowser) {
|
|
207
|
-
try {
|
|
208
|
-
await cdpBrowser.close();
|
|
209
|
-
} catch {
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
try {
|
|
213
|
-
const pages = browser.pages();
|
|
214
|
-
for (const page of pages) {
|
|
215
|
-
await page.close().catch(() => {
|
|
216
|
-
});
|
|
217
|
-
}
|
|
218
|
-
} catch {
|
|
219
|
-
}
|
|
220
|
-
if (chromeProcess && chromeProcess.pid) {
|
|
221
|
-
killProcessTree(chromeProcess.pid);
|
|
222
|
-
}
|
|
223
|
-
killProcessOnPort(CDP_PORT);
|
|
224
|
-
log(!!verbose, "\u6D4F\u89C8\u5668\u5DF2\u5173\u95ED");
|
|
225
|
-
}
|
|
226
|
-
async function navigateToYiyan(page, verbose = false) {
|
|
227
|
-
log(verbose, "\u5BFC\u822A\u5230\u6587\u5FC3\u4E00\u8A00\u804A\u5929\u9875\u9762...");
|
|
228
|
-
await page.goto(YIYAN_CHAT_URL, {
|
|
229
|
-
waitUntil: "networkidle",
|
|
230
|
-
timeout: 6e4
|
|
231
|
-
});
|
|
232
|
-
await page.waitForTimeout(5e3);
|
|
233
|
-
try {
|
|
234
|
-
await page.waitForSelector('[contenteditable="true"]', { timeout: 15e3 });
|
|
235
|
-
log(verbose, "\u9875\u9762\u52A0\u8F7D\u5B8C\u6210\uFF0C\u8F93\u5165\u6846\u5DF2\u5C31\u7EEA");
|
|
236
|
-
} catch {
|
|
237
|
-
log(verbose, "\u8B66\u544A\uFF1A\u672A\u68C0\u6D4B\u5230\u8F93\u5165\u6846\uFF0C\u7EE7\u7EED\u6267\u884C");
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
async function checkCaptcha(page, verbose = false) {
|
|
241
|
-
const captchaIndicators = [
|
|
242
|
-
"\u8BF7\u5B8C\u6210\u4E0B\u5217\u9A8C\u8BC1\u540E\u7EE7\u7EED",
|
|
243
|
-
"\u6309\u4F4F\u5DE6\u8FB9\u6309\u94AE\u62D6\u52A8",
|
|
244
|
-
"\u6ED1\u52A8\u9A8C\u8BC1",
|
|
245
|
-
"\u70B9\u51FB\u9A8C\u8BC1",
|
|
246
|
-
"\u5B89\u5168\u9A8C\u8BC1",
|
|
247
|
-
"captcha",
|
|
248
|
-
"\u8BF7\u5B8C\u6210\u9A8C\u8BC1"
|
|
249
|
-
];
|
|
250
|
-
const hasCaptcha = await page.evaluate((indicators) => {
|
|
251
|
-
const bodyText = document.body.innerText || "";
|
|
252
|
-
for (const indicator of indicators) {
|
|
253
|
-
if (bodyText.includes(indicator)) return true;
|
|
254
|
-
}
|
|
255
|
-
const dialogs = document.querySelectorAll('[role="dialog"], [class*="captcha"], [class*="verify"]');
|
|
256
|
-
if (dialogs.length > 0) return true;
|
|
257
|
-
return false;
|
|
258
|
-
}, captchaIndicators);
|
|
259
|
-
if (hasCaptcha) {
|
|
260
|
-
log(verbose, "\u26A0 \u68C0\u6D4B\u5230\u9A8C\u8BC1\u7801\u5F39\u7A97\uFF01");
|
|
261
|
-
}
|
|
262
|
-
return hasCaptcha;
|
|
263
|
-
}
|
|
264
|
-
async function checkLoggedIn(page, verbose = false) {
|
|
265
|
-
const isLoggedIn = await page.evaluate(() => {
|
|
266
|
-
const bodyText = document.body.innerText || "";
|
|
267
|
-
if (bodyText.includes("\u672A\u767B\u5F55")) return false;
|
|
268
|
-
const loginButtons = document.querySelectorAll("button");
|
|
269
|
-
for (const btn of loginButtons) {
|
|
270
|
-
const text = btn.textContent?.trim() || "";
|
|
271
|
-
if (text === "\u767B\u5F55") {
|
|
272
|
-
return false;
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
return true;
|
|
276
|
-
});
|
|
277
|
-
if (isLoggedIn) {
|
|
278
|
-
log(verbose, "\u2713 \u5DF2\u68C0\u6D4B\u5230\u767B\u5F55\u72B6\u6001");
|
|
279
|
-
} else {
|
|
280
|
-
log(verbose, "\u26A0 \u672A\u68C0\u6D4B\u5230\u767B\u5F55\u72B6\u6001");
|
|
281
|
-
}
|
|
282
|
-
return isLoggedIn;
|
|
283
|
-
}
|
|
284
|
-
async function waitForUserAction(page, reason, verbose = false) {
|
|
285
|
-
const reasonText = reason === "captcha" ? "\u68C0\u6D4B\u5230\u9A8C\u8BC1\u7801\uFF0C\u8BF7\u5728\u6D4F\u89C8\u5668\u4E2D\u624B\u52A8\u5B8C\u6210\u9A8C\u8BC1" : reason === "login" ? "\u68C0\u6D4B\u5230\u672A\u767B\u5F55\uFF0C\u8BF7\u5728\u6D4F\u89C8\u5668\u4E2D\u624B\u52A8\u767B\u5F55" : "AI \u672A\u56DE\u590D\uFF0C\u8BF7\u68C0\u67E5\u6D4F\u89C8\u5668\u662F\u5426\u9700\u8981\u624B\u52A8\u64CD\u4F5C\uFF08\u9A8C\u8BC1\u7801/\u767B\u5F55\uFF09";
|
|
286
|
-
process.stderr.write(`
|
|
287
|
-
[yiyan-agent] \u26A0 ${reasonText}
|
|
288
|
-
`);
|
|
289
|
-
process.stderr.write("[yiyan-agent] \u7B49\u5F85\u60A8\u64CD\u4F5C\u5B8C\u6210...\uFF08\u64CD\u4F5C\u5B8C\u6210\u540E\u4F1A\u81EA\u52A8\u7EE7\u7EED\uFF09\n\n");
|
|
290
|
-
const maxWait = 18e4;
|
|
291
|
-
const startTime = Date.now();
|
|
292
|
-
while (Date.now() - startTime < maxWait) {
|
|
293
|
-
await new Promise((resolve) => setTimeout(resolve, 2e3));
|
|
294
|
-
if (reason === "captcha") {
|
|
295
|
-
const stillHasCaptcha = await checkCaptcha(page, false);
|
|
296
|
-
if (!stillHasCaptcha) {
|
|
297
|
-
log(verbose, "\u2713 \u9A8C\u8BC1\u7801\u5DF2\u901A\u8FC7\uFF01");
|
|
298
|
-
return;
|
|
299
|
-
}
|
|
300
|
-
} else if (reason === "login") {
|
|
301
|
-
const loggedIn = await checkLoggedIn(page, false);
|
|
302
|
-
if (loggedIn) {
|
|
303
|
-
log(verbose, "\u2713 \u767B\u5F55\u6210\u529F\uFF01");
|
|
304
|
-
return;
|
|
305
|
-
}
|
|
306
|
-
} else {
|
|
307
|
-
const hasReply = await page.evaluate(() => {
|
|
308
|
-
const answerBox = document.querySelector('[class*="answerBox"]');
|
|
309
|
-
if (!answerBox) return false;
|
|
310
|
-
const text = answerBox.innerText?.trim() || "";
|
|
311
|
-
return text.length > 0;
|
|
312
|
-
});
|
|
313
|
-
if (hasReply) {
|
|
314
|
-
log(verbose, "\u2713 AI \u5DF2\u5F00\u59CB\u56DE\u590D\uFF01");
|
|
315
|
-
return;
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
log(verbose, "\u26A0 \u7B49\u5F85\u7528\u6237\u64CD\u4F5C\u8D85\u65F6\uFF083\u5206\u949F\uFF09");
|
|
320
|
-
}
|
|
321
|
-
async function sendMessage(page, message, verbose = false, headful = false) {
|
|
322
|
-
const inputSelector = '[contenteditable="true"]';
|
|
323
|
-
log(verbose, "\u7B49\u5F85\u8F93\u5165\u6846\u51FA\u73B0...");
|
|
324
|
-
const inputElement = await page.waitForSelector(inputSelector, { timeout: 15e3 });
|
|
325
|
-
if (!inputElement) {
|
|
326
|
-
log(verbose, "\u26A0 \u8F93\u5165\u6846\u672A\u627E\u5230\uFF01");
|
|
327
|
-
throw new YiyanAgentError("NETWORK", "Input element not found on page");
|
|
328
|
-
}
|
|
329
|
-
log(verbose, "\u2713 \u8F93\u5165\u6846\u5DF2\u627E\u5230");
|
|
330
|
-
await inputElement.click();
|
|
331
|
-
await page.waitForTimeout(500);
|
|
332
|
-
log(verbose, `\u8F93\u5165\u95EE\u9898: "${message}"`);
|
|
333
|
-
await page.keyboard.type(message, { delay: 30 });
|
|
334
|
-
const filledValue = await inputElement.innerText();
|
|
335
|
-
if (filledValue.includes(message)) {
|
|
336
|
-
log(verbose, "\u2713 \u95EE\u9898\u5DF2\u6210\u529F\u586B\u5165\u8F93\u5165\u6846");
|
|
337
|
-
} else {
|
|
338
|
-
log(verbose, `\u26A0 \u8F93\u5165\u6846\u5185\u5BB9: "${filledValue.substring(0, 50)}"`);
|
|
339
|
-
}
|
|
340
|
-
log(verbose, "\u6309 Enter \u53D1\u9001\u6D88\u606F...");
|
|
341
|
-
await page.keyboard.press("Enter");
|
|
342
|
-
await page.waitForTimeout(3e3);
|
|
343
|
-
if (await checkCaptcha(page, verbose)) {
|
|
344
|
-
if (headful) {
|
|
345
|
-
await waitForUserAction(page, "captcha", verbose);
|
|
346
|
-
log(verbose, "\u9A8C\u8BC1\u7801\u5DF2\u901A\u8FC7\uFF0C\u91CD\u65B0\u53D1\u9001\u6D88\u606F...");
|
|
347
|
-
const inputEl = await page.waitForSelector(inputSelector, { timeout: 1e4 });
|
|
348
|
-
if (inputEl) {
|
|
349
|
-
await inputEl.click();
|
|
350
|
-
await page.waitForTimeout(300);
|
|
351
|
-
await page.keyboard.type(message, { delay: 30 });
|
|
352
|
-
await page.keyboard.press("Enter");
|
|
353
|
-
await page.waitForTimeout(3e3);
|
|
354
|
-
}
|
|
355
|
-
} else {
|
|
356
|
-
throw new YiyanAgentError(
|
|
357
|
-
"CAPTCHA",
|
|
358
|
-
'Yiyan detected automation and triggered a captcha. Use headed mode (default) to manually solve it, or run "login" first.'
|
|
359
|
-
);
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
const url = page.url();
|
|
363
|
-
log(verbose, `\u5F53\u524D URL: ${url}`);
|
|
364
|
-
if (url.includes("/chat/")) {
|
|
365
|
-
log(verbose, "\u2713 \u5DF2\u81EA\u52A8\u8FDB\u5165\u5BF9\u8BDD\u9875\u9762");
|
|
366
|
-
} else if (url === "https://yiyan.baidu.com/" || url === "https://yiyan.baidu.com") {
|
|
367
|
-
log(verbose, "\u4ECD\u5728\u6B22\u8FCE\u9875\uFF0C\u5C1D\u8BD5\u70B9\u51FB\u53D1\u9001\u6309\u94AE...");
|
|
368
|
-
try {
|
|
369
|
-
const sendBtn = page.locator('[class*="send__"]').first();
|
|
370
|
-
await sendBtn.click({ timeout: 5e3 });
|
|
371
|
-
await page.waitForTimeout(3e3);
|
|
372
|
-
log(verbose, "\u2713 \u5DF2\u70B9\u51FB\u53D1\u9001\u6309\u94AE");
|
|
373
|
-
} catch {
|
|
374
|
-
log(verbose, "\u26A0 \u70B9\u51FB\u53D1\u9001\u6309\u94AE\u5931\u8D25\uFF0C\u7EE7\u7EED\u7B49\u5F85\u56DE\u590D...");
|
|
375
|
-
}
|
|
376
|
-
} else {
|
|
377
|
-
log(verbose, `\u5F53\u524D\u9875\u9762: ${url}`);
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
async function saveDebugScreenshot(page, name, verbose) {
|
|
381
|
-
try {
|
|
382
|
-
ensureDebugDir();
|
|
383
|
-
const screenshotPath = path2.join(DEBUG_DIR, `${name}.png`);
|
|
384
|
-
await page.screenshot({ path: screenshotPath, fullPage: true });
|
|
385
|
-
log(verbose, `\u8C03\u8BD5\u622A\u56FE\u5DF2\u4FDD\u5B58: ${screenshotPath}`);
|
|
386
|
-
} catch {
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
async function waitForReply(page, timeout, verbose = false, headful = false) {
|
|
390
|
-
const maxWait = Math.min(timeout, 6e4);
|
|
391
|
-
log(verbose, `\u7B49\u5F85 AI \u56DE\u590D\uFF08\u6700\u591A ${maxWait / 1e3} \u79D2\uFF09...`);
|
|
392
|
-
const startTime = Date.now();
|
|
393
|
-
let lastLen = 0;
|
|
394
|
-
let stableCount = 0;
|
|
395
|
-
let replyStarted = false;
|
|
396
|
-
while (Date.now() - startTime < maxWait) {
|
|
397
|
-
const state = await page.evaluate(() => {
|
|
398
|
-
const answerBox = document.querySelector('[class*="answerBox"]');
|
|
399
|
-
if (answerBox) {
|
|
400
|
-
const text = answerBox.innerText?.trim() || "";
|
|
401
|
-
return {
|
|
402
|
-
hasAiReply: text.length > 0,
|
|
403
|
-
aiTextLen: text.length,
|
|
404
|
-
aiPreview: text.substring(0, 100)
|
|
405
|
-
};
|
|
406
|
-
}
|
|
407
|
-
const cardList = document.querySelector('[class*="dialogueCardList"]');
|
|
408
|
-
if (cardList) {
|
|
409
|
-
const lastCard = cardList.lastElementChild;
|
|
410
|
-
if (lastCard) {
|
|
411
|
-
const text = lastCard.innerText?.trim() || "";
|
|
412
|
-
return {
|
|
413
|
-
hasAiReply: text.length > 0,
|
|
414
|
-
aiTextLen: text.length,
|
|
415
|
-
aiPreview: text.substring(0, 100)
|
|
416
|
-
};
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
return { hasAiReply: false, aiTextLen: 0, aiPreview: "" };
|
|
420
|
-
});
|
|
421
|
-
if (state.hasAiReply && state.aiTextLen > 0) {
|
|
422
|
-
if (!replyStarted) {
|
|
423
|
-
log(verbose, `\u2713 AI \u56DE\u590D\u5DF2\u5F00\u59CB\u751F\u6210\uFF08${state.aiTextLen}\u5B57\uFF09: ${state.aiPreview.substring(0, 60)}...`);
|
|
424
|
-
replyStarted = true;
|
|
425
|
-
}
|
|
426
|
-
if (state.aiTextLen === lastLen) {
|
|
427
|
-
stableCount++;
|
|
428
|
-
if (stableCount >= 3) {
|
|
429
|
-
log(verbose, `\u2713 AI \u56DE\u590D\u5DF2\u5B8C\u6210\uFF08${state.aiTextLen}\u5B57\uFF0C\u8FDE\u7EED ${stableCount} \u6B21\u957F\u5EA6\u4E0D\u53D8\uFF09`);
|
|
430
|
-
break;
|
|
431
|
-
}
|
|
432
|
-
} else {
|
|
433
|
-
stableCount = 0;
|
|
434
|
-
lastLen = state.aiTextLen;
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
await page.waitForTimeout(1500);
|
|
438
|
-
}
|
|
439
|
-
if (!replyStarted) {
|
|
440
|
-
if (await checkCaptcha(page, verbose)) {
|
|
441
|
-
if (headful) {
|
|
442
|
-
await waitForUserAction(page, "captcha", verbose);
|
|
443
|
-
const retryStart = Date.now();
|
|
444
|
-
const retryMaxWait = 3e4;
|
|
445
|
-
while (Date.now() - retryStart < retryMaxWait) {
|
|
446
|
-
const retryState = await page.evaluate(() => {
|
|
447
|
-
const answerBox = document.querySelector('[class*="answerBox"]');
|
|
448
|
-
if (answerBox) {
|
|
449
|
-
const text = answerBox.innerText?.trim() || "";
|
|
450
|
-
return { hasAiReply: text.length > 0, aiTextLen: text.length };
|
|
451
|
-
}
|
|
452
|
-
return { hasAiReply: false, aiTextLen: 0 };
|
|
453
|
-
});
|
|
454
|
-
if (retryState.hasAiReply) {
|
|
455
|
-
log(verbose, "\u2713 \u9A8C\u8BC1\u7801\u901A\u8FC7\u540E AI \u5DF2\u5F00\u59CB\u56DE\u590D");
|
|
456
|
-
replyStarted = true;
|
|
457
|
-
break;
|
|
458
|
-
}
|
|
459
|
-
await page.waitForTimeout(1500);
|
|
460
|
-
}
|
|
461
|
-
} else {
|
|
462
|
-
throw new YiyanAgentError(
|
|
463
|
-
"CAPTCHA",
|
|
464
|
-
'Yiyan detected automation and triggered a captcha. Use headed mode (default) to manually solve it, or run "login" first.'
|
|
465
|
-
);
|
|
466
|
-
}
|
|
467
|
-
} else if (headful) {
|
|
468
|
-
await waitForUserAction(page, "no-reply", verbose);
|
|
469
|
-
const retryStart = Date.now();
|
|
470
|
-
const retryMaxWait = 6e4;
|
|
471
|
-
while (Date.now() - retryStart < retryMaxWait) {
|
|
472
|
-
const retryState = await page.evaluate(() => {
|
|
473
|
-
const answerBox = document.querySelector('[class*="answerBox"]');
|
|
474
|
-
if (answerBox) {
|
|
475
|
-
const text = answerBox.innerText?.trim() || "";
|
|
476
|
-
return { hasAiReply: text.length > 0, aiTextLen: text.length };
|
|
477
|
-
}
|
|
478
|
-
return { hasAiReply: false, aiTextLen: 0 };
|
|
479
|
-
});
|
|
480
|
-
if (retryState.hasAiReply) {
|
|
481
|
-
if (retryState.aiTextLen === lastLen) {
|
|
482
|
-
stableCount++;
|
|
483
|
-
if (stableCount >= 3) {
|
|
484
|
-
log(verbose, `\u2713 \u7528\u6237\u64CD\u4F5C\u540E AI \u56DE\u590D\u5DF2\u5B8C\u6210\uFF08${retryState.aiTextLen}\u5B57\uFF09`);
|
|
485
|
-
replyStarted = true;
|
|
486
|
-
break;
|
|
487
|
-
}
|
|
488
|
-
} else {
|
|
489
|
-
stableCount = 0;
|
|
490
|
-
lastLen = retryState.aiTextLen;
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
await page.waitForTimeout(1500);
|
|
494
|
-
}
|
|
495
|
-
} else {
|
|
496
|
-
log(verbose, "\u26A0 \u7B49\u5F85\u8D85\u65F6\uFF0CAI \u56DE\u590D\u6587\u672C\u672A\u51FA\u73B0");
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
await page.waitForTimeout(2e3);
|
|
500
|
-
const pageDump = await page.evaluate(() => {
|
|
501
|
-
const result = {};
|
|
502
|
-
const selectors = [
|
|
503
|
-
'[class*="answerBox"]',
|
|
504
|
-
'[class*="dialogueCardList"]',
|
|
505
|
-
'[class*="dialogue_card_item"]',
|
|
506
|
-
'[class*="chatViewer"]',
|
|
507
|
-
'[class*="flowBox"]',
|
|
508
|
-
'[class*="roleSystem"]',
|
|
509
|
-
'[class*="mdRenderContainer"]',
|
|
510
|
-
'[class*="agent-markdown"]',
|
|
511
|
-
'[class*="markdown"]',
|
|
512
|
-
'[class*="content"]',
|
|
513
|
-
'[class*="chat"]',
|
|
514
|
-
'[class*="message"]'
|
|
515
|
-
];
|
|
516
|
-
for (const sel of selectors) {
|
|
517
|
-
result[`selector:${sel}`] = document.querySelectorAll(sel).length;
|
|
518
|
-
}
|
|
519
|
-
const answerBox = document.querySelector('[class*="answerBox"]');
|
|
520
|
-
if (answerBox) {
|
|
521
|
-
result["answerBox"] = {
|
|
522
|
-
textLen: answerBox.innerText?.trim().length || 0,
|
|
523
|
-
preview: (answerBox.innerText?.trim() || "").substring(0, 200),
|
|
524
|
-
classes: answerBox.className?.toString().substring(0, 100) || ""
|
|
525
|
-
};
|
|
526
|
-
}
|
|
527
|
-
result["bodyText"] = document.body.innerText?.substring(0, 1e3) || "";
|
|
528
|
-
const longTextEls = [];
|
|
529
|
-
const mainContent = document.querySelector('[class*="chatViewer"]') || document.querySelector('[class*="chat"]') || document.body;
|
|
530
|
-
for (const el of Array.from(mainContent.querySelectorAll("div, section, article"))) {
|
|
531
|
-
const text = el.innerText?.trim() || "";
|
|
532
|
-
if (text.length > 50 && text.length < 1e4) {
|
|
533
|
-
longTextEls.push({
|
|
534
|
-
tag: el.tagName,
|
|
535
|
-
classes: el.className?.toString().split(" ").slice(0, 3).join(" ") || "",
|
|
536
|
-
textLen: text.length,
|
|
537
|
-
preview: text.substring(0, 80)
|
|
538
|
-
});
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
result["longTextElements"] = longTextEls.sort((a, b) => b.textLen - a.textLen).slice(0, 10);
|
|
542
|
-
return result;
|
|
543
|
-
});
|
|
544
|
-
log(verbose, "=== \u9875\u9762\u8BCA\u65AD ===");
|
|
545
|
-
for (const [key, value] of Object.entries(pageDump)) {
|
|
546
|
-
if (key === "bodyText" || key === "longTextElements") continue;
|
|
547
|
-
log(verbose, ` ${key}: ${JSON.stringify(value)}`);
|
|
548
|
-
}
|
|
549
|
-
if (pageDump["longTextElements"] && pageDump["longTextElements"].length > 0) {
|
|
550
|
-
log(verbose, " \u957F\u6587\u672C\u5143\u7D20 TOP 10:");
|
|
551
|
-
for (const el of pageDump["longTextElements"]) {
|
|
552
|
-
log(verbose, ` <${el.tag}> .${el.classes} (${el.textLen}\u5B57): ${el.preview}...`);
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
if (pageDump["bodyText"]) {
|
|
556
|
-
log(verbose, ` \u9875\u9762\u6587\u672C(\u524D1000\u5B57): ${pageDump["bodyText"].substring(0, 1e3)}`);
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
async function extractReply(page, question, verbose = false) {
|
|
560
|
-
log(verbose, "\u63D0\u53D6 AI \u56DE\u590D\u5185\u5BB9...");
|
|
561
|
-
await saveDebugScreenshot(page, "before-extract", verbose);
|
|
562
|
-
const reply = await page.evaluate((userQuestion) => {
|
|
563
|
-
const debugInfo = [];
|
|
564
|
-
const answerBox = document.querySelector('[class*="answerBox"]');
|
|
565
|
-
debugInfo.push(`answerBox found: ${!!answerBox}`);
|
|
566
|
-
if (answerBox) {
|
|
567
|
-
const text = answerBox.innerText?.trim() || "";
|
|
568
|
-
debugInfo.push(`answerBox textLen: ${text.length}`);
|
|
569
|
-
if (text.length > 0) {
|
|
570
|
-
const mdContainer = answerBox.querySelector('[class*="agent-markdown"], [class*="mdRenderContainer"]');
|
|
571
|
-
if (mdContainer) {
|
|
572
|
-
const mdText = mdContainer.innerText?.trim() || "";
|
|
573
|
-
if (mdText.length > 0) {
|
|
574
|
-
debugInfo.push(`method1 markdown success: ${mdText.length} chars`);
|
|
575
|
-
return JSON.stringify({ text: mdText, debug: debugInfo });
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
debugInfo.push(`method1 answerBox success: ${text.length} chars`);
|
|
579
|
-
return JSON.stringify({ text, debug: debugInfo });
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
const cardList = document.querySelector('[class*="dialogueCardList"]');
|
|
583
|
-
debugInfo.push(`dialogueCardList found: ${!!cardList}`);
|
|
584
|
-
if (cardList && cardList.children.length > 0) {
|
|
585
|
-
const lastCard = cardList.lastElementChild;
|
|
586
|
-
const text = lastCard?.innerText?.trim() || "";
|
|
587
|
-
debugInfo.push(`lastCard textLen: ${text.length}`);
|
|
588
|
-
if (text.length > 0) {
|
|
589
|
-
debugInfo.push(`method2 success: ${text.length} chars`);
|
|
590
|
-
return JSON.stringify({ text, debug: debugInfo });
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
const flowBoxes = document.querySelectorAll('[class*="flowBox"]');
|
|
594
|
-
debugInfo.push(`flowBoxes count: ${flowBoxes.length}`);
|
|
595
|
-
for (let i = flowBoxes.length - 1; i >= 0; i--) {
|
|
596
|
-
const box = flowBoxes[i];
|
|
597
|
-
const text = box.innerText?.trim() || "";
|
|
598
|
-
if (text.length > 10) {
|
|
599
|
-
debugInfo.push(`method3 success: flowBox[${i}] ${text.length} chars`);
|
|
600
|
-
return JSON.stringify({ text, debug: debugInfo });
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
const botContainers = document.querySelectorAll('[class*="roleSystemBot"]');
|
|
604
|
-
debugInfo.push(`roleSystemBot count: ${botContainers.length}`);
|
|
605
|
-
for (let i = botContainers.length - 1; i >= 0; i--) {
|
|
606
|
-
const container = botContainers[i];
|
|
607
|
-
const text = container.innerText?.trim() || "";
|
|
608
|
-
if (text.length > 10) {
|
|
609
|
-
debugInfo.push(`method4 success: botContainer[${i}] ${text.length} chars`);
|
|
610
|
-
return JSON.stringify({ text, debug: debugInfo });
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
const mdContainers = document.querySelectorAll('[class*="mdRenderContainer"], [class*="agent-markdown"]');
|
|
614
|
-
debugInfo.push(`md containers count: ${mdContainers.length}`);
|
|
615
|
-
for (let i = mdContainers.length - 1; i >= 0; i--) {
|
|
616
|
-
const container = mdContainers[i];
|
|
617
|
-
const text = container.innerText?.trim() || "";
|
|
618
|
-
if (text.length > 10) {
|
|
619
|
-
debugInfo.push(`method5 success: mdContainer[${i}] ${text.length} chars`);
|
|
620
|
-
return JSON.stringify({ text, debug: debugInfo });
|
|
621
|
-
}
|
|
622
|
-
}
|
|
623
|
-
const fullText = document.body.innerText;
|
|
624
|
-
const lines = fullText.split("\n").map((l) => l.trim()).filter((l) => l.length > 0);
|
|
625
|
-
const uiWords = /* @__PURE__ */ new Set([
|
|
626
|
-
"\u6587\u5FC3\u4E00\u8A00",
|
|
627
|
-
"\u65B0\u5BF9\u8BDD",
|
|
628
|
-
"\u521B\u610F\u5199\u4F5C",
|
|
629
|
-
"\u667A\u6167\u7ED8\u56FE",
|
|
630
|
-
"\u8D85\u7EA7\u667A\u80FD\u4F53",
|
|
631
|
-
"\u66F4\u591A",
|
|
632
|
-
"\u6211\u7684\u6536\u85CF",
|
|
633
|
-
"\u9879\u76EE",
|
|
634
|
-
"\u5BF9\u8BDD",
|
|
635
|
-
"\u6682\u65E0\u8BB0\u5F55",
|
|
636
|
-
"\u672A\u767B\u5F55",
|
|
637
|
-
"\u767B\u5F55",
|
|
638
|
-
"\u5185\u5BB9\u7531AI\u751F\u6210\uFF0C\u4EC5\u4F9B\u53C2\u8003\uFF0C\u8BF7\u4ED4\u7EC6\u7504\u522B",
|
|
639
|
-
"\u53C2\u8003",
|
|
640
|
-
"\u6DF1\u5EA6\u5206\u6790\u9700\u6C42\u5E76\u89E3\u7B54",
|
|
641
|
-
"\u901A\u7528\u5199\u4F5C",
|
|
642
|
-
"\u9605\u8BFB\u5206\u6790",
|
|
643
|
-
"\u7F51\u9875\u5DE5\u574A",
|
|
644
|
-
"\u667A\u80FD\u7FFB\u8BD1",
|
|
645
|
-
"\u4EE3\u7801\u7F16\u7A0B"
|
|
646
|
-
]);
|
|
647
|
-
const userQIdx = lines.findIndex((l) => l === userQuestion || l.includes(userQuestion));
|
|
648
|
-
debugInfo.push(`userQ idx in body text: ${userQIdx}, total lines: ${lines.length}`);
|
|
649
|
-
if (userQIdx >= 0) {
|
|
650
|
-
const replyLines = [];
|
|
651
|
-
for (let i = userQIdx + 1; i < lines.length; i++) {
|
|
652
|
-
const line = lines[i];
|
|
653
|
-
if (line.length > 0 && !uiWords.has(line) && !line.startsWith("\u641C\u7D22") && !line.includes("\u7BC7\u8D44\u6599")) {
|
|
654
|
-
replyLines.push(line);
|
|
655
|
-
}
|
|
656
|
-
if (line === "\u5FEB\u901F" || line === "\u66F4\u591A" || line.includes("\u5185\u5BB9\u7531AI\u751F\u6210")) break;
|
|
657
|
-
}
|
|
658
|
-
if (replyLines.length > 0) {
|
|
659
|
-
debugInfo.push(`method6 success: ${replyLines.length} lines`);
|
|
660
|
-
return JSON.stringify({ text: replyLines.join("\n"), debug: debugInfo });
|
|
661
|
-
}
|
|
662
|
-
}
|
|
663
|
-
const allTextBlocks = [];
|
|
664
|
-
const blockElements = document.querySelectorAll("div, section, article, p, span");
|
|
665
|
-
for (const el of blockElements) {
|
|
666
|
-
const elText = el.innerText?.trim() || "";
|
|
667
|
-
if (elText.length > 50 && elText.length < 1e4) {
|
|
668
|
-
if (!uiWords.has(elText)) {
|
|
669
|
-
allTextBlocks.push({ text: elText, length: elText.length });
|
|
670
|
-
}
|
|
671
|
-
}
|
|
672
|
-
}
|
|
673
|
-
allTextBlocks.sort((a, b) => b.length - a.length);
|
|
674
|
-
if (allTextBlocks.length > 0) {
|
|
675
|
-
debugInfo.push(`method7 fallback: longest text block ${allTextBlocks[0].length} chars`);
|
|
676
|
-
return JSON.stringify({ text: allTextBlocks[0].text, debug: debugInfo });
|
|
677
|
-
}
|
|
678
|
-
debugInfo.push(`ALL METHODS FAILED`);
|
|
679
|
-
debugInfo.push(`body text length: ${fullText.length}`);
|
|
680
|
-
debugInfo.push(`body text first 500: ${fullText.substring(0, 500)}`);
|
|
681
|
-
return JSON.stringify({ text: "", debug: debugInfo });
|
|
682
|
-
}, question);
|
|
683
|
-
let parsed;
|
|
684
|
-
try {
|
|
685
|
-
parsed = JSON.parse(reply);
|
|
686
|
-
} catch {
|
|
687
|
-
parsed = { text: reply, debug: [] };
|
|
688
|
-
}
|
|
689
|
-
for (const line of parsed.debug) {
|
|
690
|
-
log(verbose, ` [extract] ${line}`);
|
|
691
|
-
}
|
|
692
|
-
if (parsed.text && parsed.text.length > 0) {
|
|
693
|
-
log(verbose, `\u63D0\u53D6\u6210\u529F\uFF0C\u56DE\u590D\u957F\u5EA6: ${parsed.text.length} \u5B57\u7B26`);
|
|
694
|
-
return parsed.text;
|
|
695
|
-
}
|
|
696
|
-
log(verbose, "\u63D0\u53D6\u5931\u8D25\uFF0C\u6240\u6709\u65B9\u6CD5\u5747\u672A\u627E\u5230\u56DE\u590D\u5185\u5BB9");
|
|
697
|
-
throw new YiyanAgentError("TIMEOUT", "Failed to extract reply content");
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
// src/agent.ts
|
|
701
|
-
var DEFAULT_PROFILE_BASE_DIR = path3.join(
|
|
702
|
-
os2.homedir(),
|
|
703
|
-
".yiyan-browser-agent"
|
|
704
|
-
);
|
|
705
|
-
var YiyanAgent = class {
|
|
706
|
-
options;
|
|
707
|
-
profileDir;
|
|
708
|
-
verbose;
|
|
709
|
-
constructor(options) {
|
|
710
|
-
this.options = {
|
|
711
|
-
...DEFAULT_OPTIONS,
|
|
712
|
-
...options
|
|
713
|
-
};
|
|
714
|
-
this.profileDir = this.options.profileDir || DEFAULT_PROFILE_BASE_DIR;
|
|
715
|
-
this.verbose = options?.verbose ?? true;
|
|
716
|
-
}
|
|
717
|
-
/**
|
|
718
|
-
* 发送问题并获取答案
|
|
719
|
-
* @param question 问题文本
|
|
720
|
-
* @param headful 是否使用有头浏览器(可见窗口),默认为 true(headless 模式会被检测拦截)
|
|
721
|
-
*/
|
|
722
|
-
async ask(question, headful = true) {
|
|
723
|
-
let lastError = null;
|
|
724
|
-
for (let attempt = 0; attempt < this.options.retryCount; attempt++) {
|
|
725
|
-
try {
|
|
726
|
-
if (attempt > 0) {
|
|
727
|
-
process.stderr.write(`[yiyan-agent] \u7B2C ${attempt + 1} \u6B21\u91CD\u8BD5...
|
|
728
|
-
`);
|
|
729
|
-
}
|
|
730
|
-
return await this.executeAsk(question, headful);
|
|
731
|
-
} catch (error) {
|
|
732
|
-
if (error instanceof YiyanAgentError) {
|
|
733
|
-
lastError = error;
|
|
734
|
-
if (lastError.type === "CAPTCHA") {
|
|
735
|
-
throw lastError;
|
|
736
|
-
}
|
|
737
|
-
} else {
|
|
738
|
-
lastError = new YiyanAgentError(
|
|
739
|
-
"NETWORK",
|
|
740
|
-
error instanceof Error ? error.message : String(error)
|
|
741
|
-
);
|
|
742
|
-
}
|
|
743
|
-
if (attempt < this.options.retryCount - 1) {
|
|
744
|
-
await this.sleep(2e3);
|
|
745
|
-
}
|
|
746
|
-
}
|
|
747
|
-
}
|
|
748
|
-
throw lastError || new YiyanAgentError("TIMEOUT", "Unknown error");
|
|
749
|
-
}
|
|
750
|
-
/**
|
|
751
|
-
* 执行单次问答
|
|
752
|
-
*/
|
|
753
|
-
async executeAsk(question, headful = true) {
|
|
754
|
-
const platform = os2.platform();
|
|
755
|
-
const chromePath = this.options.chromePath || getChromeExecutablePath(platform);
|
|
756
|
-
if (!chromePath) {
|
|
757
|
-
throw new YiyanAgentError(
|
|
758
|
-
"BROWSER_LAUNCH",
|
|
759
|
-
`Unsupported platform: ${platform}`
|
|
760
|
-
);
|
|
761
|
-
}
|
|
762
|
-
const profilePath = path3.join(this.profileDir, "chrome-profile");
|
|
763
|
-
const useHeadful = headful !== false;
|
|
764
|
-
const { browser, cdpBrowser, page, chromeProcess } = await launchBrowser({
|
|
765
|
-
chromePath,
|
|
766
|
-
profilePath,
|
|
767
|
-
headless: !useHeadful,
|
|
768
|
-
timeout: 3e4,
|
|
769
|
-
verbose: this.verbose
|
|
770
|
-
});
|
|
771
|
-
try {
|
|
772
|
-
await navigateToYiyan(page, this.verbose);
|
|
773
|
-
if (useHeadful) {
|
|
774
|
-
const isLoggedIn = await checkLoggedIn(page, this.verbose);
|
|
775
|
-
if (!isLoggedIn) {
|
|
776
|
-
await waitForUserAction(page, "login", this.verbose);
|
|
777
|
-
}
|
|
778
|
-
}
|
|
779
|
-
await sendMessage(page, question, this.verbose, useHeadful);
|
|
780
|
-
await waitForReply(page, this.options.timeout, this.verbose, useHeadful);
|
|
781
|
-
const reply = await extractReply(page, question, this.verbose);
|
|
782
|
-
return reply;
|
|
783
|
-
} finally {
|
|
784
|
-
await closeBrowser(browser, chromeProcess, cdpBrowser, this.verbose);
|
|
785
|
-
}
|
|
786
|
-
}
|
|
787
|
-
/**
|
|
788
|
-
* 登录文心一言(打开有头浏览器让用户手动登录)
|
|
789
|
-
* 登录成功后,后续的 ask 调用将自动使用登录状态
|
|
790
|
-
*/
|
|
791
|
-
async login() {
|
|
792
|
-
const platform = os2.platform();
|
|
793
|
-
const chromePath = this.options.chromePath || getChromeExecutablePath(platform);
|
|
794
|
-
if (!chromePath) {
|
|
795
|
-
throw new YiyanAgentError(
|
|
796
|
-
"BROWSER_LAUNCH",
|
|
797
|
-
`Unsupported platform: ${platform}`
|
|
798
|
-
);
|
|
799
|
-
}
|
|
800
|
-
const profilePath = path3.join(this.profileDir, "chrome-profile");
|
|
801
|
-
const { browser, cdpBrowser, page, chromeProcess } = await launchBrowser({
|
|
802
|
-
chromePath,
|
|
803
|
-
profilePath,
|
|
804
|
-
headless: false,
|
|
805
|
-
timeout: 6e4,
|
|
806
|
-
verbose: this.verbose
|
|
807
|
-
});
|
|
808
|
-
try {
|
|
809
|
-
await page.goto("https://yiyan.baidu.com/", {
|
|
810
|
-
waitUntil: "networkidle",
|
|
811
|
-
timeout: 6e4
|
|
812
|
-
});
|
|
813
|
-
await page.waitForTimeout(5e3);
|
|
814
|
-
process.stderr.write("[yiyan-agent] \u7B49\u5F85\u60A8\u5728\u6D4F\u89C8\u5668\u4E2D\u767B\u5F55\u6587\u5FC3\u4E00\u8A00...\n");
|
|
815
|
-
process.stderr.write("[yiyan-agent] \u767B\u5F55\u6210\u529F\u540E\uFF0C\u8BF7\u6309 Enter \u952E\u7EE7\u7EED...\n");
|
|
816
|
-
const loginDetected = Promise.resolve().then(async () => {
|
|
817
|
-
const maxWait = 3e5;
|
|
818
|
-
const startTime = Date.now();
|
|
819
|
-
while (Date.now() - startTime < maxWait) {
|
|
820
|
-
const isLoggedIn = await page.evaluate(() => {
|
|
821
|
-
const bodyText = document.body.innerText || "";
|
|
822
|
-
if (bodyText.includes("\u672A\u767B\u5F55")) return false;
|
|
823
|
-
const loginButtons = Array.from(document.querySelectorAll("button"));
|
|
824
|
-
for (const btn of loginButtons) {
|
|
825
|
-
const text = btn.textContent?.trim() || "";
|
|
826
|
-
if (text === "\u767B\u5F55") return false;
|
|
827
|
-
}
|
|
828
|
-
return true;
|
|
829
|
-
});
|
|
830
|
-
if (isLoggedIn) {
|
|
831
|
-
process.stderr.write("[yiyan-agent] \u2713 \u68C0\u6D4B\u5230\u767B\u5F55\u6210\u529F\uFF01\n");
|
|
832
|
-
return true;
|
|
833
|
-
}
|
|
834
|
-
await new Promise((resolve) => setTimeout(resolve, 2e3));
|
|
835
|
-
}
|
|
836
|
-
return false;
|
|
837
|
-
});
|
|
838
|
-
const enterPressed = new Promise((resolve) => {
|
|
839
|
-
const readline = __require("readline");
|
|
840
|
-
const rl = readline.createInterface({
|
|
841
|
-
input: process.stdin,
|
|
842
|
-
output: process.stderr
|
|
843
|
-
});
|
|
844
|
-
rl.question("", () => {
|
|
845
|
-
rl.close();
|
|
846
|
-
resolve(true);
|
|
847
|
-
});
|
|
848
|
-
});
|
|
849
|
-
await Promise.race([loginDetected, enterPressed]);
|
|
850
|
-
await new Promise((resolve) => setTimeout(resolve, 3e3));
|
|
851
|
-
} finally {
|
|
852
|
-
await closeBrowser(browser, chromeProcess, cdpBrowser, this.verbose);
|
|
853
|
-
}
|
|
854
|
-
}
|
|
855
|
-
sleep(ms) {
|
|
856
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
857
|
-
}
|
|
858
|
-
async reset() {
|
|
859
|
-
await clearCopiedProfile(this.profileDir);
|
|
860
|
-
}
|
|
861
|
-
status() {
|
|
862
|
-
return {
|
|
863
|
-
loggedIn: profileExists(this.profileDir),
|
|
864
|
-
profilePath: path3.join(this.profileDir, "chrome-profile")
|
|
865
|
-
};
|
|
866
|
-
}
|
|
867
|
-
};
|
|
868
|
-
|
|
869
|
-
// src/cli.ts
|
|
870
|
-
function printHelp() {
|
|
871
|
-
console.log(`
|
|
872
|
-
yiyan-agent - \u6587\u5FC3\u4E00\u8A00\u6D4F\u89C8\u5668\u4EE3\u7406 CLI
|
|
873
|
-
|
|
874
|
-
\u7528\u6CD5:
|
|
875
|
-
yiyan-agent login \u9996\u6B21\u767B\u5F55\u6587\u5FC3\u4E00\u8A00\uFF08\u4F1A\u6253\u5F00\u6D4F\u89C8\u5668\u7A97\u53E3\uFF09
|
|
876
|
-
yiyan-agent ask "\u95EE\u9898" [--timeout ms] [--retry n] [--headless]
|
|
877
|
-
yiyan-agent status \u68C0\u67E5\u767B\u5F55\u72B6\u6001
|
|
878
|
-
yiyan-agent reset \u6E05\u9664\u4FDD\u5B58\u7684 profile
|
|
879
|
-
|
|
880
|
-
\u547D\u4EE4:
|
|
881
|
-
login \u6253\u5F00\u6D4F\u89C8\u5668\u624B\u52A8\u767B\u5F55\u6587\u5FC3\u4E00\u8A00\uFF08\u9996\u6B21\u4F7F\u7528\u5EFA\u8BAE\u5148\u767B\u5F55\uFF09
|
|
882
|
-
ask \u53D1\u9001\u95EE\u9898\u5E76\u83B7\u53D6\u7B54\u6848\uFF08\u9ED8\u8BA4\u6709\u5934\u6A21\u5F0F\uFF0C\u9047\u5230\u9A8C\u8BC1\u7801/\u672A\u767B\u5F55\u4F1A\u6682\u505C\u7B49\u5F85\u624B\u52A8\u64CD\u4F5C\uFF09
|
|
883
|
-
status \u68C0\u67E5\u767B\u5F55\u72B6\u6001
|
|
884
|
-
reset \u6E05\u9664\u4FDD\u5B58\u7684 profile
|
|
885
|
-
|
|
886
|
-
\u9009\u9879:
|
|
887
|
-
--timeout <ms> \u8D85\u65F6\u65F6\u95F4\uFF08\u6BEB\u79D2\uFF09\uFF0C\u9ED8\u8BA4 120000
|
|
888
|
-
--retry <n> \u91CD\u8BD5\u6B21\u6570\uFF0C\u9ED8\u8BA4 3
|
|
889
|
-
--headless \u4F7F\u7528\u65E0\u5934\u6D4F\u89C8\u5668\uFF08\u4E0D\u53EF\u89C1\u7A97\u53E3\uFF0C\u4E0D\u63A8\u8350\uFF09
|
|
890
|
-
--help \u663E\u793A\u5E2E\u52A9\u4FE1\u606F
|
|
891
|
-
|
|
892
|
-
\u793A\u4F8B:
|
|
893
|
-
yiyan-agent login # \u9996\u6B21\u4F7F\u7528\uFF1A\u767B\u5F55\u6587\u5FC3\u4E00\u8A00
|
|
894
|
-
yiyan-agent ask "\u4EC0\u4E48\u662F TypeScript\uFF1F" # \u63D0\u95EE\uFF08\u9ED8\u8BA4\u6709\u5934\u6A21\u5F0F\uFF0C\u53EF\u624B\u52A8\u8FC7\u9A8C\u8BC1\u7801\uFF09
|
|
895
|
-
yiyan-agent ask "\u89E3\u91CA Promise" --timeout 60000 --retry 5
|
|
896
|
-
yiyan-agent ask "30+30=" --headless # \u65E0\u5934\u6A21\u5F0F\uFF08\u4E0D\u63A8\u8350\uFF09
|
|
897
|
-
yiyan-agent status
|
|
898
|
-
yiyan-agent reset
|
|
899
|
-
`);
|
|
900
|
-
}
|
|
901
|
-
function parseCliArgs(args) {
|
|
902
|
-
const result = {
|
|
903
|
-
command: null
|
|
904
|
-
};
|
|
905
|
-
for (let i = 0; i < args.length; i++) {
|
|
906
|
-
const arg = args[i];
|
|
907
|
-
if (arg === "--help") {
|
|
908
|
-
result.command = "help";
|
|
909
|
-
return result;
|
|
910
|
-
}
|
|
911
|
-
if (arg === "ask") {
|
|
912
|
-
result.command = "ask";
|
|
913
|
-
if (i + 1 < args.length && !args[i + 1].startsWith("--")) {
|
|
914
|
-
result.question = args[i + 1];
|
|
915
|
-
i++;
|
|
916
|
-
}
|
|
917
|
-
}
|
|
918
|
-
if (arg === "status") {
|
|
919
|
-
result.command = "status";
|
|
920
|
-
}
|
|
921
|
-
if (arg === "reset") {
|
|
922
|
-
result.command = "reset";
|
|
923
|
-
}
|
|
924
|
-
if (arg === "login") {
|
|
925
|
-
result.command = "login";
|
|
926
|
-
}
|
|
927
|
-
if (arg === "--timeout") {
|
|
928
|
-
if (i + 1 < args.length) {
|
|
929
|
-
result.timeout = parseInt(args[i + 1], 10);
|
|
930
|
-
i++;
|
|
931
|
-
}
|
|
932
|
-
}
|
|
933
|
-
if (arg === "--retry") {
|
|
934
|
-
if (i + 1 < args.length) {
|
|
935
|
-
result.retry = parseInt(args[i + 1], 10);
|
|
936
|
-
i++;
|
|
937
|
-
}
|
|
938
|
-
}
|
|
939
|
-
if (arg === "--headless") {
|
|
940
|
-
result.headless = true;
|
|
941
|
-
}
|
|
942
|
-
}
|
|
943
|
-
return result;
|
|
944
|
-
}
|
|
945
|
-
function formatCliOutput(output) {
|
|
946
|
-
return JSON.stringify(output, null, 2);
|
|
947
|
-
}
|
|
948
|
-
async function runCli(args) {
|
|
949
|
-
const parsed = parseCliArgs(args);
|
|
950
|
-
if (parsed.command === "help" || parsed.command === null) {
|
|
951
|
-
printHelp();
|
|
952
|
-
return;
|
|
953
|
-
}
|
|
954
|
-
const options = {};
|
|
955
|
-
if (parsed.timeout) {
|
|
956
|
-
options.timeout = parsed.timeout;
|
|
957
|
-
}
|
|
958
|
-
if (parsed.retry) {
|
|
959
|
-
options.retryCount = parsed.retry;
|
|
960
|
-
}
|
|
961
|
-
const agent = new YiyanAgent(options);
|
|
962
|
-
if (parsed.command === "login") {
|
|
963
|
-
console.log("Opening browser for login... Please login to Yiyan (\u6587\u5FC3\u4E00\u8A00) in the browser window.");
|
|
964
|
-
try {
|
|
965
|
-
await agent.login();
|
|
966
|
-
console.log(formatCliOutput({
|
|
967
|
-
success: true,
|
|
968
|
-
question: "login",
|
|
969
|
-
answer: 'Login successful! You can now use "ask" command.',
|
|
970
|
-
duration: 0
|
|
971
|
-
}));
|
|
972
|
-
} catch (error) {
|
|
973
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
974
|
-
console.log(formatCliOutput({
|
|
975
|
-
success: false,
|
|
976
|
-
question: "login",
|
|
977
|
-
error: errorMessage,
|
|
978
|
-
duration: 0
|
|
979
|
-
}));
|
|
980
|
-
}
|
|
981
|
-
return;
|
|
982
|
-
}
|
|
983
|
-
if (parsed.command === "status") {
|
|
984
|
-
const status = agent.status();
|
|
985
|
-
console.log(formatCliOutput({
|
|
986
|
-
success: true,
|
|
987
|
-
question: "status",
|
|
988
|
-
answer: JSON.stringify(status),
|
|
989
|
-
duration: 0
|
|
990
|
-
}));
|
|
991
|
-
return;
|
|
992
|
-
}
|
|
993
|
-
if (parsed.command === "reset") {
|
|
994
|
-
await agent.reset();
|
|
995
|
-
console.log(formatCliOutput({
|
|
996
|
-
success: true,
|
|
997
|
-
question: "reset",
|
|
998
|
-
answer: "Profile cleared successfully",
|
|
999
|
-
duration: 0
|
|
1000
|
-
}));
|
|
1001
|
-
return;
|
|
1002
|
-
}
|
|
1003
|
-
if (parsed.command === "ask") {
|
|
1004
|
-
if (!parsed.question) {
|
|
1005
|
-
console.log(formatCliOutput({
|
|
1006
|
-
success: false,
|
|
1007
|
-
question: "",
|
|
1008
|
-
error: 'No question provided. Usage: ask "your question"',
|
|
1009
|
-
duration: 0
|
|
1010
|
-
}));
|
|
1011
|
-
return;
|
|
1012
|
-
}
|
|
1013
|
-
const startTime = Date.now();
|
|
1014
|
-
try {
|
|
1015
|
-
const answer = await agent.ask(parsed.question, !parsed.headless);
|
|
1016
|
-
const duration = Date.now() - startTime;
|
|
1017
|
-
console.log(formatCliOutput({
|
|
1018
|
-
success: true,
|
|
1019
|
-
question: parsed.question,
|
|
1020
|
-
answer,
|
|
1021
|
-
duration
|
|
1022
|
-
}));
|
|
1023
|
-
} catch (error) {
|
|
1024
|
-
const duration = Date.now() - startTime;
|
|
1025
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1026
|
-
console.log(formatCliOutput({
|
|
1027
|
-
success: false,
|
|
1028
|
-
question: parsed.question,
|
|
1029
|
-
error: errorMessage,
|
|
1030
|
-
duration
|
|
1031
|
-
}));
|
|
1032
|
-
}
|
|
1033
|
-
return;
|
|
1034
|
-
}
|
|
1035
|
-
}
|
|
1036
|
-
var scriptPath = process.argv[1]?.replace(/\\/g, "/");
|
|
1037
|
-
var importUrl = import.meta.url.replace(/^file:\/\//, "");
|
|
1038
|
-
if (scriptPath && (importUrl === scriptPath || importUrl === "/" + scriptPath)) {
|
|
1039
|
-
const args = process.argv.slice(2);
|
|
1040
|
-
runCli(args).catch(console.error);
|
|
1041
|
-
}
|
|
1042
|
-
export {
|
|
1043
|
-
formatCliOutput,
|
|
1044
|
-
parseCliArgs,
|
|
1045
|
-
printHelp,
|
|
1046
|
-
runCli
|
|
1047
|
-
};
|
|
1048
|
-
//# sourceMappingURL=cli.js.map
|