@bbclaw/core 0.1.0
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/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/providers/anthropic/index.d.ts +44 -0
- package/dist/providers/anthropic/index.d.ts.map +1 -0
- package/dist/providers/anthropic/index.js +75 -0
- package/dist/providers/anthropic/index.js.map +1 -0
- package/dist/providers/index.d.ts +8 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +8 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/openai-compat/index.d.ts +59 -0
- package/dist/providers/openai-compat/index.d.ts.map +1 -0
- package/dist/providers/openai-compat/index.js +175 -0
- package/dist/providers/openai-compat/index.js.map +1 -0
- package/dist/providers/openai-compat/presets.d.ts +22 -0
- package/dist/providers/openai-compat/presets.d.ts.map +1 -0
- package/dist/providers/openai-compat/presets.js +73 -0
- package/dist/providers/openai-compat/presets.js.map +1 -0
- package/dist/providers/openai-compat/requestTranslate.d.ts +39 -0
- package/dist/providers/openai-compat/requestTranslate.d.ts.map +1 -0
- package/dist/providers/openai-compat/requestTranslate.js +228 -0
- package/dist/providers/openai-compat/requestTranslate.js.map +1 -0
- package/dist/providers/openai-compat/sseParser.d.ts +29 -0
- package/dist/providers/openai-compat/sseParser.d.ts.map +1 -0
- package/dist/providers/openai-compat/sseParser.js +139 -0
- package/dist/providers/openai-compat/sseParser.js.map +1 -0
- package/dist/providers/openai-compat/streamAdapter.d.ts +45 -0
- package/dist/providers/openai-compat/streamAdapter.d.ts.map +1 -0
- package/dist/providers/openai-compat/streamAdapter.js +233 -0
- package/dist/providers/openai-compat/streamAdapter.js.map +1 -0
- package/dist/providers/openai-compat/types.d.ts +126 -0
- package/dist/providers/openai-compat/types.d.ts.map +1 -0
- package/dist/providers/openai-compat/types.js +11 -0
- package/dist/providers/openai-compat/types.js.map +1 -0
- package/dist/providers/selector.d.ts +52 -0
- package/dist/providers/selector.d.ts.map +1 -0
- package/dist/providers/selector.js +101 -0
- package/dist/providers/selector.js.map +1 -0
- package/dist/providers/types.d.ts +114 -0
- package/dist/providers/types.d.ts.map +1 -0
- package/dist/providers/types.js +152 -0
- package/dist/providers/types.js.map +1 -0
- package/dist/providers/web-bridge/deepseek/spec.d.ts +22 -0
- package/dist/providers/web-bridge/deepseek/spec.d.ts.map +1 -0
- package/dist/providers/web-bridge/deepseek/spec.js +70 -0
- package/dist/providers/web-bridge/deepseek/spec.js.map +1 -0
- package/dist/providers/web-bridge/historySerializer.d.ts +49 -0
- package/dist/providers/web-bridge/historySerializer.d.ts.map +1 -0
- package/dist/providers/web-bridge/historySerializer.js +152 -0
- package/dist/providers/web-bridge/historySerializer.js.map +1 -0
- package/dist/providers/web-bridge/index.d.ts +10 -0
- package/dist/providers/web-bridge/index.d.ts.map +1 -0
- package/dist/providers/web-bridge/index.js +10 -0
- package/dist/providers/web-bridge/index.js.map +1 -0
- package/dist/providers/web-bridge/promptInjector.d.ts +63 -0
- package/dist/providers/web-bridge/promptInjector.d.ts.map +1 -0
- package/dist/providers/web-bridge/promptInjector.js +189 -0
- package/dist/providers/web-bridge/promptInjector.js.map +1 -0
- package/dist/providers/web-bridge/provider.d.ts +59 -0
- package/dist/providers/web-bridge/provider.d.ts.map +1 -0
- package/dist/providers/web-bridge/provider.js +176 -0
- package/dist/providers/web-bridge/provider.js.map +1 -0
- package/dist/providers/web-bridge/shared/BrowserSession.d.ts +51 -0
- package/dist/providers/web-bridge/shared/BrowserSession.d.ts.map +1 -0
- package/dist/providers/web-bridge/shared/BrowserSession.js +88 -0
- package/dist/providers/web-bridge/shared/BrowserSession.js.map +1 -0
- package/dist/providers/web-bridge/shared/WebBridgeAdapter.d.ts +97 -0
- package/dist/providers/web-bridge/shared/WebBridgeAdapter.d.ts.map +1 -0
- package/dist/providers/web-bridge/shared/WebBridgeAdapter.js +359 -0
- package/dist/providers/web-bridge/shared/WebBridgeAdapter.js.map +1 -0
- package/dist/providers/web-bridge/shared/observerScript.d.ts +41 -0
- package/dist/providers/web-bridge/shared/observerScript.d.ts.map +1 -0
- package/dist/providers/web-bridge/shared/observerScript.js +138 -0
- package/dist/providers/web-bridge/shared/observerScript.js.map +1 -0
- package/dist/providers/web-bridge/shared/types.d.ts +94 -0
- package/dist/providers/web-bridge/shared/types.d.ts.map +1 -0
- package/dist/providers/web-bridge/shared/types.js +25 -0
- package/dist/providers/web-bridge/shared/types.js.map +1 -0
- package/dist/providers/web-bridge/toolUseParser.d.ts +70 -0
- package/dist/providers/web-bridge/toolUseParser.d.ts.map +1 -0
- package/dist/providers/web-bridge/toolUseParser.js +360 -0
- package/dist/providers/web-bridge/toolUseParser.js.map +1 -0
- package/package.json +36 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BrowserSession.js","sourceRoot":"","sources":["../../../../src/providers/web-bridge/shared/BrowserSession.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebBridgeAdapter — service-agnostic driver for chat-web targets.
|
|
3
|
+
*
|
|
4
|
+
* Given a {@link WebBridgeSpec}, this class manages the browser session,
|
|
5
|
+
* injects the DOM observer, and exposes a high-level streaming API for the
|
|
6
|
+
* upper layers (WebBridgeProvider). Adding a new service (Kimi, ChatGPT, ...)
|
|
7
|
+
* is just providing another spec — no subclassing needed.
|
|
8
|
+
*
|
|
9
|
+
* Lifecycle:
|
|
10
|
+
* 1. start() — open the browser, navigate, inject observer.
|
|
11
|
+
* 2. isLoggedIn() — verify the composer is reachable.
|
|
12
|
+
* 3. newChat() — try locator strategies, fall back to URL nav.
|
|
13
|
+
* 4. askStream(prompt) — fill composer, submit, yield streaming text.
|
|
14
|
+
* 5. stop() — close the context.
|
|
15
|
+
*
|
|
16
|
+
* Concurrency model: one in-flight ask at a time. The upper layer is
|
|
17
|
+
* responsible for serializing turns; concurrent `askStream` calls reject
|
|
18
|
+
* with a clear error.
|
|
19
|
+
*/
|
|
20
|
+
import { type Logger } from '@bbclaw/shared';
|
|
21
|
+
import { type BrowserSessionOptions } from './BrowserSession.js';
|
|
22
|
+
import { type ExternalUserMessageListener, type ResponseTextListener, type StateChangeListener, type WebBridgeSpec, type WebBridgeState } from './types.js';
|
|
23
|
+
export interface WebBridgeAdapterOptions {
|
|
24
|
+
/** Persistent data dir (cookies, localStorage). */
|
|
25
|
+
userDataDir: string;
|
|
26
|
+
/** Default to headless. */
|
|
27
|
+
headless?: boolean;
|
|
28
|
+
/** Throw if no first response text arrives within this many ms. */
|
|
29
|
+
askTimeoutMs?: number;
|
|
30
|
+
/**
|
|
31
|
+
* After the response text stops changing for this many ms, declare the
|
|
32
|
+
* turn done. Defaults to 2_000ms — DeepSeek-class services have small
|
|
33
|
+
* inter-token gaps but multi-second pauses while rendering markdown.
|
|
34
|
+
*/
|
|
35
|
+
stabilityWindowMs?: number;
|
|
36
|
+
/** Logger; defaults to silent. */
|
|
37
|
+
logger?: Logger;
|
|
38
|
+
/** Customize browser session options (user agent, viewport, etc). */
|
|
39
|
+
browserSessionOptions?: Partial<Omit<BrowserSessionOptions, 'userDataDir' | 'headless'>>;
|
|
40
|
+
}
|
|
41
|
+
export declare class WebBridgeAdapter {
|
|
42
|
+
private readonly spec;
|
|
43
|
+
private readonly opts;
|
|
44
|
+
private readonly session;
|
|
45
|
+
private page;
|
|
46
|
+
private inFlight;
|
|
47
|
+
/** Messages we sent ourselves and haven't yet seen reflected in DOM — used to skip our own as "external". */
|
|
48
|
+
private pendingOwnUserMessages;
|
|
49
|
+
/** Cumulative text across all observed deltas for the current turn. */
|
|
50
|
+
private currentTurnText;
|
|
51
|
+
/** Stability timer that fires when text stops changing. */
|
|
52
|
+
private stabilityTimer;
|
|
53
|
+
/** Observer-exposed callback, registered once per context. */
|
|
54
|
+
private observerExposed;
|
|
55
|
+
private responseListeners;
|
|
56
|
+
private externalListeners;
|
|
57
|
+
private stateListeners;
|
|
58
|
+
constructor(spec: WebBridgeSpec, opts: WebBridgeAdapterOptions);
|
|
59
|
+
get serviceId(): string;
|
|
60
|
+
get displayName(): string;
|
|
61
|
+
get isRunning(): boolean;
|
|
62
|
+
getState(): WebBridgeState;
|
|
63
|
+
/** Current URL the page is showing, or undefined if no page open. */
|
|
64
|
+
currentUrl(): string | undefined;
|
|
65
|
+
/** Start the browser, navigate to home, install observer. Idempotent. */
|
|
66
|
+
start(): Promise<void>;
|
|
67
|
+
stop(): Promise<void>;
|
|
68
|
+
/** Toggle visibility. The browser is restarted on the next start() call. */
|
|
69
|
+
setHeadless(headless: boolean): Promise<void>;
|
|
70
|
+
/**
|
|
71
|
+
* Probe whether the composer is visible (i.e. user is logged in and the
|
|
72
|
+
* service is reachable). Starts the bridge if needed.
|
|
73
|
+
*/
|
|
74
|
+
isLoggedIn(timeoutMs?: number): Promise<boolean>;
|
|
75
|
+
/**
|
|
76
|
+
* Click the service's "new chat" button using its fallback ladder. If
|
|
77
|
+
* every locator strategy fails, fall back to a fresh navigation to the
|
|
78
|
+
* home URL — most services treat that as starting a new conversation.
|
|
79
|
+
*/
|
|
80
|
+
newChat(): Promise<void>;
|
|
81
|
+
/**
|
|
82
|
+
* Send a prompt and return an async iterable of cumulative text + done flag.
|
|
83
|
+
* The caller is responsible for diffing to deltas.
|
|
84
|
+
*/
|
|
85
|
+
askStream(prompt: string): AsyncIterable<{
|
|
86
|
+
text: string;
|
|
87
|
+
done: boolean;
|
|
88
|
+
}>;
|
|
89
|
+
onResponseProgress(cb: ResponseTextListener): () => void;
|
|
90
|
+
onExternalUserMessage(cb: ExternalUserMessageListener): () => void;
|
|
91
|
+
onStateChange(cb: StateChangeListener): () => void;
|
|
92
|
+
private cleanupAsk;
|
|
93
|
+
private emitState;
|
|
94
|
+
private installObserver;
|
|
95
|
+
private handleObserverEvent;
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=WebBridgeAdapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WebBridgeAdapter.d.ts","sourceRoot":"","sources":["../../../../src/providers/web-bridge/shared/WebBridgeAdapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAGH,OAAO,EAAE,KAAK,MAAM,EAAgB,MAAM,gBAAgB,CAAA;AAC1D,OAAO,EAAkB,KAAK,qBAAqB,EAAE,MAAM,qBAAqB,CAAA;AAEhF,OAAO,EAGL,KAAK,2BAA2B,EAChC,KAAK,oBAAoB,EACzB,KAAK,mBAAmB,EACxB,KAAK,aAAa,EAClB,KAAK,cAAc,EACpB,MAAM,YAAY,CAAA;AAEnB,MAAM,WAAW,uBAAuB;IACtC,mDAAmD;IACnD,WAAW,EAAE,MAAM,CAAA;IACnB,2BAA2B;IAC3B,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,mEAAmE;IACnE,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,kCAAkC;IAClC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,qEAAqE;IACrE,qBAAqB,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,qBAAqB,EAAE,aAAa,GAAG,UAAU,CAAC,CAAC,CAAA;CACzF;AAgBD,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAe;IACpC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAGpB;IAED,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAgB;IACxC,OAAO,CAAC,IAAI,CAAoB;IAEhC,OAAO,CAAC,QAAQ,CAA2B;IAC3C,6GAA6G;IAC7G,OAAO,CAAC,sBAAsB,CAAI;IAClC,uEAAuE;IACvE,OAAO,CAAC,eAAe,CAAK;IAC5B,2DAA2D;IAC3D,OAAO,CAAC,cAAc,CAA8B;IACpD,8DAA8D;IAC9D,OAAO,CAAC,eAAe,CAAQ;IAE/B,OAAO,CAAC,iBAAiB,CAA6B;IACtD,OAAO,CAAC,iBAAiB,CAAoC;IAC7D,OAAO,CAAC,cAAc,CAA4B;gBAEtC,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,uBAAuB;IAmB9D,IAAI,SAAS,IAAI,MAAM,CAEtB;IACD,IAAI,WAAW,IAAI,MAAM,CAExB;IACD,IAAI,SAAS,IAAI,OAAO,CAEvB;IAED,QAAQ,IAAI,cAAc;IAS1B,qEAAqE;IACrE,UAAU,IAAI,MAAM,GAAG,SAAS;IAKhC,yEAAyE;IACnE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAYtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ3B,4EAA4E;IACtE,WAAW,CAAC,QAAQ,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAQnD;;;OAGG;IACG,UAAU,CAAC,SAAS,SAAQ,GAAG,OAAO,CAAC,OAAO,CAAC;IAcrD;;;;OAIG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IA8B9B;;;OAGG;IACI,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,OAAO,CAAA;KAAE,CAAC;IAyGhF,kBAAkB,CAAC,EAAE,EAAE,oBAAoB,GAAG,MAAM,IAAI;IAOxD,qBAAqB,CAAC,EAAE,EAAE,2BAA2B,GAAG,MAAM,IAAI;IAOlE,aAAa,CAAC,EAAE,EAAE,mBAAmB,GAAG,MAAM,IAAI;IASlD,OAAO,CAAC,UAAU;IAWlB,OAAO,CAAC,SAAS;YAKH,eAAe;IAoB7B,OAAO,CAAC,mBAAmB;CAsC5B"}
|
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebBridgeAdapter — service-agnostic driver for chat-web targets.
|
|
3
|
+
*
|
|
4
|
+
* Given a {@link WebBridgeSpec}, this class manages the browser session,
|
|
5
|
+
* injects the DOM observer, and exposes a high-level streaming API for the
|
|
6
|
+
* upper layers (WebBridgeProvider). Adding a new service (Kimi, ChatGPT, ...)
|
|
7
|
+
* is just providing another spec — no subclassing needed.
|
|
8
|
+
*
|
|
9
|
+
* Lifecycle:
|
|
10
|
+
* 1. start() — open the browser, navigate, inject observer.
|
|
11
|
+
* 2. isLoggedIn() — verify the composer is reachable.
|
|
12
|
+
* 3. newChat() — try locator strategies, fall back to URL nav.
|
|
13
|
+
* 4. askStream(prompt) — fill composer, submit, yield streaming text.
|
|
14
|
+
* 5. stop() — close the context.
|
|
15
|
+
*
|
|
16
|
+
* Concurrency model: one in-flight ask at a time. The upper layer is
|
|
17
|
+
* responsible for serializing turns; concurrent `askStream` calls reject
|
|
18
|
+
* with a clear error.
|
|
19
|
+
*/
|
|
20
|
+
import { silentLogger } from '@bbclaw/shared';
|
|
21
|
+
import { BrowserSession } from './BrowserSession.js';
|
|
22
|
+
import { buildObserverScriptSource } from './observerScript.js';
|
|
23
|
+
import { BridgeDOMUnrecognizedError, BridgeLoginRequiredError, } from './types.js';
|
|
24
|
+
const DEFAULT_STABILITY_MS = 2_000;
|
|
25
|
+
const DEFAULT_ASK_TIMEOUT_MS = 180_000;
|
|
26
|
+
export class WebBridgeAdapter {
|
|
27
|
+
spec;
|
|
28
|
+
opts;
|
|
29
|
+
session;
|
|
30
|
+
page = null;
|
|
31
|
+
inFlight = null;
|
|
32
|
+
/** Messages we sent ourselves and haven't yet seen reflected in DOM — used to skip our own as "external". */
|
|
33
|
+
pendingOwnUserMessages = 0;
|
|
34
|
+
/** Cumulative text across all observed deltas for the current turn. */
|
|
35
|
+
currentTurnText = '';
|
|
36
|
+
/** Stability timer that fires when text stops changing. */
|
|
37
|
+
stabilityTimer = null;
|
|
38
|
+
/** Observer-exposed callback, registered once per context. */
|
|
39
|
+
observerExposed = false;
|
|
40
|
+
responseListeners = [];
|
|
41
|
+
externalListeners = [];
|
|
42
|
+
stateListeners = [];
|
|
43
|
+
constructor(spec, opts) {
|
|
44
|
+
this.spec = spec;
|
|
45
|
+
this.opts = {
|
|
46
|
+
userDataDir: opts.userDataDir,
|
|
47
|
+
headless: opts.headless ?? true,
|
|
48
|
+
askTimeoutMs: opts.askTimeoutMs ?? DEFAULT_ASK_TIMEOUT_MS,
|
|
49
|
+
stabilityWindowMs: opts.stabilityWindowMs ?? DEFAULT_STABILITY_MS,
|
|
50
|
+
browserSessionOptions: opts.browserSessionOptions ?? {},
|
|
51
|
+
logger: opts.logger ?? silentLogger,
|
|
52
|
+
};
|
|
53
|
+
this.session = new BrowserSession({
|
|
54
|
+
userDataDir: this.opts.userDataDir,
|
|
55
|
+
headless: this.opts.headless,
|
|
56
|
+
...this.opts.browserSessionOptions,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
// ---- public surface ----
|
|
60
|
+
get serviceId() {
|
|
61
|
+
return this.spec.serviceId;
|
|
62
|
+
}
|
|
63
|
+
get displayName() {
|
|
64
|
+
return this.spec.displayName;
|
|
65
|
+
}
|
|
66
|
+
get isRunning() {
|
|
67
|
+
return this.session.isOpen;
|
|
68
|
+
}
|
|
69
|
+
getState() {
|
|
70
|
+
return {
|
|
71
|
+
running: this.session.isOpen,
|
|
72
|
+
headless: this.session.headless,
|
|
73
|
+
busy: this.inFlight !== null,
|
|
74
|
+
loggedIn: 'unknown',
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/** Current URL the page is showing, or undefined if no page open. */
|
|
78
|
+
currentUrl() {
|
|
79
|
+
if (!this.page || this.page.isClosed())
|
|
80
|
+
return undefined;
|
|
81
|
+
return this.page.url();
|
|
82
|
+
}
|
|
83
|
+
/** Start the browser, navigate to home, install observer. Idempotent. */
|
|
84
|
+
async start() {
|
|
85
|
+
if (this.page && !this.page.isClosed())
|
|
86
|
+
return;
|
|
87
|
+
this.page = await this.session.ensurePage(this.spec.homeUrl);
|
|
88
|
+
this.page.on('close', () => {
|
|
89
|
+
if (this.inFlight)
|
|
90
|
+
this.inFlight.reject(new Error('Web bridge: page closed mid-turn'));
|
|
91
|
+
this.page = null;
|
|
92
|
+
this.emitState();
|
|
93
|
+
});
|
|
94
|
+
await this.installObserver(this.page);
|
|
95
|
+
this.emitState();
|
|
96
|
+
}
|
|
97
|
+
async stop() {
|
|
98
|
+
if (this.inFlight)
|
|
99
|
+
this.inFlight.reject(new Error('Web bridge stopped'));
|
|
100
|
+
this.observerExposed = false;
|
|
101
|
+
await this.session.close();
|
|
102
|
+
this.page = null;
|
|
103
|
+
this.emitState();
|
|
104
|
+
}
|
|
105
|
+
/** Toggle visibility. The browser is restarted on the next start() call. */
|
|
106
|
+
async setHeadless(headless) {
|
|
107
|
+
if (this.session.headless === headless && this.session.isOpen)
|
|
108
|
+
return;
|
|
109
|
+
await this.session.restart(headless);
|
|
110
|
+
this.observerExposed = false;
|
|
111
|
+
this.page = null;
|
|
112
|
+
this.emitState();
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Probe whether the composer is visible (i.e. user is logged in and the
|
|
116
|
+
* service is reachable). Starts the bridge if needed.
|
|
117
|
+
*/
|
|
118
|
+
async isLoggedIn(timeoutMs = 3_000) {
|
|
119
|
+
await this.start();
|
|
120
|
+
if (!this.page || this.page.isClosed())
|
|
121
|
+
return false;
|
|
122
|
+
try {
|
|
123
|
+
await this.page.waitForSelector(this.spec.selectors.composer, {
|
|
124
|
+
state: 'visible',
|
|
125
|
+
timeout: timeoutMs,
|
|
126
|
+
});
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Click the service's "new chat" button using its fallback ladder. If
|
|
135
|
+
* every locator strategy fails, fall back to a fresh navigation to the
|
|
136
|
+
* home URL — most services treat that as starting a new conversation.
|
|
137
|
+
*/
|
|
138
|
+
async newChat() {
|
|
139
|
+
await this.start();
|
|
140
|
+
const page = this.page;
|
|
141
|
+
if (!page)
|
|
142
|
+
throw new Error('Web bridge: cannot newChat without a page');
|
|
143
|
+
for (const strategy of this.spec.newChatStrategies) {
|
|
144
|
+
try {
|
|
145
|
+
const loc = strategy(page);
|
|
146
|
+
await loc.click({ timeout: 1_500 });
|
|
147
|
+
// After clicking new chat, the observer's turnStartIdx self-resets via the userCount drop.
|
|
148
|
+
this.currentTurnText = '';
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
// try next strategy
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
// Last resort.
|
|
156
|
+
try {
|
|
157
|
+
await page.goto(this.spec.homeUrl, { waitUntil: 'domcontentloaded', timeout: 8_000 });
|
|
158
|
+
this.currentTurnText = '';
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
catch (e) {
|
|
162
|
+
throw new BridgeDOMUnrecognizedError(`Couldn't start new chat on ${this.spec.serviceId} — all fallbacks (including URL navigation) failed. Underlying: ${e instanceof Error ? e.message : String(e)}`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Send a prompt and return an async iterable of cumulative text + done flag.
|
|
167
|
+
* The caller is responsible for diffing to deltas.
|
|
168
|
+
*/
|
|
169
|
+
async *askStream(prompt) {
|
|
170
|
+
await this.start();
|
|
171
|
+
const page = this.page;
|
|
172
|
+
if (!page)
|
|
173
|
+
throw new Error('Web bridge: cannot ask without a page');
|
|
174
|
+
if (this.inFlight)
|
|
175
|
+
throw new Error('Web bridge: another ask is in flight');
|
|
176
|
+
// Verify composer reachable; surface login-required cleanly.
|
|
177
|
+
try {
|
|
178
|
+
await page.waitForSelector(this.spec.selectors.composer, { state: 'visible', timeout: 8_000 });
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
throw new BridgeLoginRequiredError(`${this.spec.displayName} composer not visible — service likely needs login.`);
|
|
182
|
+
}
|
|
183
|
+
let resolveOne = null;
|
|
184
|
+
const queue = [];
|
|
185
|
+
const push = (item) => {
|
|
186
|
+
if (resolveOne) {
|
|
187
|
+
const fn = resolveOne;
|
|
188
|
+
resolveOne = null;
|
|
189
|
+
fn(item);
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
queue.push(item);
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
const progressEmit = (text, done) => {
|
|
196
|
+
push({ kind: 'progress', text, done });
|
|
197
|
+
};
|
|
198
|
+
const ask = {
|
|
199
|
+
resolve: () => progressEmit(this.currentTurnText, true),
|
|
200
|
+
reject: (err) => {
|
|
201
|
+
this.opts.logger.warn(`[${this.spec.serviceId}] askStream rejected:`, err.message);
|
|
202
|
+
// Surface the error to the consumer via the queue. The generator will
|
|
203
|
+
// see it and re-throw, instead of silently completing with empty text.
|
|
204
|
+
push({ kind: 'error', error: err });
|
|
205
|
+
},
|
|
206
|
+
lastText: '',
|
|
207
|
+
watchdog: setTimeout(() => {
|
|
208
|
+
if (this.inFlight !== ask)
|
|
209
|
+
return;
|
|
210
|
+
if (ask.lastText) {
|
|
211
|
+
ask.resolve();
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
ask.reject(new Error(`Web bridge: response timeout after ${this.opts.askTimeoutMs}ms — no text observed. ` +
|
|
215
|
+
`Likely causes: DOM selectors out of date (responseRoot did not match anything), the service is showing a captcha, or the request was silently dropped. ` +
|
|
216
|
+
`Run with --no-headless to see what's on screen.`));
|
|
217
|
+
}
|
|
218
|
+
}, this.opts.askTimeoutMs),
|
|
219
|
+
progressEmit,
|
|
220
|
+
};
|
|
221
|
+
this.inFlight = ask;
|
|
222
|
+
this.pendingOwnUserMessages++;
|
|
223
|
+
this.currentTurnText = '';
|
|
224
|
+
this.emitState();
|
|
225
|
+
// Fire-and-forget the actual send. We DON'T await it here because the
|
|
226
|
+
// browser-side observer will start firing events as soon as the response
|
|
227
|
+
// begins streaming; we want to consume those concurrently.
|
|
228
|
+
void (async () => {
|
|
229
|
+
try {
|
|
230
|
+
await page.fill(this.spec.selectors.composer, prompt);
|
|
231
|
+
await page.keyboard.press('Enter');
|
|
232
|
+
this.opts.logger.debug(`[${this.spec.serviceId}] prompt sent (${prompt.length} chars); waiting for response...`);
|
|
233
|
+
}
|
|
234
|
+
catch (e) {
|
|
235
|
+
ask.reject(new Error(`Web bridge: failed to send prompt — ${e instanceof Error ? e.message : String(e)}. ` +
|
|
236
|
+
`The composer (${this.spec.selectors.composer}) might have moved or become disabled.`));
|
|
237
|
+
}
|
|
238
|
+
})();
|
|
239
|
+
try {
|
|
240
|
+
while (true) {
|
|
241
|
+
const next = await new Promise((resolve) => {
|
|
242
|
+
if (queue.length > 0) {
|
|
243
|
+
resolve(queue.shift());
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
resolveOne = resolve;
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
if (next.kind === 'error') {
|
|
250
|
+
throw next.error;
|
|
251
|
+
}
|
|
252
|
+
yield { text: next.text, done: next.done };
|
|
253
|
+
if (next.done)
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
finally {
|
|
258
|
+
this.cleanupAsk(ask);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
// ---- subscriptions ----
|
|
262
|
+
onResponseProgress(cb) {
|
|
263
|
+
this.responseListeners.push(cb);
|
|
264
|
+
return () => {
|
|
265
|
+
this.responseListeners = this.responseListeners.filter((f) => f !== cb);
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
onExternalUserMessage(cb) {
|
|
269
|
+
this.externalListeners.push(cb);
|
|
270
|
+
return () => {
|
|
271
|
+
this.externalListeners = this.externalListeners.filter((f) => f !== cb);
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
onStateChange(cb) {
|
|
275
|
+
this.stateListeners.push(cb);
|
|
276
|
+
return () => {
|
|
277
|
+
this.stateListeners = this.stateListeners.filter((f) => f !== cb);
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
// ---- internals ----
|
|
281
|
+
cleanupAsk(ask) {
|
|
282
|
+
if (this.inFlight !== ask)
|
|
283
|
+
return;
|
|
284
|
+
clearTimeout(ask.watchdog);
|
|
285
|
+
if (this.stabilityTimer) {
|
|
286
|
+
clearTimeout(this.stabilityTimer);
|
|
287
|
+
this.stabilityTimer = null;
|
|
288
|
+
}
|
|
289
|
+
this.inFlight = null;
|
|
290
|
+
this.emitState();
|
|
291
|
+
}
|
|
292
|
+
emitState() {
|
|
293
|
+
const s = this.getState();
|
|
294
|
+
for (const fn of this.stateListeners)
|
|
295
|
+
fn(s);
|
|
296
|
+
}
|
|
297
|
+
async installObserver(page) {
|
|
298
|
+
if (!this.observerExposed) {
|
|
299
|
+
try {
|
|
300
|
+
await page.exposeFunction('__bbclawBridgeEvent', (event) => {
|
|
301
|
+
this.handleObserverEvent(event);
|
|
302
|
+
});
|
|
303
|
+
this.observerExposed = true;
|
|
304
|
+
}
|
|
305
|
+
catch {
|
|
306
|
+
// Already exposed for this context.
|
|
307
|
+
this.observerExposed = true;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
// We pass the script as a plain JS string (not a function) so it bypasses
|
|
311
|
+
// any compiler-added wrappers (esbuild's __name, etc.) that would crash
|
|
312
|
+
// in the page context.
|
|
313
|
+
const source = buildObserverScriptSource(this.spec.selectors);
|
|
314
|
+
await page.addInitScript({ content: source });
|
|
315
|
+
await page.evaluate(source);
|
|
316
|
+
}
|
|
317
|
+
handleObserverEvent(event) {
|
|
318
|
+
if (event.type === 'response-progress') {
|
|
319
|
+
const text = event.text ?? '';
|
|
320
|
+
if (text === this.currentTurnText)
|
|
321
|
+
return;
|
|
322
|
+
this.currentTurnText = text;
|
|
323
|
+
// Forward to passive listeners.
|
|
324
|
+
for (const fn of this.responseListeners)
|
|
325
|
+
fn(text, false);
|
|
326
|
+
// Forward to the in-flight ask, if any.
|
|
327
|
+
const ask = this.inFlight;
|
|
328
|
+
if (ask) {
|
|
329
|
+
ask.lastText = text;
|
|
330
|
+
ask.progressEmit(text, false);
|
|
331
|
+
}
|
|
332
|
+
// Reset the stability timer.
|
|
333
|
+
if (this.stabilityTimer)
|
|
334
|
+
clearTimeout(this.stabilityTimer);
|
|
335
|
+
this.stabilityTimer = setTimeout(() => {
|
|
336
|
+
this.stabilityTimer = null;
|
|
337
|
+
const finalText = this.currentTurnText;
|
|
338
|
+
for (const fn of this.responseListeners)
|
|
339
|
+
fn(finalText, true);
|
|
340
|
+
if (this.inFlight) {
|
|
341
|
+
this.inFlight.lastText = finalText;
|
|
342
|
+
this.inFlight.resolve();
|
|
343
|
+
}
|
|
344
|
+
}, this.opts.stabilityWindowMs);
|
|
345
|
+
}
|
|
346
|
+
else if (event.type === 'external-user-message') {
|
|
347
|
+
// Skip our own messages — they always echo via the observer first.
|
|
348
|
+
if (this.pendingOwnUserMessages > 0) {
|
|
349
|
+
this.pendingOwnUserMessages--;
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
const text = event.text ?? '';
|
|
353
|
+
for (const fn of this.externalListeners)
|
|
354
|
+
fn(text);
|
|
355
|
+
}
|
|
356
|
+
// composer-ready: we currently don't act on this; future use for state machine.
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
//# sourceMappingURL=WebBridgeAdapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WebBridgeAdapter.js","sourceRoot":"","sources":["../../../../src/providers/web-bridge/shared/WebBridgeAdapter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-side observer script (string template form).
|
|
3
|
+
*
|
|
4
|
+
* Why a string and not a function? When we pass a TypeScript function to
|
|
5
|
+
* Playwright's page.evaluate/addInitScript, it stringifies the function with
|
|
6
|
+
* Function.prototype.toString(). But tsx/esbuild (and other TS compilers)
|
|
7
|
+
* wrap functions with helpers like `__name` for name-reflection support.
|
|
8
|
+
* Those helpers don't exist in the page's browser context, so the injected
|
|
9
|
+
* script crashes with "__name is not defined".
|
|
10
|
+
*
|
|
11
|
+
* By writing the body as a string, we bypass the compiler entirely — what
|
|
12
|
+
* goes to the page is exactly what we wrote.
|
|
13
|
+
*
|
|
14
|
+
* The script is parameterized by selectors, injected as a JSON literal.
|
|
15
|
+
*
|
|
16
|
+
* It watches the DOM for two things:
|
|
17
|
+
* 1. New user-bubble appearances — each one marks a turn boundary and may
|
|
18
|
+
* indicate a user typed directly into the chat (passive capture).
|
|
19
|
+
* 2. Text changes in the response container that follows the latest user
|
|
20
|
+
* bubble — these are streamed back as response progress.
|
|
21
|
+
* It calls back into Node via the exposed function `__bbclawBridgeEvent`.
|
|
22
|
+
*/
|
|
23
|
+
export interface BridgeObserverSelectors {
|
|
24
|
+
composer: string;
|
|
25
|
+
responseRoot: string;
|
|
26
|
+
userMessage: string;
|
|
27
|
+
}
|
|
28
|
+
export interface BridgeObserverEvent {
|
|
29
|
+
type: 'response-progress' | 'external-user-message' | 'composer-ready';
|
|
30
|
+
text?: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Build a self-contained JS source string that can be passed to Playwright's
|
|
34
|
+
* page.evaluate / page.addInitScript via the `{ content }` form.
|
|
35
|
+
*
|
|
36
|
+
* The selectors are inlined as a JSON literal, so the page receives a single
|
|
37
|
+
* standalone IIFE invocation — no compiler artifacts, no closures over
|
|
38
|
+
* Node-side values.
|
|
39
|
+
*/
|
|
40
|
+
export declare function buildObserverScriptSource(selectors: BridgeObserverSelectors): string;
|
|
41
|
+
//# sourceMappingURL=observerScript.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"observerScript.d.ts","sourceRoot":"","sources":["../../../../src/providers/web-bridge/shared/observerScript.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,MAAM,CAAA;IAChB,YAAY,EAAE,MAAM,CAAA;IACpB,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,mBAAmB,GAAG,uBAAuB,GAAG,gBAAgB,CAAA;IACtE,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AA2GD;;;;;;;GAOG;AACH,wBAAgB,yBAAyB,CAAC,SAAS,EAAE,uBAAuB,GAAG,MAAM,CAEpF"}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-side observer script (string template form).
|
|
3
|
+
*
|
|
4
|
+
* Why a string and not a function? When we pass a TypeScript function to
|
|
5
|
+
* Playwright's page.evaluate/addInitScript, it stringifies the function with
|
|
6
|
+
* Function.prototype.toString(). But tsx/esbuild (and other TS compilers)
|
|
7
|
+
* wrap functions with helpers like `__name` for name-reflection support.
|
|
8
|
+
* Those helpers don't exist in the page's browser context, so the injected
|
|
9
|
+
* script crashes with "__name is not defined".
|
|
10
|
+
*
|
|
11
|
+
* By writing the body as a string, we bypass the compiler entirely — what
|
|
12
|
+
* goes to the page is exactly what we wrote.
|
|
13
|
+
*
|
|
14
|
+
* The script is parameterized by selectors, injected as a JSON literal.
|
|
15
|
+
*
|
|
16
|
+
* It watches the DOM for two things:
|
|
17
|
+
* 1. New user-bubble appearances — each one marks a turn boundary and may
|
|
18
|
+
* indicate a user typed directly into the chat (passive capture).
|
|
19
|
+
* 2. Text changes in the response container that follows the latest user
|
|
20
|
+
* bubble — these are streamed back as response progress.
|
|
21
|
+
* It calls back into Node via the exposed function `__bbclawBridgeEvent`.
|
|
22
|
+
*/
|
|
23
|
+
const OBSERVER_BODY = String.raw `(function(selectors) {
|
|
24
|
+
if (window.__bbclawBridgeInstalled) return;
|
|
25
|
+
|
|
26
|
+
// addInitScript runs before any page scripts — document.body may not exist
|
|
27
|
+
// yet. Defer the actual installation until the body is available so that
|
|
28
|
+
// MutationObserver.observe doesn't throw on null. We set the installed flag
|
|
29
|
+
// only AFTER successful attach, so a transient race doesn't permanently
|
|
30
|
+
// mark the observer as installed.
|
|
31
|
+
function install() {
|
|
32
|
+
if (window.__bbclawBridgeInstalled) return;
|
|
33
|
+
if (!document.body) {
|
|
34
|
+
// Try again on next tick.
|
|
35
|
+
setTimeout(install, 50);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
window.__bbclawBridgeInstalled = true;
|
|
39
|
+
|
|
40
|
+
// The starting index of response nodes BEFORE the latest user turn.
|
|
41
|
+
// Everything at or after this index belongs to the in-progress / latest
|
|
42
|
+
// assistant turn.
|
|
43
|
+
var turnStartIdx = document.querySelectorAll(selectors.responseRoot).length;
|
|
44
|
+
var prevUserCount = document.querySelectorAll(selectors.userMessage).length;
|
|
45
|
+
var prevResponseText = '';
|
|
46
|
+
|
|
47
|
+
function isReasoningNode(el) {
|
|
48
|
+
// Walk up the ancestor chain — many services wrap CoT inside a collapsed
|
|
49
|
+
// <details> or a div with a "thinking"/"reasoning" class.
|
|
50
|
+
var cur = el;
|
|
51
|
+
while (cur && cur !== document.body) {
|
|
52
|
+
if (cur.tagName === 'DETAILS') return true;
|
|
53
|
+
var cls = typeof cur.className === 'string' ? cur.className.toLowerCase() : '';
|
|
54
|
+
if (/think|reason|thought|chain-of-thought|深度思考|思考/.test(cls)) return true;
|
|
55
|
+
cur = cur.parentElement;
|
|
56
|
+
}
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function computeCurrentResponseText() {
|
|
61
|
+
var all = Array.prototype.slice.call(document.querySelectorAll(selectors.responseRoot));
|
|
62
|
+
var slice = all.slice(turnStartIdx);
|
|
63
|
+
if (slice.length === 0) return '';
|
|
64
|
+
// Drop wrappers — keep only leaves (nodes that don't contain other matched nodes).
|
|
65
|
+
var leaves = [];
|
|
66
|
+
for (var i = 0; i < slice.length; i++) {
|
|
67
|
+
var node = slice[i];
|
|
68
|
+
var hasInner = false;
|
|
69
|
+
for (var j = 0; j < slice.length; j++) {
|
|
70
|
+
if (i !== j && node.contains(slice[j])) { hasInner = true; break; }
|
|
71
|
+
}
|
|
72
|
+
if (!hasInner) leaves.push(node);
|
|
73
|
+
}
|
|
74
|
+
// Drop reasoning / CoT blocks; they're model-internal scratch.
|
|
75
|
+
var answers = [];
|
|
76
|
+
for (var k = 0; k < leaves.length; k++) {
|
|
77
|
+
if (!isReasoningNode(leaves[k])) answers.push(leaves[k]);
|
|
78
|
+
}
|
|
79
|
+
var parts = [];
|
|
80
|
+
for (var m = 0; m < answers.length; m++) parts.push(answers[m].innerText);
|
|
81
|
+
return parts.join('\n\n');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function emit(ev) {
|
|
85
|
+
if (typeof window.__bbclawBridgeEvent === 'function') {
|
|
86
|
+
window.__bbclawBridgeEvent(ev);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
var obs = new MutationObserver(function() {
|
|
91
|
+
var userNodes = document.querySelectorAll(selectors.userMessage);
|
|
92
|
+
|
|
93
|
+
if (userNodes.length > prevUserCount) {
|
|
94
|
+
turnStartIdx = document.querySelectorAll(selectors.responseRoot).length;
|
|
95
|
+
for (var i = prevUserCount; i < userNodes.length; i++) {
|
|
96
|
+
emit({ type: 'external-user-message', text: userNodes[i].innerText });
|
|
97
|
+
}
|
|
98
|
+
prevUserCount = userNodes.length;
|
|
99
|
+
prevResponseText = '';
|
|
100
|
+
} else if (userNodes.length < prevUserCount) {
|
|
101
|
+
prevUserCount = userNodes.length;
|
|
102
|
+
turnStartIdx = document.querySelectorAll(selectors.responseRoot).length;
|
|
103
|
+
prevResponseText = '';
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
var text = computeCurrentResponseText();
|
|
107
|
+
if (text !== prevResponseText) {
|
|
108
|
+
prevResponseText = text;
|
|
109
|
+
emit({ type: 'response-progress', text: text });
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
obs.observe(document.body, { childList: true, subtree: true, characterData: true });
|
|
114
|
+
|
|
115
|
+
// Poll composer reachability for "logged-in" detection.
|
|
116
|
+
var composerCheck = setInterval(function() {
|
|
117
|
+
if (document.querySelector(selectors.composer)) {
|
|
118
|
+
emit({ type: 'composer-ready' });
|
|
119
|
+
clearInterval(composerCheck);
|
|
120
|
+
}
|
|
121
|
+
}, 200);
|
|
122
|
+
setTimeout(function() { clearInterval(composerCheck); }, 30000);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
install();
|
|
126
|
+
})`;
|
|
127
|
+
/**
|
|
128
|
+
* Build a self-contained JS source string that can be passed to Playwright's
|
|
129
|
+
* page.evaluate / page.addInitScript via the `{ content }` form.
|
|
130
|
+
*
|
|
131
|
+
* The selectors are inlined as a JSON literal, so the page receives a single
|
|
132
|
+
* standalone IIFE invocation — no compiler artifacts, no closures over
|
|
133
|
+
* Node-side values.
|
|
134
|
+
*/
|
|
135
|
+
export function buildObserverScriptSource(selectors) {
|
|
136
|
+
return `${OBSERVER_BODY}(${JSON.stringify(selectors)});`;
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=observerScript.js.map
|