@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,70 @@
1
+ /**
2
+ * Watch a streaming source (typically the last assistant message) until its
3
+ * text stops mutating for `stableMs` consecutive milliseconds, or the
4
+ * `timeoutMs` budget is exhausted.
5
+ *
6
+ * `getText` should return "" (or throw) while the message doesn't exist yet;
7
+ * we treat both cases the same and keep polling.
8
+ *
9
+ * RFC §10 — also recommends combining this with "stop button vanished"
10
+ * and "send button re-enabled"; those checks live in the provider adapters
11
+ * because the selectors are provider-specific.
12
+ */
13
+ export async function waitUntilStable(getText, options = {}) {
14
+ const stableMs = options.stableMs ?? 2_000;
15
+ const timeoutMs = options.timeoutMs ?? 120_000;
16
+ const pollIntervalMs = options.pollIntervalMs ?? 300;
17
+ const deadline = Date.now() + timeoutMs;
18
+ let last = "";
19
+ let stableSince = Date.now();
20
+ let everSeen = false;
21
+ while (true) {
22
+ if (options.signal?.aborted) {
23
+ throw new DOMException("Aborted", "AbortError");
24
+ }
25
+ let current = "";
26
+ try {
27
+ current = await getText();
28
+ }
29
+ catch {
30
+ current = "";
31
+ }
32
+ if (current !== last) {
33
+ last = current;
34
+ stableSince = Date.now();
35
+ if (current) {
36
+ everSeen = true;
37
+ options.onProgress?.(current);
38
+ }
39
+ }
40
+ const sinceChange = Date.now() - stableSince;
41
+ if (everSeen && sinceChange >= stableMs) {
42
+ return last;
43
+ }
44
+ if (Date.now() > deadline) {
45
+ // Return whatever we have rather than throwing — the caller (provider
46
+ // adapter or `ask` flow) decides whether to error or surface partial.
47
+ return last;
48
+ }
49
+ await sleep(pollIntervalMs);
50
+ }
51
+ }
52
+ export function sleep(ms) {
53
+ return new Promise((resolve) => setTimeout(resolve, ms));
54
+ }
55
+ /**
56
+ * Convenience: wait until the assistant message count grows past a baseline.
57
+ * Useful right after pressing Enter, so we know the streaming has started.
58
+ */
59
+ export async function waitForResponseStart(page, selector, baseline, options = {}) {
60
+ const timeoutMs = options.timeoutMs ?? 30_000;
61
+ const pollIntervalMs = options.pollIntervalMs ?? 200;
62
+ const deadline = Date.now() + timeoutMs;
63
+ while (Date.now() < deadline) {
64
+ const count = await page.locator(selector).count().catch(() => 0);
65
+ if (count > baseline)
66
+ return;
67
+ await sleep(pollIntervalMs);
68
+ }
69
+ }
70
+ //# sourceMappingURL=response-watcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"response-watcher.js","sourceRoot":"","sources":["../../src/core/response-watcher.ts"],"names":[],"mappings":"AAeA;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,OAA8B,EAC9B,UAAkC,EAAE;IAEpC,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,KAAK,CAAC;IAC3C,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,OAAO,CAAC;IAC/C,MAAM,cAAc,GAAG,OAAO,CAAC,cAAc,IAAI,GAAG,CAAC;IAErD,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;IACxC,IAAI,IAAI,GAAG,EAAE,CAAC;IACd,IAAI,WAAW,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC7B,IAAI,QAAQ,GAAG,KAAK,CAAC;IAErB,OAAO,IAAI,EAAE,CAAC;QACZ,IAAI,OAAO,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC;YAC5B,MAAM,IAAI,YAAY,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC;QAClD,CAAC;QAED,IAAI,OAAO,GAAG,EAAE,CAAC;QACjB,IAAI,CAAC;YACH,OAAO,GAAG,MAAM,OAAO,EAAE,CAAC;QAC5B,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,GAAG,EAAE,CAAC;QACf,CAAC;QAED,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;YACrB,IAAI,GAAG,OAAO,CAAC;YACf,WAAW,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YACzB,IAAI,OAAO,EAAE,CAAC;gBACZ,QAAQ,GAAG,IAAI,CAAC;gBAChB,OAAO,CAAC,UAAU,EAAE,CAAC,OAAO,CAAC,CAAC;YAChC,CAAC;QACH,CAAC;QAED,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,WAAW,CAAC;QAC7C,IAAI,QAAQ,IAAI,WAAW,IAAI,QAAQ,EAAE,CAAC;YACxC,OAAO,IAAI,CAAC;QACd,CAAC;QAED,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,EAAE,CAAC;YAC1B,sEAAsE;YACtE,sEAAsE;YACtE,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,KAAK,CAAC,cAAc,CAAC,CAAC;IAC9B,CAAC;AACH,CAAC;AAED,MAAM,UAAU,KAAK,CAAC,EAAU;IAC9B,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;AAC3D,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,IAAU,EACV,QAAgB,EAChB,QAAgB,EAChB,UAA2D,EAAE;IAE7D,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,MAAM,CAAC;IAC9C,MAAM,cAAc,GAAG,OAAO,CAAC,cAAc,IAAI,GAAG,CAAC;IACrD,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;IAExC,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,EAAE,CAAC;QAC7B,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC;QAClE,IAAI,KAAK,GAAG,QAAQ;YAAE,OAAO;QAC7B,MAAM,KAAK,CAAC,cAAc,CAAC,CAAC;IAC9B,CAAC;AACH,CAAC"}
@@ -0,0 +1,38 @@
1
+ import type { Locator, Page } from "playwright";
2
+ /**
3
+ * Lightweight snapshot of the page's interactive elements, modelled after
4
+ * the `ref` concept from Stagehand / agent-browser (RFC §11).
5
+ *
6
+ * Unlike a full accessibility tree, this is a flat list of element
7
+ * descriptors with stable, opaque refs (`e1`, `e2`, ...) that the rest of
8
+ * the system can use to fill / click / extract. The refs survive the
9
+ * lifetime of a Page session but are not persisted.
10
+ */
11
+ export interface SnapshotEntry {
12
+ ref: string;
13
+ tag: string;
14
+ role: string | null;
15
+ ariaLabel: string | null;
16
+ placeholder: string | null;
17
+ type: string | null;
18
+ visible: boolean;
19
+ editable: boolean;
20
+ enabled: boolean;
21
+ text: string;
22
+ selector: string;
23
+ }
24
+ export interface PageSnapshot {
25
+ url: string;
26
+ takenAt: string;
27
+ entries: SnapshotEntry[];
28
+ }
29
+ /**
30
+ * Collect a coarse snapshot of "things a user / agent might interact with"
31
+ * on the current page. The set of selectors is intentionally simple — it's
32
+ * fed to {@link locator-score} for ranking, not pretty-printed verbatim.
33
+ */
34
+ export declare function takeSnapshot(page: Page): Promise<PageSnapshot>;
35
+ /** Convert a snapshot entry back into a Playwright Locator. */
36
+ export declare function locatorFromEntry(page: Page, entry: SnapshotEntry): Locator;
37
+ /** Human-readable rendering, useful for `chat-web doctor --snapshot`. */
38
+ export declare function formatSnapshot(snap: PageSnapshot): string;
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Collect a coarse snapshot of "things a user / agent might interact with"
3
+ * on the current page. The set of selectors is intentionally simple — it's
4
+ * fed to {@link locator-score} for ranking, not pretty-printed verbatim.
5
+ */
6
+ export async function takeSnapshot(page) {
7
+ // We query JS-side so we don't have to round-trip 100s of locator calls.
8
+ const raw = await page.evaluate(() => {
9
+ const SELECTORS = [
10
+ // contenteditable first — ChatGPT's ProseMirror has both a fallback
11
+ // textarea AND the real contenteditable; the contenteditable is what
12
+ // the user actually types into.
13
+ '[contenteditable="true"]',
14
+ '[role="textbox"]',
15
+ "#prompt-textarea",
16
+ "textarea",
17
+ "input[type=text]",
18
+ "button",
19
+ '[role="button"]',
20
+ "[data-message-author-role]",
21
+ "main article",
22
+ ".markdown",
23
+ ".prose",
24
+ ];
25
+ const seen = new Set();
26
+ const out = [];
27
+ function cssPath(el) {
28
+ const parts = [];
29
+ let cur = el;
30
+ while (cur && cur.nodeType === 1 && parts.length < 6) {
31
+ const node = cur;
32
+ let part = node.tagName.toLowerCase();
33
+ if (node.id) {
34
+ part += `#${node.id}`;
35
+ parts.unshift(part);
36
+ break;
37
+ }
38
+ const cls = (node.getAttribute("class") || "")
39
+ .split(/\s+/)
40
+ .filter(Boolean)
41
+ .slice(0, 2)
42
+ .map((c) => `.${CSS.escape(c)}`)
43
+ .join("");
44
+ if (cls)
45
+ part += cls;
46
+ const parentEl = node.parentElement;
47
+ if (parentEl) {
48
+ const siblings = Array.from(parentEl.children).filter((s) => s.tagName === node.tagName);
49
+ if (siblings.length > 1) {
50
+ const idx = siblings.indexOf(node) + 1;
51
+ part += `:nth-of-type(${idx})`;
52
+ }
53
+ }
54
+ parts.unshift(part);
55
+ cur = parentEl;
56
+ }
57
+ return parts.join(" > ");
58
+ }
59
+ function isVisible(el) {
60
+ const rect = el.getBoundingClientRect();
61
+ if (rect.width === 0 || rect.height === 0)
62
+ return false;
63
+ const style = window.getComputedStyle(el);
64
+ if (style.visibility === "hidden" || style.display === "none")
65
+ return false;
66
+ if (parseFloat(style.opacity) === 0)
67
+ return false;
68
+ return true;
69
+ }
70
+ for (const sel of SELECTORS) {
71
+ const nodes = document.querySelectorAll(sel);
72
+ nodes.forEach((node) => {
73
+ if (seen.has(node))
74
+ return;
75
+ seen.add(node);
76
+ const html = node;
77
+ out.push({
78
+ tag: html.tagName.toLowerCase(),
79
+ role: html.getAttribute("role"),
80
+ ariaLabel: html.getAttribute("aria-label"),
81
+ placeholder: html.getAttribute("placeholder"),
82
+ type: html.getAttribute("type"),
83
+ visible: isVisible(html),
84
+ editable: html.tagName === "TEXTAREA" ||
85
+ (html.tagName === "INPUT" && html.getAttribute("type") !== "hidden") ||
86
+ html.isContentEditable,
87
+ enabled: !html.disabled,
88
+ text: (html.innerText || html.textContent || "").trim().slice(0, 240),
89
+ selector: cssPath(html),
90
+ });
91
+ });
92
+ }
93
+ return out;
94
+ });
95
+ const entries = raw.map((r, i) => ({
96
+ ref: `e${i + 1}`,
97
+ tag: String(r.tag ?? ""),
98
+ role: r.role ?? null,
99
+ ariaLabel: r.ariaLabel ?? null,
100
+ placeholder: r.placeholder ?? null,
101
+ type: r.type ?? null,
102
+ visible: Boolean(r.visible),
103
+ editable: Boolean(r.editable),
104
+ enabled: Boolean(r.enabled),
105
+ text: String(r.text ?? ""),
106
+ selector: String(r.selector ?? ""),
107
+ }));
108
+ return {
109
+ url: page.url(),
110
+ takenAt: new Date().toISOString(),
111
+ entries,
112
+ };
113
+ }
114
+ /** Convert a snapshot entry back into a Playwright Locator. */
115
+ export function locatorFromEntry(page, entry) {
116
+ return page.locator(entry.selector);
117
+ }
118
+ /** Human-readable rendering, useful for `chat-web doctor --snapshot`. */
119
+ export function formatSnapshot(snap) {
120
+ const lines = [`URL: ${snap.url}`, `At: ${snap.takenAt}`, ""];
121
+ for (const e of snap.entries) {
122
+ const bits = [
123
+ `[${e.ref}]`,
124
+ e.tag,
125
+ e.role ? `role=${e.role}` : null,
126
+ e.ariaLabel ? `aria=${JSON.stringify(e.ariaLabel)}` : null,
127
+ e.placeholder ? `placeholder=${JSON.stringify(e.placeholder)}` : null,
128
+ e.visible ? "visible" : "hidden",
129
+ e.editable ? "editable" : null,
130
+ e.enabled ? "enabled" : "disabled",
131
+ e.text ? `text=${JSON.stringify(e.text.slice(0, 60))}` : null,
132
+ ].filter(Boolean);
133
+ lines.push(bits.join(" "));
134
+ }
135
+ return lines.join("\n");
136
+ }
137
+ //# sourceMappingURL=snapshot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"snapshot.js","sourceRoot":"","sources":["../../src/core/snapshot.ts"],"names":[],"mappings":"AA+BA;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,IAAU;IAC3C,yEAAyE;IACzE,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE;QACnC,MAAM,SAAS,GAAG;YAChB,oEAAoE;YACpE,qEAAqE;YACrE,gCAAgC;YAChC,0BAA0B;YAC1B,kBAAkB;YAClB,kBAAkB;YAClB,UAAU;YACV,kBAAkB;YAClB,QAAQ;YACR,iBAAiB;YACjB,4BAA4B;YAC5B,cAAc;YACd,WAAW;YACX,QAAQ;SACT,CAAC;QAEF,MAAM,IAAI,GAAG,IAAI,GAAG,EAAW,CAAC;QAChC,MAAM,GAAG,GAAmC,EAAE,CAAC;QAE/C,SAAS,OAAO,CAAC,EAAW;YAC1B,MAAM,KAAK,GAAa,EAAE,CAAC;YAC3B,IAAI,GAAG,GAAmB,EAAE,CAAC;YAC7B,OAAO,GAAG,IAAI,GAAG,CAAC,QAAQ,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACrD,MAAM,IAAI,GAAY,GAAG,CAAC;gBAC1B,IAAI,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC;gBACtC,IAAI,IAAI,CAAC,EAAE,EAAE,CAAC;oBACZ,IAAI,IAAI,IAAI,IAAI,CAAC,EAAE,EAAE,CAAC;oBACtB,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;oBACpB,MAAM;gBACR,CAAC;gBACD,MAAM,GAAG,GAAG,CAAC,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;qBAC3C,KAAK,CAAC,KAAK,CAAC;qBACZ,MAAM,CAAC,OAAO,CAAC;qBACf,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC;qBACX,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;qBAC/B,IAAI,CAAC,EAAE,CAAC,CAAC;gBACZ,IAAI,GAAG;oBAAE,IAAI,IAAI,GAAG,CAAC;gBACrB,MAAM,QAAQ,GAAmB,IAAI,CAAC,aAAa,CAAC;gBACpD,IAAI,QAAQ,EAAE,CAAC;oBACb,MAAM,QAAQ,GAAc,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,MAAM,CAC9D,CAAC,CAAU,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,IAAI,CAAC,OAAO,CAC3C,CAAC;oBACF,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;wBACxB,MAAM,GAAG,GAAG,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;wBACvC,IAAI,IAAI,gBAAgB,GAAG,GAAG,CAAC;oBACjC,CAAC;gBACH,CAAC;gBACD,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;gBACpB,GAAG,GAAG,QAAQ,CAAC;YACjB,CAAC;YACD,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC3B,CAAC;QAED,SAAS,SAAS,CAAC,EAAW;YAC5B,MAAM,IAAI,GAAG,EAAE,CAAC,qBAAqB,EAAE,CAAC;YACxC,IAAI,IAAI,CAAC,KAAK,KAAK,CAAC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO,KAAK,CAAC;YACxD,MAAM,KAAK,GAAG,MAAM,CAAC,gBAAgB,CAAC,EAAiB,CAAC,CAAC;YACzD,IAAI,KAAK,CAAC,UAAU,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,KAAK,MAAM;gBAAE,OAAO,KAAK,CAAC;YAC5E,IAAI,UAAU,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;gBAAE,OAAO,KAAK,CAAC;YAClD,OAAO,IAAI,CAAC;QACd,CAAC;QAED,KAAK,MAAM,GAAG,IAAI,SAAS,EAAE,CAAC;YAC5B,MAAM,KAAK,GAAG,QAAQ,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC;YAC7C,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE;gBACrB,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC;oBAAE,OAAO;gBAC3B,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;gBACf,MAAM,IAAI,GAAG,IAAmB,CAAC;gBACjC,GAAG,CAAC,IAAI,CAAC;oBACP,GAAG,EAAE,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE;oBAC/B,IAAI,EAAE,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC;oBAC/B,SAAS,EAAE,IAAI,CAAC,YAAY,CAAC,YAAY,CAAC;oBAC1C,WAAW,EAAE,IAAI,CAAC,YAAY,CAAC,aAAa,CAAC;oBAC7C,IAAI,EAAE,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC;oBAC/B,OAAO,EAAE,SAAS,CAAC,IAAI,CAAC;oBACxB,QAAQ,EACN,IAAI,CAAC,OAAO,KAAK,UAAU;wBAC3B,CAAC,IAAI,CAAC,OAAO,KAAK,OAAO,IAAI,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,KAAK,QAAQ,CAAC;wBACpE,IAAI,CAAC,iBAAiB;oBACxB,OAAO,EAAE,CAAE,IAA0B,CAAC,QAAQ;oBAC9C,IAAI,EAAE,CAAC,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC;oBACrE,QAAQ,EAAE,OAAO,CAAC,IAAI,CAAC;iBACxB,CAAC,CAAC;YACL,CAAC,CAAC,CAAC;QACL,CAAC;QAED,OAAO,GAAG,CAAC;IACb,CAAC,CAAC,CAAC;IAEH,MAAM,OAAO,GAAoB,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;QAClD,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,EAAE;QAChB,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,IAAI,EAAE,CAAC;QACxB,IAAI,EAAG,CAAC,CAAC,IAAsB,IAAI,IAAI;QACvC,SAAS,EAAG,CAAC,CAAC,SAA2B,IAAI,IAAI;QACjD,WAAW,EAAG,CAAC,CAAC,WAA6B,IAAI,IAAI;QACrD,IAAI,EAAG,CAAC,CAAC,IAAsB,IAAI,IAAI;QACvC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC;QAC3B,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC;QAC7B,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC;QAC3B,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC;QAC1B,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC,QAAQ,IAAI,EAAE,CAAC;KACnC,CAAC,CAAC,CAAC;IAEJ,OAAO;QACL,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE;QACf,OAAO,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACjC,OAAO;KACR,CAAC;AACJ,CAAC;AAED,+DAA+D;AAC/D,MAAM,UAAU,gBAAgB,CAAC,IAAU,EAAE,KAAoB;IAC/D,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;AACtC,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,cAAc,CAAC,IAAkB;IAC/C,MAAM,KAAK,GAAa,CAAC,QAAQ,IAAI,CAAC,GAAG,EAAE,EAAE,QAAQ,IAAI,CAAC,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC;IACzE,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;QAC7B,MAAM,IAAI,GAAG;YACX,IAAI,CAAC,CAAC,GAAG,GAAG;YACZ,CAAC,CAAC,GAAG;YACL,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI;YAChC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI;YAC1D,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,eAAe,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI;YACrE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ;YAChC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI;YAC9B,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,UAAU;YAClC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI;SAC9D,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAClB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IAC7B,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Minimal Server-Sent Events parser.
3
+ *
4
+ * Implements the subset of the WHATWG EventSource spec that ChatGPT,
5
+ * DeepSeek and similar streaming endpoints actually use:
6
+ * - Events separated by a blank line.
7
+ * - `data:` fields concatenated with "\n" within an event.
8
+ * - `event:` and `id:` fields read into the corresponding properties.
9
+ * - Lines starting with ":" are comments and discarded.
10
+ * - A single leading space after the field colon is stripped.
11
+ *
12
+ * Doesn't handle retry hints (`retry:`) or BOM stripping — neither matters
13
+ * for our consumers.
14
+ */
15
+ export interface SSEEvent {
16
+ event?: string;
17
+ data: string;
18
+ id?: string;
19
+ }
20
+ export declare function parseSSE(text: string): SSEEvent[];
@@ -0,0 +1,49 @@
1
+ export function parseSSE(text) {
2
+ const events = [];
3
+ // Normalise newlines so the blank-line split is robust against \r\n vs \n.
4
+ const normalised = text.replace(/\r\n?/g, "\n");
5
+ for (const block of normalised.split(/\n\n+/)) {
6
+ const ev = parseBlock(block);
7
+ if (ev)
8
+ events.push(ev);
9
+ }
10
+ return events;
11
+ }
12
+ function parseBlock(block) {
13
+ if (!block.trim())
14
+ return null;
15
+ const ev = { data: "" };
16
+ const dataLines = [];
17
+ for (const rawLine of block.split("\n")) {
18
+ if (!rawLine)
19
+ continue;
20
+ if (rawLine.startsWith(":"))
21
+ continue; // comment
22
+ const colon = rawLine.indexOf(":");
23
+ const field = colon === -1 ? rawLine : rawLine.slice(0, colon);
24
+ let value = colon === -1 ? "" : rawLine.slice(colon + 1);
25
+ if (value.startsWith(" "))
26
+ value = value.slice(1);
27
+ switch (field) {
28
+ case "data":
29
+ dataLines.push(value);
30
+ break;
31
+ case "event":
32
+ ev.event = value;
33
+ break;
34
+ case "id":
35
+ ev.id = value;
36
+ break;
37
+ default:
38
+ // ignore unknown fields (retry, etc.)
39
+ break;
40
+ }
41
+ }
42
+ if (dataLines.length === 0) {
43
+ // An event with no data line is meaningless to us; drop it.
44
+ return null;
45
+ }
46
+ ev.data = dataLines.join("\n");
47
+ return ev;
48
+ }
49
+ //# sourceMappingURL=sse-parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sse-parser.js","sourceRoot":"","sources":["../../src/core/sse-parser.ts"],"names":[],"mappings":"AAoBA,MAAM,UAAU,QAAQ,CAAC,IAAY;IACnC,MAAM,MAAM,GAAe,EAAE,CAAC;IAC9B,2EAA2E;IAC3E,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IAChD,KAAK,MAAM,KAAK,IAAI,UAAU,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;QAC9C,MAAM,EAAE,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;QAC7B,IAAI,EAAE;YAAE,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC1B,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,UAAU,CAAC,KAAa;IAC/B,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE;QAAE,OAAO,IAAI,CAAC;IAC/B,MAAM,EAAE,GAAa,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;IAClC,MAAM,SAAS,GAAa,EAAE,CAAC;IAC/B,KAAK,MAAM,OAAO,IAAI,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACxC,IAAI,CAAC,OAAO;YAAE,SAAS;QACvB,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,SAAS,CAAC,UAAU;QACjD,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACnC,MAAM,KAAK,GAAG,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;QAC/D,IAAI,KAAK,GAAG,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;QACzD,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAClD,QAAQ,KAAK,EAAE,CAAC;YACd,KAAK,MAAM;gBACT,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;gBACtB,MAAM;YACR,KAAK,OAAO;gBACV,EAAE,CAAC,KAAK,GAAG,KAAK,CAAC;gBACjB,MAAM;YACR,KAAK,IAAI;gBACP,EAAE,CAAC,EAAE,GAAG,KAAK,CAAC;gBACd,MAAM;YACR;gBACE,sCAAsC;gBACtC,MAAM;QACV,CAAC;IACH,CAAC;IACD,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3B,4DAA4D;QAC5D,OAAO,IAAI,CAAC;IACd,CAAC;IACD,EAAE,CAAC,IAAI,GAAG,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC/B,OAAO,EAAE,CAAC;AACZ,CAAC"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Public entry point for `@love-moon/chat-web`.
3
+ *
4
+ * The primary SDK surface is `ChatSession` — multi-turn dialogue against
5
+ * a persistent browser profile. CLI users get `login`, `doctor`, `info`;
6
+ * everything else (ask, daemon, ...) lives at the SDK level.
7
+ *
8
+ * @example
9
+ * import { ChatSession, registerBuiltinProviders } from "@love-moon/chat-web";
10
+ *
11
+ * registerBuiltinProviders();
12
+ * const session = await ChatSession.open("chatgpt");
13
+ * try {
14
+ * const r1 = await session.send("Hello");
15
+ * const r2 = await session.send("Tell me more");
16
+ * console.log(r1.response, r2.response);
17
+ * } finally {
18
+ * await session.close();
19
+ * }
20
+ */
21
+ export { ChatSession, withSession, type SendOptions, type SendResult, type SessionOpenOptions, } from "./session.js";
22
+ export * from "./core/provider.js";
23
+ export * from "./providers/index.js";
24
+ export * from "./core/errors.js";
25
+ export * from "./core/profile-manager.js";
26
+ export * from "./core/browser.js";
27
+ export * from "./core/install-chromium.js";
28
+ export * from "./core/paths.js";
29
+ export * from "./core/response-watcher.js";
30
+ export * from "./core/snapshot.js";
31
+ export * from "./core/locator-score.js";
32
+ export * from "./core/logger.js";
33
+ export * from "./commands/index.js";
package/dist/index.js ADDED
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Public entry point for `@love-moon/chat-web`.
3
+ *
4
+ * The primary SDK surface is `ChatSession` — multi-turn dialogue against
5
+ * a persistent browser profile. CLI users get `login`, `doctor`, `info`;
6
+ * everything else (ask, daemon, ...) lives at the SDK level.
7
+ *
8
+ * @example
9
+ * import { ChatSession, registerBuiltinProviders } from "@love-moon/chat-web";
10
+ *
11
+ * registerBuiltinProviders();
12
+ * const session = await ChatSession.open("chatgpt");
13
+ * try {
14
+ * const r1 = await session.send("Hello");
15
+ * const r2 = await session.send("Tell me more");
16
+ * console.log(r1.response, r2.response);
17
+ * } finally {
18
+ * await session.close();
19
+ * }
20
+ */
21
+ // Primary session API.
22
+ export { ChatSession, withSession, } from "./session.js";
23
+ // Provider plumbing.
24
+ export * from "./core/provider.js";
25
+ export * from "./providers/index.js";
26
+ // Supporting types & utilities — exposed because SDK consumers occasionally
27
+ // need them (custom providers, custom snapshots, custom error handling, ...).
28
+ export * from "./core/errors.js";
29
+ export * from "./core/profile-manager.js";
30
+ export * from "./core/browser.js";
31
+ export * from "./core/install-chromium.js";
32
+ export * from "./core/paths.js";
33
+ export * from "./core/response-watcher.js";
34
+ export * from "./core/snapshot.js";
35
+ export * from "./core/locator-score.js";
36
+ export * from "./core/logger.js";
37
+ // CLI commands as SDK functions, in case callers want to drive them
38
+ // programmatically (e.g. a UI that runs doctor and renders the report).
39
+ export * from "./commands/index.js";
40
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,uBAAuB;AACvB,OAAO,EACL,WAAW,EACX,WAAW,GAIZ,MAAM,cAAc,CAAC;AAEtB,qBAAqB;AACrB,cAAc,oBAAoB,CAAC;AACnC,cAAc,sBAAsB,CAAC;AAErC,4EAA4E;AAC5E,8EAA8E;AAC9E,cAAc,kBAAkB,CAAC;AACjC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,mBAAmB,CAAC;AAClC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,iBAAiB,CAAC;AAChC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,oBAAoB,CAAC;AACnC,cAAc,yBAAyB,CAAC;AACxC,cAAc,kBAAkB,CAAC;AAEjC,oEAAoE;AACpE,wEAAwE;AACxE,cAAc,qBAAqB,CAAC"}
@@ -0,0 +1,76 @@
1
+ import type { Page } from "playwright";
2
+ /**
3
+ * URL pattern for ChatGPT's streaming conversation endpoint. The exact
4
+ * path has shifted historically (`/backend-api/conversation`,
5
+ * `/backend-api/f/conversation`, `/backend-api/lat/r/conversation`, ...);
6
+ * `conversation` has stayed as the final path segment in every form.
7
+ *
8
+ * NOTE: must NOT match the sibling JSON endpoints `/conversation/init`,
9
+ * `/conversation/prepare`, `/conversation/textdocs/...` — those land
10
+ * before the real SSE response and would otherwise be captured first.
11
+ */
12
+ export declare const CHATGPT_CONVERSATION_URL: RegExp;
13
+ /**
14
+ * Listens to ChatGPT's streaming conversation responses and reconstructs
15
+ * the assistant's original markdown (code fences, list prefixes, table
16
+ * pipes, link URLs — everything that `innerText` would have stripped).
17
+ *
18
+ * Usage:
19
+ * const collector = new ChatGPTSSECollector();
20
+ * collector.attach(page);
21
+ * …
22
+ * const wait = collector.beginTurn();
23
+ * await adapter.sendMessage(page, "...");
24
+ * const markdown = await wait; // resolves to the assistant's raw markdown
25
+ *
26
+ * Robust to multiple SSE formats observed in the wild:
27
+ * - Full message snapshots: `{message: {author, content: {parts}, status}, ...}`
28
+ * - v1 delta encoding: `{p, o: "append", v}` followed by `{v}` shorthand frames
29
+ * - Terminators: `[DONE]` or `{"type": "message_stream_complete"}`
30
+ */
31
+ export declare class ChatGPTSSECollector {
32
+ private detach?;
33
+ private pendingTurn;
34
+ private currentTurn;
35
+ /** Attach to a Playwright Page. Idempotent. */
36
+ attach(page: Page): void;
37
+ dispose(): void;
38
+ /**
39
+ * Start watching for the next conversation response. Returns a promise
40
+ * that resolves to the accumulated markdown once that response finishes
41
+ * streaming (or rejects on the supplied timeout).
42
+ */
43
+ beginTurn(options?: {
44
+ timeoutMs?: number;
45
+ }): Promise<string>;
46
+ /**
47
+ * Assistant text accumulated for the CURRENT turn only.
48
+ *
49
+ * Returns "" until SSE events for the current turn arrive. NEVER returns
50
+ * text from a previous turn — that would leak stale answers into the
51
+ * next prompt's extraction fallback. Bug fixed in this method: an
52
+ * earlier implementation also returned the latest *finalized* turn's
53
+ * text, which made the DOM-fallback path silently substitute the prior
54
+ * turn's reply when the current turn's SSE was empty / racy.
55
+ */
56
+ getCurrentTurnText(): string;
57
+ /**
58
+ * @deprecated Use {@link getCurrentTurnText}. Kept as a hard error so
59
+ * callers fail loudly rather than silently consume stale data.
60
+ */
61
+ getLastAssistantText(): string;
62
+ /** Has the current turn's SSE stream ended? */
63
+ isStreamComplete(): boolean;
64
+ /**
65
+ * Apply a raw SSE body directly. Exposed for tests and for callers that
66
+ * want to feed in a recorded transcript.
67
+ */
68
+ ingest(body: string): void;
69
+ private maybeConsume;
70
+ private applyEvent;
71
+ private applyDelta;
72
+ private finishPendingTurn;
73
+ private finishPendingTurnWithError;
74
+ /** Best-effort assistant text from the current turn's snapshots. */
75
+ private bestAssistantText;
76
+ }