@vymalo/opencode-browser 0.7.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.
@@ -0,0 +1,117 @@
1
+ import { AgentClient } from "./agent-client.js";
2
+ import { Broker } from "./broker.js";
3
+ import { isAddrInUse } from "./transport.js";
4
+ /**
5
+ * Unifies host and guest: tries to **bind** the port (election) — on success it
6
+ * hosts the broker and drives it via an in-process agent; on `EADDRINUSE` it
7
+ * connects to the existing broker as a guest agent. Re-elects on drop. Tools
8
+ * call `send()` and never know which mode they're in.
9
+ */
10
+ export async function createEndpoint(opts, deps) {
11
+ const url = deps.url ?? `ws://${opts.host}:${opts.port}`;
12
+ const reelectMs = opts.reelectMs ?? 500;
13
+ let mode = "electing";
14
+ let broker = null;
15
+ let agentClient = null;
16
+ let current = null;
17
+ let closed = false;
18
+ let reelectTimer = null;
19
+ const scheduleReelect = () => {
20
+ broker = null;
21
+ agentClient = null;
22
+ current = null;
23
+ mode = "electing";
24
+ if (closed || reelectTimer) {
25
+ return;
26
+ }
27
+ reelectTimer = setTimeout(() => {
28
+ reelectTimer = null;
29
+ void elect();
30
+ }, reelectMs);
31
+ };
32
+ async function elect() {
33
+ if (closed) {
34
+ return;
35
+ }
36
+ mode = "electing";
37
+ // 1) Try to host (win the bind).
38
+ const transport = deps.createServerTransport();
39
+ const candidate = new Broker({
40
+ host: opts.host,
41
+ port: opts.port,
42
+ token: opts.token,
43
+ executor: opts.executor,
44
+ timeoutMs: opts.timeoutMs
45
+ }, { logger: deps.logger, transport });
46
+ try {
47
+ await candidate.start();
48
+ broker = candidate;
49
+ current = candidate.createLocalAgent();
50
+ mode = "host";
51
+ deps.logger.info("browser_endpoint_mode", { mode: "host" });
52
+ return;
53
+ }
54
+ catch (err) {
55
+ try {
56
+ candidate.stop();
57
+ }
58
+ catch {
59
+ /* never bound */
60
+ }
61
+ if (!isAddrInUse(err)) {
62
+ deps.logger.warn("browser_endpoint_bind_error", {
63
+ message: err instanceof Error ? err.message : String(err)
64
+ });
65
+ }
66
+ }
67
+ // 2) Someone else hosts — join as a guest.
68
+ const client = new AgentClient({ url, token: opts.token, label: opts.label, timeoutMs: opts.timeoutMs }, {
69
+ logger: deps.logger,
70
+ createSocket: deps.createAgentSocket,
71
+ onClose: () => {
72
+ if (!closed) {
73
+ scheduleReelect();
74
+ }
75
+ }
76
+ });
77
+ try {
78
+ await client.connect();
79
+ agentClient = client;
80
+ current = client;
81
+ mode = "guest";
82
+ deps.logger.info("browser_endpoint_mode", { mode: "guest" });
83
+ }
84
+ catch {
85
+ scheduleReelect();
86
+ }
87
+ }
88
+ await elect();
89
+ return {
90
+ send: (action, group, params, signal, target) => current
91
+ ? current.send(action, group, params, signal, target)
92
+ : Promise.reject(new Error("bridge is re-electing — retry shortly")),
93
+ release: () => current?.release(),
94
+ shutdown: () => {
95
+ closed = true;
96
+ if (reelectTimer) {
97
+ clearTimeout(reelectTimer);
98
+ reelectTimer = null;
99
+ }
100
+ try {
101
+ broker?.stop();
102
+ }
103
+ catch {
104
+ /* ignore */
105
+ }
106
+ try {
107
+ agentClient?.stop();
108
+ }
109
+ catch {
110
+ /* ignore */
111
+ }
112
+ },
113
+ mode: () => mode,
114
+ broker: () => broker
115
+ };
116
+ }
117
+ //# sourceMappingURL=endpoint.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"endpoint.js","sourceRoot":"","sources":["../src/endpoint.ts"],"names":[],"mappings":"AAAA,OAAO,EAA2B,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACzE,OAAO,EAAsB,MAAM,EAAE,MAAM,aAAa,CAAC;AAGzD,OAAO,EAAwB,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAyCnE;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,IAAqB,EAAE,IAAkB;IAC5E,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,QAAQ,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;IACzD,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,GAAG,CAAC;IAExC,IAAI,IAAI,GAAiB,UAAU,CAAC;IACpC,IAAI,MAAM,GAAkB,IAAI,CAAC;IACjC,IAAI,WAAW,GAAuB,IAAI,CAAC;IAC3C,IAAI,OAAO,GAAyB,IAAI,CAAC;IACzC,IAAI,MAAM,GAAG,KAAK,CAAC;IACnB,IAAI,YAAY,GAAyC,IAAI,CAAC;IAE9D,MAAM,eAAe,GAAG,GAAS,EAAE;QACjC,MAAM,GAAG,IAAI,CAAC;QACd,WAAW,GAAG,IAAI,CAAC;QACnB,OAAO,GAAG,IAAI,CAAC;QACf,IAAI,GAAG,UAAU,CAAC;QAClB,IAAI,MAAM,IAAI,YAAY,EAAE,CAAC;YAC3B,OAAO;QACT,CAAC;QACD,YAAY,GAAG,UAAU,CAAC,GAAG,EAAE;YAC7B,YAAY,GAAG,IAAI,CAAC;YACpB,KAAK,KAAK,EAAE,CAAC;QACf,CAAC,EAAE,SAAS,CAAC,CAAC;IAChB,CAAC,CAAC;IAEF,KAAK,UAAU,KAAK;QAClB,IAAI,MAAM,EAAE,CAAC;YACX,OAAO;QACT,CAAC;QACD,IAAI,GAAG,UAAU,CAAC;QAElB,iCAAiC;QACjC,MAAM,SAAS,GAAG,IAAI,CAAC,qBAAqB,EAAE,CAAC;QAC/C,MAAM,SAAS,GAAG,IAAI,MAAM,CAC1B;YACE,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,SAAS,EAAE,IAAI,CAAC,SAAS;SAC1B,EACD,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,CACnC,CAAC;QACF,IAAI,CAAC;YACH,MAAM,SAAS,CAAC,KAAK,EAAE,CAAC;YACxB,MAAM,GAAG,SAAS,CAAC;YACnB,OAAO,GAAG,SAAS,CAAC,gBAAgB,EAAE,CAAC;YACvC,IAAI,GAAG,MAAM,CAAC;YACd,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,uBAAuB,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;YAC5D,OAAO;QACT,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,CAAC;gBACH,SAAS,CAAC,IAAI,EAAE,CAAC;YACnB,CAAC;YAAC,MAAM,CAAC;gBACP,iBAAiB;YACnB,CAAC;YACD,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC;gBACtB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,6BAA6B,EAAE;oBAC9C,OAAO,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;iBAC1D,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QAED,2CAA2C;QAC3C,MAAM,MAAM,GAAG,IAAI,WAAW,CAC5B,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,EACxE;YACE,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,YAAY,EAAE,IAAI,CAAC,iBAAiB;YACpC,OAAO,EAAE,GAAG,EAAE;gBACZ,IAAI,CAAC,MAAM,EAAE,CAAC;oBACZ,eAAe,EAAE,CAAC;gBACpB,CAAC;YACH,CAAC;SACF,CACF,CAAC;QACF,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,OAAO,EAAE,CAAC;YACvB,WAAW,GAAG,MAAM,CAAC;YACrB,OAAO,GAAG,MAAM,CAAC;YACjB,IAAI,GAAG,OAAO,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,uBAAuB,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;QAC/D,CAAC;QAAC,MAAM,CAAC;YACP,eAAe,EAAE,CAAC;QACpB,CAAC;IACH,CAAC;IAED,MAAM,KAAK,EAAE,CAAC;IAEd,OAAO;QACL,IAAI,EAAE,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,CAC9C,OAAO;YACL,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC;YACrD,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC;QACxE,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE;QACjC,QAAQ,EAAE,GAAG,EAAE;YACb,MAAM,GAAG,IAAI,CAAC;YACd,IAAI,YAAY,EAAE,CAAC;gBACjB,YAAY,CAAC,YAAY,CAAC,CAAC;gBAC3B,YAAY,GAAG,IAAI,CAAC;YACtB,CAAC;YACD,IAAI,CAAC;gBACH,MAAM,EAAE,IAAI,EAAE,CAAC;YACjB,CAAC;YAAC,MAAM,CAAC;gBACP,YAAY;YACd,CAAC;YACD,IAAI,CAAC;gBACH,WAAW,EAAE,IAAI,EAAE,CAAC;YACtB,CAAC;YAAC,MAAM,CAAC;gBACP,YAAY;YACd,CAAC;QACH,CAAC;QACD,IAAI,EAAE,GAAG,EAAE,CAAC,IAAI;QAChB,MAAM,EAAE,GAAG,EAAE,CAAC,MAAM;KACrB,CAAC;AACJ,CAAC"}
@@ -0,0 +1 @@
1
+ export { default } from "./opencode.js";
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ // Intentionally tiny. OpenCode iterates every named export of the main entry
2
+ // and rejects anything that isn't a `Plugin` function, so the only thing this
3
+ // module exposes is the default plugin. Library/utility exports live in
4
+ // `./lib` (see lib.ts), which OpenCode never inspects.
5
+ export { default } from "./opencode.js";
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,8EAA8E;AAC9E,wEAAwE;AACxE,uDAAuD;AACvD,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC"}
package/dist/lib.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ export { type BrowserPluginFactoryOptions, createBrowserPlugin, OpencodeBrowserPlugin } from "./opencode.js";
2
+ export { type AgentEndpoint, Broker, BrokerError, type BrokerDeps, type BrokerOptions } from "./broker.js";
3
+ export { AgentClient, AgentClientError, type AgentClientDeps, type AgentClientOptions, type AgentSocket, type AgentSocketFactory, type AgentSocketHandlers } from "./agent-client.js";
4
+ export { createEndpoint, type Endpoint, type EndpointDeps, type EndpointMode, type EndpointOptions } from "./endpoint.js";
5
+ export { type BridgeTransport, type ClientConnection, createBunTransport, isAddrInUse, type TransportHandlers } from "./transport.js";
6
+ export { type BridgeFile, readBridgeFile, resolveSharedToken, type TokenSource, writeBridgeFile } from "./token-file.js";
7
+ export { createBrowserTools, type SaveScreenshot, type SendFn, type ToolDeps } from "./tools.js";
8
+ export { BROWSER_TOOLS, DEFAULT_GROUPS, type NeutralResult, TOOL_GROUPS, type ToolGroup, type ToolSpec } from "./catalog.js";
9
+ export { type Field, type JsonInput, type JsonSchema, toJsonSchema } from "./schema.js";
10
+ export { createJsonConsoleLogger, DEFAULT_LOG_LEVEL, fromOpenCodeLogLevel, type LogFields, type Logger, type LogLevel } from "./logging.js";
11
+ export { BROWSER_ACTIONS, type BrowserAction, type BrowserEventName, type ClientRole, type CommandFrame, decodeFrame, encodeFrame, type EventFrame, type Frame, type HelloFrame, helloFrame, nextId, PROTOCOL_VERSION, type ReadyFrame, type ResultFrame } from "./protocol.js";
12
+ export type { BrowserPluginOptions, ExecutorMode, ResolvedBrowserOptions, ScreenshotResult } from "./types.js";
package/dist/lib.js ADDED
@@ -0,0 +1,12 @@
1
+ export { createBrowserPlugin, OpencodeBrowserPlugin } from "./opencode.js";
2
+ export { Broker, BrokerError } from "./broker.js";
3
+ export { AgentClient, AgentClientError } from "./agent-client.js";
4
+ export { createEndpoint } from "./endpoint.js";
5
+ export { createBunTransport, isAddrInUse } from "./transport.js";
6
+ export { readBridgeFile, resolveSharedToken, writeBridgeFile } from "./token-file.js";
7
+ export { createBrowserTools } from "./tools.js";
8
+ export { BROWSER_TOOLS, DEFAULT_GROUPS, TOOL_GROUPS } from "./catalog.js";
9
+ export { toJsonSchema } from "./schema.js";
10
+ export { createJsonConsoleLogger, DEFAULT_LOG_LEVEL, fromOpenCodeLogLevel } from "./logging.js";
11
+ export { BROWSER_ACTIONS, decodeFrame, encodeFrame, helloFrame, nextId, PROTOCOL_VERSION } from "./protocol.js";
12
+ //# sourceMappingURL=lib.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lib.js","sourceRoot":"","sources":["../src/lib.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,mBAAmB,EACnB,qBAAqB,EACtB,MAAM,eAAe,CAAC;AAEvB,OAAO,EAEL,MAAM,EACN,WAAW,EAGZ,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,WAAW,EACX,gBAAgB,EAMjB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACL,cAAc,EAKf,MAAM,eAAe,CAAC;AAEvB,OAAO,EAGL,kBAAkB,EAClB,WAAW,EAEZ,MAAM,gBAAgB,CAAC;AAExB,OAAO,EAEL,cAAc,EACd,kBAAkB,EAElB,eAAe,EAChB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,kBAAkB,EAAmD,MAAM,YAAY,CAAC;AAEjG,OAAO,EACL,aAAa,EACb,cAAc,EAEd,WAAW,EAGZ,MAAM,cAAc,CAAC;AAEtB,OAAO,EAA+C,YAAY,EAAE,MAAM,aAAa,CAAC;AAExF,OAAO,EACL,uBAAuB,EACvB,iBAAiB,EACjB,oBAAoB,EAIrB,MAAM,cAAc,CAAC;AAEtB,OAAO,EACL,eAAe,EAKf,WAAW,EACX,WAAW,EAIX,UAAU,EACV,MAAM,EACN,gBAAgB,EAGjB,MAAM,eAAe,CAAC"}
@@ -0,0 +1,15 @@
1
+ import type { LogLevel } from "./types.js";
2
+ export type { LogLevel } from "./types.js";
3
+ export declare const LOG_LEVEL_PRIORITY: Record<LogLevel, number>;
4
+ export declare const DEFAULT_LOG_LEVEL: LogLevel;
5
+ export interface LogFields {
6
+ [key: string]: unknown;
7
+ }
8
+ export interface Logger {
9
+ debug(event: string, fields?: LogFields): void;
10
+ info(event: string, fields?: LogFields): void;
11
+ warn(event: string, fields?: LogFields): void;
12
+ error(event: string, fields?: LogFields): void;
13
+ }
14
+ export declare function createJsonConsoleLogger(minLevel?: LogLevel): Logger;
15
+ export declare function fromOpenCodeLogLevel(value: unknown): LogLevel | undefined;
@@ -0,0 +1,69 @@
1
+ export const LOG_LEVEL_PRIORITY = {
2
+ debug: 10,
3
+ info: 20,
4
+ warn: 30,
5
+ error: 40
6
+ };
7
+ export const DEFAULT_LOG_LEVEL = "info";
8
+ function redactFields(fields) {
9
+ if (!fields) {
10
+ return undefined;
11
+ }
12
+ const redacted = {};
13
+ for (const [key, value] of Object.entries(fields)) {
14
+ if (/token|secret|password|authorization/i.test(key)) {
15
+ redacted[key] = "[redacted]";
16
+ continue;
17
+ }
18
+ redacted[key] = value;
19
+ }
20
+ return redacted;
21
+ }
22
+ export function createJsonConsoleLogger(minLevel = DEFAULT_LOG_LEVEL) {
23
+ const minPriority = LOG_LEVEL_PRIORITY[minLevel];
24
+ const write = (level, event, fields) => {
25
+ if (LOG_LEVEL_PRIORITY[level] < minPriority) {
26
+ return;
27
+ }
28
+ const payload = {
29
+ ts: new Date().toISOString(),
30
+ level,
31
+ event,
32
+ ...(redactFields(fields) ?? {})
33
+ };
34
+ const line = JSON.stringify(payload);
35
+ if (level === "error") {
36
+ console.error(line);
37
+ return;
38
+ }
39
+ if (level === "warn") {
40
+ console.warn(line);
41
+ return;
42
+ }
43
+ console.log(line);
44
+ };
45
+ return {
46
+ debug: (event, fields) => write("debug", event, fields),
47
+ info: (event, fields) => write("info", event, fields),
48
+ warn: (event, fields) => write("warn", event, fields),
49
+ error: (event, fields) => write("error", event, fields)
50
+ };
51
+ }
52
+ export function fromOpenCodeLogLevel(value) {
53
+ if (typeof value !== "string") {
54
+ return undefined;
55
+ }
56
+ switch (value.toUpperCase()) {
57
+ case "DEBUG":
58
+ return "debug";
59
+ case "INFO":
60
+ return "info";
61
+ case "WARN":
62
+ return "warn";
63
+ case "ERROR":
64
+ return "error";
65
+ default:
66
+ return undefined;
67
+ }
68
+ }
69
+ //# sourceMappingURL=logging.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logging.js","sourceRoot":"","sources":["../src/logging.ts"],"names":[],"mappings":"AAIA,MAAM,CAAC,MAAM,kBAAkB,GAA6B;IAC1D,KAAK,EAAE,EAAE;IACT,IAAI,EAAE,EAAE;IACR,IAAI,EAAE,EAAE;IACR,KAAK,EAAE,EAAE;CACV,CAAC;AAEF,MAAM,CAAC,MAAM,iBAAiB,GAAa,MAAM,CAAC;AAalD,SAAS,YAAY,CAAC,MAAkB;IACtC,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,MAAM,QAAQ,GAAc,EAAE,CAAC;IAC/B,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAClD,IAAI,sCAAsC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;YACrD,QAAQ,CAAC,GAAG,CAAC,GAAG,YAAY,CAAC;YAC7B,SAAS;QACX,CAAC;QACD,QAAQ,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;IACxB,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,WAAqB,iBAAiB;IAC5E,MAAM,WAAW,GAAG,kBAAkB,CAAC,QAAQ,CAAC,CAAC;IAEjD,MAAM,KAAK,GAAG,CAAC,KAAe,EAAE,KAAa,EAAE,MAAkB,EAAQ,EAAE;QACzE,IAAI,kBAAkB,CAAC,KAAK,CAAC,GAAG,WAAW,EAAE,CAAC;YAC5C,OAAO;QACT,CAAC;QACD,MAAM,OAAO,GAAG;YACd,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YAC5B,KAAK;YACL,KAAK;YACL,GAAG,CAAC,YAAY,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;SAChC,CAAC;QACF,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QACrC,IAAI,KAAK,KAAK,OAAO,EAAE,CAAC;YACtB,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YACpB,OAAO;QACT,CAAC;QACD,IAAI,KAAK,KAAK,MAAM,EAAE,CAAC;YACrB,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACnB,OAAO;QACT,CAAC;QACD,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACpB,CAAC,CAAC;IAEF,OAAO;QACL,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC;QACvD,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC;QACrD,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC;QACrD,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC;KACxD,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,KAAc;IACjD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,QAAQ,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;QAC5B,KAAK,OAAO;YACV,OAAO,OAAO,CAAC;QACjB,KAAK,MAAM;YACT,OAAO,MAAM,CAAC;QAChB,KAAK,MAAM;YACT,OAAO,MAAM,CAAC;QAChB,KAAK,OAAO;YACV,OAAO,OAAO,CAAC;QACjB;YACE,OAAO,SAAS,CAAC;IACrB,CAAC;AACH,CAAC"}
@@ -0,0 +1,20 @@
1
+ import type { Plugin } from "@opencode-ai/plugin";
2
+ import type { AgentSocketFactory } from "./agent-client.js";
3
+ import { type Logger } from "./logging.js";
4
+ import { type SaveScreenshot } from "./tools.js";
5
+ import { type BridgeTransport } from "./transport.js";
6
+ export interface BrowserPluginFactoryOptions {
7
+ /** Inject a logger (defaults to the OpenCode-piped logger). */
8
+ logger?: Logger;
9
+ /** Inject the server transport factory for host mode (defaults to Bun.serve). */
10
+ createServerTransport?: () => BridgeTransport;
11
+ /** Inject the agent-socket factory for guest mode (defaults to global WebSocket). */
12
+ createAgentSocket?: AgentSocketFactory;
13
+ /** Inject the screenshot disk-writer (tests). */
14
+ saveScreenshot?: SaveScreenshot;
15
+ /** Inject token generation (tests). */
16
+ generateToken?: () => string;
17
+ }
18
+ export declare function createBrowserPlugin(factoryOptions?: BrowserPluginFactoryOptions): Plugin;
19
+ export declare const OpencodeBrowserPlugin: Plugin;
20
+ export default OpencodeBrowserPlugin;
@@ -0,0 +1,149 @@
1
+ import { randomBytes } from "node:crypto";
2
+ import { DEFAULT_GROUPS, TOOL_GROUPS } from "./catalog.js";
3
+ import { createEndpoint } from "./endpoint.js";
4
+ import { createJsonConsoleLogger, DEFAULT_LOG_LEVEL, fromOpenCodeLogLevel, LOG_LEVEL_PRIORITY } from "./logging.js";
5
+ import { resolveSharedToken, writeBridgeFile } from "./token-file.js";
6
+ import { createBrowserTools } from "./tools.js";
7
+ import { createBunTransport } from "./transport.js";
8
+ /** Agent-socket factory using the runtime's global WebSocket (Bun provides one). */
9
+ const bunAgentSocket = (url, handlers) => {
10
+ const Ctor = globalThis.WebSocket;
11
+ if (!Ctor) {
12
+ throw new Error("global WebSocket is not available in this runtime");
13
+ }
14
+ const ws = new Ctor(url);
15
+ ws.addEventListener("open", () => handlers.onOpen());
16
+ ws.addEventListener("message", (event) => handlers.onMessage(typeof event.data === "string" ? event.data : String(event.data)));
17
+ ws.addEventListener("close", () => handlers.onClose());
18
+ ws.addEventListener("error", () => { });
19
+ return { send: (data) => ws.send(data), close: () => ws.close() };
20
+ };
21
+ function resolveGroups(raw) {
22
+ if (!Array.isArray(raw)) {
23
+ return [...DEFAULT_GROUPS];
24
+ }
25
+ const valid = raw.filter((g) => TOOL_GROUPS.includes(g));
26
+ return valid.length > 0 ? valid : [...DEFAULT_GROUPS];
27
+ }
28
+ const PLUGIN_SERVICE_NAME = "opencode-browser-plugin";
29
+ const DEFAULTS = {
30
+ host: "127.0.0.1",
31
+ port: 4517,
32
+ executor: "auto",
33
+ timeoutMs: 30_000,
34
+ screenshotDir: ".opencode/browser"
35
+ };
36
+ /**
37
+ * Pipe plugin logs through OpenCode's `client.app.log` so they show up in the
38
+ * host's structured log stream, with the JSON console as a reliable fallback.
39
+ * Mirrors the pattern used by `@vymalo/opencode-oauth2` / `-ratelimit`.
40
+ */
41
+ function createOpenCodeLogger(client, getMinLevel) {
42
+ const fallback = createJsonConsoleLogger("debug");
43
+ const write = (level, event, fields) => {
44
+ if (LOG_LEVEL_PRIORITY[level] < LOG_LEVEL_PRIORITY[getMinLevel()]) {
45
+ return;
46
+ }
47
+ fallback[level](event, fields);
48
+ void client.app
49
+ .log({ body: { service: PLUGIN_SERVICE_NAME, level, message: event, extra: fields } })
50
+ .catch(() => {
51
+ /* best-effort */
52
+ });
53
+ };
54
+ return {
55
+ debug: (event, fields) => write("debug", event, fields),
56
+ info: (event, fields) => write("info", event, fields),
57
+ warn: (event, fields) => write("warn", event, fields),
58
+ error: (event, fields) => write("error", event, fields)
59
+ };
60
+ }
61
+ function resolveOptions(raw, generateToken) {
62
+ const opts = (raw ?? {});
63
+ const token = typeof opts.token === "string" && opts.token.length > 0 ? opts.token : generateToken();
64
+ return {
65
+ enabled: opts.enabled !== false,
66
+ host: opts.host ?? DEFAULTS.host,
67
+ port: opts.port ?? DEFAULTS.port,
68
+ token,
69
+ executor: opts.executor ?? DEFAULTS.executor,
70
+ groups: resolveGroups(opts.groups),
71
+ timeoutMs: opts.timeoutMs ?? DEFAULTS.timeoutMs,
72
+ screenshotDir: opts.screenshotDir ?? DEFAULTS.screenshotDir
73
+ };
74
+ }
75
+ export function createBrowserPlugin(factoryOptions = {}) {
76
+ return async ({ client }, pluginOptions) => {
77
+ let currentLogLevel = DEFAULT_LOG_LEVEL;
78
+ const logger = factoryOptions.logger ?? createOpenCodeLogger(client, () => currentLogLevel);
79
+ const generateToken = factoryOptions.generateToken ?? (() => randomBytes(24).toString("hex"));
80
+ const options = resolveOptions(pluginOptions, generateToken);
81
+ const rawOptions = pluginOptions;
82
+ // Empty string is NOT an explicit token (resolveOptions generates one for
83
+ // it). Treating "" as provided would mark the shared token "explicit" and
84
+ // skip the paste_into_extension log, leaving the extension unable to learn
85
+ // the generated token.
86
+ const tokenProvided = typeof rawOptions?.token === "string" && rawOptions.token.length > 0;
87
+ // Only forward the executor preference when the operator set it explicitly;
88
+ // otherwise the extension keeps its own dashboard choice.
89
+ const executorProvided = typeof rawOptions?.executor === "string";
90
+ if (!options.enabled) {
91
+ logger.info("browser_plugin_disabled", {});
92
+ return {
93
+ config: async (config) => {
94
+ currentLogLevel = fromOpenCodeLogLevel(config.logLevel) ?? DEFAULT_LOG_LEVEL;
95
+ }
96
+ };
97
+ }
98
+ // Share the token across adapters via the per-user state file (explicit wins).
99
+ const { token, source } = resolveSharedToken(options.port, tokenProvided ? options.token : undefined, generateToken);
100
+ writeBridgeFile(options.port, token);
101
+ // Auto-elect: host the broker (win the bind) or join an existing one as a guest.
102
+ const endpoint = await createEndpoint({
103
+ host: options.host,
104
+ port: options.port,
105
+ token,
106
+ executor: executorProvided ? options.executor : undefined,
107
+ timeoutMs: options.timeoutMs,
108
+ label: "opencode-plugin"
109
+ }, {
110
+ logger,
111
+ createServerTransport: factoryOptions.createServerTransport ?? createBunTransport,
112
+ createAgentSocket: factoryOptions.createAgentSocket ?? bunAgentSocket
113
+ });
114
+ // When OpenCode exits, hand the browser back automatically — the user
115
+ // shouldn't have to click Disconnect.
116
+ process.once("exit", () => {
117
+ try {
118
+ endpoint.shutdown();
119
+ }
120
+ catch {
121
+ /* best-effort */
122
+ }
123
+ });
124
+ if (source !== "explicit") {
125
+ // Make the token visible so it can be pasted into the extension. The
126
+ // `paste_into_extension` field name dodges the logger's redaction filter.
127
+ logger.info("browser_bridge_token", {
128
+ paste_into_extension: token,
129
+ source,
130
+ mode: endpoint.mode()
131
+ });
132
+ }
133
+ const tools = createBrowserTools({
134
+ send: endpoint.send,
135
+ options,
136
+ logger,
137
+ saveScreenshot: factoryOptions.saveScreenshot
138
+ });
139
+ return {
140
+ tool: tools,
141
+ config: async (config) => {
142
+ currentLogLevel = fromOpenCodeLogLevel(config.logLevel) ?? DEFAULT_LOG_LEVEL;
143
+ }
144
+ };
145
+ };
146
+ }
147
+ export const OpencodeBrowserPlugin = createBrowserPlugin();
148
+ export default OpencodeBrowserPlugin;
149
+ //# sourceMappingURL=opencode.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"opencode.js","sourceRoot":"","sources":["../src/opencode.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,138 @@
1
+ /**
2
+ * Wire protocol shared by the OpenCode plugin (the bridge **server**) and the
3
+ * browser extension (the **client**). This module is intentionally
4
+ * dependency-free and runtime-agnostic so the extension can mirror it verbatim
5
+ * into a browser bundle without pulling in any Bun/Node code.
6
+ *
7
+ * Canonical copy lives here (`packages/opencode-browser/src/protocol.ts`); the
8
+ * extension keeps a byte-for-byte copy at
9
+ * `apps/browser-extension/src/shared/protocol.ts`. Keep them in sync.
10
+ *
11
+ * Topology: extensions cannot host servers, so the plugin opens the WebSocket
12
+ * server on 127.0.0.1 and the extension's background worker dials out to it.
13
+ * The extension authenticates with a `hello`; the server replies `ready`. From
14
+ * then on the server issues `command` frames and the extension answers with a
15
+ * matching `result` frame (correlated by `id`). The extension may also push
16
+ * unsolicited `event` frames. Heartbeats use `ping`/`pong`.
17
+ */
18
+ /** Bump when the frame shapes change incompatibly. */
19
+ export declare const PROTOCOL_VERSION = 1;
20
+ /** Every browser action the plugin can ask the extension to perform. */
21
+ export type BrowserAction = "open" | "navigate" | "click" | "double_click" | "type" | "fill" | "select" | "scroll" | "press_key" | "screenshot" | "snapshot" | "get_text" | "wait" | "tabs" | "close" | "back" | "forward" | "reload" | "hover" | "activate" | "drag" | "upload" | "get_html" | "get_attribute" | "query" | "eval" | "console" | "network" | "handle_dialog" | "set_viewport" | "cookies" | "targets" | "release";
22
+ export declare const BROWSER_ACTIONS: readonly BrowserAction[];
23
+ /** A connection's role on the bridge. Absent → "extension" (back-compat). */
24
+ export type ClientRole = "agent" | "extension";
25
+ /** Client → broker: first frame after the socket opens. */
26
+ export interface HelloFrame {
27
+ v: number;
28
+ type: "hello";
29
+ /** Shared secret; must equal the bridge token. */
30
+ token: string;
31
+ /** "extension" (executor) or "agent" (producer). Default extension. */
32
+ role?: ClientRole;
33
+ /** Free-form client descriptor for logging (browser name/version, agent name). */
34
+ client?: string;
35
+ /** Extensions: stable per-install id (persisted), usable as a routing target. */
36
+ id?: string;
37
+ /** Extensions: user-editable label from the dashboard (defaults to id). */
38
+ label?: string;
39
+ /** Extensions: "chrome" | "firefox" | UA hint. */
40
+ browser?: string;
41
+ }
42
+ /** Broker → client: handshake accepted. */
43
+ export interface ReadyFrame {
44
+ v: number;
45
+ type: "ready";
46
+ server: "opencode-browser";
47
+ protocol: number;
48
+ /** Role the broker accepted this connection as. */
49
+ role?: ClientRole;
50
+ /** Id the broker assigned/echoed for this client. */
51
+ clientId?: string;
52
+ /** Operator's executor preference, if configured plugin-side (extensions only). */
53
+ executor?: "auto" | "cdp" | "content";
54
+ }
55
+ /** Server → extension: perform an action, expect a matching `result`. */
56
+ export interface CommandFrame {
57
+ v: number;
58
+ type: "command";
59
+ /** Correlation id; the `result` echoes it. */
60
+ id: string;
61
+ action: BrowserAction;
62
+ /** Named tab group the action targets. */
63
+ group: string;
64
+ /** Action-specific arguments (already validated plugin-side). */
65
+ params: Record<string, unknown>;
66
+ /**
67
+ * Optional executor selector (browser label or id) used by the broker when a
68
+ * command creates a new group; ignored by executors.
69
+ */
70
+ target?: string;
71
+ }
72
+ /** Extension → server: response to a `command`. */
73
+ export interface ResultFrame {
74
+ v: number;
75
+ type: "result";
76
+ id: string;
77
+ ok: boolean;
78
+ /** Present when `ok` is true. */
79
+ data?: unknown;
80
+ /** Present when `ok` is false. */
81
+ error?: {
82
+ message: string;
83
+ code?: string;
84
+ };
85
+ }
86
+ /** Names of unsolicited events the extension can push. */
87
+ export type BrowserEventName = "tab_closed" | "navigated" | "group_removed" | "tab_created";
88
+ /** Extension → server: unsolicited notification (not correlated to a command). */
89
+ export interface EventFrame {
90
+ v: number;
91
+ type: "event";
92
+ name: BrowserEventName;
93
+ group?: string;
94
+ data?: Record<string, unknown>;
95
+ }
96
+ /**
97
+ * Server → extension: stop driving and release control (detach the CDP
98
+ * debugger) without closing tabs. The next command re-attaches. Lets the plugin
99
+ * hand the browser back without waiting for the socket to drop.
100
+ */
101
+ export interface ReleaseFrame {
102
+ v: number;
103
+ type: "release";
104
+ }
105
+ export interface PingFrame {
106
+ v: number;
107
+ type: "ping";
108
+ }
109
+ export interface PongFrame {
110
+ v: number;
111
+ type: "pong";
112
+ }
113
+ export type Frame = HelloFrame | ReadyFrame | CommandFrame | ResultFrame | EventFrame | ReleaseFrame | PingFrame | PongFrame;
114
+ /** Serialize a frame for the wire. */
115
+ export declare function encodeFrame(frame: Frame): string;
116
+ /**
117
+ * Parse and shape-check an inbound frame. Returns the typed frame, or `null` if
118
+ * the payload is not a recognizable frame (caller decides whether to drop or
119
+ * log). Validation is deliberately lightweight — both ends are trusted once the
120
+ * token handshake succeeds; this guards against malformed JSON and version skew,
121
+ * not a hostile peer.
122
+ */
123
+ export declare function decodeFrame(raw: string): Frame | null;
124
+ /**
125
+ * Monotonic correlation id for command frames. Process-local and collision-free
126
+ * within a single bridge instance, which is all that's required (the server is
127
+ * the only id minter).
128
+ */
129
+ export declare function nextId(): string;
130
+ export declare function helloFrame(token: string, opts?: {
131
+ role?: ClientRole;
132
+ client?: string;
133
+ id?: string;
134
+ label?: string;
135
+ browser?: string;
136
+ }): HelloFrame;
137
+ export declare function resultFrame(id: string, data: unknown): ResultFrame;
138
+ export declare function errorFrame(id: string, message: string, code?: string): ResultFrame;