@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,298 @@
1
+ import { parseSSE } from "../core/sse-parser.js";
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 const CHATGPT_CONVERSATION_URL = /\/backend-api\/(?:[^/]+\/)*conversation(?:\?|$)/;
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 class ChatGPTSSECollector {
32
+ detach;
33
+ pendingTurn;
34
+ currentTurn = freshTurn();
35
+ /** Attach to a Playwright Page. Idempotent. */
36
+ attach(page) {
37
+ if (this.detach)
38
+ return;
39
+ const handler = (response) => {
40
+ void this.maybeConsume(response);
41
+ };
42
+ page.on("response", handler);
43
+ this.detach = () => page.off("response", handler);
44
+ }
45
+ dispose() {
46
+ this.detach?.();
47
+ this.detach = undefined;
48
+ if (this.pendingTurn?.timer)
49
+ clearTimeout(this.pendingTurn.timer);
50
+ this.pendingTurn = undefined;
51
+ }
52
+ /**
53
+ * Start watching for the next conversation response. Returns a promise
54
+ * that resolves to the accumulated markdown once that response finishes
55
+ * streaming (or rejects on the supplied timeout).
56
+ */
57
+ beginTurn(options = {}) {
58
+ // Reject any prior unresolved turn first — we only track one in flight.
59
+ if (this.pendingTurn) {
60
+ this.pendingTurn.reject(new Error("Superseded by a new turn"));
61
+ if (this.pendingTurn.timer)
62
+ clearTimeout(this.pendingTurn.timer);
63
+ }
64
+ this.currentTurn = freshTurn();
65
+ const timeoutMs = options.timeoutMs ?? 180_000;
66
+ return new Promise((resolve, reject) => {
67
+ const timer = setTimeout(() => {
68
+ if (this.pendingTurn) {
69
+ const partial = this.bestAssistantText();
70
+ this.pendingTurn = undefined;
71
+ // Resolve with whatever we got rather than rejecting — callers
72
+ // can decide to fall back. Empty string means "no SSE captured".
73
+ resolve(partial);
74
+ }
75
+ }, timeoutMs);
76
+ this.pendingTurn = { resolve, reject, captured: false, timer };
77
+ });
78
+ }
79
+ /**
80
+ * Assistant text accumulated for the CURRENT turn only.
81
+ *
82
+ * Returns "" until SSE events for the current turn arrive. NEVER returns
83
+ * text from a previous turn — that would leak stale answers into the
84
+ * next prompt's extraction fallback. Bug fixed in this method: an
85
+ * earlier implementation also returned the latest *finalized* turn's
86
+ * text, which made the DOM-fallback path silently substitute the prior
87
+ * turn's reply when the current turn's SSE was empty / racy.
88
+ */
89
+ getCurrentTurnText() {
90
+ return this.bestAssistantText();
91
+ }
92
+ /**
93
+ * @deprecated Use {@link getCurrentTurnText}. Kept as a hard error so
94
+ * callers fail loudly rather than silently consume stale data.
95
+ */
96
+ getLastAssistantText() {
97
+ throw new Error("ChatGPTSSECollector.getLastAssistantText() was removed; use getCurrentTurnText() — it never returns stale text from a prior turn.");
98
+ }
99
+ /** Has the current turn's SSE stream ended? */
100
+ isStreamComplete() {
101
+ return this.currentTurn.streamComplete;
102
+ }
103
+ /**
104
+ * Apply a raw SSE body directly. Exposed for tests and for callers that
105
+ * want to feed in a recorded transcript.
106
+ */
107
+ ingest(body) {
108
+ const events = parseSSE(body);
109
+ for (const ev of events)
110
+ this.applyEvent(ev);
111
+ this.currentTurn.streamComplete = true;
112
+ this.finishPendingTurn();
113
+ }
114
+ async maybeConsume(response) {
115
+ if (!this.pendingTurn || this.pendingTurn.captured)
116
+ return;
117
+ const url = response.url();
118
+ if (!CHATGPT_CONVERSATION_URL.test(url))
119
+ return;
120
+ if (response.request().method() !== "POST")
121
+ return;
122
+ // Only the streaming reply matters. ChatGPT's sibling POSTs to the
123
+ // same path family (`/conversation/init`, `/conversation/prepare`)
124
+ // return `application/json` and would otherwise race us to claim the
125
+ // pendingTurn before the real `text/event-stream` arrives.
126
+ const contentType = response.headers()["content-type"] ?? "";
127
+ if (!contentType.includes("event-stream"))
128
+ return;
129
+ // Claim this response so concurrent matches don't double-process.
130
+ this.pendingTurn.captured = true;
131
+ let body;
132
+ try {
133
+ // `response.finished()` resolves only after the entire body has been
134
+ // fetched (the network-level "request finished" signal). For chunked
135
+ // SSE this is non-trivial: the `response` event fires when headers
136
+ // arrive, but the body keeps streaming. Without this wait, certain
137
+ // Playwright builds let `response.text()` return whatever was
138
+ // buffered at handler-time — usually a partial SSE that cuts the
139
+ // assistant reply mid-token.
140
+ await response.finished().catch(() => undefined);
141
+ body = await response.text();
142
+ }
143
+ catch (err) {
144
+ // Network aborted etc. Don't reject — let the caller fall back.
145
+ this.finishPendingTurnWithError(err);
146
+ return;
147
+ }
148
+ this.ingest(body);
149
+ }
150
+ applyEvent(ev) {
151
+ this.currentTurn.rawEvents.push(ev);
152
+ const raw = ev.data.trim();
153
+ if (raw === "[DONE]") {
154
+ this.currentTurn.streamComplete = true;
155
+ return;
156
+ }
157
+ let parsed;
158
+ try {
159
+ parsed = JSON.parse(raw);
160
+ }
161
+ catch {
162
+ // delta_encoding marker: `event: delta_encoding\ndata: "v1"`. The
163
+ // bare-string data isn't valid as a top-level JSON value in our
164
+ // pipeline (it parses but doesn't carry payload), so ignore quietly.
165
+ return;
166
+ }
167
+ if (!parsed || typeof parsed !== "object")
168
+ return;
169
+ const obj = parsed;
170
+ // Case 1: full message snapshot.
171
+ if (isMessageEnvelope(obj)) {
172
+ const msg = obj.message;
173
+ const id = String(msg.id ?? `snap-${this.currentTurn.messages.size}`);
174
+ const role = (msg.author?.role ?? "");
175
+ const content = msg.content;
176
+ const rawParts = Array.isArray(content?.parts) ? content.parts : [];
177
+ const parts = rawParts.map((p) => (typeof p === "string" ? p : ""));
178
+ const status = String(msg.status ?? "in_progress");
179
+ this.currentTurn.messages.set(id, { parts, status, authorRole: role });
180
+ this.currentTurn.lastDeltaTargetId = id;
181
+ if (status === "finished_successfully" && role === "assistant") {
182
+ // Don't set streamComplete here — a later [DONE] is the real signal.
183
+ }
184
+ return;
185
+ }
186
+ // Case 2: terminator frame.
187
+ if (obj.type === "message_stream_complete") {
188
+ this.currentTurn.streamComplete = true;
189
+ return;
190
+ }
191
+ // Case 3: v1 delta encoding.
192
+ if ("v" in obj) {
193
+ this.applyDelta(obj);
194
+ return;
195
+ }
196
+ // Case 4: anything else (heartbeats, moderation hints, …) — ignore.
197
+ }
198
+ applyDelta(obj) {
199
+ const path = typeof obj.p === "string" ? obj.p : this.currentTurn.lastDeltaPath;
200
+ const op = typeof obj.o === "string" ? obj.o : this.currentTurn.lastDeltaOp ?? "append";
201
+ if (path !== undefined)
202
+ this.currentTurn.lastDeltaPath = path;
203
+ this.currentTurn.lastDeltaOp = op;
204
+ // PATCH first: ChatGPT delivers the message's CLOSING tokens (the
205
+ // final "```", the status flip to "finished_successfully", end_turn,
206
+ // metadata, ...) inside one wrapper frame:
207
+ // {"p":"", "o":"patch", "v":[ {p,o,v}, {p,o,v}, ... ]}
208
+ // The wrapper's own `p` is empty, so we MUST recurse before we
209
+ // bail on the parts-path check below — otherwise the closing
210
+ // backticks of code blocks (and any trailing content) get dropped.
211
+ if (op === "patch" && Array.isArray(obj.v)) {
212
+ for (const sub of obj.v) {
213
+ if (sub && typeof sub === "object")
214
+ this.applyDelta(sub);
215
+ }
216
+ return;
217
+ }
218
+ // Beyond patches, we only care about deltas that mutate assistant
219
+ // message text. ChatGPT uses paths shaped like
220
+ // "/message/content/parts/0".
221
+ const partMatch = path?.match(/parts\/(\d+)/);
222
+ if (!partMatch)
223
+ return;
224
+ const idx = parseInt(partMatch[1], 10);
225
+ // Identify the target message. v1 delta frames often omit the id, in
226
+ // which case the prior snapshot's id is the active one.
227
+ const id = this.currentTurn.lastDeltaTargetId ?? "__current";
228
+ let snap = this.currentTurn.messages.get(id);
229
+ if (!snap) {
230
+ snap = { parts: [], status: "in_progress", authorRole: "assistant" };
231
+ this.currentTurn.messages.set(id, snap);
232
+ }
233
+ while (snap.parts.length <= idx)
234
+ snap.parts.push("");
235
+ const v = obj.v;
236
+ if (op === "append") {
237
+ const piece = typeof v === "string" ? v : "";
238
+ snap.parts[idx] = (snap.parts[idx] ?? "") + piece;
239
+ }
240
+ else if (op === "replace") {
241
+ const piece = typeof v === "string" ? v : "";
242
+ snap.parts[idx] = piece;
243
+ }
244
+ }
245
+ finishPendingTurn() {
246
+ if (!this.pendingTurn)
247
+ return;
248
+ const text = this.bestAssistantText();
249
+ if (this.pendingTurn.timer)
250
+ clearTimeout(this.pendingTurn.timer);
251
+ const { resolve } = this.pendingTurn;
252
+ this.pendingTurn = undefined;
253
+ resolve(text);
254
+ }
255
+ finishPendingTurnWithError(_err) {
256
+ if (!this.pendingTurn)
257
+ return;
258
+ if (this.pendingTurn.timer)
259
+ clearTimeout(this.pendingTurn.timer);
260
+ const { resolve } = this.pendingTurn;
261
+ this.pendingTurn = undefined;
262
+ // Surface as empty so the caller falls back to innerText extraction.
263
+ resolve("");
264
+ }
265
+ /** Best-effort assistant text from the current turn's snapshots. */
266
+ bestAssistantText() {
267
+ // Prefer the most recently-seen finished assistant message; fall back
268
+ // to whatever's most recent in any status.
269
+ let lastFinished;
270
+ let lastAny;
271
+ for (const snap of this.currentTurn.messages.values()) {
272
+ if (snap.authorRole !== "assistant")
273
+ continue;
274
+ lastAny = snap;
275
+ if (snap.status === "finished_successfully")
276
+ lastFinished = snap;
277
+ }
278
+ const target = lastFinished ?? lastAny;
279
+ if (!target)
280
+ return "";
281
+ return target.parts.join("");
282
+ }
283
+ }
284
+ function freshTurn() {
285
+ return {
286
+ messages: new Map(),
287
+ streamComplete: false,
288
+ rawEvents: [],
289
+ };
290
+ }
291
+ function isMessageEnvelope(obj) {
292
+ const msg = obj.message;
293
+ if (!msg || typeof msg !== "object")
294
+ return false;
295
+ const author = msg.author;
296
+ return Boolean(author && typeof author === "object");
297
+ }
298
+ //# sourceMappingURL=chatgpt-sse-collector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chatgpt-sse-collector.js","sourceRoot":"","sources":["../../src/providers/chatgpt-sse-collector.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,QAAQ,EAAiB,MAAM,uBAAuB,CAAC;AAEhE;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,iDAAiD,CAAC;AAyB1F;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,OAAO,mBAAmB;IACtB,MAAM,CAAc;IAEpB,WAAW,CAOL;IAEN,WAAW,GAAc,SAAS,EAAE,CAAC;IAE7C,+CAA+C;IAC/C,MAAM,CAAC,IAAU;QACf,IAAI,IAAI,CAAC,MAAM;YAAE,OAAO;QACxB,MAAM,OAAO,GAAG,CAAC,QAAkB,EAAE,EAAE;YACrC,KAAK,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;QACnC,CAAC,CAAC;QACF,IAAI,CAAC,EAAE,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;QAC7B,IAAI,CAAC,MAAM,GAAG,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;IACpD,CAAC;IAED,OAAO;QACL,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;QAChB,IAAI,CAAC,MAAM,GAAG,SAAS,CAAC;QACxB,IAAI,IAAI,CAAC,WAAW,EAAE,KAAK;YAAE,YAAY,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;QAClE,IAAI,CAAC,WAAW,GAAG,SAAS,CAAC;IAC/B,CAAC;IAED;;;;OAIG;IACH,SAAS,CAAC,UAAkC,EAAE;QAC5C,wEAAwE;QACxE,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;YACrB,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAC,CAAC;YAC/D,IAAI,IAAI,CAAC,WAAW,CAAC,KAAK;gBAAE,YAAY,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;QACnE,CAAC;QACD,IAAI,CAAC,WAAW,GAAG,SAAS,EAAE,CAAC;QAE/B,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,OAAO,CAAC;QAC/C,OAAO,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YAC7C,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;gBAC5B,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;oBACrB,MAAM,OAAO,GAAG,IAAI,CAAC,iBAAiB,EAAE,CAAC;oBACzC,IAAI,CAAC,WAAW,GAAG,SAAS,CAAC;oBAC7B,+DAA+D;oBAC/D,iEAAiE;oBACjE,OAAO,CAAC,OAAO,CAAC,CAAC;gBACnB,CAAC;YACH,CAAC,EAAE,SAAS,CAAC,CAAC;YACd,IAAI,CAAC,WAAW,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;QACjE,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;;;;;;;OASG;IACH,kBAAkB;QAChB,OAAO,IAAI,CAAC,iBAAiB,EAAE,CAAC;IAClC,CAAC;IAED;;;OAGG;IACH,oBAAoB;QAClB,MAAM,IAAI,KAAK,CACb,mIAAmI,CACpI,CAAC;IACJ,CAAC;IAED,+CAA+C;IAC/C,gBAAgB;QACd,OAAO,IAAI,CAAC,WAAW,CAAC,cAAc,CAAC;IACzC,CAAC;IAED;;;OAGG;IACH,MAAM,CAAC,IAAY;QACjB,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;QAC9B,KAAK,MAAM,EAAE,IAAI,MAAM;YAAE,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;QAC7C,IAAI,CAAC,WAAW,CAAC,cAAc,GAAG,IAAI,CAAC;QACvC,IAAI,CAAC,iBAAiB,EAAE,CAAC;IAC3B,CAAC;IAEO,KAAK,CAAC,YAAY,CAAC,QAAkB;QAC3C,IAAI,CAAC,IAAI,CAAC,WAAW,IAAI,IAAI,CAAC,WAAW,CAAC,QAAQ;YAAE,OAAO;QAC3D,MAAM,GAAG,GAAG,QAAQ,CAAC,GAAG,EAAE,CAAC;QAC3B,IAAI,CAAC,wBAAwB,CAAC,IAAI,CAAC,GAAG,CAAC;YAAE,OAAO;QAChD,IAAI,QAAQ,CAAC,OAAO,EAAE,CAAC,MAAM,EAAE,KAAK,MAAM;YAAE,OAAO;QAEnD,mEAAmE;QACnE,mEAAmE;QACnE,qEAAqE;QACrE,2DAA2D;QAC3D,MAAM,WAAW,GAAG,QAAQ,CAAC,OAAO,EAAE,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC;QAC7D,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,cAAc,CAAC;YAAE,OAAO;QAElD,kEAAkE;QAClE,IAAI,CAAC,WAAW,CAAC,QAAQ,GAAG,IAAI,CAAC;QAEjC,IAAI,IAAY,CAAC;QACjB,IAAI,CAAC;YACH,qEAAqE;YACrE,qEAAqE;YACrE,mEAAmE;YACnE,mEAAmE;YACnE,8DAA8D;YAC9D,iEAAiE;YACjE,6BAA6B;YAC7B,MAAM,QAAQ,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;YACjD,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;QAC/B,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,gEAAgE;YAChE,IAAI,CAAC,0BAA0B,CAAC,GAAG,CAAC,CAAC;YACrC,OAAO;QACT,CAAC;QACD,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACpB,CAAC;IAEO,UAAU,CAAC,EAAY;QAC7B,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAEpC,MAAM,GAAG,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;QAC3B,IAAI,GAAG,KAAK,QAAQ,EAAE,CAAC;YACrB,IAAI,CAAC,WAAW,CAAC,cAAc,GAAG,IAAI,CAAC;YACvC,OAAO;QACT,CAAC;QAED,IAAI,MAAe,CAAC;QACpB,IAAI,CAAC;YACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC3B,CAAC;QAAC,MAAM,CAAC;YACP,kEAAkE;YAClE,gEAAgE;YAChE,qEAAqE;YACrE,OAAO;QACT,CAAC;QAED,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ;YAAE,OAAO;QAClD,MAAM,GAAG,GAAG,MAAiC,CAAC;QAE9C,iCAAiC;QACjC,IAAI,iBAAiB,CAAC,GAAG,CAAC,EAAE,CAAC;YAC3B,MAAM,GAAG,GAAG,GAAG,CAAC,OAAkC,CAAC;YACnD,MAAM,EAAE,GAAG,MAAM,CAAC,GAAG,CAAC,EAAE,IAAI,QAAQ,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;YACtE,MAAM,IAAI,GAAG,CAAE,GAAG,CAAC,MAA8C,EAAE,IAAI,IAAI,EAAE,CAAW,CAAC;YACzF,MAAM,OAAO,GAAG,GAAG,CAAC,OAA8C,CAAC;YACnE,MAAM,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,OAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;YACrE,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;YACpE,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,IAAI,aAAa,CAAC,CAAC;YACnD,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC;YACvE,IAAI,CAAC,WAAW,CAAC,iBAAiB,GAAG,EAAE,CAAC;YACxC,IAAI,MAAM,KAAK,uBAAuB,IAAI,IAAI,KAAK,WAAW,EAAE,CAAC;gBAC/D,qEAAqE;YACvE,CAAC;YACD,OAAO;QACT,CAAC;QAED,4BAA4B;QAC5B,IAAI,GAAG,CAAC,IAAI,KAAK,yBAAyB,EAAE,CAAC;YAC3C,IAAI,CAAC,WAAW,CAAC,cAAc,GAAG,IAAI,CAAC;YACvC,OAAO;QACT,CAAC;QAED,6BAA6B;QAC7B,IAAI,GAAG,IAAI,GAAG,EAAE,CAAC;YACf,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;YACrB,OAAO;QACT,CAAC;QAED,oEAAoE;IACtE,CAAC;IAEO,UAAU,CAAC,GAA4B;QAC7C,MAAM,IAAI,GAAG,OAAO,GAAG,CAAC,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,aAAa,CAAC;QAChF,MAAM,EAAE,GAAG,OAAO,GAAG,CAAC,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,WAAW,IAAI,QAAQ,CAAC;QACxF,IAAI,IAAI,KAAK,SAAS;YAAE,IAAI,CAAC,WAAW,CAAC,aAAa,GAAG,IAAI,CAAC;QAC9D,IAAI,CAAC,WAAW,CAAC,WAAW,GAAG,EAAE,CAAC;QAElC,kEAAkE;QAClE,qEAAqE;QACrE,2CAA2C;QAC3C,yDAAyD;QACzD,+DAA+D;QAC/D,6DAA6D;QAC7D,mEAAmE;QACnE,IAAI,EAAE,KAAK,OAAO,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;YAC3C,KAAK,MAAM,GAAG,IAAI,GAAG,CAAC,CAAC,EAAE,CAAC;gBACxB,IAAI,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ;oBAAE,IAAI,CAAC,UAAU,CAAC,GAA8B,CAAC,CAAC;YACtF,CAAC;YACD,OAAO;QACT,CAAC;QAED,kEAAkE;QAClE,+CAA+C;QAC/C,8BAA8B;QAC9B,MAAM,SAAS,GAAG,IAAI,EAAE,KAAK,CAAC,cAAc,CAAC,CAAC;QAC9C,IAAI,CAAC,SAAS;YAAE,OAAO;QACvB,MAAM,GAAG,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAE,EAAE,EAAE,CAAC,CAAC;QAExC,qEAAqE;QACrE,wDAAwD;QACxD,MAAM,EAAE,GAAG,IAAI,CAAC,WAAW,CAAC,iBAAiB,IAAI,WAAW,CAAC;QAC7D,IAAI,IAAI,GAAG,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC7C,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,IAAI,GAAG,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,aAAa,EAAE,UAAU,EAAE,WAAW,EAAE,CAAC;YACrE,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;QAC1C,CAAC;QACD,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,IAAI,GAAG;YAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAErD,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;QAChB,IAAI,EAAE,KAAK,QAAQ,EAAE,CAAC;YACpB,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YAC7C,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,GAAG,KAAK,CAAC;QACpD,CAAC;aAAM,IAAI,EAAE,KAAK,SAAS,EAAE,CAAC;YAC5B,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YAC7C,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;QAC1B,CAAC;IACH,CAAC;IAEO,iBAAiB;QACvB,IAAI,CAAC,IAAI,CAAC,WAAW;YAAE,OAAO;QAC9B,MAAM,IAAI,GAAG,IAAI,CAAC,iBAAiB,EAAE,CAAC;QACtC,IAAI,IAAI,CAAC,WAAW,CAAC,KAAK;YAAE,YAAY,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;QACjE,MAAM,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC,WAAW,CAAC;QACrC,IAAI,CAAC,WAAW,GAAG,SAAS,CAAC;QAC7B,OAAO,CAAC,IAAI,CAAC,CAAC;IAChB,CAAC;IAEO,0BAA0B,CAAC,IAAa;QAC9C,IAAI,CAAC,IAAI,CAAC,WAAW;YAAE,OAAO;QAC9B,IAAI,IAAI,CAAC,WAAW,CAAC,KAAK;YAAE,YAAY,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;QACjE,MAAM,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC,WAAW,CAAC;QACrC,IAAI,CAAC,WAAW,GAAG,SAAS,CAAC;QAC7B,qEAAqE;QACrE,OAAO,CAAC,EAAE,CAAC,CAAC;IACd,CAAC;IAED,oEAAoE;IAC5D,iBAAiB;QACvB,sEAAsE;QACtE,2CAA2C;QAC3C,IAAI,YAA2C,CAAC;QAChD,IAAI,OAAsC,CAAC;QAC3C,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC;YACtD,IAAI,IAAI,CAAC,UAAU,KAAK,WAAW;gBAAE,SAAS;YAC9C,OAAO,GAAG,IAAI,CAAC;YACf,IAAI,IAAI,CAAC,MAAM,KAAK,uBAAuB;gBAAE,YAAY,GAAG,IAAI,CAAC;QACnE,CAAC;QACD,MAAM,MAAM,GAAG,YAAY,IAAI,OAAO,CAAC;QACvC,IAAI,CAAC,MAAM;YAAE,OAAO,EAAE,CAAC;QACvB,OAAO,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC/B,CAAC;CACF;AAED,SAAS,SAAS;IAChB,OAAO;QACL,QAAQ,EAAE,IAAI,GAAG,EAAE;QACnB,cAAc,EAAE,KAAK;QACrB,SAAS,EAAE,EAAE;KACd,CAAC;AACJ,CAAC;AAED,SAAS,iBAAiB,CAAC,GAA4B;IACrD,MAAM,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC;IACxB,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAClD,MAAM,MAAM,GAAI,GAA+B,CAAC,MAAM,CAAC;IACvD,OAAO,OAAO,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC;AACvD,CAAC"}
@@ -0,0 +1,56 @@
1
+ import type { Locator, Page } from "playwright";
2
+ import type { ChatProvider, ProviderDiagnostics, WaitOptions } from "../core/provider.js";
3
+ /**
4
+ * ChatGPT (chatgpt.com) provider adapter.
5
+ *
6
+ * Selectors are intentionally written as fallbacks (RFC §8) — when one
7
+ * breaks we fall back to the next. The strongest contracts we rely on:
8
+ * - the prompt input is a textarea or contenteditable in the viewport
9
+ * - assistant messages carry data-message-author-role="assistant"
10
+ * - while streaming, a "Stop generating" button is present
11
+ *
12
+ * Reply extraction has two paths (see `extractLastAssistantMessage`):
13
+ * 1. SSE collector — listens to `/backend-api/.../conversation` and
14
+ * reconstructs the model's original markdown (bullets, fences,
15
+ * tables, link URLs). Preferred.
16
+ * 2. DOM `innerText` fallback — used when SSE parsing returned nothing
17
+ * (endpoint changed, request errored, ...).
18
+ */
19
+ export declare class ChatGPTAdapter implements ChatProvider {
20
+ readonly name = "chatgpt";
21
+ readonly homeUrl = "https://chatgpt.com/";
22
+ /** One collector per Page. Lazily attached on `open()`. */
23
+ private collectors;
24
+ /**
25
+ * Promise that resolves to the assistant markdown for the currently
26
+ * in-flight turn (between `sendMessage` and `waitForResponse`).
27
+ * Per-page so concurrent pages don't collide.
28
+ */
29
+ private pendingTurns;
30
+ open(page: Page): Promise<void>;
31
+ isLoggedIn(page: Page): Promise<boolean>;
32
+ findInput(page: Page): Promise<Locator>;
33
+ findSendButton(page: Page): Promise<Locator | null>;
34
+ sendMessage(page: Page, message: string): Promise<void>;
35
+ /** Wait up to `timeoutMs` for the ProseMirror composer to be ready. */
36
+ private waitForComposerReady;
37
+ extractLastAssistantMessage(page: Page): Promise<string>;
38
+ waitForResponse(page: Page, options?: WaitOptions): Promise<string>;
39
+ /**
40
+ * Extract the ChatGPT conversation UUID from the page URL.
41
+ *
42
+ * Once a turn lands, ChatGPT navigates the page to
43
+ * `https://chatgpt.com/c/{conversation-uuid}`
44
+ * The fresh-chat URL (`/` or `/?model=...`) has no UUID — return `null`
45
+ * in that case. The UUID is the natural cross-process session id and is
46
+ * what we plumb into ai-sdk's session model and onward to the UI.
47
+ */
48
+ getConversationId(page: Page): string | null;
49
+ newChat(page: Page): Promise<void>;
50
+ diagnose(page: Page): Promise<ProviderDiagnostics>;
51
+ /** Look up (and lazily create + attach) the SSE collector for a Page. */
52
+ private ensureCollector;
53
+ private waitForStopButton;
54
+ private waitForStopButtonGone;
55
+ private isStopButtonVisible;
56
+ }