@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.
Files changed (85) hide show
  1. package/dist/index.d.ts +8 -0
  2. package/dist/index.d.ts.map +1 -0
  3. package/dist/index.js +8 -0
  4. package/dist/index.js.map +1 -0
  5. package/dist/providers/anthropic/index.d.ts +44 -0
  6. package/dist/providers/anthropic/index.d.ts.map +1 -0
  7. package/dist/providers/anthropic/index.js +75 -0
  8. package/dist/providers/anthropic/index.js.map +1 -0
  9. package/dist/providers/index.d.ts +8 -0
  10. package/dist/providers/index.d.ts.map +1 -0
  11. package/dist/providers/index.js +8 -0
  12. package/dist/providers/index.js.map +1 -0
  13. package/dist/providers/openai-compat/index.d.ts +59 -0
  14. package/dist/providers/openai-compat/index.d.ts.map +1 -0
  15. package/dist/providers/openai-compat/index.js +175 -0
  16. package/dist/providers/openai-compat/index.js.map +1 -0
  17. package/dist/providers/openai-compat/presets.d.ts +22 -0
  18. package/dist/providers/openai-compat/presets.d.ts.map +1 -0
  19. package/dist/providers/openai-compat/presets.js +73 -0
  20. package/dist/providers/openai-compat/presets.js.map +1 -0
  21. package/dist/providers/openai-compat/requestTranslate.d.ts +39 -0
  22. package/dist/providers/openai-compat/requestTranslate.d.ts.map +1 -0
  23. package/dist/providers/openai-compat/requestTranslate.js +228 -0
  24. package/dist/providers/openai-compat/requestTranslate.js.map +1 -0
  25. package/dist/providers/openai-compat/sseParser.d.ts +29 -0
  26. package/dist/providers/openai-compat/sseParser.d.ts.map +1 -0
  27. package/dist/providers/openai-compat/sseParser.js +139 -0
  28. package/dist/providers/openai-compat/sseParser.js.map +1 -0
  29. package/dist/providers/openai-compat/streamAdapter.d.ts +45 -0
  30. package/dist/providers/openai-compat/streamAdapter.d.ts.map +1 -0
  31. package/dist/providers/openai-compat/streamAdapter.js +233 -0
  32. package/dist/providers/openai-compat/streamAdapter.js.map +1 -0
  33. package/dist/providers/openai-compat/types.d.ts +126 -0
  34. package/dist/providers/openai-compat/types.d.ts.map +1 -0
  35. package/dist/providers/openai-compat/types.js +11 -0
  36. package/dist/providers/openai-compat/types.js.map +1 -0
  37. package/dist/providers/selector.d.ts +52 -0
  38. package/dist/providers/selector.d.ts.map +1 -0
  39. package/dist/providers/selector.js +101 -0
  40. package/dist/providers/selector.js.map +1 -0
  41. package/dist/providers/types.d.ts +114 -0
  42. package/dist/providers/types.d.ts.map +1 -0
  43. package/dist/providers/types.js +152 -0
  44. package/dist/providers/types.js.map +1 -0
  45. package/dist/providers/web-bridge/deepseek/spec.d.ts +22 -0
  46. package/dist/providers/web-bridge/deepseek/spec.d.ts.map +1 -0
  47. package/dist/providers/web-bridge/deepseek/spec.js +70 -0
  48. package/dist/providers/web-bridge/deepseek/spec.js.map +1 -0
  49. package/dist/providers/web-bridge/historySerializer.d.ts +49 -0
  50. package/dist/providers/web-bridge/historySerializer.d.ts.map +1 -0
  51. package/dist/providers/web-bridge/historySerializer.js +152 -0
  52. package/dist/providers/web-bridge/historySerializer.js.map +1 -0
  53. package/dist/providers/web-bridge/index.d.ts +10 -0
  54. package/dist/providers/web-bridge/index.d.ts.map +1 -0
  55. package/dist/providers/web-bridge/index.js +10 -0
  56. package/dist/providers/web-bridge/index.js.map +1 -0
  57. package/dist/providers/web-bridge/promptInjector.d.ts +63 -0
  58. package/dist/providers/web-bridge/promptInjector.d.ts.map +1 -0
  59. package/dist/providers/web-bridge/promptInjector.js +189 -0
  60. package/dist/providers/web-bridge/promptInjector.js.map +1 -0
  61. package/dist/providers/web-bridge/provider.d.ts +59 -0
  62. package/dist/providers/web-bridge/provider.d.ts.map +1 -0
  63. package/dist/providers/web-bridge/provider.js +176 -0
  64. package/dist/providers/web-bridge/provider.js.map +1 -0
  65. package/dist/providers/web-bridge/shared/BrowserSession.d.ts +51 -0
  66. package/dist/providers/web-bridge/shared/BrowserSession.d.ts.map +1 -0
  67. package/dist/providers/web-bridge/shared/BrowserSession.js +88 -0
  68. package/dist/providers/web-bridge/shared/BrowserSession.js.map +1 -0
  69. package/dist/providers/web-bridge/shared/WebBridgeAdapter.d.ts +97 -0
  70. package/dist/providers/web-bridge/shared/WebBridgeAdapter.d.ts.map +1 -0
  71. package/dist/providers/web-bridge/shared/WebBridgeAdapter.js +359 -0
  72. package/dist/providers/web-bridge/shared/WebBridgeAdapter.js.map +1 -0
  73. package/dist/providers/web-bridge/shared/observerScript.d.ts +41 -0
  74. package/dist/providers/web-bridge/shared/observerScript.d.ts.map +1 -0
  75. package/dist/providers/web-bridge/shared/observerScript.js +138 -0
  76. package/dist/providers/web-bridge/shared/observerScript.js.map +1 -0
  77. package/dist/providers/web-bridge/shared/types.d.ts +94 -0
  78. package/dist/providers/web-bridge/shared/types.d.ts.map +1 -0
  79. package/dist/providers/web-bridge/shared/types.js +25 -0
  80. package/dist/providers/web-bridge/shared/types.js.map +1 -0
  81. package/dist/providers/web-bridge/toolUseParser.d.ts +70 -0
  82. package/dist/providers/web-bridge/toolUseParser.d.ts.map +1 -0
  83. package/dist/providers/web-bridge/toolUseParser.js +360 -0
  84. package/dist/providers/web-bridge/toolUseParser.js.map +1 -0
  85. package/package.json +36 -0
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Tool-use emulation prompt protocol — arg-tag format.
3
+ *
4
+ * Web-bridge backends (DeepSeek/Kimi/ChatGPT/...) don't speak native function
5
+ * calling on the wire — they only see prompt text. We teach them this XML
6
+ * protocol via the prompt:
7
+ *
8
+ * 1. Inject a system prompt that explains how to invoke tools.
9
+ * 2. Stream-parse the model's output looking for `<tool_use>` blocks.
10
+ * 3. Convert detected blocks back into Anthropic `content_block.tool_use`
11
+ * events so the upper agent layer never sees the difference.
12
+ *
13
+ * Why arg tags and not a single JSON `<input>`? The original protocol asked
14
+ * the model to emit ONE JSON object containing all argument values. For
15
+ * string arguments holding file contents (HTML, code), the model had to
16
+ * JSON-escape every quote/newline/backslash — which doubles payload size,
17
+ * dramatically raises the failure rate (models routinely produce subtly
18
+ * invalid JSON when escaping a few thousand characters of HTML), and gets
19
+ * silently truncated by max_tokens with no clean recovery.
20
+ *
21
+ * The arg-tag protocol moves each argument into its own delimited block of
22
+ * RAW text. No JSON escaping at all. The only thing the model has to escape
23
+ * is the literal substring `</arg>`, which essentially never appears in
24
+ * real code.
25
+ *
26
+ * Format the model is told to emit:
27
+ *
28
+ * <tool_use>
29
+ * <name>ToolName</name>
30
+ * <arg name="path">/some/path/file.html</arg>
31
+ * <arg name="content">
32
+ * <!DOCTYPE html>
33
+ * <html>...</html>
34
+ * </arg>
35
+ * </tool_use>
36
+ *
37
+ * The parser collects each <arg>...</arg> body into a string and assembles
38
+ * them into an `{ argName: value }` object.
39
+ */
40
+ import type { ToolUnion } from '@anthropic-ai/sdk/resources/messages/messages.js';
41
+ export declare const TOOL_USE_OPEN = "<tool_use>";
42
+ export declare const TOOL_USE_CLOSE = "</tool_use>";
43
+ export declare const TOOL_NAME_OPEN = "<name>";
44
+ export declare const TOOL_NAME_CLOSE = "</name>";
45
+ export declare const ARG_OPEN_PREFIX = "<arg name=\"";
46
+ export declare const ARG_OPEN_SUFFIX = "\">";
47
+ export declare const ARG_CLOSE = "</arg>";
48
+ /** What the model writes when it needs the literal text "</arg>" inside content. */
49
+ export declare const ARG_CLOSE_ESCAPE_TOKEN = "&lt;/arg&gt;";
50
+ /**
51
+ * Build the system-prompt fragment that teaches the protocol. Caller is
52
+ * responsible for combining this with any other system text and the message
53
+ * history.
54
+ *
55
+ * If no tools are present, returns an empty string — letting the upper layer
56
+ * skip the whole tool-emulation overhead.
57
+ */
58
+ export declare function buildToolProtocolPrompt(tools: ToolUnion[] | undefined): string;
59
+ /** Decode the single escape token `&lt;/arg&gt;` back into the literal `</arg>`. */
60
+ export declare function unescapeArgContent(s: string): string;
61
+ /** Inverse of unescapeArgContent — used when serializing prior tool_use blocks. */
62
+ export declare function escapeArgContent(s: string): string;
63
+ //# sourceMappingURL=promptInjector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"promptInjector.d.ts","sourceRoot":"","sources":["../../../src/providers/web-bridge/promptInjector.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH,OAAO,KAAK,EAAQ,SAAS,EAAE,MAAM,kDAAkD,CAAA;AAEvF,eAAO,MAAM,aAAa,eAAe,CAAA;AACzC,eAAO,MAAM,cAAc,gBAAgB,CAAA;AAC3C,eAAO,MAAM,cAAc,WAAW,CAAA;AACtC,eAAO,MAAM,eAAe,YAAY,CAAA;AACxC,eAAO,MAAM,eAAe,iBAAgB,CAAA;AAC5C,eAAO,MAAM,eAAe,QAAO,CAAA;AACnC,eAAO,MAAM,SAAS,WAAW,CAAA;AACjC,oFAAoF;AACpF,eAAO,MAAM,sBAAsB,iBAAiB,CAAA;AAEpD;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,SAAS,GAAG,MAAM,CAsC9E;AA8FD,oFAAoF;AACpF,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED,mFAAmF;AACnF,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAElD"}
@@ -0,0 +1,189 @@
1
+ /**
2
+ * Tool-use emulation prompt protocol — arg-tag format.
3
+ *
4
+ * Web-bridge backends (DeepSeek/Kimi/ChatGPT/...) don't speak native function
5
+ * calling on the wire — they only see prompt text. We teach them this XML
6
+ * protocol via the prompt:
7
+ *
8
+ * 1. Inject a system prompt that explains how to invoke tools.
9
+ * 2. Stream-parse the model's output looking for `<tool_use>` blocks.
10
+ * 3. Convert detected blocks back into Anthropic `content_block.tool_use`
11
+ * events so the upper agent layer never sees the difference.
12
+ *
13
+ * Why arg tags and not a single JSON `<input>`? The original protocol asked
14
+ * the model to emit ONE JSON object containing all argument values. For
15
+ * string arguments holding file contents (HTML, code), the model had to
16
+ * JSON-escape every quote/newline/backslash — which doubles payload size,
17
+ * dramatically raises the failure rate (models routinely produce subtly
18
+ * invalid JSON when escaping a few thousand characters of HTML), and gets
19
+ * silently truncated by max_tokens with no clean recovery.
20
+ *
21
+ * The arg-tag protocol moves each argument into its own delimited block of
22
+ * RAW text. No JSON escaping at all. The only thing the model has to escape
23
+ * is the literal substring `</arg>`, which essentially never appears in
24
+ * real code.
25
+ *
26
+ * Format the model is told to emit:
27
+ *
28
+ * <tool_use>
29
+ * <name>ToolName</name>
30
+ * <arg name="path">/some/path/file.html</arg>
31
+ * <arg name="content">
32
+ * <!DOCTYPE html>
33
+ * <html>...</html>
34
+ * </arg>
35
+ * </tool_use>
36
+ *
37
+ * The parser collects each <arg>...</arg> body into a string and assembles
38
+ * them into an `{ argName: value }` object.
39
+ */
40
+ export const TOOL_USE_OPEN = '<tool_use>';
41
+ export const TOOL_USE_CLOSE = '</tool_use>';
42
+ export const TOOL_NAME_OPEN = '<name>';
43
+ export const TOOL_NAME_CLOSE = '</name>';
44
+ export const ARG_OPEN_PREFIX = '<arg name="';
45
+ export const ARG_OPEN_SUFFIX = '">';
46
+ export const ARG_CLOSE = '</arg>';
47
+ /** What the model writes when it needs the literal text "</arg>" inside content. */
48
+ export const ARG_CLOSE_ESCAPE_TOKEN = '&lt;/arg&gt;';
49
+ /**
50
+ * Build the system-prompt fragment that teaches the protocol. Caller is
51
+ * responsible for combining this with any other system text and the message
52
+ * history.
53
+ *
54
+ * If no tools are present, returns an empty string — letting the upper layer
55
+ * skip the whole tool-emulation overhead.
56
+ */
57
+ export function buildToolProtocolPrompt(tools) {
58
+ const customTools = (tools ?? []).filter(isCustomTool);
59
+ if (customTools.length === 0)
60
+ return '';
61
+ const toolListing = customTools.map(renderToolDescription).join('\n\n---\n\n');
62
+ return [
63
+ '## Tool use protocol',
64
+ '',
65
+ 'You have access to a set of tools. When you need to call a tool, output ONLY this exact format and then stop:',
66
+ '',
67
+ '```',
68
+ TOOL_USE_OPEN,
69
+ `${TOOL_NAME_OPEN}ToolName${TOOL_NAME_CLOSE}`,
70
+ `${ARG_OPEN_PREFIX}argument1${ARG_OPEN_SUFFIX}first argument value as plain text${ARG_CLOSE}`,
71
+ `${ARG_OPEN_PREFIX}argument2${ARG_OPEN_SUFFIX}`,
72
+ 'second argument value can be multi-line raw text — code, JSON, markdown,',
73
+ 'anything. Quotes "like this", backslashes \\, angle brackets <like this>',
74
+ 'are all literal. Do not escape them.',
75
+ ARG_CLOSE,
76
+ TOOL_USE_CLOSE,
77
+ '```',
78
+ '',
79
+ 'Rules — read carefully:',
80
+ '',
81
+ '1. Output exactly ONE `<tool_use>` block per response. If you need multiple tools, call them in successive turns (the system will return each result before you decide the next call).',
82
+ '2. After the closing `</tool_use>` tag, STOP. Do not add commentary or further text. The system will return the result in the next user message.',
83
+ `3. Each tool argument lives inside its own \`${ARG_OPEN_PREFIX}<argName>${ARG_OPEN_SUFFIX} ... ${ARG_CLOSE}\` tag. The \`name\` attribute MUST match the parameter name from the tool's description.`,
84
+ '4. Content between an opening `<arg ...>` tag and its `</arg>` close is RAW TEXT. Do NOT JSON-encode it. Do not wrap in quotes. Newlines, double-quotes, backslashes, and angle brackets are all literal — write code, HTML, or markdown directly.',
85
+ `5. The ONLY thing you must escape is the literal substring \`${ARG_CLOSE}\` if it appears in your content — write it as \`${ARG_CLOSE_ESCAPE_TOKEN}\`. This is rare; most code, markup, and prose never contain the exact text \`${ARG_CLOSE}\`.`,
86
+ '6. Do NOT wrap the `<tool_use>` block in a markdown code fence or quote it. Output the tags directly.',
87
+ '7. If you do NOT need a tool, answer the user in plain text as you normally would. Do not output `<tool_use>` tags speculatively.',
88
+ '8. Tool results from previous turns appear as `[Tool result for ToolName]: ...` in user messages. Trust them as authoritative; do not re-issue the same call.',
89
+ '',
90
+ `## Available tools (${customTools.length})`,
91
+ '',
92
+ toolListing,
93
+ ].join('\n');
94
+ }
95
+ function renderToolDescription(tool) {
96
+ const lines = [];
97
+ lines.push(`### Tool: ${tool.name}`);
98
+ if (tool.description)
99
+ lines.push('', tool.description.trim());
100
+ const schema = tool.input_schema;
101
+ if (schema?.properties && typeof schema.properties === 'object') {
102
+ const required = new Set(schema.required ?? []);
103
+ lines.push('', 'Parameters:');
104
+ for (const [key, raw] of Object.entries(schema.properties)) {
105
+ const desc = describeProperty(key, raw, required.has(key));
106
+ lines.push(` - ${desc}`);
107
+ }
108
+ }
109
+ // A short example helps the model nail the format on the first try.
110
+ const example = buildExampleArgs(schema);
111
+ if (example) {
112
+ lines.push('', 'Example:');
113
+ lines.push('```');
114
+ lines.push(TOOL_USE_OPEN);
115
+ lines.push(`${TOOL_NAME_OPEN}${tool.name}${TOOL_NAME_CLOSE}`);
116
+ for (const [argName, argValue] of example) {
117
+ lines.push(`${ARG_OPEN_PREFIX}${argName}${ARG_OPEN_SUFFIX}${argValue}${ARG_CLOSE}`);
118
+ }
119
+ lines.push(TOOL_USE_CLOSE);
120
+ lines.push('```');
121
+ }
122
+ return lines.join('\n');
123
+ }
124
+ function describeProperty(key, raw, required) {
125
+ if (typeof raw !== 'object' || raw === null)
126
+ return `\`${key}\``;
127
+ const prop = raw;
128
+ const typeStr = prop.type ?? 'any';
129
+ const reqStr = required ? 'required' : 'optional';
130
+ const enumStr = Array.isArray(prop.enum)
131
+ ? ` (one of: ${prop.enum.map((v) => JSON.stringify(v)).join(', ')})`
132
+ : '';
133
+ const descStr = prop.description
134
+ ? ` — ${prop.description.replace(/\s+/g, ' ').trim()}`
135
+ : '';
136
+ return `\`${key}\` (${typeStr}, ${reqStr})${enumStr}${descStr}`;
137
+ }
138
+ function buildExampleArgs(schema) {
139
+ if (!schema?.properties)
140
+ return null;
141
+ const required = schema.required ?? [];
142
+ if (required.length === 0)
143
+ return null;
144
+ const out = [];
145
+ for (const key of required) {
146
+ const prop = schema.properties[key];
147
+ if (!prop)
148
+ continue;
149
+ let example = '...';
150
+ if (Array.isArray(prop.enum) && prop.enum.length > 0) {
151
+ example = String(prop.enum[0] ?? '...');
152
+ }
153
+ else {
154
+ switch (prop.type) {
155
+ case 'number':
156
+ case 'integer':
157
+ example = '1';
158
+ break;
159
+ case 'boolean':
160
+ example = 'true';
161
+ break;
162
+ case 'array':
163
+ example = '[]';
164
+ break;
165
+ case 'object':
166
+ example = '{}';
167
+ break;
168
+ default:
169
+ example = '...';
170
+ }
171
+ }
172
+ out.push([key, example]);
173
+ }
174
+ return out;
175
+ }
176
+ function isCustomTool(t) {
177
+ // Anthropic's built-in tools (computer_20250124, bash_20250124, ...) have a
178
+ // `type` field; user-defined tools don't.
179
+ return !(typeof t === 'object' && t !== null && 'type' in t);
180
+ }
181
+ /** Decode the single escape token `&lt;/arg&gt;` back into the literal `</arg>`. */
182
+ export function unescapeArgContent(s) {
183
+ return s.split(ARG_CLOSE_ESCAPE_TOKEN).join(ARG_CLOSE);
184
+ }
185
+ /** Inverse of unescapeArgContent — used when serializing prior tool_use blocks. */
186
+ export function escapeArgContent(s) {
187
+ return s.split(ARG_CLOSE).join(ARG_CLOSE_ESCAPE_TOKEN);
188
+ }
189
+ //# sourceMappingURL=promptInjector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"promptInjector.js","sourceRoot":"","sources":["../../../src/providers/web-bridge/promptInjector.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * WebBridgeProvider — adapts the web-bridge stack to the Provider interface.
3
+ *
4
+ * Responsibilities:
5
+ * - Capability declaration (nativeTools=false, promptCaching=false, etc).
6
+ * - Per-turn lifecycle: open new chat → serialize history → submit prompt →
7
+ * parse streamed text into Anthropic events → yield events.
8
+ * - Login orchestration via setHeadless(false) / setHeadless(true).
9
+ *
10
+ * What it deliberately doesn't do:
11
+ * - Multi-Agent concurrency: one browser session, one turn at a time. The
12
+ * upper agent layer is responsible for serializing.
13
+ * - Prompt caching: the web bridge has no cache control. We strip
14
+ * cache_control hints in the request translator already.
15
+ */
16
+ import type { Message, MessageCreateParamsStreaming, MessageStreamParams, RawMessageStreamEvent } from '@anthropic-ai/sdk/resources/messages/messages.js';
17
+ import type { Logger } from '@bbclaw/shared';
18
+ import { BaseProvider, type HealthCheckResult, type ProviderCapabilities } from '../types.js';
19
+ import { type WebBridgeAdapterOptions } from './shared/WebBridgeAdapter.js';
20
+ import type { WebBridgeSpec, WebBridgeState } from './shared/types.js';
21
+ export interface WebBridgeProviderOptions extends WebBridgeAdapterOptions {
22
+ /** Stable provider id (defaults to the spec's serviceId). */
23
+ id?: string;
24
+ /** Display name override. */
25
+ displayName?: string;
26
+ /** Capability overrides — most users won't need to touch this. */
27
+ capabilities?: Partial<ProviderCapabilities>;
28
+ logger?: Logger;
29
+ }
30
+ export declare class WebBridgeProvider extends BaseProvider {
31
+ readonly kind: "web-bridge";
32
+ readonly id: string;
33
+ readonly displayName: string;
34
+ readonly capabilities: ProviderCapabilities;
35
+ readonly defaultModel: string;
36
+ private readonly adapter;
37
+ private readonly spec;
38
+ constructor(spec: WebBridgeSpec, opts: WebBridgeProviderOptions);
39
+ streamMessage(params: MessageStreamParams | MessageCreateParamsStreaming): Promise<AsyncIterable<RawMessageStreamEvent>>;
40
+ createMessage(params: MessageStreamParams): Promise<Message>;
41
+ healthCheck(): Promise<HealthCheckResult>;
42
+ close(): Promise<void>;
43
+ /** Show the browser so the user can complete the login flow. */
44
+ showForLogin(): Promise<void>;
45
+ /** Toggle headless mode (e.g. for debugging silent failures). */
46
+ setHeadless(headless: boolean): Promise<void>;
47
+ /** Confirm login worked and switch back to headless. Returns whether composer is reachable. */
48
+ confirmLogin(): Promise<boolean>;
49
+ /** Current page URL — useful diagnostic during login flows. */
50
+ currentUrl(): string | undefined;
51
+ getBridgeState(): WebBridgeState;
52
+ /**
53
+ * Convert the cumulative-text stream from the adapter into Anthropic
54
+ * RawMessageStreamEvent events. The adapter yields snapshots of the full
55
+ * response so far; we diff to deltas and feed them into the protocol parser.
56
+ */
57
+ private adaptToAnthropicEvents;
58
+ }
59
+ //# sourceMappingURL=provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../../src/providers/web-bridge/provider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,KAAK,EACV,OAAO,EACP,4BAA4B,EAC5B,mBAAmB,EACnB,qBAAqB,EACtB,MAAM,kDAAkD,CAAA;AACzD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;AAC5C,OAAO,EACL,YAAY,EAEZ,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EAC1B,MAAM,aAAa,CAAA;AAGpB,OAAO,EAAoB,KAAK,uBAAuB,EAAE,MAAM,8BAA8B,CAAA;AAC7F,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAEtE,MAAM,WAAW,wBAAyB,SAAQ,uBAAuB;IACvE,6DAA6D;IAC7D,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,6BAA6B;IAC7B,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,kEAAkE;IAClE,YAAY,CAAC,EAAE,OAAO,CAAC,oBAAoB,CAAC,CAAA;IAC5C,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAoBD,qBAAa,iBAAkB,SAAQ,YAAY;IACjD,QAAQ,CAAC,IAAI,EAAG,YAAY,CAAS;IACrC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,YAAY,EAAE,oBAAoB,CAAA;IAC3C,SAAkB,YAAY,EAAE,MAAM,CAAA;IAEtC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAkB;IAC1C,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAe;gBAExB,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,wBAAwB;IAmBzD,aAAa,CACjB,MAAM,EAAE,mBAAmB,GAAG,4BAA4B,GACzD,OAAO,CAAC,aAAa,CAAC,qBAAqB,CAAC,CAAC;IAYjC,aAAa,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,OAAO,CAAC;IAKrE,WAAW,IAAI,OAAO,CAAC,iBAAiB,CAAC;IAiBhC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAMrC,gEAAgE;IAC1D,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAKnC,iEAAiE;IAC3D,WAAW,CAAC,QAAQ,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAInD,+FAA+F;IACzF,YAAY,IAAI,OAAO,CAAC,OAAO,CAAC;IAQtC,+DAA+D;IAC/D,UAAU,IAAI,MAAM,GAAG,SAAS;IAIhC,cAAc,IAAI,cAAc;IAMhC;;;;OAIG;YACY,sBAAsB;CAoDtC"}
@@ -0,0 +1,176 @@
1
+ /**
2
+ * WebBridgeProvider — adapts the web-bridge stack to the Provider interface.
3
+ *
4
+ * Responsibilities:
5
+ * - Capability declaration (nativeTools=false, promptCaching=false, etc).
6
+ * - Per-turn lifecycle: open new chat → serialize history → submit prompt →
7
+ * parse streamed text into Anthropic events → yield events.
8
+ * - Login orchestration via setHeadless(false) / setHeadless(true).
9
+ *
10
+ * What it deliberately doesn't do:
11
+ * - Multi-Agent concurrency: one browser session, one turn at a time. The
12
+ * upper agent layer is responsible for serializing.
13
+ * - Prompt caching: the web bridge has no cache control. We strip
14
+ * cache_control hints in the request translator already.
15
+ */
16
+ import { randomUUID } from 'node:crypto';
17
+ import { BaseProvider, collectStreamToMessage, } from '../types.js';
18
+ import { serializeHistory } from './historySerializer.js';
19
+ import { ToolUseStreamParser } from './toolUseParser.js';
20
+ import { WebBridgeAdapter } from './shared/WebBridgeAdapter.js';
21
+ /**
22
+ * Capability baseline for a "generic modern chat-web" service. Individual
23
+ * specs / instances can override via constructor option.
24
+ */
25
+ const WEB_BRIDGE_CAPABILITIES_DEFAULT = {
26
+ // No native function calling. Tool support is emulated via the XML protocol
27
+ // we inject into the prompt. nativeTools=false signals to the upper agent
28
+ // that it should expect emulation behavior (smaller tool counts, simpler
29
+ // schemas, more retries on malformed tool input).
30
+ nativeTools: false,
31
+ promptCaching: false,
32
+ extendedThinking: false, // we strip thinking blocks visually but don't echo them
33
+ vision: false, // image uploads via DOM are technically possible but out of scope for v0.1
34
+ contextWindow: 32_000,
35
+ maxOutputTokens: 8_192,
36
+ streaming: true,
37
+ };
38
+ export class WebBridgeProvider extends BaseProvider {
39
+ kind = 'web-bridge';
40
+ id;
41
+ displayName;
42
+ capabilities;
43
+ defaultModel;
44
+ adapter;
45
+ spec;
46
+ constructor(spec, opts) {
47
+ super(opts.logger ?? { debug: () => { }, info: () => { }, warn: () => { }, error: () => { } });
48
+ this.spec = spec;
49
+ this.id = opts.id ?? spec.serviceId;
50
+ this.displayName = opts.displayName ?? spec.displayName;
51
+ // The web bridge doesn't route by model name (the service picks its own),
52
+ // but a sentinel value flows through message_start.model so downstream
53
+ // consumers see a stable identifier instead of an empty string.
54
+ this.defaultModel = spec.serviceId;
55
+ this.capabilities = {
56
+ ...WEB_BRIDGE_CAPABILITIES_DEFAULT,
57
+ contextWindow: spec.contextWindowEstimate,
58
+ ...opts.capabilities,
59
+ };
60
+ this.adapter = new WebBridgeAdapter(spec, opts);
61
+ }
62
+ // ---- Provider interface ----
63
+ async streamMessage(params) {
64
+ // Each turn starts in a fresh chat — messages[] is the source of truth.
65
+ await this.adapter.start();
66
+ await this.adapter.newChat();
67
+ const prompt = serializeHistory(params);
68
+ if (prompt.length === 0)
69
+ throw new Error('WebBridgeProvider: empty prompt after serialization');
70
+ const rawStream = this.adapter.askStream(prompt);
71
+ return this.adaptToAnthropicEvents(rawStream, params.model);
72
+ }
73
+ async createMessage(params) {
74
+ const stream = await this.streamMessage(params);
75
+ return collectStreamToMessage(stream);
76
+ }
77
+ async healthCheck() {
78
+ const start = Date.now();
79
+ try {
80
+ await this.adapter.start();
81
+ const ok = await this.adapter.isLoggedIn(3_000);
82
+ return ok
83
+ ? { ok: true, latencyMs: Date.now() - start }
84
+ : { ok: false, latencyMs: Date.now() - start, reason: 'login required' };
85
+ }
86
+ catch (e) {
87
+ return {
88
+ ok: false,
89
+ latencyMs: Date.now() - start,
90
+ reason: e instanceof Error ? e.message : String(e),
91
+ };
92
+ }
93
+ }
94
+ async close() {
95
+ await this.adapter.stop();
96
+ }
97
+ // ---- web-bridge specific ----
98
+ /** Show the browser so the user can complete the login flow. */
99
+ async showForLogin() {
100
+ await this.adapter.setHeadless(false);
101
+ await this.adapter.start();
102
+ }
103
+ /** Toggle headless mode (e.g. for debugging silent failures). */
104
+ async setHeadless(headless) {
105
+ await this.adapter.setHeadless(headless);
106
+ }
107
+ /** Confirm login worked and switch back to headless. Returns whether composer is reachable. */
108
+ async confirmLogin() {
109
+ // First-time post-login the SPA can take several seconds to hydrate the
110
+ // composer. Give it longer than the routine isLoggedIn() probe.
111
+ const ok = await this.adapter.isLoggedIn(15_000);
112
+ if (ok)
113
+ await this.adapter.setHeadless(true);
114
+ return ok;
115
+ }
116
+ /** Current page URL — useful diagnostic during login flows. */
117
+ currentUrl() {
118
+ return this.adapter.currentUrl();
119
+ }
120
+ getBridgeState() {
121
+ return this.adapter.getState();
122
+ }
123
+ // ---- internals ----
124
+ /**
125
+ * Convert the cumulative-text stream from the adapter into Anthropic
126
+ * RawMessageStreamEvent events. The adapter yields snapshots of the full
127
+ * response so far; we diff to deltas and feed them into the protocol parser.
128
+ */
129
+ async *adaptToAnthropicEvents(rawStream, model) {
130
+ const messageId = `msg_${randomUUID()}`;
131
+ const parser = new ToolUseStreamParser();
132
+ let lastEmittedLength = 0;
133
+ yield {
134
+ type: 'message_start',
135
+ message: {
136
+ id: messageId,
137
+ type: 'message',
138
+ role: 'assistant',
139
+ model,
140
+ content: [],
141
+ stop_reason: null,
142
+ stop_sequence: null,
143
+ usage: {
144
+ input_tokens: 0,
145
+ output_tokens: 0,
146
+ cache_creation_input_tokens: null,
147
+ cache_read_input_tokens: null,
148
+ },
149
+ },
150
+ };
151
+ let lastText = '';
152
+ for await (const chunk of rawStream) {
153
+ const delta = chunk.text.slice(lastEmittedLength);
154
+ lastEmittedLength = chunk.text.length;
155
+ lastText = chunk.text;
156
+ if (delta.length > 0)
157
+ yield* parser.feed(delta);
158
+ if (chunk.done)
159
+ break;
160
+ }
161
+ yield* parser.flush();
162
+ // Rough token estimate. Web bridges don't give us real usage numbers, so
163
+ // we approximate based on char count. Mixed-language average ~ 0.3 tok/char.
164
+ const outputTokens = Math.max(1, Math.ceil(lastText.length * 0.3));
165
+ yield {
166
+ type: 'message_delta',
167
+ delta: {
168
+ stop_reason: parser.stopReason,
169
+ stop_sequence: null,
170
+ },
171
+ usage: { output_tokens: outputTokens },
172
+ };
173
+ yield { type: 'message_stop' };
174
+ }
175
+ }
176
+ //# sourceMappingURL=provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.js","sourceRoot":"","sources":["../../../src/providers/web-bridge/provider.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Persistent Chromium session wrapper.
3
+ *
4
+ * Holds the cookies, localStorage, and IndexedDB of a chat service across
5
+ * invocations, so the user only logs in once. Toggleable headless mode lets
6
+ * the same userDataDir serve a visible browser during login flows and a
7
+ * background browser during day-to-day prompts.
8
+ *
9
+ * One BrowserSession owns ONE BrowserContext and ONE Page. We don't need
10
+ * multiple pages — each chat service uses a single tab.
11
+ */
12
+ import { type Page } from 'playwright';
13
+ export interface BrowserSessionOptions {
14
+ /** Directory for persistent profile (cookies, etc). Must be writable. */
15
+ userDataDir: string;
16
+ /** Default headless. Can be toggled later via .restart(). */
17
+ headless?: boolean;
18
+ /** Viewport. Match a common laptop default. */
19
+ viewport?: {
20
+ width: number;
21
+ height: number;
22
+ };
23
+ /** Custom user agent. When omitted, Playwright's default is used. */
24
+ userAgent?: string;
25
+ /** Locale and timezone — match a real machine to avoid trivial fingerprints. */
26
+ locale?: string;
27
+ timezoneId?: string;
28
+ /** Extra launch args passed to Chromium. */
29
+ extraArgs?: string[];
30
+ }
31
+ export declare class BrowserSession {
32
+ private opts;
33
+ private context?;
34
+ private page?;
35
+ private currentHeadless;
36
+ constructor(opts: BrowserSessionOptions);
37
+ get isOpen(): boolean;
38
+ get headless(): boolean;
39
+ /**
40
+ * Open the persistent context (if not already open) and return a page at
41
+ * `url`. If a page is already open on a matching origin, it's reused.
42
+ */
43
+ ensurePage(url: string): Promise<Page>;
44
+ /**
45
+ * Switch headless on/off. Requires a full restart since Chromium can't
46
+ * change modes mid-flight. Persistent data survives.
47
+ */
48
+ restart(headless: boolean): Promise<void>;
49
+ close(): Promise<void>;
50
+ }
51
+ //# sourceMappingURL=BrowserSession.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BrowserSession.d.ts","sourceRoot":"","sources":["../../../../src/providers/web-bridge/shared/BrowserSession.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,EAAiC,KAAK,IAAI,EAAE,MAAM,YAAY,CAAA;AAErE,MAAM,WAAW,qBAAqB;IACpC,yEAAyE;IACzE,WAAW,EAAE,MAAM,CAAA;IACnB,6DAA6D;IAC7D,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,+CAA+C;IAC/C,QAAQ,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAA;IAC5C,qEAAqE;IACrE,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,gFAAgF;IAChF,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,4CAA4C;IAC5C,SAAS,CAAC,EAAE,MAAM,EAAE,CAAA;CACrB;AAED,qBAAa,cAAc;IAKb,OAAO,CAAC,IAAI;IAJxB,OAAO,CAAC,OAAO,CAAC,CAAgB;IAChC,OAAO,CAAC,IAAI,CAAC,CAAM;IACnB,OAAO,CAAC,eAAe,CAAS;gBAEZ,IAAI,EAAE,qBAAqB;IAI/C,IAAI,MAAM,IAAI,OAAO,CAEpB;IAED,IAAI,QAAQ,IAAI,OAAO,CAEtB;IAED;;;OAGG;IACG,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA6B5C;;;OAGG;IACG,OAAO,CAAC,QAAQ,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAOzC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAS7B"}
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Persistent Chromium session wrapper.
3
+ *
4
+ * Holds the cookies, localStorage, and IndexedDB of a chat service across
5
+ * invocations, so the user only logs in once. Toggleable headless mode lets
6
+ * the same userDataDir serve a visible browser during login flows and a
7
+ * background browser during day-to-day prompts.
8
+ *
9
+ * One BrowserSession owns ONE BrowserContext and ONE Page. We don't need
10
+ * multiple pages — each chat service uses a single tab.
11
+ */
12
+ import { mkdir } from 'node:fs/promises';
13
+ import { chromium } from 'playwright';
14
+ export class BrowserSession {
15
+ opts;
16
+ context;
17
+ page;
18
+ currentHeadless;
19
+ constructor(opts) {
20
+ this.opts = opts;
21
+ this.currentHeadless = opts.headless ?? true;
22
+ }
23
+ get isOpen() {
24
+ return !!this.page && !this.page.isClosed();
25
+ }
26
+ get headless() {
27
+ return this.currentHeadless;
28
+ }
29
+ /**
30
+ * Open the persistent context (if not already open) and return a page at
31
+ * `url`. If a page is already open on a matching origin, it's reused.
32
+ */
33
+ async ensurePage(url) {
34
+ if (this.page && !this.page.isClosed()) {
35
+ // Reuse only if origin matches — otherwise navigate.
36
+ if (!sameOrigin(this.page.url(), url)) {
37
+ await this.page.goto(url, { waitUntil: 'domcontentloaded' });
38
+ }
39
+ return this.page;
40
+ }
41
+ await mkdir(this.opts.userDataDir, { recursive: true });
42
+ this.context = await chromium.launchPersistentContext(this.opts.userDataDir, {
43
+ headless: this.currentHeadless,
44
+ viewport: this.opts.viewport ?? { width: 1280, height: 800 },
45
+ userAgent: this.opts.userAgent,
46
+ locale: this.opts.locale,
47
+ timezoneId: this.opts.timezoneId,
48
+ args: this.opts.extraArgs,
49
+ });
50
+ // launchPersistentContext gives back a context that may or may not have a
51
+ // default page depending on platform — be tolerant.
52
+ this.page = this.context.pages()[0] ?? (await this.context.newPage());
53
+ if (!sameOrigin(this.page.url(), url)) {
54
+ await this.page.goto(url, { waitUntil: 'domcontentloaded' });
55
+ }
56
+ return this.page;
57
+ }
58
+ /**
59
+ * Switch headless on/off. Requires a full restart since Chromium can't
60
+ * change modes mid-flight. Persistent data survives.
61
+ */
62
+ async restart(headless) {
63
+ if (this.currentHeadless === headless && this.isOpen)
64
+ return;
65
+ await this.close();
66
+ this.currentHeadless = headless;
67
+ // Next ensurePage() will relaunch.
68
+ }
69
+ async close() {
70
+ try {
71
+ await this.context?.close();
72
+ }
73
+ catch {
74
+ // Context may already be torn down; ignore.
75
+ }
76
+ this.context = undefined;
77
+ this.page = undefined;
78
+ }
79
+ }
80
+ function sameOrigin(current, target) {
81
+ try {
82
+ return new URL(current).origin === new URL(target).origin;
83
+ }
84
+ catch {
85
+ return false;
86
+ }
87
+ }
88
+ //# sourceMappingURL=BrowserSession.js.map