@love-moon/chat-web 0.3.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.
Files changed (77) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/cli.d.ts +2 -0
  3. package/dist/cli.js +142 -0
  4. package/dist/cli.js.map +1 -0
  5. package/dist/commands/doctor.d.ts +27 -0
  6. package/dist/commands/doctor.js +116 -0
  7. package/dist/commands/doctor.js.map +1 -0
  8. package/dist/commands/index.d.ts +3 -0
  9. package/dist/commands/index.js +4 -0
  10. package/dist/commands/index.js.map +1 -0
  11. package/dist/commands/info.d.ts +32 -0
  12. package/dist/commands/info.js +81 -0
  13. package/dist/commands/info.js.map +1 -0
  14. package/dist/commands/login.d.ts +19 -0
  15. package/dist/commands/login.js +61 -0
  16. package/dist/commands/login.js.map +1 -0
  17. package/dist/core/browser.d.ts +70 -0
  18. package/dist/core/browser.js +96 -0
  19. package/dist/core/browser.js.map +1 -0
  20. package/dist/core/errors.d.ts +60 -0
  21. package/dist/core/errors.js +153 -0
  22. package/dist/core/errors.js.map +1 -0
  23. package/dist/core/install-chromium.d.ts +55 -0
  24. package/dist/core/install-chromium.js +156 -0
  25. package/dist/core/install-chromium.js.map +1 -0
  26. package/dist/core/keyboard.d.ts +39 -0
  27. package/dist/core/keyboard.js +54 -0
  28. package/dist/core/keyboard.js.map +1 -0
  29. package/dist/core/locator-score.d.ts +41 -0
  30. package/dist/core/locator-score.js +101 -0
  31. package/dist/core/locator-score.js.map +1 -0
  32. package/dist/core/logger.d.ts +10 -0
  33. package/dist/core/logger.js +38 -0
  34. package/dist/core/logger.js.map +1 -0
  35. package/dist/core/navigate.d.ts +52 -0
  36. package/dist/core/navigate.js +102 -0
  37. package/dist/core/navigate.js.map +1 -0
  38. package/dist/core/paths.d.ts +12 -0
  39. package/dist/core/paths.js +30 -0
  40. package/dist/core/paths.js.map +1 -0
  41. package/dist/core/profile-manager.d.ts +13 -0
  42. package/dist/core/profile-manager.js +44 -0
  43. package/dist/core/profile-manager.js.map +1 -0
  44. package/dist/core/provider.d.ts +64 -0
  45. package/dist/core/provider.js +31 -0
  46. package/dist/core/provider.js.map +1 -0
  47. package/dist/core/response-watcher.d.ts +35 -0
  48. package/dist/core/response-watcher.js +70 -0
  49. package/dist/core/response-watcher.js.map +1 -0
  50. package/dist/core/snapshot.d.ts +38 -0
  51. package/dist/core/snapshot.js +137 -0
  52. package/dist/core/snapshot.js.map +1 -0
  53. package/dist/core/sse-parser.d.ts +20 -0
  54. package/dist/core/sse-parser.js +49 -0
  55. package/dist/core/sse-parser.js.map +1 -0
  56. package/dist/index.d.ts +33 -0
  57. package/dist/index.js +40 -0
  58. package/dist/index.js.map +1 -0
  59. package/dist/providers/chatgpt-sse-collector.d.ts +76 -0
  60. package/dist/providers/chatgpt-sse-collector.js +298 -0
  61. package/dist/providers/chatgpt-sse-collector.js.map +1 -0
  62. package/dist/providers/chatgpt.d.ts +56 -0
  63. package/dist/providers/chatgpt.js +357 -0
  64. package/dist/providers/chatgpt.js.map +1 -0
  65. package/dist/providers/deepseek.d.ts +22 -0
  66. package/dist/providers/deepseek.js +153 -0
  67. package/dist/providers/deepseek.js.map +1 -0
  68. package/dist/providers/gemini.d.ts +102 -0
  69. package/dist/providers/gemini.js +480 -0
  70. package/dist/providers/gemini.js.map +1 -0
  71. package/dist/providers/index.d.ts +8 -0
  72. package/dist/providers/index.js +17 -0
  73. package/dist/providers/index.js.map +1 -0
  74. package/dist/session.d.ts +121 -0
  75. package/dist/session.js +242 -0
  76. package/dist/session.js.map +1 -0
  77. package/package.json +47 -0
@@ -0,0 +1,480 @@
1
+ import { InputNotFoundError, ProviderApiKeyRequiredError, ProviderAutomationBlockedError, ProviderPermissionDeniedError, ProviderRateLimitedError, ResponseExtractionError, ResponseTimeoutError, } from "../core/errors.js";
2
+ import { typeMultiline } from "../core/keyboard.js";
3
+ import { gotoOrThrowNetworkError } from "../core/navigate.js";
4
+ import { sleep, waitUntilStable } from "../core/response-watcher.js";
5
+ /**
6
+ * Google AI Studio (aistudio.google.com/prompts/new_chat) provider
7
+ * adapter.
8
+ *
9
+ * This is what users colloquially mean by "Gemini" — the free web-chat
10
+ * interface that lets you talk to Gemini models without an API key
11
+ * (the "No API key selected" button on the page is a separate, optional
12
+ * feature for users who want to use their own key; the page is fully
13
+ * usable without it). chat-web does NOT target gemini.google.com — in
14
+ * practice many networks block that domain while AI Studio reaches
15
+ * fine, and AI Studio is the surface users actually expect.
16
+ *
17
+ * Observed structure (2026-05):
18
+ * - Composer: `<ms-prompt-box>` (Angular Material) wrapping a textarea
19
+ * with `aria="Enter a prompt"`, `placeholder="Start typing..."`.
20
+ * - Run button: NO aria-label; visible text is "Run" + two Material
21
+ * icon font ligatures (`keyboard_command_key`, `keyboard_return`).
22
+ * `type="submit"`. Use `getByRole("button", { name: /^Run$/ })`.
23
+ * - Stop: the same submit button flips to a stop state; its
24
+ * aria-label changes to include "Stop". Detect via aria-label
25
+ * substring.
26
+ * - Assistant turn: `<ms-chat-turn>` containing a
27
+ * `.chat-turn-container.model` with `.turn-content`. innerText
28
+ * leaks "Model HH:MM AM/PM" headers and Material icon font
29
+ * ligatures (`edit`, `more_vert`, `error`, ...) as plain text —
30
+ * stripped by {@link stripChromeFromTurn}.
31
+ * - Conversation id: `/prompts/{slug}` once the prompt is saved; the
32
+ * placeholder `/prompts/new_chat` does NOT count.
33
+ */
34
+ export class GeminiAdapter {
35
+ name = "gemini";
36
+ homeUrl = "https://aistudio.google.com/prompts/new_chat";
37
+ async open(page) {
38
+ await gotoOrThrowNetworkError(page, this.homeUrl, this.name);
39
+ await this.waitForComposerReady(page).catch(() => undefined);
40
+ }
41
+ async isLoggedIn(page) {
42
+ const url = page.url();
43
+ if (url.includes("accounts.google.com") || url.includes("/signin/"))
44
+ return false;
45
+ const candidates = [
46
+ 'ms-prompt-box textarea',
47
+ 'textarea[aria-label="Enter a prompt"]',
48
+ 'textarea[aria-label*="prompt" i]',
49
+ 'textarea',
50
+ ];
51
+ for (const sel of candidates) {
52
+ const visible = await page.locator(sel).first().isVisible().catch(() => false);
53
+ if (visible)
54
+ return true;
55
+ }
56
+ return false;
57
+ }
58
+ async findInput(page) {
59
+ const candidates = [
60
+ page.locator('ms-prompt-box textarea').first(),
61
+ page.locator('textarea[aria-label="Enter a prompt"]').first(),
62
+ page.locator('textarea[aria-label*="prompt" i]').first(),
63
+ page.locator('textarea[placeholder*="Start typing" i]').first(),
64
+ page.locator('textarea').first(),
65
+ ];
66
+ for (const locator of candidates) {
67
+ if (await locator.isVisible().catch(() => false))
68
+ return locator;
69
+ }
70
+ throw new InputNotFoundError(this.name);
71
+ }
72
+ async findSendButton(page) {
73
+ // The Run button has NO aria-label — getByRole("button", { name: /^Run$/ })
74
+ // resolves the accessible name from visible "Run" text, then we fall
75
+ // back to type=submit / :has-text("Run").
76
+ const candidates = [
77
+ page.getByRole("button", { name: /^Run$/i }).first(),
78
+ page.locator('ms-prompt-box button[type="submit"]').first(),
79
+ page.locator('button[type="submit"]').first(),
80
+ page.locator('ms-prompt-box button:has-text("Run")').first(),
81
+ ];
82
+ for (const locator of candidates) {
83
+ if (await locator.isVisible().catch(() => false))
84
+ return locator;
85
+ }
86
+ return null;
87
+ }
88
+ async sendMessage(page, message) {
89
+ // CRITICAL: AI Studio defaults to "Grounding with Google Search"
90
+ // enabled on every new chat. With Grounding on, the page calls a
91
+ // Google Search backend BEFORE invoking the model — and on many
92
+ // user networks (especially behind DPI boxes that drop Google
93
+ // Search Suggest / search infrastructure traffic) that call hangs
94
+ // silently, leaving the UI stuck on "Thinking" forever even though
95
+ // the model invocation request (GenerateContent / streamGenerateContent)
96
+ // is never made. Disabling Grounding makes a simple "1+1=" return
97
+ // "2" instantly, since there's no pre-flight search step.
98
+ //
99
+ // We close the Grounding chip if it's present. Idempotent: if the
100
+ // chip has already been removed (sticky across the profile's
101
+ // session storage), this is a no-op.
102
+ await this.disableGroundingWithGoogleSearch(page).catch(() => undefined);
103
+ const input = await this.findInput(page);
104
+ await input.click();
105
+ // typeMultiline preserves newlines correctly. Angular Material's
106
+ // form state requires real input events (locator.fill() leaves Run
107
+ // disabled forever).
108
+ await typeMultiline(page, message);
109
+ await sleep(200);
110
+ const send = await this.findSendButton(page);
111
+ if (send && (await send.isEnabled().catch(() => false))) {
112
+ await send.click().catch(async () => {
113
+ await this.pressRunShortcut(page);
114
+ });
115
+ }
116
+ else {
117
+ await this.pressRunShortcut(page);
118
+ }
119
+ }
120
+ /**
121
+ * AI Studio shows a "Grounding with Google Search" chip on every new
122
+ * chat by default. The chip has a close (×) button with
123
+ * `aria-label="Remove Grounding with Google Search"`. Clicking it
124
+ * disables the grounding step for this conversation — and removes a
125
+ * silent hang point on networks that can't reach Google Search.
126
+ */
127
+ async disableGroundingWithGoogleSearch(page) {
128
+ const closer = page
129
+ .locator('button[aria-label="Remove Grounding with Google Search"]')
130
+ .first();
131
+ if (await closer.isVisible().catch(() => false)) {
132
+ await closer.click({ force: true }).catch(() => undefined);
133
+ await sleep(150);
134
+ }
135
+ }
136
+ async extractLastAssistantMessage(page) {
137
+ const candidates = [
138
+ page.locator('ms-chat-turn .chat-turn-container.model .turn-content').last(),
139
+ page.locator('.chat-turn-container.model .turn-content').last(),
140
+ page.locator('ms-chat-turn .turn-content').last(),
141
+ page.locator('ms-chat-turn').last(),
142
+ ];
143
+ for (const locator of candidates) {
144
+ if (await locator.count().then((n) => n > 0).catch(() => false)) {
145
+ const raw = await locator.innerText().catch(() => "");
146
+ const cleaned = stripChromeFromTurn(raw);
147
+ if (cleaned)
148
+ return cleaned;
149
+ }
150
+ }
151
+ throw new ResponseExtractionError(this.name);
152
+ }
153
+ async waitForResponse(page, options = {}) {
154
+ // AI Studio defaults to a high "Thinking level" + Grounding with
155
+ // Google Search (the chip is on at first visit) — a single prompt
156
+ // routinely takes 2-4 minutes. Bump the default budget to 5 min so
157
+ // we don't time out before the model finishes streaming. Callers
158
+ // who want a tighter cap pass an explicit timeoutMs.
159
+ const timeoutMs = options.timeoutMs ?? 5 * 60 * 1000;
160
+ const stableMs = options.stableMs ?? 2_000;
161
+ const deadline = Date.now() + timeoutMs;
162
+ // Phase 1: wait for streaming to begin (stop button appears).
163
+ await this.waitForStopButton(page, { timeoutMs: 15_000 }).catch(() => undefined);
164
+ // Phase 2: wait for streaming to end.
165
+ const remaining = Math.max(deadline - Date.now(), 5_000);
166
+ await this.waitForStopButtonGone(page, remaining).catch(() => undefined);
167
+ // Phase 3: text stability — but treat the "Thinking" placeholder
168
+ // (rendered while AI Studio is generating) as "not yet" so we don't
169
+ // accidentally return that as the final answer.
170
+ const stable = await waitUntilStable(async () => {
171
+ const text = await this.extractLastAssistantMessage(page).catch(() => "");
172
+ if (isThinkingPlaceholder(text))
173
+ return "";
174
+ return text;
175
+ }, {
176
+ timeoutMs: Math.max(deadline - Date.now(), 1_500),
177
+ stableMs,
178
+ signal: options.signal,
179
+ onProgress: options.onProgress,
180
+ });
181
+ if (!stable) {
182
+ // Distinguish "took too long" from "AI Studio's WAA anti-abuse
183
+ // silently blocked the request". If the page never navigated off
184
+ // /prompts/new_chat AND the model turn body is still "Thinking",
185
+ // the JS pipeline never actually invoked the model (it's stuck
186
+ // in the WAA challenge retry loop — see the lesson doc). Throw a
187
+ // typed error with an actionable hint instead of a vague timeout.
188
+ if (await this.looksAutomationBlocked(page)) {
189
+ throw new ProviderAutomationBlockedError(this.name, "The model invocation request was never made — Google's anti-abuse challenge (WAA) likely blocked it.");
190
+ }
191
+ throw new ResponseTimeoutError(this.name, timeoutMs);
192
+ }
193
+ // AI Studio renders upstream errors AS the model turn body (so
194
+ // "stable" successfully extracts an "answer" that's actually an
195
+ // error message). Catch the well-known patterns and throw typed
196
+ // errors instead of surfacing the error string as the model's reply.
197
+ await this.throwIfKnownUpstreamError(page, stable);
198
+ return stable;
199
+ }
200
+ /**
201
+ * Inspect the freshly-extracted "assistant" text and raise a typed
202
+ * error if it's really an upstream failure that AI Studio just
203
+ * rendered in the model turn slot.
204
+ *
205
+ * Known patterns:
206
+ * - "An internal error has occurred." → typically quota /
207
+ * server-side error (the user has reproduced this when their
208
+ * free-tier daily quota was exhausted).
209
+ * - "Failed to generate content: permission denied." → API key
210
+ * missing or model not enabled on the account.
211
+ *
212
+ * The actual root cause for "internal error" varies (preview model
213
+ * outage, daily quota cap, region restriction, transient backend
214
+ * error). We don't try to disambiguate further — the hint just lists
215
+ * the common ones so the user knows where to look.
216
+ */
217
+ async throwIfKnownUpstreamError(page, text) {
218
+ const lower = text.toLowerCase();
219
+ if (lower.includes("an internal error has occurred")) {
220
+ const cause = await this.detectFailureCause(page);
221
+ if (cause === "no-api-key") {
222
+ throw new ProviderApiKeyRequiredError(this.name, 'AI Studio shows "No API key selected"; configure an API key to run prompts.');
223
+ }
224
+ if (cause === "permission-denied") {
225
+ throw new ProviderPermissionDeniedError(this.name, text);
226
+ }
227
+ // No specific UI indicator — surface as a rate-limited / quota
228
+ // problem since that's the most common cause of an opaque
229
+ // "internal error" on the free tier.
230
+ throw new ProviderRateLimitedError(this.name);
231
+ }
232
+ if (lower.includes("failed to generate content")) {
233
+ if (lower.includes("permission denied")) {
234
+ throw new ProviderPermissionDeniedError(this.name, text);
235
+ }
236
+ throw new ProviderPermissionDeniedError(this.name, text);
237
+ }
238
+ }
239
+ async detectFailureCause(page) {
240
+ const noKey = await page
241
+ .locator('button[aria-label="No API key selected"]')
242
+ .first()
243
+ .isVisible()
244
+ .catch(() => false);
245
+ if (noKey)
246
+ return "no-api-key";
247
+ const found = await page
248
+ .evaluate(() => {
249
+ const t = (document.body.innerText || "").toLowerCase();
250
+ if (t.includes("permission denied"))
251
+ return "permission-denied";
252
+ if (t.includes("no api key") || t.includes("get api key"))
253
+ return "no-api-key";
254
+ return "";
255
+ })
256
+ .catch(() => "");
257
+ if (found === "permission-denied" || found === "no-api-key")
258
+ return found;
259
+ return "";
260
+ }
261
+ /**
262
+ * Heuristic: are we stuck in the WAA-blocked state where AI Studio
263
+ * never moves past "Thinking" and the URL still says new_chat? Used
264
+ * to surface a clearer error than a generic timeout.
265
+ */
266
+ async looksAutomationBlocked(page) {
267
+ const url = page.url();
268
+ if (!url.includes("/prompts/new_chat"))
269
+ return false;
270
+ const text = await this.extractLastAssistantMessage(page).catch(() => "");
271
+ return isThinkingPlaceholder(text);
272
+ }
273
+ /**
274
+ * AI Studio's per-prompt URL is `/prompts/{slug}` after the prompt is
275
+ * saved. Until then the URL stays at `/prompts/new_chat` (no
276
+ * conversation id).
277
+ */
278
+ getConversationId(page) {
279
+ const url = page.url();
280
+ const match = url.match(/\/prompts\/([^/?#]+)/);
281
+ if (!match)
282
+ return null;
283
+ const slug = match[1];
284
+ if (slug === "new_chat" || slug === "")
285
+ return null;
286
+ return slug;
287
+ }
288
+ async newChat(page) {
289
+ const candidates = [
290
+ page.getByRole("button", { name: /new (chat|prompt)|新对话|新建对话/i }).first(),
291
+ page.getByRole("link", { name: /new (chat|prompt)|新对话/i }).first(),
292
+ page.locator('a[href*="/prompts/new_chat"]').first(),
293
+ ];
294
+ for (const locator of candidates) {
295
+ if (await locator.isVisible().catch(() => false)) {
296
+ await locator.click().catch(() => undefined);
297
+ await page.waitForTimeout(500);
298
+ return;
299
+ }
300
+ }
301
+ await this.open(page);
302
+ }
303
+ async diagnose(page) {
304
+ const [loggedIn, sendBtn] = await Promise.all([
305
+ this.isLoggedIn(page),
306
+ this.findSendButton(page).then((l) => l !== null),
307
+ ]);
308
+ let inputFound = false;
309
+ try {
310
+ await this.findInput(page);
311
+ inputFound = true;
312
+ }
313
+ catch {
314
+ inputFound = false;
315
+ }
316
+ const assistantMessageCount = await page
317
+ .locator("ms-chat-turn, ms-prompt-chunk")
318
+ .count()
319
+ .catch(() => 0);
320
+ const stopButtonFound = await this.isStopButtonVisible(page);
321
+ let lastAssistantLength = 0;
322
+ try {
323
+ const text = await this.extractLastAssistantMessage(page);
324
+ lastAssistantLength = text.length;
325
+ }
326
+ catch {
327
+ lastAssistantLength = 0;
328
+ }
329
+ return {
330
+ loggedIn,
331
+ inputFound,
332
+ sendButtonFound: sendBtn,
333
+ assistantMessageCount,
334
+ stopButtonFound,
335
+ lastAssistantLength,
336
+ pageUrl: page.url(),
337
+ };
338
+ }
339
+ async pressRunShortcut(page) {
340
+ // AI Studio binds Cmd/Ctrl+Enter to Run.
341
+ const isMac = process.platform === "darwin";
342
+ await page.keyboard.press(isMac ? "Meta+Enter" : "Control+Enter");
343
+ }
344
+ async waitForComposerReady(page, timeoutMs = 15_000) {
345
+ const sel = 'ms-prompt-box textarea, textarea[aria-label="Enter a prompt"], textarea[aria-label*="prompt" i], textarea';
346
+ await page.locator(sel).first().waitFor({ state: "visible", timeout: timeoutMs });
347
+ }
348
+ async waitForStopButton(page, options = {}) {
349
+ const timeoutMs = options.timeoutMs ?? 15_000;
350
+ const deadline = Date.now() + timeoutMs;
351
+ while (Date.now() < deadline) {
352
+ if (await this.isStopButtonVisible(page))
353
+ return;
354
+ await sleep(150);
355
+ }
356
+ }
357
+ async waitForStopButtonGone(page, timeoutMs = 5_000) {
358
+ const deadline = Date.now() + timeoutMs;
359
+ let consecutiveGone = 0;
360
+ while (Date.now() < deadline) {
361
+ const visible = await this.isStopButtonVisible(page);
362
+ if (!visible) {
363
+ consecutiveGone += 1;
364
+ if (consecutiveGone >= 3)
365
+ return;
366
+ }
367
+ else {
368
+ consecutiveGone = 0;
369
+ }
370
+ await sleep(200);
371
+ }
372
+ }
373
+ async isStopButtonVisible(page) {
374
+ // AI Studio's Stop button has NO aria-label — its visible text is
375
+ // "progress_activity Stop" (a Material icon ligature + the word
376
+ // "Stop"). The accessible name resolves to "Stop" via the visible
377
+ // text, so getByRole(name=/Stop/) catches it. We keep the
378
+ // aria-label fallbacks in case Google adds them later.
379
+ const stopByRole = await page
380
+ .getByRole("button", { name: /^Stop$/i })
381
+ .first()
382
+ .isVisible()
383
+ .catch(() => false);
384
+ if (stopByRole)
385
+ return true;
386
+ const stopByText = await page
387
+ .locator('button:has-text("Stop")')
388
+ .first()
389
+ .isVisible()
390
+ .catch(() => false);
391
+ if (stopByText)
392
+ return true;
393
+ const selectors = [
394
+ 'button[aria-label*="Stop" i]',
395
+ 'button[aria-label*="Cancel" i]',
396
+ 'button[aria-label*="停止" i]',
397
+ 'button[data-test-id="stop-button"]',
398
+ ];
399
+ for (const sel of selectors) {
400
+ const visible = await page.locator(sel).first().isVisible().catch(() => false);
401
+ if (visible)
402
+ return true;
403
+ }
404
+ return false;
405
+ }
406
+ }
407
+ /**
408
+ * Strip the UI chrome that leaks into `innerText` on AI Studio model
409
+ * turns. Removes:
410
+ * - The "Model HH:MM AM/PM" turn header
411
+ * - Single-line Material icon font ligatures (rendered as plain text
412
+ * words because the icon-font substitution doesn't affect text
413
+ * extraction): edit, more_vert, error, content_copy, thumb_up,
414
+ * thumb_down, refresh, delete, close, expand_more, expand_less,
415
+ * code, play_arrow, stop, menu.
416
+ *
417
+ * Does NOT remove lines where the same word appears INSIDE a sentence
418
+ * (e.g. "an error occurred during parsing" stays as-is).
419
+ */
420
+ export function stripChromeFromTurn(text) {
421
+ if (!text)
422
+ return "";
423
+ const lines = text.split(/\r?\n/);
424
+ const HEADER = /^(Model|User|System)\s+\d{1,2}:\d{2}\s*(AM|PM)?\s*$/i;
425
+ const ICON_LIGATURES = new Set([
426
+ "edit",
427
+ "more_vert",
428
+ "error",
429
+ "content_copy",
430
+ "thumb_up",
431
+ "thumb_down",
432
+ "refresh",
433
+ "delete",
434
+ "close",
435
+ "expand_more",
436
+ "expand_less",
437
+ "code",
438
+ "play_arrow",
439
+ "stop",
440
+ "menu",
441
+ ]);
442
+ const kept = [];
443
+ for (const raw of lines) {
444
+ const trimmed = raw.trim();
445
+ if (!trimmed) {
446
+ kept.push(raw);
447
+ continue;
448
+ }
449
+ if (HEADER.test(trimmed))
450
+ continue;
451
+ if (ICON_LIGATURES.has(trimmed))
452
+ continue;
453
+ kept.push(raw);
454
+ }
455
+ return kept.join("\n").trim();
456
+ }
457
+ /**
458
+ * AI Studio renders "Thinking" / "Thought for Ns" / "正在思考" while the
459
+ * model is reasoning before its first content token. Those strings
460
+ * appear in the model turn's innerText for a while; treating them as
461
+ * "the final answer" is the same class of bug as ChatGPT's pre-stream
462
+ * `Thinking` placeholder.
463
+ */
464
+ function isThinkingPlaceholder(text) {
465
+ const t = text.trim();
466
+ if (!t)
467
+ return true;
468
+ if (t.length > 80)
469
+ return false;
470
+ const lower = t.toLowerCase();
471
+ return (lower === "thinking" ||
472
+ lower === "thinking…" ||
473
+ lower === "thinking..." ||
474
+ lower.startsWith("thinking\n") ||
475
+ lower.startsWith("thought for ") ||
476
+ lower.startsWith("reasoning") ||
477
+ lower.startsWith("思考") ||
478
+ lower.startsWith("正在思考"));
479
+ }
480
+ //# sourceMappingURL=gemini.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gemini.js","sourceRoot":"","sources":["../../src/providers/gemini.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ export { ChatGPTAdapter } from "./chatgpt.js";
2
+ export { DeepSeekAdapter } from "./deepseek.js";
3
+ export { GeminiAdapter } from "./gemini.js";
4
+ /**
5
+ * Register all bundled providers. Call this once at process startup
6
+ * (CLI / daemon entrypoints already do so).
7
+ */
8
+ export declare function registerBuiltinProviders(): void;
@@ -0,0 +1,17 @@
1
+ import { registerProvider } from "../core/provider.js";
2
+ import { ChatGPTAdapter } from "./chatgpt.js";
3
+ import { DeepSeekAdapter } from "./deepseek.js";
4
+ import { GeminiAdapter } from "./gemini.js";
5
+ export { ChatGPTAdapter } from "./chatgpt.js";
6
+ export { DeepSeekAdapter } from "./deepseek.js";
7
+ export { GeminiAdapter } from "./gemini.js";
8
+ /**
9
+ * Register all bundled providers. Call this once at process startup
10
+ * (CLI / daemon entrypoints already do so).
11
+ */
12
+ export function registerBuiltinProviders() {
13
+ registerProvider(new ChatGPTAdapter());
14
+ registerProvider(new DeepSeekAdapter());
15
+ registerProvider(new GeminiAdapter());
16
+ }
17
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/providers/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAEvD,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C;;;GAGG;AACH,MAAM,UAAU,wBAAwB;IACtC,gBAAgB,CAAC,IAAI,cAAc,EAAE,CAAC,CAAC;IACvC,gBAAgB,CAAC,IAAI,eAAe,EAAE,CAAC,CAAC;IACxC,gBAAgB,CAAC,IAAI,aAAa,EAAE,CAAC,CAAC;AACxC,CAAC"}
@@ -0,0 +1,121 @@
1
+ import type { BrowserContext, Page } from "playwright";
2
+ import { type LaunchOptions } from "./core/browser.js";
3
+ import { type Logger } from "./core/logger.js";
4
+ import { type ChatProvider, type ProviderDiagnostics } from "./core/provider.js";
5
+ import { type PageSnapshot } from "./core/snapshot.js";
6
+ export interface SessionOpenOptions {
7
+ /** Run Chromium headless. Defaults to headed (RFC §19.3). */
8
+ headless?: boolean;
9
+ /** Override the logger. */
10
+ logger?: Logger;
11
+ /** Extra Chromium args / viewport overrides forwarded to the browser launcher. */
12
+ launch?: Omit<LaunchOptions, "headless" | "profileManager">;
13
+ /** Auto-open the provider home URL after launch. Default true. */
14
+ autoOpen?: boolean;
15
+ }
16
+ export interface SendOptions {
17
+ /** Start a fresh chat before sending (clears the conversation context). */
18
+ newChat?: boolean;
19
+ /** Hard upper bound on the response wait. Forwarded to the provider. */
20
+ timeoutMs?: number;
21
+ /** How long the assistant text must stop changing before we declare done. */
22
+ stableMs?: number;
23
+ /** AbortSignal — aborts the wait but does NOT undo the network message. */
24
+ signal?: AbortSignal;
25
+ /** Streaming progress callback. */
26
+ onProgress?: (text: string) => void;
27
+ }
28
+ export interface SendResult {
29
+ /** 0-based index of this turn within the session's lifetime. */
30
+ turnIndex: number;
31
+ message: string;
32
+ response: string;
33
+ /** Wall-clock duration of this send (ms). */
34
+ durationMs: number;
35
+ /**
36
+ * Provider-side conversation id, when the provider exposes one. For
37
+ * ChatGPT this is the UUID at `chatgpt.com/c/{uuid}`. Stable across
38
+ * turns within the same conversation, undefined on the first response
39
+ * if the provider hasn't navigated to its conversation URL yet.
40
+ */
41
+ conversationId?: string;
42
+ }
43
+ /**
44
+ * A long-lived chat session bound to one provider account.
45
+ *
46
+ * Think of it as "the thing you'd get if you opened ChatGPT in a browser
47
+ * tab and kept it open" — same persistent profile, same Chromium context,
48
+ * same conversation memory. Multi-turn dialogue happens by calling
49
+ * `session.send()` repeatedly. `session.newChat()` starts a fresh
50
+ * conversation but keeps the browser alive.
51
+ *
52
+ * RFC §19.2: a single account must not be hit concurrently. We enforce
53
+ * this by serialising `send` / `newChat` through an internal FIFO queue,
54
+ * so callers can safely fire-and-await from multiple async paths.
55
+ */
56
+ export declare class ChatSession {
57
+ private readonly providerImpl;
58
+ private readonly context;
59
+ private readonly pageImpl;
60
+ readonly userDataDir: string;
61
+ private readonly logger;
62
+ /** Open and return a ready-to-use session. */
63
+ static open(providerName: string, options?: SessionOpenOptions): Promise<ChatSession>;
64
+ /**
65
+ * Build a session around an already-launched browser context. Mostly
66
+ * used by tests / advanced callers that manage the browser themselves;
67
+ * regular code should go through `ChatSession.open()`.
68
+ */
69
+ static fromBrowser(args: {
70
+ provider: ChatProvider;
71
+ context: BrowserContext;
72
+ page: Page;
73
+ userDataDir: string;
74
+ logger?: Logger;
75
+ }): ChatSession;
76
+ private turnCounter;
77
+ private queue;
78
+ private closed;
79
+ /** Provider-side conversation id (e.g. ChatGPT `/c/{uuid}`), set after the first turn lands. */
80
+ private providerConversationId;
81
+ private constructor();
82
+ get provider(): string;
83
+ get page(): Page;
84
+ get turns(): number;
85
+ get isClosed(): boolean;
86
+ /** Quick check: is the provider profile signed in? */
87
+ isLoggedIn(): Promise<boolean>;
88
+ /**
89
+ * Send one message and await the assistant's full reply.
90
+ *
91
+ * Calls are serialised per session — overlapping `send`s from different
92
+ * callers won't trample each other; they queue up and run in order.
93
+ */
94
+ send(message: string, options?: SendOptions): Promise<SendResult>;
95
+ /**
96
+ * Provider-side conversation id (e.g. ChatGPT's /c/{uuid}). `undefined`
97
+ * before the first turn has landed, then stable for the rest of this
98
+ * `ChatSession` unless `newChat()` is called.
99
+ */
100
+ get conversationId(): string | undefined;
101
+ /**
102
+ * Read the conversation id from the current page (provider permitting)
103
+ * and store it on the session. Idempotent — repeated calls within the
104
+ * same conversation will see the same id; the first call captures it.
105
+ */
106
+ private captureConversationId;
107
+ /** Start a fresh conversation while keeping the session/browser open. */
108
+ newChat(): Promise<void>;
109
+ /** Provider-specific health probe (input found, send button found, ...). */
110
+ diagnose(): Promise<ProviderDiagnostics>;
111
+ /** Snapshot of interactive DOM elements (ref-based). */
112
+ snapshot(): Promise<PageSnapshot>;
113
+ /** Close the browser context. Safe to call multiple times. */
114
+ close(): Promise<void>;
115
+ /** Enables `await using session = …` (TC39 explicit resource management). */
116
+ [Symbol.asyncDispose](): Promise<void>;
117
+ private assertOpen;
118
+ private enqueue;
119
+ }
120
+ /** Convenience wrapper: open → callback → close, regardless of throws. */
121
+ export declare function withSession<T>(providerName: string, body: (session: ChatSession) => Promise<T>, options?: SessionOpenOptions): Promise<T>;